Generic window system

This commit is contained in:
Tobias Berger 2022-11-06 17:07:21 +01:00
parent 489733bd50
commit 2ffdd87712
Signed by: toby
GPG key ID: 2D05EFAB764D6A88
10 changed files with 200 additions and 34 deletions

View file

@ -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
echo "Release-build features: logging,globe_view" &&
cargo build --release --no-default-features --features=logging,globe_view &&
echo "Done!"

View file

@ -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;

View file

@ -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;

View file

@ -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::<Assets<Image>>());
},
ToolbarButton::PlanetView => {
ToolbarButton::Overlays => {
open_window::<Overlay>(&mut world.resource_mut::<OpenedWindows>());
},
ToolbarButton::ToggleBiomes => {
world_manager.cycle_view();
update_textures(&world_manager, &mut world.resource_mut::<Assets<Image>>());
},
@ -129,7 +135,8 @@ impl From<ToolbarButton> 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",

105
src/gui/window.rs Normal file
View file

@ -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<Self>, 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::<windows::Overlay>(world, ctx);
window::<windows::TileInfo>(world, ctx);
}
pub(crate) fn open_window<S: 'static + WindowSystem>(windows: &mut OpenedWindows) {
windows.open(S::name().into());
}
pub(crate) fn close_window<S: 'static + WindowSystem>(windows: &mut OpenedWindows) {
windows.close(&S::name().into());
}
fn window<S: 'static + WindowSystem>(world: &mut World, ctx: &Context) {
// We need to cache `SystemState` to allow for a system's locally tracked state
if !world.contains_resource::<StateInstances<S>>() {
// 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::<S>());
world.insert_resource(StateInstances::<S> {
instances: HashMap::new(),
});
}
world.resource_scope(|world, mut states: Mut<'_, StateInstances<S>>| {
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::<S>()
);
_ = 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::<OpenedWindows>().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::<S>(&mut world.resource_mut::<OpenedWindows>());
}
cached_state.apply(world);
}
});
}
#[derive(Default)]
struct StateInstances<T: WindowSystem> {
instances: HashMap<WindowId, SystemState<T>>,
}
#[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)
}
}

4
src/gui/windows/mod.rs Normal file
View file

@ -0,0 +1,4 @@
mod overlay;
pub(crate) use overlay::Overlay;
mod tile_info;
pub(crate) use tile_info::TileInfo;

View file

@ -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<Self>, ui: &mut Ui) {
ui.label(format!("{world:#?}"));
}
fn name() -> &'static str {
"Overlay Selection"
}
}

View file

@ -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<Self>, 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<Self>, 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"
}
}

View file

@ -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::<InfoPanel<'_, '_>>(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::<ToolbarWidget<'_, '_>>(world, ui, "Toolbar".into());
});
render_windows(world, ctx);
});
}
@ -310,6 +304,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
..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);

View file

@ -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<WindowId>);
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)
}
}