From 2ffdd8771223ee111574d6d53c52c41639777649 Mon Sep 17 00:00:00 2001 From: Tobias Berger Date: Sun, 6 Nov 2022 17:07:21 +0100 Subject: [PATCH] Generic window system --- build_all.sh.ps1 | 15 ++- src/gui/mod.rs | 6 +- src/gui/widgets/mod.rs | 4 +- src/gui/widgets/toolbar.rs | 17 ++- src/gui/window.rs | 105 ++++++++++++++++++ src/gui/windows/mod.rs | 4 + src/gui/windows/overlay.rs | 25 +++++ .../info_panel.rs => windows/tile_info.rs} | 17 ++- src/main.rs | 17 +-- src/resources/mod.rs | 24 +++- 10 files changed, 200 insertions(+), 34 deletions(-) create mode 100644 src/gui/window.rs create mode 100644 src/gui/windows/mod.rs create mode 100644 src/gui/windows/overlay.rs rename src/gui/{widgets/info_panel.rs => windows/tile_info.rs} (85%) diff --git a/build_all.sh.ps1 b/build_all.sh.ps1 index 9554274..0382f31 100755 --- a/build_all.sh.ps1 +++ b/build_all.sh.ps1 @@ -1,13 +1,26 @@ #!/bin/env /bin/sh +echo "Debug-build features: minimal" cargo build --no-default-features --features= && +echo "Debug-build features: logging" && cargo build --no-default-features --features=logging && +echo "Debug-build features: render" && cargo build --no-default-features --features=render && +echo "Debug-build features: logging,render" && cargo build --no-default-features --features=logging,render && +echo "Debug-build features: globe_view" && cargo build --no-default-features --features=globe_view && +echo "Debug-build features: logging,globe_view" && cargo build --no-default-features --features=logging,globe_view && +echo "Release-build features: minimal" cargo build --release --no-default-features --features= && +echo "Release-build features: logging" && cargo build --release --no-default-features --features=logging && +echo "Release-build features: render" && cargo build --release --no-default-features --features=render && +echo "Release-build features: logging,render" && cargo build --release --no-default-features --features=logging,render && +echo "Release-build features: globe_view" && cargo build --release --no-default-features --features=globe_view && -cargo build --release --no-default-features --features=logging,globe_view \ No newline at end of file +echo "Release-build features: logging,globe_view" && +cargo build --release --no-default-features --features=logging,globe_view && +echo "Done!" \ No newline at end of file diff --git a/src/gui/mod.rs b/src/gui/mod.rs index aae4e4c..1bca875 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -1,3 +1,7 @@ pub(crate) mod widget; -pub(crate) mod widgets; pub(crate) use widget::*; +pub(crate) mod window; +pub(crate) use window::*; + +pub(crate) mod widgets; +pub(crate) mod windows; diff --git a/src/gui/widgets/mod.rs b/src/gui/widgets/mod.rs index 7e24c3c..65bcf96 100644 --- a/src/gui/widgets/mod.rs +++ b/src/gui/widgets/mod.rs @@ -1,4 +1,2 @@ -pub(crate) mod info_panel; -pub(crate) use info_panel::InfoPanel; -pub(crate) mod toolbar; +mod toolbar; pub(crate) use toolbar::ToolbarWidget; diff --git a/src/gui/widgets/toolbar.rs b/src/gui/widgets/toolbar.rs index 60a9400..e068a2b 100644 --- a/src/gui/widgets/toolbar.rs +++ b/src/gui/widgets/toolbar.rs @@ -9,8 +9,9 @@ use { }; use { crate::{ - gui::{WidgetId, WidgetSystem}, + gui::{open_window, windows::Overlay, WidgetId, WidgetSystem}, macros::iterable_enum, + resources::OpenedWindows, }, bevy::{ asset::Assets, @@ -35,7 +36,8 @@ iterable_enum!(ToolbarButton { LoadWorld, Rainfall, Temperature, - PlanetView, + Overlays, + ToggleBiomes, Contours, }); #[cfg(feature = "globe_view")] @@ -45,7 +47,8 @@ iterable_enum!(ToolbarButton { LoadWorld, Rainfall, Temperature, - PlanetView, + Overlays, + ToggleBiomes, Contours, GlobeView, }); @@ -98,7 +101,10 @@ impl ToolbarButton { world_manager.toggle_temperature(); update_textures(&world_manager, &mut world.resource_mut::>()); }, - ToolbarButton::PlanetView => { + ToolbarButton::Overlays => { + open_window::(&mut world.resource_mut::()); + }, + ToolbarButton::ToggleBiomes => { world_manager.cycle_view(); update_textures(&world_manager, &mut world.resource_mut::>()); }, @@ -129,7 +135,8 @@ impl From for &'static str { ToolbarButton::Rainfall => "Toggle rainfall", ToolbarButton::Temperature => "Toggle temperature", ToolbarButton::Contours => "Toggle contours", - ToolbarButton::PlanetView => "Cycle view", + ToolbarButton::Overlays => "Overlays", + ToolbarButton::ToggleBiomes => "Toggle biome view", ToolbarButton::GenerateWorld => "Generate new world", ToolbarButton::SaveWorld => "Save", ToolbarButton::LoadWorld => "Load", diff --git a/src/gui/window.rs b/src/gui/window.rs new file mode 100644 index 0000000..f13b4df --- /dev/null +++ b/src/gui/window.rs @@ -0,0 +1,105 @@ +use { + super::windows, + crate::resources::OpenedWindows, + bevy::{ + ecs::{ + change_detection::Mut, + system::{SystemParam, SystemState}, + world::World, + }, + log::debug, + utils::HashMap, + }, + bevy_egui::egui::{Context, Ui, Window}, + fxhash::FxHasher32, + std::hash::Hasher, +}; + +pub(crate) trait WindowSystem: SystemParam { + fn draw_contents(world: &mut World, state: &mut SystemState, ui: &mut Ui); + fn name() -> &'static str; +} + +pub(crate) fn render_windows(world: &mut World, ctx: &Context) { + // TODO: Windows are hard-coded here instead of being iterable. + // Is that good enough? Probably, yea. + window::(world, ctx); + window::(world, ctx); +} + +pub(crate) fn open_window(windows: &mut OpenedWindows) { + windows.open(S::name().into()); +} +pub(crate) fn close_window(windows: &mut OpenedWindows) { + windows.close(&S::name().into()); +} + +fn window(world: &mut World, ctx: &Context) { + // We need to cache `SystemState` to allow for a system's locally tracked state + if !world.contains_resource::>() { + // Note, this message should only appear once! If you see it twice in the logs, + // the function may have been called recursively, and will panic. + debug!("Init system state {}", std::any::type_name::()); + world.insert_resource(StateInstances:: { + instances: HashMap::new(), + }); + } + world.resource_scope(|world, mut states: Mut<'_, StateInstances>| { + let id: WindowId = S::name().into(); + if !states.instances.contains_key(&id) { + debug!( + "Registering system state for window {id:?} of type {}", + std::any::type_name::() + ); + _ = states.instances.insert(id, SystemState::new(world)); + } + // Instead of passing this to open, don't render manually. + // Saves fetching states, but might fuck up states? + // TODO: Check that + if world.resource::().is_open(&id) { + let cached_state = states.instances.get_mut(&id).unwrap(); + + let mut still_open = true; + Window::new(S::name()) + .resizable(false) + .open(&mut still_open) + .title_bar(true) + .show(ctx, |ui| { + S::draw_contents(world, cached_state, ui); + }); + if !still_open { + close_window::(&mut world.resource_mut::()); + } + + cached_state.apply(world); + } + }); +} + +#[derive(Default)] +struct StateInstances { + instances: HashMap>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct WindowId(pub(crate) u64); +impl WindowId { + #[must_use] + pub(crate) fn new(name: &str) -> Self { + let bytes = name.as_bytes(); + let mut hasher = FxHasher32::default(); + hasher.write(bytes); + WindowId(hasher.finish()) + } + + // #[must_use] + // pub(crate) fn with(&self, name: &str) -> Self { + // Self::new(&format!("{}{name}", self.0)) + // } +} +impl From<&str> for WindowId { + #[must_use] + fn from(str: &str) -> Self { + Self::new(str) + } +} diff --git a/src/gui/windows/mod.rs b/src/gui/windows/mod.rs new file mode 100644 index 0000000..2cf1f28 --- /dev/null +++ b/src/gui/windows/mod.rs @@ -0,0 +1,4 @@ +mod overlay; +pub(crate) use overlay::Overlay; +mod tile_info; +pub(crate) use tile_info::TileInfo; diff --git a/src/gui/windows/overlay.rs b/src/gui/windows/overlay.rs new file mode 100644 index 0000000..3e4b0ce --- /dev/null +++ b/src/gui/windows/overlay.rs @@ -0,0 +1,25 @@ +use { + crate::gui::WindowSystem, + bevy::ecs::{ + system::{SystemParam, SystemState}, + world::World, + }, + bevy_egui::egui::Ui, + std::marker::PhantomData, +}; + +#[derive(SystemParam)] +pub(crate) struct Overlay<'w, 's> { + #[system_param(ignore)] + _phantom: PhantomData<(&'w (), &'s ())>, +} + +impl WindowSystem for Overlay<'_, '_> { + fn draw_contents(world: &mut World, _state: &mut SystemState, ui: &mut Ui) { + ui.label(format!("{world:#?}")); + } + + fn name() -> &'static str { + "Overlay Selection" + } +} diff --git a/src/gui/widgets/info_panel.rs b/src/gui/windows/tile_info.rs similarity index 85% rename from src/gui/widgets/info_panel.rs rename to src/gui/windows/tile_info.rs index ad3ea86..45af2b5 100644 --- a/src/gui/widgets/info_panel.rs +++ b/src/gui/windows/tile_info.rs @@ -1,8 +1,5 @@ use { - crate::{ - gui::{WidgetId, WidgetSystem}, - resources::CursorMapPosition, - }, + crate::{gui::WindowSystem, resources::CursorMapPosition}, bevy::ecs::{ system::{SystemParam, SystemState}, world::World, @@ -13,15 +10,13 @@ use { }; #[derive(SystemParam)] -pub(crate) struct InfoPanel<'w, 's> { +pub(crate) struct TileInfo<'w, 's> { #[system_param(ignore)] _phantom: PhantomData<(&'w (), &'s ())>, } -impl WidgetSystem for InfoPanel<'_, '_> { - fn system(world: &mut World, _state: &mut SystemState, ui: &mut Ui, _id: WidgetId) { - // This will get everything our system/widget requested - // let mut params = state.get_mut(world); +impl WindowSystem for TileInfo<'_, '_> { + fn draw_contents(world: &mut World, _state: &mut SystemState, ui: &mut Ui) { _ = Grid::new("info_panel") .num_columns(2) .striped(false) @@ -68,4 +63,8 @@ impl WidgetSystem for InfoPanel<'_, '_> { } }); } + + fn name() -> &'static str { + "Tile Info" + } } diff --git a/src/main.rs b/src/main.rs index 9746905..f38baad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ pub(crate) mod components; pub(crate) mod gui; pub(crate) mod macros; pub(crate) mod plugins; +#[cfg(feature = "render")] pub(crate) mod resources; #[cfg(all(feature = "render", feature = "logging"))] @@ -52,11 +53,8 @@ use { EguiContext, }, components::panning::Pan2d, - gui::{ - widget, - widgets::{InfoPanel, ToolbarWidget}, - }, - resources::CursorMapPosition, + gui::{render_windows, widget, widgets::ToolbarWidget}, + resources::{CursorMapPosition, OpenedWindows}, }; #[cfg(all(feature = "render", feature = "globe_view"))] use { @@ -257,12 +255,6 @@ fn generate_graphics( fn update_gui(world: &mut World) { world.resource_scope(|world, mut ctx: Mut<'_, EguiContext>| { let ctx = ctx.ctx_mut(); - _ = bevy_egui::egui::Window::new("Tile Info") - .resizable(false) - .show(ctx, |ui| { - widget::>(world, ui, "Tile Info Panel".into()); - }); - #[cfg(feature = "logging")] { bevy_egui::egui::CentralPanel::default() @@ -287,6 +279,8 @@ fn update_gui(world: &mut World) { .show(ctx, |ui| { widget::>(world, ui, "Toolbar".into()); }); + + render_windows(world, ctx); }); } @@ -310,6 +304,7 @@ fn main() -> Result<(), Box> { ..default() }) .insert_resource(CursorMapPosition::default()) + .insert_resource(OpenedWindows::default()) .add_startup_system(generate_graphics) .add_system(update_gui.exclusive_system()) .add_system(update_cursor_map_position); diff --git a/src/resources/mod.rs b/src/resources/mod.rs index 5dae01b..79d650c 100644 --- a/src/resources/mod.rs +++ b/src/resources/mod.rs @@ -1,15 +1,31 @@ -#[cfg(feature = "render")] -use std::fmt::Display; +use {crate::gui::WindowId, bevy::utils::HashSet, std::fmt::Display}; -#[cfg(feature = "render")] #[derive(Default, Debug)] pub(crate) struct CursorMapPosition { pub(crate) x: i32, pub(crate) y: i32, } -#[cfg(feature = "render")] impl Display for CursorMapPosition { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("x: {}, y: {}", self.x, self.y)) } } + +#[derive(Default)] +pub(crate) struct OpenedWindows(HashSet); + +impl OpenedWindows { + pub(crate) fn open(&mut self, id: WindowId) { + // Ignore opening already opened windows + _ = self.0.insert(id); + } + + pub(crate) fn close(&mut self, id: &WindowId) { + // Ignore closing already closed windows + _ = self.0.remove(id); + } + + pub(crate) fn is_open(&self, id: &WindowId) -> bool { + self.0.contains(id) + } +}