diff --git a/examples/ui/scroll.rs b/examples/ui/scroll.rs index a3a443b991..34390911ee 100644 --- a/examples/ui/scroll.rs +++ b/examples/ui/scroll.rs @@ -3,6 +3,7 @@ use accesskit::{Node as Accessible, Role}; use bevy::{ a11y::AccessibilityNode, + ecs::spawn::SpawnIter, input::mouse::{MouseScrollUnit, MouseWheel}, picking::hover::HoverMap, prelude::*, @@ -26,6 +27,9 @@ fn setup(mut commands: Commands, asset_server: Res) { // Camera commands.spawn((Camera2d, IsDefaultUiCamera)); + // Font + let font_handle = asset_server.load("fonts/FiraSans-Bold.ttf"); + // root node commands .spawn(Node { @@ -49,7 +53,7 @@ fn setup(mut commands: Commands, asset_server: Res) { parent.spawn(( Text::new("Horizontally Scrolling list (Ctrl + MouseWheel)"), TextFont { - font: asset_server.load("fonts/FiraSans-Bold.ttf"), + font: font_handle.clone(), font_size: FONT_SIZE, ..default() }, @@ -71,260 +75,243 @@ fn setup(mut commands: Commands, asset_server: Res) { .with_children(|parent| { for i in 0..100 { parent.spawn((Text(format!("Item {i}")), - TextFont { - font: asset_server - .load("fonts/FiraSans-Bold.ttf"), - ..default() - }, - Label, - AccessibilityNode(Accessible::new(Role::ListItem)), + TextFont { + font: font_handle.clone(), + ..default() + }, + Label, + AccessibilityNode(Accessible::new(Role::ListItem)), )) - .insert(Node { - min_width: Val::Px(200.), - align_content: AlignContent::Center, - ..default() - }) - .insert(Pickable { - should_block_lower: false, - ..default() - }) - .observe(| - trigger: Trigger>, - mut commands: Commands - | { - if trigger.event().button == PointerButton::Primary { - commands.entity(trigger.target().unwrap()).despawn(); - } - }); + .insert(Node { + min_width: Val::Px(200.), + align_content: AlignContent::Center, + ..default() + }) + .insert(Pickable { + should_block_lower: false, + ..default() + }) + .observe(| + trigger: Trigger>, + mut commands: Commands + | { + if trigger.event().button == PointerButton::Primary { + commands.entity(trigger.target().unwrap()).despawn(); + } + }); } }); }); // container for all other examples - parent - .spawn(Node { + parent.spawn(( + Node { width: Val::Percent(100.), height: Val::Percent(100.), flex_direction: FlexDirection::Row, justify_content: JustifyContent::SpaceBetween, ..default() - }) - .with_children(|parent| { - // vertical scroll example - parent - .spawn(Node { - flex_direction: FlexDirection::Column, - justify_content: JustifyContent::Center, - align_items: AlignItems::Center, - width: Val::Px(200.), - ..default() - }) - .with_children(|parent| { - // Title - parent.spawn(( - Text::new("Vertically Scrolling List"), - TextFont { - font: asset_server.load("fonts/FiraSans-Bold.ttf"), - font_size: FONT_SIZE, - ..default() - }, - Label, - )); - // Scrolling list - parent - .spawn(( - Node { - flex_direction: FlexDirection::Column, - align_self: AlignSelf::Stretch, - height: Val::Percent(50.), - overflow: Overflow::scroll_y(), // n.b. - ..default() - }, - BackgroundColor(Color::srgb(0.10, 0.10, 0.10)), - )) - .with_children(|parent| { - // List items - for i in 0..25 { - parent - .spawn(Node { - min_height: Val::Px(LINE_HEIGHT), - max_height: Val::Px(LINE_HEIGHT), - ..default() - }) - .insert(Pickable { - should_block_lower: false, - ..default() - }) - .with_children(|parent| { - parent - .spawn(( - Text(format!("Item {i}")), - TextFont { - font: asset_server - .load("fonts/FiraSans-Bold.ttf"), - ..default() - }, - Label, - AccessibilityNode(Accessible::new( - Role::ListItem, - )), - )) - .insert(Pickable { - should_block_lower: false, - ..default() - }); - }); - } - }); - }); - - // Bidirectional scroll example - parent - .spawn(Node { - flex_direction: FlexDirection::Column, - justify_content: JustifyContent::Center, - align_items: AlignItems::Center, - width: Val::Px(200.), - ..default() - }) - .with_children(|parent| { - // Title - parent.spawn(( - Text::new("Bidirectionally Scrolling List"), - TextFont { - font: asset_server.load("fonts/FiraSans-Bold.ttf"), - font_size: FONT_SIZE, - ..default() - }, - Label, - )); - // Scrolling list - parent - .spawn(( - Node { - flex_direction: FlexDirection::Column, - align_self: AlignSelf::Stretch, - height: Val::Percent(50.), - overflow: Overflow::scroll(), // n.b. - ..default() - }, - BackgroundColor(Color::srgb(0.10, 0.10, 0.10)), - )) - .with_children(|parent| { - // Rows in each column - for oi in 0..10 { - parent - .spawn(Node { - flex_direction: FlexDirection::Row, - ..default() - }) - .insert(Pickable::IGNORE) - .with_children(|parent| { - // Elements in each row - for i in 0..25 { - parent - .spawn(( - Text(format!("Item {}", (oi * 25) + i)), - TextFont { - font: asset_server.load( - "fonts/FiraSans-Bold.ttf", - ), - ..default() - }, - Label, - AccessibilityNode(Accessible::new( - Role::ListItem, - )), - )) - .insert(Pickable { - should_block_lower: false, - ..default() - }); - } - }); - } - }); - }); - - // Nested scrolls example - parent - .spawn(Node { - flex_direction: FlexDirection::Column, - justify_content: JustifyContent::Center, - align_items: AlignItems::Center, - width: Val::Px(200.), - ..default() - }) - .with_children(|parent| { - // Title - parent.spawn(( - Text::new("Nested Scrolling Lists"), - TextFont { - font: asset_server.load("fonts/FiraSans-Bold.ttf"), - font_size: FONT_SIZE, - ..default() - }, - Label, - )); - // Outer, horizontal scrolling container - parent - .spawn(( - Node { - column_gap: Val::Px(20.), - flex_direction: FlexDirection::Row, - align_self: AlignSelf::Stretch, - height: Val::Percent(50.), - overflow: Overflow::scroll_x(), // n.b. - ..default() - }, - BackgroundColor(Color::srgb(0.10, 0.10, 0.10)), - )) - .with_children(|parent| { - // Inner, scrolling columns - for oi in 0..30 { - parent - .spawn(( - Node { - flex_direction: FlexDirection::Column, - align_self: AlignSelf::Stretch, - overflow: Overflow::scroll_y(), - ..default() - }, - BackgroundColor(Color::srgb(0.05, 0.05, 0.05)), - )) - .insert(Pickable { - should_block_lower: false, - ..default() - }) - .with_children(|parent| { - for i in 0..25 { - parent - .spawn(( - Text(format!("Item {}", (oi * 25) + i)), - TextFont { - font: asset_server.load( - "fonts/FiraSans-Bold.ttf", - ), - ..default() - }, - Label, - AccessibilityNode(Accessible::new( - Role::ListItem, - )), - )) - .insert(Pickable { - should_block_lower: false, - ..default() - }); - } - }); - } - }); - }); - }); + }, + children![ + vertically_scrolling_list(asset_server.load("fonts/FiraSans-Bold.ttf")), + bidirectional_scrolling_list(asset_server.load("fonts/FiraSans-Bold.ttf")), + nested_scrolling_list(asset_server.load("fonts/FiraSans-Bold.ttf")), + ], + )); }); } +fn vertically_scrolling_list(font_handle: Handle) -> impl Bundle { + ( + Node { + flex_direction: FlexDirection::Column, + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + width: Val::Px(200.), + ..default() + }, + children![ + ( + // Title + Text::new("Vertically Scrolling List"), + TextFont { + font: font_handle.clone(), + font_size: FONT_SIZE, + ..default() + }, + Label, + ), + ( + // Scrolling list + Node { + flex_direction: FlexDirection::Column, + align_self: AlignSelf::Stretch, + height: Val::Percent(50.), + overflow: Overflow::scroll_y(), // n.b. + ..default() + }, + BackgroundColor(Color::srgb(0.10, 0.10, 0.10)), + Children::spawn(SpawnIter((0..25).map(move |i| { + ( + Node { + min_height: Val::Px(LINE_HEIGHT), + max_height: Val::Px(LINE_HEIGHT), + ..default() + }, + Pickable { + should_block_lower: false, + ..default() + }, + children![( + Text(format!("Item {i}")), + TextFont { + font: font_handle.clone(), + ..default() + }, + Label, + AccessibilityNode(Accessible::new(Role::ListItem)), + Pickable { + should_block_lower: false, + ..default() + } + )], + ) + }))) + ), + ], + ) +} + +fn bidirectional_scrolling_list(font_handle: Handle) -> impl Bundle { + ( + Node { + flex_direction: FlexDirection::Column, + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + width: Val::Px(200.), + ..default() + }, + children![ + ( + Text::new("Bidirectionally Scrolling List"), + TextFont { + font: font_handle.clone(), + font_size: FONT_SIZE, + ..default() + }, + Label, + ), + ( + Node { + flex_direction: FlexDirection::Column, + align_self: AlignSelf::Stretch, + height: Val::Percent(50.), + overflow: Overflow::scroll(), // n.b. + ..default() + }, + BackgroundColor(Color::srgb(0.10, 0.10, 0.10)), + Children::spawn(SpawnIter((0..25).map(move |oi| { + ( + Node { + flex_direction: FlexDirection::Row, + ..default() + }, + Pickable::IGNORE, + Children::spawn(SpawnIter((0..10).map({ + let value = font_handle.clone(); + move |i| { + ( + Text(format!("Item {}", (oi * 10) + i)), + TextFont { + font: value.clone(), + ..default() + }, + Label, + AccessibilityNode(Accessible::new(Role::ListItem)), + Pickable { + should_block_lower: false, + ..default() + }, + ) + } + }))), + ) + }))) + ) + ], + ) +} + +fn nested_scrolling_list(font_handle: Handle) -> impl Bundle { + ( + Node { + flex_direction: FlexDirection::Column, + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + width: Val::Px(200.), + ..default() + }, + children![ + ( + // Title + Text::new("Nested Scrolling Lists"), + TextFont { + font: font_handle.clone(), + font_size: FONT_SIZE, + ..default() + }, + Label, + ), + ( + // Outer, horizontal scrolling container + Node { + column_gap: Val::Px(20.), + flex_direction: FlexDirection::Row, + align_self: AlignSelf::Stretch, + height: Val::Percent(50.), + overflow: Overflow::scroll_x(), // n.b. + ..default() + }, + BackgroundColor(Color::srgb(0.10, 0.10, 0.10)), + // Inner, scrolling columns + Children::spawn(SpawnIter((0..30).map(move |oi| { + ( + Node { + flex_direction: FlexDirection::Column, + align_self: AlignSelf::Stretch, + overflow: Overflow::scroll_y(), + ..default() + }, + BackgroundColor(Color::srgb(0.05, 0.05, 0.05)), + Pickable { + should_block_lower: false, + ..default() + }, + Children::spawn(SpawnIter((0..30).map({ + let value = font_handle.clone(); + move |i| { + ( + Text(format!("Item {}", (oi * 25) + i)), + TextFont { + font: value.clone(), + ..default() + }, + Label, + AccessibilityNode(Accessible::new(Role::ListItem)), + Pickable { + should_block_lower: false, + ..default() + }, + ) + } + }))), + ) + }))) + ) + ], + ) +} + /// Updates the scroll position of scrollable nodes in response to mouse input pub fn update_scroll_position( mut mouse_wheel_events: EventReader,