//! This example illustrates the various features of Bevy UI. use bevy::{ input::mouse::{MouseScrollUnit, MouseWheel}, prelude::*, winit::WinitSettings, }; fn main() { App::new() .add_plugins(DefaultPlugins) // Only run the app when there is user input. This will significantly reduce CPU/GPU use. .insert_resource(WinitSettings::desktop_app()) .add_startup_system(setup) .add_system(mouse_scroll) .run(); } fn setup(mut commands: Commands, asset_server: Res) { // Camera commands.spawn(Camera2dBundle::default()); // root node commands .spawn(NodeBundle { style: Style { size: Size::new(Val::Percent(100.0), Val::Percent(100.0)), justify_content: JustifyContent::SpaceBetween, ..default() }, ..default() }) .with_children(|parent| { // left vertical fill (border) parent .spawn(NodeBundle { style: Style { size: Size::new(Val::Px(200.0), Val::Percent(100.0)), border: UiRect::all(Val::Px(2.0)), ..default() }, background_color: Color::rgb(0.65, 0.65, 0.65).into(), ..default() }) .with_children(|parent| { // left vertical fill (content) parent .spawn(NodeBundle { style: Style { size: Size::new(Val::Percent(100.0), Val::Percent(100.0)), align_items: AlignItems::FlexEnd, ..default() }, background_color: Color::rgb(0.15, 0.15, 0.15).into(), ..default() }) .with_children(|parent| { // text parent.spawn( TextBundle::from_section( "Text Example", TextStyle { font: asset_server.load("fonts/FiraSans-Bold.ttf"), font_size: 30.0, color: Color::WHITE, }, ) .with_style(Style { margin: UiRect::all(Val::Px(5.0)), ..default() }), ); }); }); // right vertical fill parent .spawn(NodeBundle { style: Style { flex_direction: FlexDirection::ColumnReverse, justify_content: JustifyContent::Center, size: Size::new(Val::Px(200.0), Val::Percent(100.0)), ..default() }, background_color: Color::rgb(0.15, 0.15, 0.15).into(), ..default() }) .with_children(|parent| { // Title parent.spawn( TextBundle::from_section( "Scrolling list", TextStyle { font: asset_server.load("fonts/FiraSans-Bold.ttf"), font_size: 25., color: Color::WHITE, }, ) .with_style(Style { size: Size::new(Val::Undefined, Val::Px(25.)), margin: UiRect { left: Val::Auto, right: Val::Auto, ..default() }, ..default() }), ); // List with hidden overflow parent .spawn(NodeBundle { style: Style { flex_direction: FlexDirection::ColumnReverse, align_self: AlignSelf::Center, size: Size::new(Val::Percent(100.0), Val::Percent(50.0)), overflow: Overflow::Hidden, ..default() }, background_color: Color::rgb(0.10, 0.10, 0.10).into(), ..default() }) .with_children(|parent| { // Moving panel parent .spawn(( NodeBundle { style: Style { flex_direction: FlexDirection::ColumnReverse, flex_grow: 1.0, max_size: Size::UNDEFINED, ..default() }, ..default() }, ScrollingList::default(), )) .with_children(|parent| { // List items for i in 0..30 { parent.spawn( TextBundle::from_section( format!("Item {i}"), TextStyle { font: asset_server .load("fonts/FiraSans-Bold.ttf"), font_size: 20., color: Color::WHITE, }, ) .with_style(Style { flex_shrink: 0., size: Size::new(Val::Undefined, Val::Px(20.)), margin: UiRect { left: Val::Auto, right: Val::Auto, ..default() }, ..default() }), ); } }); }); }); // absolute positioning parent .spawn(NodeBundle { style: Style { size: Size::new(Val::Px(200.0), Val::Px(200.0)), position_type: PositionType::Absolute, position: UiRect { left: Val::Px(210.0), bottom: Val::Px(10.0), ..default() }, border: UiRect::all(Val::Px(20.0)), ..default() }, background_color: Color::rgb(0.4, 0.4, 1.0).into(), ..default() }) .with_children(|parent| { parent.spawn(NodeBundle { style: Style { size: Size::new(Val::Percent(100.0), Val::Percent(100.0)), ..default() }, background_color: Color::rgb(0.8, 0.8, 1.0).into(), ..default() }); }); // render order test: reddest in the back, whitest in the front (flex center) parent .spawn(NodeBundle { style: Style { size: Size::new(Val::Percent(100.0), Val::Percent(100.0)), position_type: PositionType::Absolute, align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() }, ..default() }) .with_children(|parent| { parent .spawn(NodeBundle { style: Style { size: Size::new(Val::Px(100.0), Val::Px(100.0)), ..default() }, background_color: Color::rgb(1.0, 0.0, 0.0).into(), ..default() }) .with_children(|parent| { parent.spawn(NodeBundle { style: Style { size: Size::new(Val::Px(100.0), Val::Px(100.0)), position_type: PositionType::Absolute, position: UiRect { left: Val::Px(20.0), bottom: Val::Px(20.0), ..default() }, ..default() }, background_color: Color::rgb(1.0, 0.3, 0.3).into(), ..default() }); parent.spawn(NodeBundle { style: Style { size: Size::new(Val::Px(100.0), Val::Px(100.0)), position_type: PositionType::Absolute, position: UiRect { left: Val::Px(40.0), bottom: Val::Px(40.0), ..default() }, ..default() }, background_color: Color::rgb(1.0, 0.5, 0.5).into(), ..default() }); parent.spawn(NodeBundle { style: Style { size: Size::new(Val::Px(100.0), Val::Px(100.0)), position_type: PositionType::Absolute, position: UiRect { left: Val::Px(60.0), bottom: Val::Px(60.0), ..default() }, ..default() }, background_color: Color::rgb(1.0, 0.7, 0.7).into(), ..default() }); // alpha test parent.spawn(NodeBundle { style: Style { size: Size::new(Val::Px(100.0), Val::Px(100.0)), position_type: PositionType::Absolute, position: UiRect { left: Val::Px(80.0), bottom: Val::Px(80.0), ..default() }, ..default() }, background_color: Color::rgba(1.0, 0.9, 0.9, 0.4).into(), ..default() }); }); }); // bevy logo (flex center) parent .spawn(NodeBundle { style: Style { size: Size::new(Val::Percent(100.0), Val::Percent(100.0)), position_type: PositionType::Absolute, justify_content: JustifyContent::Center, align_items: AlignItems::FlexEnd, ..default() }, ..default() }) .with_children(|parent| { // bevy logo (image) parent.spawn(ImageBundle { style: Style { size: Size::new(Val::Px(500.0), Val::Auto), ..default() }, image: asset_server.load("branding/bevy_logo_dark_big.png").into(), ..default() }); }); }); } #[derive(Component, Default)] struct ScrollingList { position: f32, } fn mouse_scroll( mut mouse_wheel_events: EventReader, mut query_list: Query<(&mut ScrollingList, &mut Style, &Children, &Node)>, query_item: Query<&Node>, ) { for mouse_wheel_event in mouse_wheel_events.iter() { for (mut scrolling_list, mut style, children, uinode) in &mut query_list { let items_height: f32 = children .iter() .map(|entity| query_item.get(*entity).unwrap().size.y) .sum(); let panel_height = uinode.size.y; let max_scroll = (items_height - panel_height).max(0.); let dy = match mouse_wheel_event.unit { MouseScrollUnit::Line => mouse_wheel_event.y * 20., MouseScrollUnit::Pixel => mouse_wheel_event.y, }; scrolling_list.position += dy; scrolling_list.position = scrolling_list.position.clamp(-max_scroll, 0.); style.position.top = Val::Px(scrolling_list.position); } } }