#![feature(iter_intersperse)] //! This example illustrates scrolling in Bevy UI. use bevy::picking::hover::HoverMap; use games::*; use lipsum::*; use rand::*; fn main() { let mut app = App::new(); app.add_plugins(BaseGamePlugin::default()) .add_systems(Startup, (setup_list, setup_nav_tree)) .add_systems( Update, ( hide_menu, control_menu.run_if(on_event::>.or(on_event::>)), ), ); app.run(); } fn setup_list(mut commands: Commands) { // Scrolling list commands .spawn(( Node { flex_direction: FlexDirection::Column, // If height is not set, we need both align_self: Stetch and overflow: scroll() height: Val::Percent(50.0), // align_self: AlignSelf::Stretch, overflow: Overflow::scroll(), ..default() }, BackgroundColor(RED.into()), )) .with_children(|parent| { // List items (0..250).for_each(|i| { parent.spawn(Text(format!("Item {i}"))); }); }) .observe(scroll); } fn setup_nav_tree(mut commands: Commands) { // Nav Tree commands .spawn(( Text::new("+"), Node { align_self: AlignSelf::Start, justify_self: JustifySelf::End, min_width: Val::Px(25.0), min_height: Val::Px(25.0), align_content: AlignContent::Center, justify_content: JustifyContent::Center, ..default() }, BackgroundColor(RED.into()), )) .with_children(|parent| { parent .spawn(( Name::new("Buttons"), NavState::default(), Node { position_type: PositionType::Absolute, flex_direction: FlexDirection::Column, right: Val::Percent(100.0), width: Val::Auto, ..default() }, Visibility::Hidden, BackgroundColor(ORANGE.into()), )) .with_children(|parent| { (0..10).for_each(|_| { let title: String = lipsum_title_with_rng(thread_rng()) .split_whitespace() .take(2) .intersperse(" ") .collect(); parent.spawn(( Text::new(title), Node { width: Val::Auto, margin: UiRect::all(Val::Px(5.0)), ..default() }, BackgroundColor(ORANGE_RED.into()), Button, )); }); }) .with_children(|parent| { parent.spawn(( Name::new("Preview"), NavState::default(), Text::new(lipsum_with_rng(thread_rng(), 50)), Node { position_type: PositionType::Absolute, right: Val::Percent(100.0), ..default() }, BackgroundColor(YELLOW.into()), Visibility::Hidden, )); }); }); } // When you pointer over the '+' make the entire menu visible fn hide_menu(mut nodes: Query<(Entity, &mut Visibility, &NavState), Changed>) { nodes.iter_mut().for_each(|(e, mut v, n)| { *v = match n { NavState::Open => Visibility::Inherited, NavState::Closed => Visibility::Hidden, }; }); } // When you pointer goes off of the '+' or any of it's children make the entire menu invisible fn control_menu( mut over_events: EventReader>, mut out_events: EventReader>, children: Query<&ChildOf>, parents: Query<&Children>, mut nav: Query<&mut NavState>, hover_map: Res, ) { over_events.read().for_each(|over| { let root = children.root_ancestor(over.target); parents.iter_descendants(root).for_each(|child| { if let Ok(mut n) = nav.get_mut(child) { *n = NavState::Open; } }); }); // Gives us the entiies covered by the HoverMap let is_hovered: Vec<&Entity> = hover_map .iter() .flat_map(|(_, submap)| { let x: Vec<&Entity> = submap.iter().map(|(node, _)| node).collect(); x }) .collect(); // For all pointer out events out_events.read().for_each(|out| { // If a relative of out.target is hovered, do nothing // Otherwise set to closed let root = children.root_ancestor(out.target); let tree_still_hovered = parents .iter_descendants(root) .any(|child| is_hovered.contains(&&child)); if !tree_still_hovered { parents.iter_descendants(root).for_each(|child| { if let Ok(mut n) = nav.get_mut(child) { *n = NavState::Closed; } }) } }) }