Switch from bevy_ui to bevy_egui
Generic widgets done with inspiration from: https://github.com/bevyengine/bevy/discussions/5522
This commit is contained in:
parent
2a1a8855d8
commit
91018d1d73
12 changed files with 508 additions and 584 deletions
75
Cargo.lock
generated
75
Cargo.lock
generated
|
@ -29,6 +29,18 @@ dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ahash"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57e6e951cfbb2db8de1828d49073a113a29fd7117b1596caa781a258c7e38d72"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"getrandom",
|
||||||
|
"once_cell",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.7.19"
|
version = "0.7.19"
|
||||||
|
@ -126,6 +138,12 @@ version = "4.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524"
|
checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atomic_refcell"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73b5e5f48b927f04e952dedc932f31995a65a0bf65ec971c74436e51bf6e970d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -277,6 +295,16 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bevy_egui"
|
||||||
|
version = "0.16.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d365761fd6a5c227b1f88f38b560287334accb69cfe938443e27615464edc897"
|
||||||
|
dependencies = [
|
||||||
|
"bevy",
|
||||||
|
"egui",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bevy_encase_derive"
|
name = "bevy_encase_derive"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
|
@ -678,7 +706,7 @@ version = "0.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6e9aa1866c1cf7ee000f281ce9e90d02d701f5c7380a107252017e58e2f5246"
|
checksum = "f6e9aa1866c1cf7ee000f281ce9e90d02d701f5c7380a107252017e58e2f5246"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash 0.7.6",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
"instant",
|
"instant",
|
||||||
|
@ -1072,6 +1100,26 @@ version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
|
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "egui"
|
||||||
|
version = "0.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc9fcd393c3daaaf5909008a1d948319d538b79c51871e4df0993260260a94e4"
|
||||||
|
dependencies = [
|
||||||
|
"ahash 0.8.0",
|
||||||
|
"epaint",
|
||||||
|
"nohash-hasher",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "emath"
|
||||||
|
version = "0.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9542a40106fdba943a055f418d1746a050e1a903a049b030c2b097d4686a33cf"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encase"
|
name = "encase"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
@ -1114,6 +1162,21 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "epaint"
|
||||||
|
version = "0.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ba04741be7f6602b1a1b28f1082cce45948a7032961c52814f8946b28493300"
|
||||||
|
dependencies = [
|
||||||
|
"ab_glyph",
|
||||||
|
"ahash 0.8.0",
|
||||||
|
"atomic_refcell",
|
||||||
|
"bytemuck",
|
||||||
|
"emath",
|
||||||
|
"nohash-hasher",
|
||||||
|
"parking_lot 0.12.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "erased-serde"
|
name = "erased-serde"
|
||||||
version = "0.3.23"
|
version = "0.3.23"
|
||||||
|
@ -1366,7 +1429,7 @@ version = "0.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash 0.7.6",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1702,6 +1765,12 @@ dependencies = [
|
||||||
"memoffset",
|
"memoffset",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nohash-hasher"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.1"
|
version = "7.1.1"
|
||||||
|
@ -2768,6 +2837,8 @@ name = "worlds-history-sim-rs"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bevy",
|
"bevy",
|
||||||
|
"bevy_egui",
|
||||||
|
"fxhash",
|
||||||
"planet",
|
"planet",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
16
Cargo.toml
16
Cargo.toml
|
@ -10,13 +10,23 @@ release = { strip = "symbols", lto = "thin", opt-level = "z" }
|
||||||
[features]
|
[features]
|
||||||
logging = ["planet/logging"]
|
logging = ["planet/logging"]
|
||||||
globe_view = ["planet/globe_view", "render"]
|
globe_view = ["planet/globe_view", "render"]
|
||||||
render = ["bevy/bevy_asset", "bevy/bevy_winit", "bevy/x11", "bevy/wayland", "bevy/render", "planet/render"]
|
render = ["bevy/bevy_asset", "bevy/bevy_winit", "bevy/x11", "bevy/wayland", "bevy/render", "planet/render", "dep:fxhash", "dep:bevy_egui"]
|
||||||
default = ["render", "logging"]
|
default = ["render", "logging", "globe_view"]
|
||||||
|
|
||||||
[dependencies.planet]
|
[dependencies.planet]
|
||||||
path = "planet"
|
path = "planet"
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[dependencies.bevy]
|
[dependencies.bevy]
|
||||||
version = "0.8"
|
version = "0.8.1"
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
|
[dependencies.fxhash]
|
||||||
|
version = "0.2.1"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.bevy_egui]
|
||||||
|
version = "0.16.1"
|
||||||
|
optional = true
|
||||||
|
default-features = false
|
||||||
|
# features = ["manage_clipboard"] # In the future, when I add text input.
|
|
@ -1,65 +0,0 @@
|
||||||
#[cfg(feature = "render")]
|
|
||||||
use {crate::macros::iterable_enum, bevy::ecs::component::Component};
|
|
||||||
|
|
||||||
#[cfg(all(feature = "render", not(feature = "globe_view")))]
|
|
||||||
iterable_enum!(ToolbarButton {
|
|
||||||
GenerateWorld,
|
|
||||||
SaveWorld,
|
|
||||||
LoadWorld,
|
|
||||||
Rainfall,
|
|
||||||
Temperature,
|
|
||||||
PlanetView,
|
|
||||||
Contours,
|
|
||||||
});
|
|
||||||
#[cfg(all(feature = "render", feature = "globe_view"))]
|
|
||||||
iterable_enum!(ToolbarButton {
|
|
||||||
GenerateWorld,
|
|
||||||
SaveWorld,
|
|
||||||
LoadWorld,
|
|
||||||
Rainfall,
|
|
||||||
Temperature,
|
|
||||||
PlanetView,
|
|
||||||
Contours,
|
|
||||||
GlobeView,
|
|
||||||
});
|
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
|
||||||
impl From<ToolbarButton> for &'static str {
|
|
||||||
fn from(button: ToolbarButton) -> Self {
|
|
||||||
match button {
|
|
||||||
ToolbarButton::Rainfall => "Toggle rainfall",
|
|
||||||
ToolbarButton::Temperature => "Toggle temperature",
|
|
||||||
ToolbarButton::Contours => "Toggle contours",
|
|
||||||
ToolbarButton::PlanetView => "Cycle view",
|
|
||||||
ToolbarButton::GenerateWorld => "Generate new world",
|
|
||||||
ToolbarButton::SaveWorld => "Save",
|
|
||||||
ToolbarButton::LoadWorld => "Load",
|
|
||||||
#[cfg(feature = "globe_view")]
|
|
||||||
ToolbarButton::GlobeView => "Toggle globe",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(feature = "render")]
|
|
||||||
impl From<&ToolbarButton> for &'static str {
|
|
||||||
fn from(button: &ToolbarButton) -> Self {
|
|
||||||
(*button).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
|
||||||
impl From<ToolbarButton> for String {
|
|
||||||
fn from(button: ToolbarButton) -> Self {
|
|
||||||
<&'static str>::from(button).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
|
||||||
impl From<&ToolbarButton> for String {
|
|
||||||
fn from(button: &ToolbarButton) -> Self {
|
|
||||||
<&'static str>::from(button).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
|
||||||
#[derive(Component)]
|
|
||||||
pub(crate) struct InfoPanel;
|
|
|
@ -1,3 +1,2 @@
|
||||||
pub(crate) mod markers;
|
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
pub(crate) mod panning;
|
pub(crate) mod panning;
|
||||||
|
|
3
src/gui/mod.rs
Normal file
3
src/gui/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub(crate) mod widget;
|
||||||
|
pub(crate) mod widgets;
|
||||||
|
pub(crate) use widget::*;
|
73
src/gui/widget.rs
Normal file
73
src/gui/widget.rs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
use {
|
||||||
|
bevy::{
|
||||||
|
ecs::{
|
||||||
|
change_detection::Mut,
|
||||||
|
system::{SystemParam, SystemState},
|
||||||
|
world::World,
|
||||||
|
},
|
||||||
|
log::debug,
|
||||||
|
utils::HashMap,
|
||||||
|
},
|
||||||
|
bevy_egui::egui::Ui,
|
||||||
|
fxhash::FxHasher32,
|
||||||
|
std::hash::Hasher,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) trait WidgetSystem: SystemParam {
|
||||||
|
fn system(world: &mut World, state: &mut SystemState<Self>, ui: &mut Ui, id: WidgetId);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn widget<S: 'static + WidgetSystem>(world: &mut World, ui: &mut Ui, id: WidgetId) {
|
||||||
|
// 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>>| {
|
||||||
|
if !states.instances.contains_key(&id) {
|
||||||
|
debug!(
|
||||||
|
"Registering system state for widget {id:?} of type {}",
|
||||||
|
std::any::type_name::<S>()
|
||||||
|
);
|
||||||
|
_ = states.instances.insert(id, SystemState::new(world));
|
||||||
|
}
|
||||||
|
let cached_state = states.instances.get_mut(&id).unwrap();
|
||||||
|
S::system(world, cached_state, ui, id);
|
||||||
|
cached_state.apply(world);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A UI widget may have multiple instances. We need to ensure the local state
|
||||||
|
/// of these instances is not shared. This hashmap allows us to dynamically
|
||||||
|
/// store instance states.
|
||||||
|
#[derive(Default)]
|
||||||
|
struct StateInstances<T: WidgetSystem> {
|
||||||
|
instances: HashMap<WidgetId, SystemState<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub(crate) struct WidgetId(pub(crate) u64);
|
||||||
|
impl WidgetId {
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn new(name: &str) -> Self {
|
||||||
|
let bytes = name.as_bytes();
|
||||||
|
let mut hasher = FxHasher32::default();
|
||||||
|
hasher.write(bytes);
|
||||||
|
WidgetId(hasher.finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[must_use]
|
||||||
|
// pub(crate) fn with(&self, name: &str) -> Self {
|
||||||
|
// Self::new(&format!("{}{name}", self.0))
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
impl From<&str> for WidgetId {
|
||||||
|
#[must_use]
|
||||||
|
fn from(str: &str) -> Self {
|
||||||
|
Self::new(str)
|
||||||
|
}
|
||||||
|
}
|
50
src/gui/widgets/info_panel.rs
Normal file
50
src/gui/widgets/info_panel.rs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
#[cfg(feature = "logging")]
|
||||||
|
use bevy::diagnostic::{Diagnostics, FrameTimeDiagnosticsPlugin};
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
gui::{WidgetId, WidgetSystem},
|
||||||
|
resources::CursorMapPosition,
|
||||||
|
},
|
||||||
|
bevy::ecs::{
|
||||||
|
system::{SystemParam, SystemState},
|
||||||
|
world::World,
|
||||||
|
},
|
||||||
|
bevy_egui::egui::{Grid, Ui},
|
||||||
|
std::marker::PhantomData,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(SystemParam)]
|
||||||
|
pub(crate) struct InfoPanel<'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);
|
||||||
|
|
||||||
|
_ = Grid::new("info_panel")
|
||||||
|
.num_columns(2)
|
||||||
|
.striped(true)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
#[cfg(feature = "logging")]
|
||||||
|
{
|
||||||
|
let diagnostics = world.resource::<Diagnostics>();
|
||||||
|
|
||||||
|
_ = ui.label("Framerate");
|
||||||
|
_ = ui.label(
|
||||||
|
match diagnostics.get_measurement(FrameTimeDiagnosticsPlugin::FPS) {
|
||||||
|
None => f64::NAN,
|
||||||
|
Some(fps) => fps.value.round(),
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
ui.end_row();
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = ui.label("Cursor position");
|
||||||
|
_ = ui.label(world.resource::<CursorMapPosition>().to_string());
|
||||||
|
ui.end_row()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
4
src/gui/widgets/mod.rs
Normal file
4
src/gui/widgets/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
pub(crate) mod info_panel;
|
||||||
|
pub(crate) use info_panel::InfoPanel;
|
||||||
|
pub(crate) mod toolbar;
|
||||||
|
pub(crate) use toolbar::ToolbarWidget;
|
181
src/gui/widgets/toolbar.rs
Normal file
181
src/gui/widgets/toolbar.rs
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
components::panning::Pan2d,
|
||||||
|
gui::{WidgetId, WidgetSystem},
|
||||||
|
macros::iterable_enum,
|
||||||
|
},
|
||||||
|
bevy::{
|
||||||
|
ecs::{
|
||||||
|
component::Component,
|
||||||
|
system::{SystemParam, SystemState},
|
||||||
|
world::World,
|
||||||
|
},
|
||||||
|
log::debug,
|
||||||
|
prelude::{Assets, Camera, Camera2d, Camera3d, Image, Mut, With, Without},
|
||||||
|
render::render_resource::Extent3d,
|
||||||
|
},
|
||||||
|
bevy_egui::egui::{Layout, Ui},
|
||||||
|
planet::WorldManager,
|
||||||
|
std::marker::PhantomData,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "globe_view"))]
|
||||||
|
iterable_enum!(ToolbarButton {
|
||||||
|
GenerateWorld,
|
||||||
|
SaveWorld,
|
||||||
|
LoadWorld,
|
||||||
|
Rainfall,
|
||||||
|
Temperature,
|
||||||
|
PlanetView,
|
||||||
|
Contours,
|
||||||
|
});
|
||||||
|
#[cfg(feature = "globe_view")]
|
||||||
|
iterable_enum!(ToolbarButton {
|
||||||
|
GenerateWorld,
|
||||||
|
SaveWorld,
|
||||||
|
LoadWorld,
|
||||||
|
Rainfall,
|
||||||
|
Temperature,
|
||||||
|
PlanetView,
|
||||||
|
Contours,
|
||||||
|
GlobeView,
|
||||||
|
});
|
||||||
|
fn update_textures(world: &mut World) {
|
||||||
|
debug!("refreshing world texture");
|
||||||
|
world.resource_scope(|world, world_manager: Mut<WorldManager>| {
|
||||||
|
let mut images = world.resource_mut::<Assets<Image>>();
|
||||||
|
|
||||||
|
let map_image_handle = images.get_handle(
|
||||||
|
world_manager
|
||||||
|
.map_image_handle_id
|
||||||
|
.expect("No map image handle"),
|
||||||
|
);
|
||||||
|
let map_image = images
|
||||||
|
.get_mut(&map_image_handle)
|
||||||
|
.expect("Map image handle pointing to non-existing image");
|
||||||
|
map_image.resize(Extent3d {
|
||||||
|
width: world_manager.world().width,
|
||||||
|
height: world_manager.world().height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
});
|
||||||
|
map_image.data = world_manager.map_color_bytes();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
impl ToolbarButton {
|
||||||
|
fn clicked(self, world: &mut World) {
|
||||||
|
match self {
|
||||||
|
ToolbarButton::GenerateWorld => {
|
||||||
|
world.resource_scope(|world, mut world_manager: Mut<'_, WorldManager>| {
|
||||||
|
match world_manager.new_world() {
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Failed to generate world: {}", err);
|
||||||
|
},
|
||||||
|
Ok(_) => {
|
||||||
|
update_textures(world);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
ToolbarButton::SaveWorld => {
|
||||||
|
if let Err(err) = world.resource::<WorldManager>().save_world("planet.ron") {
|
||||||
|
eprintln!("Failed to save planet.ron: {}", err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ToolbarButton::LoadWorld => {
|
||||||
|
world.resource_scope(|world, mut images: Mut<'_, Assets<Image>>| {
|
||||||
|
if let Err(err) = world
|
||||||
|
.resource_mut::<WorldManager>()
|
||||||
|
.load_world("planet.ron", &mut images)
|
||||||
|
{
|
||||||
|
eprintln!("Failed to save planet.ron: {}", err);
|
||||||
|
} else {
|
||||||
|
update_textures(world);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
ToolbarButton::Rainfall => {
|
||||||
|
world.resource_mut::<WorldManager>().toggle_rainfall();
|
||||||
|
update_textures(world);
|
||||||
|
},
|
||||||
|
ToolbarButton::Temperature => {
|
||||||
|
world.resource_mut::<WorldManager>().toggle_temperature();
|
||||||
|
update_textures(world);
|
||||||
|
},
|
||||||
|
ToolbarButton::PlanetView => {
|
||||||
|
world.resource_mut::<WorldManager>().cycle_view();
|
||||||
|
update_textures(world);
|
||||||
|
},
|
||||||
|
ToolbarButton::Contours => {
|
||||||
|
world.resource_mut::<WorldManager>().toggle_contours();
|
||||||
|
update_textures(world);
|
||||||
|
},
|
||||||
|
#[cfg(feature = "globe_view")]
|
||||||
|
ToolbarButton::GlobeView => {
|
||||||
|
let mut camera_3d = world
|
||||||
|
.query_filtered::<&mut Camera, (With<Camera3d>, Without<Camera2d>)>()
|
||||||
|
.single_mut(world);
|
||||||
|
camera_3d.is_active = !camera_3d.is_active;
|
||||||
|
let (mut camera_2d, mut pancam) = world
|
||||||
|
.query_filtered::<(&mut Camera, &mut Pan2d), (With<Camera2d>, Without<Camera3d>)>()
|
||||||
|
.single_mut(world);
|
||||||
|
camera_2d.is_active = !camera_2d.is_active;
|
||||||
|
pancam.enabled = camera_2d.is_active;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ToolbarButton> for &'static str {
|
||||||
|
fn from(button: ToolbarButton) -> Self {
|
||||||
|
match button {
|
||||||
|
ToolbarButton::Rainfall => "Toggle rainfall",
|
||||||
|
ToolbarButton::Temperature => "Toggle temperature",
|
||||||
|
ToolbarButton::Contours => "Toggle contours",
|
||||||
|
ToolbarButton::PlanetView => "Cycle view",
|
||||||
|
ToolbarButton::GenerateWorld => "Generate new world",
|
||||||
|
ToolbarButton::SaveWorld => "Save",
|
||||||
|
ToolbarButton::LoadWorld => "Load",
|
||||||
|
#[cfg(feature = "globe_view")]
|
||||||
|
ToolbarButton::GlobeView => "Toggle globe",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&ToolbarButton> for &'static str {
|
||||||
|
fn from(button: &ToolbarButton) -> Self {
|
||||||
|
(*button).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ToolbarButton> for String {
|
||||||
|
fn from(button: ToolbarButton) -> Self {
|
||||||
|
<&'static str>::from(button).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&ToolbarButton> for String {
|
||||||
|
fn from(button: &ToolbarButton) -> Self {
|
||||||
|
<&'static str>::from(button).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SystemParam)]
|
||||||
|
pub(crate) struct ToolbarWidget<'w, 's> {
|
||||||
|
#[system_param(ignore)]
|
||||||
|
_phantom: PhantomData<(&'w (), &'s ())>,
|
||||||
|
}
|
||||||
|
impl WidgetSystem for ToolbarWidget<'_, '_> {
|
||||||
|
fn system(world: &mut World, _state: &mut SystemState<Self>, ui: &mut Ui, _id: WidgetId) {
|
||||||
|
ui.with_layout(
|
||||||
|
Layout::left_to_right(bevy_egui::egui::Align::Center),
|
||||||
|
|ui| {
|
||||||
|
for button in ToolbarButton::ITEMS {
|
||||||
|
if ui.button(<&'static str>::from(button)).clicked() {
|
||||||
|
debug!("Pressed button: {:#?}", button);
|
||||||
|
button.clicked(world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
501
src/main.rs
501
src/main.rs
|
@ -1,64 +1,36 @@
|
||||||
#![warn(absolute_paths_not_starting_with_crate)]
|
use gui::widgets::{InfoPanel, ToolbarWidget};
|
||||||
// #![warn(box_pointers)]
|
|
||||||
#![warn(elided_lifetimes_in_paths)]
|
|
||||||
#![warn(explicit_outlives_requirements)]
|
|
||||||
#![warn(keyword_idents)]
|
|
||||||
#![warn(macro_use_extern_crate)]
|
|
||||||
#![warn(meta_variable_misuse)]
|
|
||||||
#![warn(missing_abi)]
|
|
||||||
// #![warn(missing_copy_implementations)]
|
|
||||||
#![warn(missing_debug_implementations)]
|
|
||||||
// #![warn(missing_docs)]
|
|
||||||
#![warn(non_ascii_idents)]
|
|
||||||
#![warn(noop_method_call)]
|
|
||||||
#![warn(pointer_structural_match)]
|
|
||||||
#![warn(rust_2021_incompatible_closure_captures)]
|
|
||||||
#![warn(rust_2021_incompatible_or_patterns)]
|
|
||||||
#![warn(rust_2021_prefixes_incompatible_syntax)]
|
|
||||||
#![warn(rust_2021_prelude_collisions)]
|
|
||||||
#![warn(single_use_lifetimes)]
|
|
||||||
#![warn(trivial_casts)]
|
|
||||||
#![warn(trivial_numeric_casts)]
|
|
||||||
#![warn(unreachable_pub)]
|
|
||||||
#![warn(unsafe_code)]
|
|
||||||
#![warn(unsafe_op_in_unsafe_fn)]
|
|
||||||
#![warn(unstable_features)]
|
|
||||||
#![warn(unused_crate_dependencies)]
|
|
||||||
#![warn(unused_extern_crates)]
|
|
||||||
#![warn(unused_import_braces)]
|
|
||||||
#![warn(unused_lifetimes)]
|
|
||||||
#![warn(unused_macro_rules)]
|
|
||||||
#![warn(unused_qualifications)]
|
|
||||||
#![warn(unused_results)]
|
|
||||||
#![warn(variant_size_differences)]
|
|
||||||
|
|
||||||
pub(crate) mod components;
|
pub(crate) mod components;
|
||||||
|
#[cfg(feature = "render")]
|
||||||
|
pub(crate) mod gui;
|
||||||
pub(crate) mod macros;
|
pub(crate) mod macros;
|
||||||
pub(crate) mod plugins;
|
pub(crate) mod plugins;
|
||||||
pub(crate) mod resources;
|
pub(crate) mod resources;
|
||||||
pub(crate) mod ui_helpers;
|
|
||||||
|
|
||||||
#[cfg(all(feature = "logging", feature = "render"))]
|
use {
|
||||||
use bevy::{
|
bevy::{
|
||||||
diagnostic::{Diagnostics, FrameTimeDiagnosticsPlugin},
|
app::App,
|
||||||
log::debug,
|
log::LogSettings,
|
||||||
|
prelude::{IntoExclusiveSystem, World},
|
||||||
|
utils::{default, tracing::Level},
|
||||||
|
},
|
||||||
|
bevy_egui::egui::{FontData, FontDefinitions, FontFamily},
|
||||||
|
planet::WorldManager,
|
||||||
|
plugins::WorldPlugins,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
use {
|
use {
|
||||||
bevy::text::Font,
|
|
||||||
bevy::{
|
bevy::{
|
||||||
asset::Assets,
|
asset::Assets,
|
||||||
core_pipeline::core_2d::{Camera2d, Camera2dBundle},
|
core_pipeline::core_2d::{Camera2d, Camera2dBundle},
|
||||||
ecs::{
|
ecs::{
|
||||||
change_detection::ResMut,
|
change_detection::{Mut, ResMut},
|
||||||
query::{Changed, With},
|
query::With,
|
||||||
system::{Commands, Query, Res},
|
system::{Commands, Query, Res},
|
||||||
},
|
},
|
||||||
hierarchy::BuildChildren,
|
|
||||||
prelude::Vec2,
|
prelude::Vec2,
|
||||||
render::{
|
render::{
|
||||||
camera::{Camera, RenderTarget},
|
camera::{Camera, RenderTarget},
|
||||||
color::Color,
|
|
||||||
render_resource::{
|
render_resource::{
|
||||||
Extent3d,
|
Extent3d,
|
||||||
TextureDescriptor,
|
TextureDescriptor,
|
||||||
|
@ -69,47 +41,20 @@ use {
|
||||||
texture::{Image, ImageSettings},
|
texture::{Image, ImageSettings},
|
||||||
},
|
},
|
||||||
sprite::{Sprite, SpriteBundle},
|
sprite::{Sprite, SpriteBundle},
|
||||||
text::Text,
|
|
||||||
transform::components::GlobalTransform,
|
transform::components::GlobalTransform,
|
||||||
ui::{
|
window::{WindowDescriptor, Windows},
|
||||||
entity::{NodeBundle, TextBundle},
|
|
||||||
AlignSelf,
|
|
||||||
FocusPolicy,
|
|
||||||
Interaction,
|
|
||||||
JustifyContent,
|
|
||||||
PositionType,
|
|
||||||
Size,
|
|
||||||
Style,
|
|
||||||
UiColor,
|
|
||||||
UiRect,
|
|
||||||
Val,
|
|
||||||
},
|
|
||||||
window::{CursorIcon, WindowDescriptor, Windows},
|
|
||||||
winit::WinitSettings,
|
winit::WinitSettings,
|
||||||
},
|
},
|
||||||
components::{
|
bevy_egui::EguiContext,
|
||||||
markers::{InfoPanel, ToolbarButton},
|
components::panning::Pan2d,
|
||||||
panning::Pan2d,
|
gui::widget,
|
||||||
},
|
|
||||||
planet::BiomeStats,
|
|
||||||
resources::CursorMapPosition,
|
resources::CursorMapPosition,
|
||||||
ui_helpers::{toolbar_button, toolbar_button_text},
|
|
||||||
};
|
|
||||||
use {
|
|
||||||
bevy::{
|
|
||||||
app::App,
|
|
||||||
log::LogSettings,
|
|
||||||
utils::{default, tracing::Level},
|
|
||||||
},
|
|
||||||
planet::WorldManager,
|
|
||||||
plugins::WorldPlugins,
|
|
||||||
};
|
};
|
||||||
#[cfg(all(feature = "render", feature = "globe_view"))]
|
#[cfg(all(feature = "render", feature = "globe_view"))]
|
||||||
use {
|
use {
|
||||||
bevy::{
|
bevy::{
|
||||||
asset::Handle,
|
asset::Handle,
|
||||||
core_pipeline::core_3d::{Camera3d, Camera3dBundle},
|
core_pipeline::core_3d::Camera3dBundle,
|
||||||
ecs::query::Without,
|
|
||||||
pbr::{PbrBundle, PointLight, PointLightBundle, StandardMaterial},
|
pbr::{PbrBundle, PointLight, PointLightBundle, StandardMaterial},
|
||||||
prelude::{Quat, Vec3},
|
prelude::{Quat, Vec3},
|
||||||
render::camera::OrthographicProjection,
|
render::camera::OrthographicProjection,
|
||||||
|
@ -119,198 +64,6 @@ use {
|
||||||
std::f32::consts::FRAC_PI_2,
|
std::f32::consts::FRAC_PI_2,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
|
||||||
fn refresh_map_texture(
|
|
||||||
images: &mut Assets<Image>,
|
|
||||||
#[cfg(feature = "globe_view")] materials: &mut Assets<StandardMaterial>,
|
|
||||||
world_manager: &WorldManager,
|
|
||||||
) {
|
|
||||||
let world = world_manager.world();
|
|
||||||
#[cfg(feature = "logging")]
|
|
||||||
debug!("refreshing world texture");
|
|
||||||
let map_image_handle = images.get_handle(
|
|
||||||
world_manager
|
|
||||||
.map_image_handle_id
|
|
||||||
.expect("No map image handle"),
|
|
||||||
);
|
|
||||||
let map_image = images
|
|
||||||
.get_mut(&map_image_handle)
|
|
||||||
.expect("Map image handle pointing to non-existing image");
|
|
||||||
map_image.resize(Extent3d {
|
|
||||||
width: world.width,
|
|
||||||
height: world.height,
|
|
||||||
depth_or_array_layers: 1,
|
|
||||||
});
|
|
||||||
map_image.data = world_manager.map_color_bytes();
|
|
||||||
|
|
||||||
#[cfg(feature = "globe_view")]
|
|
||||||
{
|
|
||||||
let planet_image_handle = images.get_handle(
|
|
||||||
world_manager
|
|
||||||
.globe_image_handle_id
|
|
||||||
.expect("No planet image handle"),
|
|
||||||
);
|
|
||||||
let planet_image = images
|
|
||||||
.get_mut(&planet_image_handle)
|
|
||||||
.expect("Planet image handle pointing to non-existing image");
|
|
||||||
planet_image.resize(Extent3d {
|
|
||||||
width: world.width,
|
|
||||||
height: world.height,
|
|
||||||
depth_or_array_layers: 1,
|
|
||||||
});
|
|
||||||
planet_image.data = world_manager.globe_color_bytes();
|
|
||||||
|
|
||||||
let planet_material_handle = materials.get_handle(
|
|
||||||
world_manager
|
|
||||||
.globe_material_handle_id
|
|
||||||
.expect("No planet material handle"),
|
|
||||||
);
|
|
||||||
let planet_material = materials
|
|
||||||
.get_mut(&planet_material_handle)
|
|
||||||
.expect("Planet material handle pointing to non-existing material");
|
|
||||||
planet_material.base_color_texture = Some(planet_image_handle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
|
||||||
const NORMAL_BUTTON: Color = Color::rgb(0.15, 0.15, 0.15);
|
|
||||||
#[cfg(feature = "render")]
|
|
||||||
const HOVERED_BUTTON: Color = Color::rgb(0.25, 0.25, 0.25);
|
|
||||||
#[cfg(feature = "render")]
|
|
||||||
const PRESSED_BUTTON: Color = Color::rgb(0.35, 0.60, 0.35);
|
|
||||||
#[cfg(feature = "render")]
|
|
||||||
fn handle_toolbar_button(
|
|
||||||
mut interaction_query: Query<
|
|
||||||
'_,
|
|
||||||
'_,
|
|
||||||
(&Interaction, &mut UiColor, &ToolbarButton),
|
|
||||||
Changed<Interaction>,
|
|
||||||
>,
|
|
||||||
mut windows: ResMut<'_, Windows>,
|
|
||||||
mut images: ResMut<'_, Assets<Image>>,
|
|
||||||
mut world_manager: ResMut<'_, WorldManager>,
|
|
||||||
#[cfg(feature = "globe_view")] mut camera_3d_query: Query<
|
|
||||||
'_,
|
|
||||||
'_,
|
|
||||||
&mut Camera,
|
|
||||||
(With<Camera3d>, Without<Camera2d>),
|
|
||||||
>,
|
|
||||||
#[cfg(feature = "globe_view")] mut camera_2d_query: Query<
|
|
||||||
'_,
|
|
||||||
'_,
|
|
||||||
(&mut Camera, &mut Pan2d),
|
|
||||||
(With<Camera2d>, Without<Camera3d>),
|
|
||||||
>,
|
|
||||||
#[cfg(feature = "globe_view")] mut materials: ResMut<'_, Assets<StandardMaterial>>,
|
|
||||||
) {
|
|
||||||
for (interaction, mut color, toolbar_button) in &mut interaction_query {
|
|
||||||
match *interaction {
|
|
||||||
Interaction::Clicked => {
|
|
||||||
windows.primary_mut().set_cursor_icon(CursorIcon::Default);
|
|
||||||
*color = PRESSED_BUTTON.into();
|
|
||||||
match toolbar_button {
|
|
||||||
ToolbarButton::Rainfall => {
|
|
||||||
#[cfg(feature = "logging")]
|
|
||||||
debug!("Toggling rainfall");
|
|
||||||
world_manager.toggle_rainfall();
|
|
||||||
refresh_map_texture(
|
|
||||||
&mut images,
|
|
||||||
#[cfg(feature = "globe_view")]
|
|
||||||
&mut materials,
|
|
||||||
&world_manager,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
ToolbarButton::Temperature => {
|
|
||||||
#[cfg(feature = "logging")]
|
|
||||||
debug!("Toggling temperature");
|
|
||||||
world_manager.toggle_temperature();
|
|
||||||
refresh_map_texture(
|
|
||||||
&mut images,
|
|
||||||
#[cfg(feature = "globe_view")]
|
|
||||||
&mut materials,
|
|
||||||
&world_manager,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
ToolbarButton::PlanetView => {
|
|
||||||
#[cfg(feature = "logging")]
|
|
||||||
debug!("Cycling planet view");
|
|
||||||
world_manager.cycle_view();
|
|
||||||
refresh_map_texture(
|
|
||||||
&mut images,
|
|
||||||
#[cfg(feature = "globe_view")]
|
|
||||||
&mut materials,
|
|
||||||
&world_manager,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
ToolbarButton::Contours => {
|
|
||||||
#[cfg(feature = "logging")]
|
|
||||||
debug!("Toggling contours");
|
|
||||||
world_manager.toggle_contours();
|
|
||||||
refresh_map_texture(
|
|
||||||
&mut images,
|
|
||||||
#[cfg(feature = "globe_view")]
|
|
||||||
&mut materials,
|
|
||||||
&world_manager,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
ToolbarButton::GenerateWorld => {
|
|
||||||
#[cfg(feature = "logging")]
|
|
||||||
debug!("Generating new world");
|
|
||||||
_ = world_manager
|
|
||||||
.new_world()
|
|
||||||
.expect("Failed to generate new world");
|
|
||||||
refresh_map_texture(
|
|
||||||
&mut images,
|
|
||||||
#[cfg(feature = "globe_view")]
|
|
||||||
&mut materials,
|
|
||||||
&world_manager,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
ToolbarButton::SaveWorld => {
|
|
||||||
#[cfg(feature = "logging")]
|
|
||||||
debug!("Saving world");
|
|
||||||
if let Err(err) = world_manager.save_world("planet.ron") {
|
|
||||||
eprintln!("Failed to save planet.ron: {}", err);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ToolbarButton::LoadWorld => {
|
|
||||||
#[cfg(feature = "logging")]
|
|
||||||
debug!("Loading world");
|
|
||||||
if let Err(err) = world_manager.load_world("planet.ron", &mut images) {
|
|
||||||
eprintln!("Failed to load planet.ron: {}", err);
|
|
||||||
} else {
|
|
||||||
refresh_map_texture(
|
|
||||||
&mut images,
|
|
||||||
#[cfg(feature = "globe_view")]
|
|
||||||
&mut materials,
|
|
||||||
&world_manager,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
#[cfg(feature = "globe_view")]
|
|
||||||
ToolbarButton::GlobeView => {
|
|
||||||
#[cfg(feature = "logging")]
|
|
||||||
debug!("Toggling globe view");
|
|
||||||
let mut camera_3d = camera_3d_query.single_mut();
|
|
||||||
camera_3d.is_active = !camera_3d.is_active;
|
|
||||||
let (mut camera_2d, mut pancam) = camera_2d_query.single_mut();
|
|
||||||
camera_2d.is_active = !camera_2d.is_active;
|
|
||||||
pancam.enabled = camera_2d.is_active;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Interaction::Hovered => {
|
|
||||||
windows.primary_mut().set_cursor_icon(CursorIcon::Hand);
|
|
||||||
*color = HOVERED_BUTTON.into();
|
|
||||||
},
|
|
||||||
Interaction::None => {
|
|
||||||
windows.primary_mut().set_cursor_icon(CursorIcon::Default);
|
|
||||||
*color = NORMAL_BUTTON.into();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
fn update_cursor_map_position(
|
fn update_cursor_map_position(
|
||||||
mut cursor_map_position: ResMut<'_, CursorMapPosition>,
|
mut cursor_map_position: ResMut<'_, CursorMapPosition>,
|
||||||
|
@ -350,107 +103,44 @@ fn rotate_globe(mut globe_transform: Query<'_, '_, &mut Transform, With<Handle<M
|
||||||
globe_transform.single_mut().rotate_y(ROTATION_SPEED);
|
globe_transform.single_mut().rotate_y(ROTATION_SPEED);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
|
||||||
fn update_info_panel(
|
|
||||||
#[cfg(feature = "logging")] diagnostics: Res<'_, Diagnostics>,
|
|
||||||
cursor_position: Res<'_, CursorMapPosition>,
|
|
||||||
world_manager: Res<'_, WorldManager>,
|
|
||||||
mut text: Query<'_, '_, &mut Text, With<InfoPanel>>,
|
|
||||||
) {
|
|
||||||
let world = world_manager.world();
|
|
||||||
text.single_mut().sections[0].value = if cursor_position.x >= 0
|
|
||||||
&& cursor_position.x < world.width as i32
|
|
||||||
&& cursor_position.y >= 0
|
|
||||||
&& cursor_position.y < world.height as i32
|
|
||||||
{
|
|
||||||
let cell = &world.terrain[cursor_position.y as usize][cursor_position.x as usize];
|
|
||||||
|
|
||||||
#[cfg(feature = "logging")]
|
|
||||||
{
|
|
||||||
format!(
|
|
||||||
"FPS: ~{}\nMouse position: {}\nAltitude: {}\nRainfall: {}\nTemperature: {}\n\n{}",
|
|
||||||
match diagnostics.get_measurement(FrameTimeDiagnosticsPlugin::FPS) {
|
|
||||||
None => f64::NAN,
|
|
||||||
Some(fps) => fps.value.round(),
|
|
||||||
},
|
|
||||||
*cursor_position,
|
|
||||||
cell.altitude,
|
|
||||||
cell.rainfall,
|
|
||||||
cell.temperature,
|
|
||||||
cell.biome_presences
|
|
||||||
.iter()
|
|
||||||
.map(|(biome_type, presence)| {
|
|
||||||
format!(
|
|
||||||
"Biome: {} ({:.2}%)",
|
|
||||||
(<BiomeStats>::from(biome_type).name),
|
|
||||||
presence * 100.0
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join("\n")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "logging"))]
|
|
||||||
{
|
|
||||||
format!(
|
|
||||||
"Mouse position: {}\nAltitude: {}\nRainfall: {}\nTemperature: {}\n{}",
|
|
||||||
*cursor_position,
|
|
||||||
cell.altitude,
|
|
||||||
cell.rainfall,
|
|
||||||
cell.temperature,
|
|
||||||
cell.biome_presences
|
|
||||||
.iter()
|
|
||||||
.map(|(biome_type, presence)| {
|
|
||||||
format!(
|
|
||||||
"Biome: {} ({:.2}%)",
|
|
||||||
(<BiomeStats>::from(biome_type).name),
|
|
||||||
presence * 100.0
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join("\n")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
#[cfg(feature = "logging")]
|
|
||||||
{
|
|
||||||
format!(
|
|
||||||
"FPS: ~{}\nMouse position: {}\nOut of bounds",
|
|
||||||
match diagnostics.get_measurement(FrameTimeDiagnosticsPlugin::FPS) {
|
|
||||||
None => f64::NAN,
|
|
||||||
Some(fps) => fps.value.round(),
|
|
||||||
},
|
|
||||||
*cursor_position
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "logging"))]
|
|
||||||
{
|
|
||||||
format!("Mouse position: {}\nOut of bounds", *cursor_position)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
fn generate_graphics(
|
fn generate_graphics(
|
||||||
mut commands: Commands<'_, '_>,
|
mut commands: Commands<'_, '_>,
|
||||||
mut world_manager: ResMut<'_, WorldManager>,
|
mut world_manager: ResMut<'_, WorldManager>,
|
||||||
mut images: ResMut<'_, Assets<Image>>,
|
mut images: ResMut<'_, Assets<Image>>,
|
||||||
mut fonts: ResMut<'_, Assets<Font>>,
|
mut egui_context: ResMut<'_, EguiContext>,
|
||||||
#[cfg(feature = "globe_view")] mut materials: ResMut<'_, Assets<StandardMaterial>>,
|
#[cfg(feature = "globe_view")] mut materials: ResMut<'_, Assets<StandardMaterial>>,
|
||||||
#[cfg(feature = "globe_view")] mut meshes: ResMut<'_, Assets<Mesh>>,
|
#[cfg(feature = "globe_view")] mut meshes: ResMut<'_, Assets<Mesh>>,
|
||||||
) {
|
) {
|
||||||
let julia_mono_handle = fonts.add(
|
// Add Julia-Mono font to egui
|
||||||
Font::try_from_bytes(include_bytes!("../assets/JuliaMono.ttf").to_vec())
|
{
|
||||||
.expect("Failed to create JuliaMono font!"),
|
let ctx = egui_context.ctx_mut();
|
||||||
|
let mut fonts = FontDefinitions::default();
|
||||||
|
const FONT_NAME: &str = "Julia-Mono";
|
||||||
|
_ = fonts.font_data.insert(
|
||||||
|
FONT_NAME.to_owned(),
|
||||||
|
FontData::from_static(include_bytes!("../assets/JuliaMono.ttf")),
|
||||||
);
|
);
|
||||||
|
fonts
|
||||||
|
.families
|
||||||
|
.get_mut(&FontFamily::Monospace)
|
||||||
|
.expect("Failed to get 'Monospace' FontFamily")
|
||||||
|
.insert(0, FONT_NAME.to_owned());
|
||||||
|
fonts
|
||||||
|
.families
|
||||||
|
.get_mut(&FontFamily::Proportional)
|
||||||
|
.expect("Failed to get 'Proportional' FontFamily")
|
||||||
|
.push(FONT_NAME.to_owned());
|
||||||
|
ctx.set_fonts(fonts);
|
||||||
|
}
|
||||||
|
|
||||||
let world = world_manager.world();
|
let world = world_manager.world();
|
||||||
let custom_sprite_size = Vec2 {
|
let custom_sprite_size = Vec2 {
|
||||||
x: (WORLD_SCALE * world.width as i32) as f32,
|
x: (WORLD_SCALE * world.width as i32) as f32,
|
||||||
y: (WORLD_SCALE * world.height as i32) as f32,
|
y: (WORLD_SCALE * world.height as i32) as f32,
|
||||||
};
|
};
|
||||||
|
// Set up 2D map mode
|
||||||
|
{
|
||||||
let map_image_handle = images.add(Image {
|
let map_image_handle = images.add(Image {
|
||||||
data: world_manager.map_color_bytes(),
|
data: world_manager.map_color_bytes(),
|
||||||
texture_descriptor: TextureDescriptor {
|
texture_descriptor: TextureDescriptor {
|
||||||
|
@ -469,6 +159,20 @@ fn generate_graphics(
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
world_manager.map_image_handle_id = Some(map_image_handle.id);
|
world_manager.map_image_handle_id = Some(map_image_handle.id);
|
||||||
|
_ = commands
|
||||||
|
.spawn_bundle(Camera2dBundle::default())
|
||||||
|
.insert(Pan2d::new());
|
||||||
|
|
||||||
|
// TODO: Switch to egui
|
||||||
|
_ = commands.spawn_bundle(SpriteBundle {
|
||||||
|
texture: images.get_handle(world_manager.map_image_handle_id.unwrap()),
|
||||||
|
sprite: Sprite {
|
||||||
|
custom_size: Some(custom_sprite_size),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "globe_view")]
|
#[cfg(feature = "globe_view")]
|
||||||
{
|
{
|
||||||
|
@ -533,80 +237,22 @@ fn generate_graphics(
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_ = commands
|
fn update_gui(world: &mut World) {
|
||||||
.spawn_bundle(Camera2dBundle::default())
|
world.resource_scope(|world, mut ctx: Mut<'_, EguiContext>| {
|
||||||
.insert(Pan2d::new());
|
let ctx = ctx.ctx_mut();
|
||||||
_ = commands.spawn_bundle(SpriteBundle {
|
_ = bevy_egui::egui::Window::new("Info panel")
|
||||||
texture: images.get_handle(world_manager.map_image_handle_id.unwrap()),
|
.resizable(false)
|
||||||
sprite: Sprite {
|
.show(ctx, |ui| {
|
||||||
custom_size: Some(custom_sprite_size),
|
widget::<InfoPanel<'_, '_>>(world, ui, "Map Info Panel".into());
|
||||||
..default()
|
|
||||||
},
|
|
||||||
..default()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
_ = commands
|
_ = bevy_egui::egui::TopBottomPanel::bottom("Toolbar")
|
||||||
.spawn_bundle(NodeBundle {
|
.resizable(false)
|
||||||
style: Style {
|
.default_height(30.0)
|
||||||
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
|
.show(ctx, |ui| {
|
||||||
..default()
|
widget::<ToolbarWidget<'_, '_>>(world, ui, "Toolbar".into());
|
||||||
},
|
|
||||||
color: Color::NONE.into(),
|
|
||||||
..default()
|
|
||||||
})
|
|
||||||
.with_children(|root_node| {
|
|
||||||
_ = root_node
|
|
||||||
.spawn_bundle(NodeBundle {
|
|
||||||
style: Style {
|
|
||||||
align_self: AlignSelf::FlexEnd,
|
|
||||||
padding: UiRect::all(Val::Px(2.0)),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
color: Color::rgba(1.0, 1.0, 1.0, 0.05).into(),
|
|
||||||
focus_policy: FocusPolicy::Pass,
|
|
||||||
..default()
|
|
||||||
})
|
|
||||||
.with_children(|info_panel| {
|
|
||||||
_ = info_panel
|
|
||||||
.spawn_bundle(TextBundle {
|
|
||||||
text: Text::from_section(
|
|
||||||
"Info Panel",
|
|
||||||
bevy::text::TextStyle {
|
|
||||||
font: julia_mono_handle.clone(),
|
|
||||||
font_size: 15.0,
|
|
||||||
color: Color::WHITE,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
..default()
|
|
||||||
})
|
|
||||||
.insert(InfoPanel);
|
|
||||||
});
|
|
||||||
_ = root_node
|
|
||||||
.spawn_bundle(NodeBundle {
|
|
||||||
style: Style {
|
|
||||||
size: Size::new(Val::Percent(100.0), Val::Undefined),
|
|
||||||
padding: UiRect::all(Val::Px(3.0)),
|
|
||||||
justify_content: JustifyContent::SpaceAround,
|
|
||||||
position_type: PositionType::Absolute,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
color: Color::NONE.into(),
|
|
||||||
focus_policy: FocusPolicy::Pass,
|
|
||||||
..default()
|
|
||||||
})
|
|
||||||
.with_children(|button_box| {
|
|
||||||
ToolbarButton::iterator().for_each(|&button_type| {
|
|
||||||
_ = button_box
|
|
||||||
.spawn_bundle(toolbar_button())
|
|
||||||
.with_children(|button| {
|
|
||||||
_ = button.spawn_bundle(toolbar_button_text(
|
|
||||||
julia_mono_handle.clone(),
|
|
||||||
button_type,
|
|
||||||
));
|
|
||||||
})
|
|
||||||
.insert(button_type)
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -632,9 +278,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
})
|
})
|
||||||
.insert_resource(CursorMapPosition::default())
|
.insert_resource(CursorMapPosition::default())
|
||||||
.add_startup_system(generate_graphics)
|
.add_startup_system(generate_graphics)
|
||||||
.add_system(handle_toolbar_button)
|
.add_system(update_gui.exclusive_system())
|
||||||
.add_system(update_cursor_map_position)
|
.add_system(update_cursor_map_position);
|
||||||
.add_system(update_info_panel);
|
|
||||||
#[cfg(all(feature = "render", feature = "globe_view"))]
|
#[cfg(all(feature = "render", feature = "globe_view"))]
|
||||||
{
|
{
|
||||||
_ = app.add_system(rotate_globe);
|
_ = app.add_system(rotate_globe);
|
||||||
|
|
|
@ -10,10 +10,7 @@ use bevy::{
|
||||||
|
|
||||||
impl PluginGroup for WorldPlugins {
|
impl PluginGroup for WorldPlugins {
|
||||||
fn build(&mut self, group: &mut PluginGroupBuilder) {
|
fn build(&mut self, group: &mut PluginGroupBuilder) {
|
||||||
_ = group
|
_ = group.add(LogPlugin).add(CorePlugin).add(TimePlugin);
|
||||||
.add(LogPlugin::default())
|
|
||||||
.add(CorePlugin::default())
|
|
||||||
.add(TimePlugin::default());
|
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
#[cfg(feature = "render")]
|
||||||
{
|
{
|
||||||
|
@ -32,39 +29,41 @@ impl PluginGroup for WorldPlugins {
|
||||||
window::WindowPlugin,
|
window::WindowPlugin,
|
||||||
winit::WinitPlugin,
|
winit::WinitPlugin,
|
||||||
},
|
},
|
||||||
|
bevy_egui::EguiPlugin,
|
||||||
};
|
};
|
||||||
|
|
||||||
_ = group
|
_ = group
|
||||||
.add(TransformPlugin::default())
|
.add(TransformPlugin)
|
||||||
// hierarchy
|
// hierarchy
|
||||||
.add(InputPlugin::default())
|
.add(InputPlugin)
|
||||||
.add(WindowPlugin::default())
|
.add(WindowPlugin)
|
||||||
.add(AssetPlugin::default())
|
.add(AssetPlugin)
|
||||||
.add(HierarchyPlugin::default())
|
.add(HierarchyPlugin)
|
||||||
.add(WinitPlugin::default())
|
.add(WinitPlugin)
|
||||||
.add(RenderPlugin::default())
|
.add(RenderPlugin)
|
||||||
.add(CorePipelinePlugin::default())
|
.add(CorePipelinePlugin)
|
||||||
.add(SpritePlugin::default())
|
.add(SpritePlugin)
|
||||||
.add(TextPlugin::default())
|
.add(TextPlugin)
|
||||||
.add(UiPlugin::default())
|
.add(UiPlugin)
|
||||||
.add(PanningPlugin::default());
|
.add(PanningPlugin)
|
||||||
|
.add(EguiPlugin);
|
||||||
#[cfg(feature = "globe_view")]
|
#[cfg(feature = "globe_view")]
|
||||||
{
|
{
|
||||||
use bevy::pbr::PbrPlugin;
|
use bevy::pbr::PbrPlugin;
|
||||||
_ = group.add(PbrPlugin::default())
|
_ = group.add(PbrPlugin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "render"))]
|
#[cfg(not(feature = "render"))]
|
||||||
{
|
{
|
||||||
use bevy::app::ScheduleRunnerPlugin;
|
use bevy::app::ScheduleRunnerPlugin;
|
||||||
_ = group.add(ScheduleRunnerPlugin::default());
|
_ = group.add(ScheduleRunnerPlugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = group.add(DiagnosticsPlugin::default());
|
_ = group.add(DiagnosticsPlugin);
|
||||||
#[cfg(all(feature = "logging"))]
|
#[cfg(all(feature = "logging"))]
|
||||||
{
|
{
|
||||||
use bevy::diagnostic::FrameTimeDiagnosticsPlugin;
|
use bevy::diagnostic::FrameTimeDiagnosticsPlugin;
|
||||||
_ = group.add(FrameTimeDiagnosticsPlugin::default());
|
_ = group.add(FrameTimeDiagnosticsPlugin);
|
||||||
}
|
}
|
||||||
_ = group.add(LogDiagnosticsPlugin::default());
|
_ = group.add(LogDiagnosticsPlugin::default());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
#[cfg(feature = "render")]
|
|
||||||
use {
|
|
||||||
crate::{components::markers::ToolbarButton, NORMAL_BUTTON},
|
|
||||||
bevy::{
|
|
||||||
asset::Handle,
|
|
||||||
render::color::Color,
|
|
||||||
text::{Font, Text, TextStyle},
|
|
||||||
ui::{
|
|
||||||
entity::{ButtonBundle, TextBundle},
|
|
||||||
widget::Button,
|
|
||||||
AlignItems,
|
|
||||||
JustifyContent,
|
|
||||||
Style,
|
|
||||||
},
|
|
||||||
utils::default,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
|
||||||
pub(crate) fn toolbar_button() -> ButtonBundle {
|
|
||||||
ButtonBundle {
|
|
||||||
button: Button,
|
|
||||||
style: Style {
|
|
||||||
align_items: AlignItems::Center,
|
|
||||||
justify_content: JustifyContent::Center,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
color: NORMAL_BUTTON.into(),
|
|
||||||
..default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "render")]
|
|
||||||
pub(crate) fn toolbar_button_text(font: Handle<Font>, which: ToolbarButton) -> TextBundle {
|
|
||||||
TextBundle {
|
|
||||||
text: Text::from_section(
|
|
||||||
which,
|
|
||||||
TextStyle {
|
|
||||||
font,
|
|
||||||
font_size: 20.0,
|
|
||||||
color: Color::WHITE,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
..default()
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue