From dba46a99daa6893769f1bdc5884adcd46cb864ab Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Wed, 23 Jul 2025 15:47:34 -0700 Subject: [PATCH] Move trees debug code to separate module --- src/bin/trees/debug.rs | 415 +++++++++++++++++++++++++++++++++++++++++ src/bin/trees/main.rs | 389 +------------------------------------- 2 files changed, 419 insertions(+), 385 deletions(-) create mode 100644 src/bin/trees/debug.rs diff --git a/src/bin/trees/debug.rs b/src/bin/trees/debug.rs new file mode 100644 index 0000000..88eccfb --- /dev/null +++ b/src/bin/trees/debug.rs @@ -0,0 +1,415 @@ +use super::*; + +pub(crate) struct TreesDebugPlugin; + +impl Plugin for TreesDebugPlugin { + fn build(&self, app: &mut App) { + app.add_systems( + Startup, + init_debug_ui, + ).add_systems( + Update, + ( + ( + spawn_debug_buttons.run_if(on_event::>), + ), + ( + monologue_asset_tooltip + .run_if(on_event::>.or(on_event::>)), + hide_menu.run_if(any_component_changed::), + clear_monologue.run_if(any_component_changed::), + control_menu.run_if(on_event::>.or(on_event::>)), + delete_tree.run_if(on_event::>), + drag_tree.run_if(on_event::>), + ).run_if(in_state(DebuggingState::On)), + ) + ).add_observer(add_dialog_option); + } +} + +#[derive(Component)] +struct MonologuesContainer; + +#[derive(Component)] +struct MonologuesList; + +#[derive(Component)] +struct MonologuePreview; + + +fn drag_tree( + mut events: EventReader>, + state: Res>, + mut query: Query<&mut Transform, With>, + camera: Single<(&Camera, &GlobalTransform), With>, + window: Single<&Window>, +) { + debug_assert_eq!(*state.get(), DebuggingState::On); + + events.read().for_each(|event| { + if let Ok(mut t) = query.get_mut(event.target) + { + let world_position = window + .cursor_position() + .and_then(|cursor| camera.0.viewport_to_world(camera.1, cursor).ok()) + .map(|ray| { + // Compute ray's distance to entity + let distance = ray + .intersect_plane(t.translation, InfinitePlane3d::new(t.up())) + .unwrap(); + ray.get_point(distance) + }); + t.translation = world_position.unwrap(); + } + }); +} + +/// Panel for selecting which monologue tree to spawn +fn init_debug_ui(mut commands: Commands) { + let mut monologue_button = None; + commands + .spawn(( + Node { + top: Val::Px(0.0), + left: Val::Px(0.0), + flex_direction: FlexDirection::Row, + ..default() + }, + DebuggingState::On, + )) + .with_children(|parent| { + monologue_button = Some( + parent + .spawn(( + Name::new("Monologue Assignment Menu"), + children![Text::new("+Monologue"),], + Button, + Node { + min_width: Val::Px(25.0), + min_height: Val::Px(25.0), + ..default() + }, + DebuggingState::On, + MonologuesContainer, + )) + .id(), + ); + parent + .spawn(( + Name::new("Tree Planter"), + children![Text::new("+Tree"),], + Node { + min_width: Val::Px(25.0), + min_height: Val::Px(25.0), + ..default() + }, + DebuggingState::On, + MonologuesContainer, + Button, + )) + .observe(spawn_tree); + }); + + commands + .spawn(( + NavParent(monologue_button.unwrap()), + NavState::default(), + DebuggingState::On, + Name::new("Container"), + Node { + flex_direction: FlexDirection::Row, + top: Val::Px(25.0), + height: Val::Percent(90.0), + width: Val::Percent(80.0), + ..default() + }, + )) + .with_children(|parent| { + parent + .spawn(( + Name::new("Buttons"), + Node { + flex_direction: FlexDirection::Column, + height: Val::Percent(100.0), + width: Val::Percent(40.0), + overflow: Overflow::scroll_y(), + ..default() + }, + ScrollPosition::default(), + MonologuesList, + )) + .observe(scroll); + + parent.spawn(( + Name::new("Preview"), + MonologuePreview, + NavParent(monologue_button.unwrap()), + NavState::default(), + Node { + flex_direction: FlexDirection::Column, + padding: UiRect::all(Val::Px(10.0)), + overflow: Overflow::scroll_y(), + width: Val::Percent(60.0), + ..default() + }, + )); + }); +} + + +// 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>, + nav_children: Query<&NavParent>, + children: Query<&ChildOf>, + nav_parents: Query<&NavChildren>, + parents: Query<&Children>, + mut nav: Query<&mut NavState>, + hover_map: Res, +) { + over_events.read().for_each(|over| { + let root = nav_children.root_ancestor(over.target); + + nav_parents.iter_descendants(root).for_each(|child| { + if let Ok(mut n) = nav.get_mut(child) { + *n = NavState::Open; + } + }); + }); + + // Gives us the enities 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 { + if let Ok(mut n) = nav.get_mut(root) { + *n = NavState::Closed; + } + parents.iter_descendants(root).for_each(|child| { + if let Ok(mut n) = nav.get_mut(child) { + *n = NavState::Closed; + } + }) + } + }) +} + +/// When a (Text) dialog option is added: +/// 1. Add the Button component +/// 2. Change the color to Orange +/// 3. Add observers for click (select) and hover (change color) +fn add_dialog_option(trigger: Trigger, mut commands: Commands) { + commands + .entity(trigger.target()) + .insert(Button) + .insert(Node { + width: Val::Percent(100.0), + ..default() + }) + .insert(TextLayout::new_with_justify(JustifyText::Center)) + .insert(TextColor(ORANGE.into())) + .observe(choose_dialog_option) + .observe(hover_dialog_option_over) + .observe(hover_dialog_option_out); +} + + +fn assign_monologue_event( + trigger: Trigger>, + mut events: EventWriter, + monologues: Query<&TreeMonologue>, +) { + let TreeMonologue(handle) = monologues.get(trigger.target()).unwrap(); + events.write(AssignMonologue(handle.clone())); +} + + +/// Observer for the "Plant a new tree" button in the debug UI +fn spawn_tree(_trigger: Trigger>, mut events: EventWriter) { + events.write(PlantTree(None)); +} + + +fn clear_monologue( + mut nodes: Query< + (Entity, &NavState), + (Changed, With, Without), + >, + mut commands: Commands, +) { + nodes.iter_mut().for_each(|(e, n)| { + if matches!(n, NavState::Closed) { + commands.entity(e).despawn_related::(); + } + }); +} + + +// When you pointer over the '+' make the entire menu visible +fn hide_menu(mut nodes: Query<(&mut Visibility, &NavState), Changed>) { + nodes.iter_mut().for_each(|(mut v, n)| { + *v = match n { + NavState::Open => Visibility::Inherited, + NavState::Closed => Visibility::Hidden, + }; + }); +} + +fn delete_tree(mut events: EventReader>, mut commands: Commands, query: Query>) { + events.read().for_each(|event| { + if matches!(event.event.button, PointerButton::Middle) && query.contains(event.target) { + debug!("Middle Click -> Despawning {}", event.target); + commands.entity(event.target).despawn(); + } + }) +} + +/// Add the "script: path/to/file.mono" tooltip info +fn monologue_asset_tooltip( + mut over_events: EventReader>, + mut out_events: EventReader>, + mut tooltip: ResMut, + trees: Query<(&Tree, Option<&TreeMonologue>)>, +) { + out_events + .read() + .filter_map(|Pointer { target, .. }| trees.contains(*target).then_some(*target)) + .for_each(|_| { + tooltip.remove("Script"); + }); + + over_events + .read() + .filter_map(|Pointer { target, .. }| trees.contains(*target).then_some(*target)) + .for_each(|e| match trees.get(e) { + Ok((_tree, Some(TreeMonologue(handle)))) => match handle.path() { + Some(p) => tooltip.insert("Script", format!("{p}")), + None => tooltip.insert("Script", "A".into()), + }, + Ok((_tree, None)) => { + tooltip.insert("Script", "N/A".into()); + } + _ => (), + }); +} + +/// When a dialog option is chosen (clicked on) we do the following: +fn choose_dialog_option( + trigger: Trigger>, + mut dialog_events: EventWriter, + mut commands: Commands, + texts: Query<&Text>, + options: Query>, + dialog_box: Single>, +) { + debug!("Choosing dialog {:?}", trigger.target()); + + debug!("Despawning dialog options"); + options.iter().for_each(|e| { + commands.entity(e).despawn(); + }); + + debug!("Inserting dialog line"); + if let Ok(t) = texts.get(trigger.target()) { + commands.entity(*dialog_box).with_children(|parent| { + parent.spawn((t.clone(), DialogLine)); + }); + } + + // trigger the next dialog line + dialog_events.write(DialogEvent::NextBatch); +} + +fn preview_monologue( + trigger: Trigger>, + container: Single, Without