use bevy::feathers::{FeathersPlugins, containers::*, controls::*, dark_theme::create_dark_theme, display::*, theme::UiTheme}; use bevy::ui_widgets::{Activate}; use bevy::prelude::*; use crate::grid::{Grid, GridElement}; use crate::simulation::SimulationStatus; use crate::view::editor::EditorTools; pub struct UIPlugin; impl Plugin for UIPlugin { fn build(&self, app: &mut App) { app.add_plugins(FeathersPlugins) .insert_resource(UiTheme(create_dark_theme())) .add_systems(Startup, ui.spawn()) .add_systems(Update, count_water_amount); } } #[derive(Component, Clone, Default)] struct WaterLabel; #[derive(Component, Clone, Default)] struct SpeedLabel; #[derive(Component, Clone, Default)] struct ToolLabel; const MAX_FREQUENCY: u32 = 512; const MIN_FREQUENCY: u32 = 1; fn count_water_amount(grid: Res, text: Single<&mut Text, With>) { let mut sum: u32 = 0; for i in 0..grid.cell_amount() { let GridElement::Water { amount: water_amount } = grid.data[i as usize] else { continue; }; sum += water_amount; } let mut label = text.into_inner(); label.0 = format!("Water amount: {} liters",sum); } fn ui() -> impl Scene { bsn! { Node { width: percent(100), height: percent(100), align_items: AlignItems::Start, justify_content: JustifyContent::Start, display: Display::Flex, flex_direction: FlexDirection::Row, column_gap: px(8) } Children [ debug_column() editor_column() ] } } fn debug_column() -> impl Scene { bsn! { Node { display: Display::Flex, flex_direction: FlexDirection::Column, align_items: AlignItems::Stretch, justify_content: JustifyContent::Start, padding: px(8), row_gap: px(8), width: px(256), min_width: px(256), } pane() pane_header() pane_body() Children [ water_amount_label(), pause_button(), simulation_speed(), ] } } fn editor_column() -> impl Scene { bsn! { Node { display: Display::Flex, flex_direction: FlexDirection::Column, align_items: AlignItems::Stretch, justify_content: JustifyContent::Start, padding: px(8), row_gap: px(8), width: px(256), min_width: px(256), } pane() pane_header() pane_body() Children [ label("Tools") ToolLabel, @FeathersButton { @caption: bsn!{label("None")} } on(|_activate: On, mut tool: ResMut, mut label: Single<&mut Text, With>| { *tool = EditorTools::None; label.0 = "Current tool: None".to_string(); }), @FeathersButton { @caption: bsn!{label("Paint")} } on(|_activate: On, mut tool: ResMut, mut label: Single<&mut Text, With>| { *tool = EditorTools::Paint { position: UVec3::default(), block: GridElement::Solid }; label.0 = "Current tool: Paint".to_string(); }), ] } } fn water_amount_label() -> impl Scene { bsn! { Node { width: percent(100), height: px(32), } Children [ label("") WaterLabel ] } } fn pause_button() -> impl Scene { bsn!{ @FeathersButton { @caption: bsn! { label("Toggle simulation") } } on(|_activate: On, current_state: Res>, mut next_state: ResMut>| { next_state.set(match current_state.get() { SimulationStatus::Paused => SimulationStatus::Continued, SimulationStatus::Continued => SimulationStatus::Paused, }); }) } } fn simulation_speed() -> impl Scene { bsn! { Node { display: Display::Flex, align_items: AlignItems::Stretch, justify_content: JustifyContent::Start, width: percent(100), height: px(32), } Children [ @FeathersButton { @caption: bsn! { label("<<") } } on(|_activate: On, mut time: ResMut>, mut label: Single<&mut Text, With>|{ let mut current_frequency = (1./time.timestep().as_secs_f64()) as u32; current_frequency = if current_frequency <= MIN_FREQUENCY {MIN_FREQUENCY} else {current_frequency / 2}; time.set_timestep_hz(current_frequency as f64); label.0 = format!("{} hz", current_frequency); }) , label("") SpeedLabel Node { justify_self: JustifySelf::Stretch }, @FeathersButton { @caption: bsn! { label(">>") } } on(|_activate: On, mut time: ResMut>, mut label: Single<&mut Text, With>|{ let mut current_frequency = (1./time.timestep().as_secs_f64()) as u32; current_frequency = if current_frequency >= MAX_FREQUENCY {MAX_FREQUENCY} else {current_frequency * 2}; time.set_timestep_hz(current_frequency as f64); label.0 = format!("{} hz", current_frequency); }), ] } }