From 17c4ec516fc2f375e2ff60ae4b827802ec233971 Mon Sep 17 00:00:00 2001 From: Idan Arye Date: Mon, 20 May 2024 18:44:18 +0300 Subject: [PATCH] [demos] Add info "tab" to GUI, and show in it the names of the objects the character stands on. --- demos/Cargo.toml | 3 +- demos/src/bin/platformer_2d.rs | 15 ++++- demos/src/bin/platformer_3d.rs | 16 ++++- demos/src/bin/shooter_like.rs | 15 ++++- .../info_dumpeing_systems.rs | 42 ++++++++++++ demos/src/character_control_systems/mod.rs | 1 + demos/src/levels_setup/for_2d_platformer.rs | 35 +++++++--- demos/src/levels_setup/for_3d_platformer.rs | 38 +++++++---- demos/src/ui/info.rs | 66 +++++++++++++++++++ demos/src/ui/mod.rs | 54 +++++++++++---- 10 files changed, 245 insertions(+), 40 deletions(-) create mode 100644 demos/src/character_control_systems/info_dumpeing_systems.rs create mode 100644 demos/src/ui/info.rs diff --git a/demos/Cargo.toml b/demos/Cargo.toml index 2556ee2af..ec7cb55bf 100644 --- a/demos/Cargo.toml +++ b/demos/Cargo.toml @@ -14,7 +14,7 @@ default = [ "bevy_xpbd_3d?/parry-f32", "bevy_xpbd_2d?/parry-f32", ] -egui = ["dep:bevy_egui", "dep:egui_plot"] +egui = ["dep:bevy_egui", "dep:egui_plot", "dep:egui_extras"] rapier = [] rapier2d = ["rapier", "dep:bevy_rapier2d", "dep:bevy-tnua-rapier2d"] rapier3d = ["rapier", "dep:bevy_rapier3d", "dep:bevy-tnua-rapier3d"] @@ -61,6 +61,7 @@ bevy-tnua-xpbd3d = { path = "../xpbd3d", default-features = false, optional = tr bevy_egui = { version = "0.27", optional = true, default-features = false, features = ["default_fonts", "render"] } egui_plot = { version = "0.27", optional = true } +egui_extras = { version = "0.27", optional = true } clap = { version = "^4", features = ["derive"] } diff --git a/demos/src/bin/platformer_2d.rs b/demos/src/bin/platformer_2d.rs index 5af223dc9..95bb1826d 100644 --- a/demos/src/bin/platformer_2d.rs +++ b/demos/src/bin/platformer_2d.rs @@ -19,6 +19,7 @@ use bevy_tnua_xpbd2d::*; use bevy_xpbd_2d::{prelude as xpbd, prelude::*, PhysicsSchedule}; use tnua_demos_crate::app_setup_options::{AppSetupConfiguration, ScheduleToUse}; +use tnua_demos_crate::character_control_systems::info_dumpeing_systems::character_control_info_dumping_system; use tnua_demos_crate::character_control_systems::platformer_control_systems::{ apply_platformer_controls, CharacterMotionConfigForPlatformerDemo, FallingThroughControlScheme, }; @@ -26,8 +27,10 @@ use tnua_demos_crate::character_control_systems::Dimensionality; #[cfg(feature = "xpbd2d")] use tnua_demos_crate::levels_setup::for_2d_platformer::LayerNames; use tnua_demos_crate::ui::component_alterbation::CommandAlteringSelectors; +use tnua_demos_crate::ui::info::InfoSource; #[cfg(feature = "egui")] use tnua_demos_crate::ui::plotting::PlotSource; +use tnua_demos_crate::ui::DemoInfoUpdateSystemSet; use tnua_demos_crate::MovingPlatformPlugin; fn main() { @@ -101,6 +104,11 @@ fn main() { } } + #[cfg(feature = "egui")] + app.add_systems( + Update, + character_control_info_dumping_system.in_set(DemoInfoUpdateSystemSet), + ); app.add_plugins(tnua_demos_crate::ui::DemoUi::< CharacterMotionConfigForPlatformerDemo, >::default()); @@ -339,7 +347,10 @@ fn setup_player(mut commands: Commands) { // This helper keeps track of air actions like jumps or air dashes. cmd.insert(TnuaSimpleAirActionsCounter::default()); - cmd.insert(tnua_demos_crate::ui::TrackedEntity("Player".to_owned())); #[cfg(feature = "egui")] - cmd.insert(PlotSource::default()); + cmd.insert(( + tnua_demos_crate::ui::TrackedEntity("Player".to_owned()), + PlotSource::default(), + InfoSource::default(), + )); } diff --git a/demos/src/bin/platformer_3d.rs b/demos/src/bin/platformer_3d.rs index 9b8c1c824..df7e3aa78 100644 --- a/demos/src/bin/platformer_3d.rs +++ b/demos/src/bin/platformer_3d.rs @@ -22,6 +22,7 @@ use tnua_demos_crate::app_setup_options::{AppSetupConfiguration, ScheduleToUse}; use tnua_demos_crate::character_animating_systems::platformer_animating_systems::{ animate_platformer_character, AnimationState, }; +use tnua_demos_crate::character_control_systems::info_dumpeing_systems::character_control_info_dumping_system; use tnua_demos_crate::character_control_systems::platformer_control_systems::{ apply_platformer_controls, CharacterMotionConfigForPlatformerDemo, FallingThroughControlScheme, }; @@ -29,8 +30,10 @@ use tnua_demos_crate::character_control_systems::Dimensionality; #[cfg(feature = "xpbd3d")] use tnua_demos_crate::levels_setup::for_3d_platformer::LayerNames; use tnua_demos_crate::ui::component_alterbation::CommandAlteringSelectors; +use tnua_demos_crate::ui::info::InfoSource; #[cfg(feature = "egui")] use tnua_demos_crate::ui::plotting::PlotSource; +use tnua_demos_crate::ui::DemoInfoUpdateSystemSet; use tnua_demos_crate::util::animating::{animation_patcher_system, GltfSceneHandler}; use tnua_demos_crate::MovingPlatformPlugin; @@ -101,6 +104,11 @@ fn main() { } } + #[cfg(feature = "egui")] + app.add_systems( + Update, + character_control_info_dumping_system.in_set(DemoInfoUpdateSystemSet), + ); app.add_plugins(tnua_demos_crate::ui::DemoUi::< CharacterMotionConfigForPlatformerDemo, >::default()); @@ -364,7 +372,9 @@ fn setup_player(mut commands: Commands, asset_server: Res) { cmd.insert(TnuaSimpleAirActionsCounter::default()); #[cfg(feature = "egui")] - cmd.insert(tnua_demos_crate::ui::TrackedEntity("Player".to_owned())); - #[cfg(feature = "egui")] - cmd.insert(PlotSource::default()); + cmd.insert(( + tnua_demos_crate::ui::TrackedEntity("Player".to_owned()), + PlotSource::default(), + InfoSource::default(), + )); } diff --git a/demos/src/bin/shooter_like.rs b/demos/src/bin/shooter_like.rs index b66e3c35c..8a0cd3d6e 100644 --- a/demos/src/bin/shooter_like.rs +++ b/demos/src/bin/shooter_like.rs @@ -23,6 +23,7 @@ use tnua_demos_crate::app_setup_options::{AppSetupConfiguration, ScheduleToUse}; use tnua_demos_crate::character_animating_systems::platformer_animating_systems::{ animate_platformer_character, AnimationState, }; +use tnua_demos_crate::character_control_systems::info_dumpeing_systems::character_control_info_dumping_system; use tnua_demos_crate::character_control_systems::platformer_control_systems::{ apply_platformer_controls, CharacterMotionConfigForPlatformerDemo, FallingThroughControlScheme, ForwardFromCamera, @@ -31,8 +32,10 @@ use tnua_demos_crate::character_control_systems::Dimensionality; #[cfg(feature = "xpbd3d")] use tnua_demos_crate::levels_setup::for_3d_platformer::LayerNames; use tnua_demos_crate::ui::component_alterbation::CommandAlteringSelectors; +use tnua_demos_crate::ui::info::InfoSource; #[cfg(feature = "egui")] use tnua_demos_crate::ui::plotting::PlotSource; +use tnua_demos_crate::ui::DemoInfoUpdateSystemSet; use tnua_demos_crate::util::animating::{animation_patcher_system, GltfSceneHandler}; use tnua_demos_crate::MovingPlatformPlugin; @@ -103,6 +106,11 @@ fn main() { } } + #[cfg(feature = "egui")] + app.add_systems( + Update, + character_control_info_dumping_system.in_set(DemoInfoUpdateSystemSet), + ); app.add_plugins(tnua_demos_crate::ui::DemoUi::< CharacterMotionConfigForPlatformerDemo, >::default()); @@ -377,9 +385,12 @@ fn setup_player(mut commands: Commands, asset_server: Res) { // This helper keeps track of air actions like jumps or air dashes. cmd.insert(TnuaSimpleAirActionsCounter::default()); - cmd.insert(tnua_demos_crate::ui::TrackedEntity("Player".to_owned())); #[cfg(feature = "egui")] - cmd.insert(PlotSource::default()); + cmd.insert(( + tnua_demos_crate::ui::TrackedEntity("Player".to_owned()), + PlotSource::default(), + InfoSource::default(), + )); } fn grab_ungrab_mouse( diff --git a/demos/src/character_control_systems/info_dumpeing_systems.rs b/demos/src/character_control_systems/info_dumpeing_systems.rs new file mode 100644 index 000000000..17918d6a1 --- /dev/null +++ b/demos/src/character_control_systems/info_dumpeing_systems.rs @@ -0,0 +1,42 @@ +use bevy::prelude::*; +use bevy_tnua::{TnuaGhostSensor, TnuaProximitySensor}; + +use crate::ui::info::InfoSource; + +pub fn character_control_info_dumping_system( + mut query: Query<( + &mut InfoSource, + &TnuaProximitySensor, + Option<&TnuaGhostSensor>, + )>, + names_query: Query<&Name>, +) { + for (mut info_source, sensor, ghost_sensor) in query.iter_mut() { + if !info_source.is_active() { + continue; + } + if let Some(sensor_output) = sensor.output.as_ref() { + if let Ok(name) = names_query.get(sensor_output.entity) { + info_source.label("Standing on", name.as_str()); + } else { + info_source.label("Standing on", format!("{:?}", sensor_output.entity)); + } + } else { + info_source.label("Standing on", ""); + } + if let Some(ghost_sensor) = ghost_sensor.as_ref() { + let mut text = String::new(); + for hit in ghost_sensor.iter() { + if !text.is_empty() { + text.push_str(", "); + } + if let Ok(name) = names_query.get(hit.entity) { + text.push_str(name.as_str()); + } else { + text.push_str(&format!("{:?}", hit.entity)); + } + } + info_source.label("Ghost sensor", text); + } + } +} diff --git a/demos/src/character_control_systems/mod.rs b/demos/src/character_control_systems/mod.rs index 43de26b65..07bfc64ae 100644 --- a/demos/src/character_control_systems/mod.rs +++ b/demos/src/character_control_systems/mod.rs @@ -1,3 +1,4 @@ +pub mod info_dumpeing_systems; pub mod platformer_control_systems; #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/demos/src/levels_setup/for_2d_platformer.rs b/demos/src/levels_setup/for_2d_platformer.rs index 115d6183b..c6e570916 100644 --- a/demos/src/levels_setup/for_2d_platformer.rs +++ b/demos/src/levels_setup/for_2d_platformer.rs @@ -19,7 +19,7 @@ pub enum LayerNames { } pub fn setup_level(mut commands: Commands, asset_server: Res) { - let mut cmd = commands.spawn_empty(); + let mut cmd = commands.spawn(Name::new("Floor")); cmd.insert(SpriteBundle { sprite: Sprite { custom_size: Some(Vec2::new(128.0, 0.5)), @@ -36,20 +36,34 @@ pub fn setup_level(mut commands: Commands, asset_server: Res) { cmd.insert(xpbd::Collider::halfspace(Vector2::Y)); } - for ([width, height], transform) in [ + for (name, [width, height], transform) in [ ( + "Moderate Slope", [10.0, 0.1], Transform::from_xyz(7.0, 7.0, 0.0).with_rotation(Quat::from_rotation_z(0.6)), ), ( + "Steep Slope", [10.0, 0.1], Transform::from_xyz(14.0, 14.0, 0.0).with_rotation(Quat::from_rotation_z(1.0)), ), - ([4.0, 2.0], Transform::from_xyz(-4.0, 1.0, 0.0)), - ([6.0, 1.0], Transform::from_xyz(-10.0, 4.0, 0.0)), - ([6.0, 1.0], Transform::from_xyz(-20.0, 2.6, 0.0)), + ( + "Box to Step on", + [4.0, 2.0], + Transform::from_xyz(-4.0, 1.0, 0.0), + ), + ( + "Floating Box", + [6.0, 1.0], + Transform::from_xyz(-10.0, 4.0, 0.0), + ), + ( + "Box to Crawl Under", + [6.0, 1.0], + Transform::from_xyz(-20.0, 2.6, 0.0), + ), ] { - let mut cmd = commands.spawn_empty(); + let mut cmd = commands.spawn(Name::new(name)); cmd.insert(SpriteBundle { sprite: Sprite { custom_size: Some(Vec2::new(width, height)), @@ -72,8 +86,8 @@ pub fn setup_level(mut commands: Commands, asset_server: Res) { } // Fall-through platforms - for y in [5.0, 7.5] { - let mut cmd = commands.spawn_empty(); + for (i, y) in [5.0, 7.5].into_iter().enumerate() { + let mut cmd = commands.spawn(Name::new(format!("Fall Through #{}", i + 1))); cmd.insert(SpriteBundle { sprite: Sprite { custom_size: Some(Vec2::new(6.0, 0.5)), @@ -104,6 +118,7 @@ pub fn setup_level(mut commands: Commands, asset_server: Res) { } commands.spawn(( + Name::new("Collision Groups"), TransformBundle::from_transform(Transform::from_xyz(10.0, 2.0, 0.0)), #[cfg(feature = "rapier2d")] ( @@ -137,6 +152,7 @@ pub fn setup_level(mut commands: Commands, asset_server: Res) { #[cfg(feature = "rapier2d")] { commands.spawn(( + Name::new("Solver Groups"), TransformBundle::from_transform(Transform::from_xyz(15.0, 2.0, 0.0)), rapier::Collider::ball(1.0), SolverGroups { @@ -160,6 +176,7 @@ pub fn setup_level(mut commands: Commands, asset_server: Res) { } commands.spawn(( + Name::new("Sensor"), TransformBundle::from_transform(Transform::from_xyz(20.0, 2.0, 0.0)), #[cfg(feature = "rapier2d")] (rapier::Collider::ball(1.0), rapier::Sensor), @@ -186,7 +203,7 @@ pub fn setup_level(mut commands: Commands, asset_server: Res) { // spawn moving platform { - let mut cmd = commands.spawn_empty(); + let mut cmd = commands.spawn(Name::new("Moving Platform")); cmd.insert(SpriteBundle { sprite: Sprite { custom_size: Some(Vec2::new(4.0, 1.0)), diff --git a/demos/src/levels_setup/for_3d_platformer.rs b/demos/src/levels_setup/for_3d_platformer.rs index d7e04a4d5..d9e1de203 100644 --- a/demos/src/levels_setup/for_3d_platformer.rs +++ b/demos/src/levels_setup/for_3d_platformer.rs @@ -24,7 +24,7 @@ pub fn setup_level( mut materials: ResMut>, asset_server: Res, ) { - let mut cmd = commands.spawn_empty(); + let mut cmd = commands.spawn(Name::new("Floor")); cmd.insert(PbrBundle { mesh: meshes.add(Plane3d::default().mesh().size(128.0, 128.0)), material: materials.add(Color::WHITE), @@ -39,20 +39,34 @@ pub fn setup_level( } let obstacles_material = materials.add(Color::GRAY); - for ([width, height, depth], transform) in [ + for (name, [width, height, depth], transform) in [ ( + "Moderate Slope", [10.0, 0.1, 2.0], Transform::from_xyz(7.0, 7.0, 0.0).with_rotation(Quat::from_rotation_z(0.6)), ), ( + "Steep Slope", [10.0, 0.1, 2.0], Transform::from_xyz(14.0, 14.0, 0.0).with_rotation(Quat::from_rotation_z(1.0)), ), - ([4.0, 2.0, 2.0], Transform::from_xyz(-4.0, 1.0, 0.0)), - ([6.0, 1.0, 2.0], Transform::from_xyz(-10.0, 4.0, 0.0)), - ([6.0, 1.0, 2.0], Transform::from_xyz(0.0, 2.6, -5.0)), + ( + "Box to Step on", + [4.0, 2.0, 2.0], + Transform::from_xyz(-4.0, 1.0, 0.0), + ), + ( + "Floating Box", + [6.0, 1.0, 2.0], + Transform::from_xyz(-10.0, 4.0, 0.0), + ), + ( + "Box to Crawl Under", + [6.0, 1.0, 2.0], + Transform::from_xyz(0.0, 2.6, -5.0), + ), ] { - let mut cmd = commands.spawn_empty(); + let mut cmd = commands.spawn(Name::new(name)); cmd.insert(PbrBundle { mesh: meshes.add(Cuboid::new(width, height, depth)), material: obstacles_material.clone(), @@ -78,8 +92,8 @@ pub fn setup_level( // Fall-through platforms let fall_through_obstacles_material = materials.add(Color::PINK.with_a(0.8)); - for y in [2.0, 4.5] { - let mut cmd = commands.spawn_empty(); + for (i, y) in [2.0, 4.5].into_iter().enumerate() { + let mut cmd = commands.spawn(Name::new(format!("Fall Through #{}", i + 1))); cmd.insert(PbrBundle { mesh: meshes.add(Cuboid::new(6.0, 0.5, 2.0)), material: fall_through_obstacles_material.clone(), @@ -107,6 +121,7 @@ pub fn setup_level( } commands.spawn(( + Name::new("Collision Groups"), SceneBundle { scene: asset_server.load("collision-groups-text.glb#Scene0"), transform: Transform::from_xyz(10.0, 2.0, 1.0), // .with_scale(0.01 * Vec3::ONE), @@ -130,6 +145,7 @@ pub fn setup_level( #[cfg(feature = "rapier3d")] commands.spawn(( + Name::new("Solver Groups"), SceneBundle { scene: asset_server.load("solver-groups-text.glb#Scene0"), transform: Transform::from_xyz(15.0, 2.0, 1.0), // .with_scale(0.01 * Vec3::ONE), @@ -143,6 +159,7 @@ pub fn setup_level( )); commands.spawn(( + Name::new("Sensor"), SceneBundle { scene: asset_server.load("sensor-text.glb#Scene0"), transform: Transform::from_xyz(20.0, 2.0, 1.0), // .with_scale(0.01 * Vec3::ONE), @@ -160,8 +177,7 @@ pub fn setup_level( // spawn moving platform { - let mut cmd = commands.spawn_empty(); - + let mut cmd = commands.spawn(Name::new("Moving Platform")); cmd.insert(PbrBundle { mesh: meshes.add(Cuboid::new(4.0, 1.0, 4.0)), material: materials.add(Color::BLUE), @@ -194,7 +210,7 @@ pub fn setup_level( // spawn spinning platform { - let mut cmd = commands.spawn_empty(); + let mut cmd = commands.spawn(Name::new("Spinning Platform")); cmd.insert(PbrBundle { mesh: meshes.add(Cylinder { diff --git a/demos/src/ui/info.rs b/demos/src/ui/info.rs new file mode 100644 index 000000000..7c3281aa8 --- /dev/null +++ b/demos/src/ui/info.rs @@ -0,0 +1,66 @@ +use std::collections::BTreeMap; + +use bevy::prelude::*; +use bevy_egui::egui; + +#[derive(Component, Default)] +pub struct InfoSource { + is_active: bool, + data: BTreeMap>, +} + +pub enum InfoBit { + Label(egui::WidgetText), +} + +impl InfoSource { + pub fn is_active(&self) -> bool { + self.is_active + } + + pub fn set_active(&mut self, active: bool) { + self.is_active = active; + if !active { + self.data.clear(); + } + } + + pub(crate) fn show(&mut self, _entity: Entity, ui: &mut egui::Ui) { + egui_extras::TableBuilder::new(ui) + .striped(true) + .column(egui_extras::Column::auto().at_least(80.0).resizable(true)) + .column(egui_extras::Column::remainder()) + .body(|mut body| { + self.data.retain(|key, info_bit| { + let Some(info_bit) = info_bit.take() else { + return false; + }; + + body.row(30.0, |mut row| { + row.col(|ui| { + ui.strong(key); + }); + row.col(|ui| match info_bit { + InfoBit::Label(label) => { + ui.label(label); + } + }); + }); + + true + }); + }); + } + + fn set_info_bit(&mut self, key: &str, info_bit: InfoBit) { + if let Some(value) = self.data.get_mut(key) { + *value = Some(info_bit); + } else { + self.data.insert(key.to_owned(), Some(info_bit)); + } + } + + pub fn label(&mut self, key: &str, label: impl Into) { + self.set_info_bit(key, InfoBit::Label(label.into())); + } +} diff --git a/demos/src/ui/mod.rs b/demos/src/ui/mod.rs index 0ea016a62..d7814f3c2 100644 --- a/demos/src/ui/mod.rs +++ b/demos/src/ui/mod.rs @@ -1,4 +1,5 @@ pub mod component_alterbation; +pub mod info; #[cfg(feature = "egui")] pub mod plotting; pub mod tuning; @@ -23,6 +24,9 @@ use self::plotting::{make_update_plot_data_system, plot_source_rolling_update}; use tuning::UiTunable; +#[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)] +pub struct DemoInfoUpdateSystemSet; + pub struct DemoUi { _phantom: PhantomData, } @@ -40,9 +44,13 @@ impl Plugin for DemoUi { #[cfg(feature = "egui")] app.add_plugins(EguiPlugin); app.insert_resource(DemoUiPhysicsBackendActive(true)); + app.configure_sets( + Update, + DemoInfoUpdateSystemSet.after(bevy_tnua::TnuaUserControlsSystemSet), + ); app.add_systems(Update, apply_selectors); #[cfg(feature = "egui")] - app.add_systems(Update, ui_system::); + app.add_systems(Update, ui_system::.after(DemoInfoUpdateSystemSet)); #[cfg(feature = "egui")] app.add_systems(Update, plot_source_rolling_update); #[cfg(feature = "egui")] @@ -110,7 +118,8 @@ fn ui_system( mut query: Query<( Entity, &TrackedEntity, - &plotting::PlotSource, + Option<&plotting::PlotSource>, + Option<&mut info::InfoSource>, &mut TnuaToggle, Option<&mut C>, Option<&mut CommandAlteringSelectors>, @@ -186,6 +195,7 @@ fn ui_system( entity, TrackedEntity(name), plot_source, + mut info_source, mut tnua_toggle, mut tunable, command_altering_selectors, @@ -198,6 +208,7 @@ fn ui_system( #[default] Settings, Plots, + Info, } let thing_to_show_id = ui.make_persistent_id((TypeId::of::(), entity)); @@ -207,23 +218,31 @@ fn ui_system( let mut collapse_state = collapse_state.show_header(ui, |ui| { ui.label(name); - for (option, text) in [ - (ThingToShow::Settings, "settings"), - (ThingToShow::Plots, "plots"), + for (possible, option, text) in [ + (true, ThingToShow::Settings, "settings"), + (plot_source.is_some(), ThingToShow::Plots, "plots"), + (info_source.is_some(), ThingToShow::Info, "info"), ] { let mut selected = is_open && option == thing_to_show; - if ui.toggle_value(&mut selected, text).changed() { - set_open = Some(selected); - if selected { - thing_to_show = option; - ui.memory_mut(|mem| *mem.data.get_temp_mut_or_default::(thing_to_show_id) = option); + ui.add_enabled_ui(possible, |ui| { + if ui.toggle_value(&mut selected, text).changed() { + set_open = Some(selected); + if selected { + thing_to_show = option; + ui.memory_mut(|mem| *mem.data.get_temp_mut_or_default::(thing_to_show_id) = option); + } } - } + }); } }); if let Some(set_open) = set_open { collapse_state.set_open(set_open); } + + if let Some(info_source) = info_source.as_mut() { + info_source.set_active(collapse_state.is_open() && thing_to_show == ThingToShow::Info); + } + collapse_state.body(|ui| { match thing_to_show { ThingToShow::Settings => { @@ -250,7 +269,18 @@ fn ui_system( } } ThingToShow::Plots => { - plot_source.show(entity, ui); + if let Some(plot_source) = plot_source { + plot_source.show(entity, ui); + } else { + ui.colored_label(egui::Color32::DARK_RED, "No plotting configured for this entity"); + } + } + ThingToShow::Info => { + if let Some(info_source) = info_source.as_mut() { + info_source.show(entity, ui); + } else { + ui.colored_label(egui::Color32::DARK_RED, "No info configured for this entity"); + } } } });