From 86b91341562c3a04cf0f663e6c326dbabe45b704 Mon Sep 17 00:00:00 2001 From: Rendo Date: Sun, 2 Aug 2026 21:35:19 +0500 Subject: [PATCH] UI --- src/main.rs | 5 +- src/simulation.rs | 17 ++++++ src/view/game.rs | 11 ++-- src/view/mod.rs | 4 +- src/view/ui.rs | 143 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 172 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index 51bfa94..45bad8e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use bevy::prelude::*; use crate::grid::Grid; -use crate::simulation::tick; +use crate::simulation::SimulationPlugin; use crate::view::ViewPlugin; @@ -12,9 +12,8 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugins(ViewPlugin) + .add_plugins(SimulationPlugin) .insert_resource(Grid::cube(16)) - .insert_resource(Time::::from_hz(20.)) - .add_systems(FixedUpdate, tick) .run(); } diff --git a/src/simulation.rs b/src/simulation.rs index 3e37008..31dca7f 100644 --- a/src/simulation.rs +++ b/src/simulation.rs @@ -1,6 +1,16 @@ use bevy::{prelude::*,math::UVec3}; use crate::{MAX_VERTICAL_FLOW,MAX_HORIZONTAL_FLOW,grid::{Grid,GridElement}}; +pub struct SimulationPlugin; + +impl Plugin for SimulationPlugin { + fn build(&self, app: &mut App) { + app.init_state::() + .insert_resource(Time::::from_hz(32.)) + .add_systems(FixedUpdate, tick.run_if(in_state(SimulationStatus::Continued))); + } +} + enum GridAction{ MoveLiquid{ from: UVec3, @@ -9,6 +19,13 @@ enum GridAction{ } } +#[derive(States,Default,Hash,Eq,PartialEq,Clone,Debug)] +pub enum SimulationStatus { + #[default] + Paused, + Continued, +} + pub fn tick(mut grid: ResMut) { let mut stack: Vec = Vec::new(); diff --git a/src/view/game.rs b/src/view/game.rs index 1b791e5..1469186 100644 --- a/src/view/game.rs +++ b/src/view/game.rs @@ -31,7 +31,7 @@ pub struct TileMaterials { pub fn debug_setup_grid(mut grid: ResMut) { use crate::grid::GridElement; let mut tool = grid.manipulate(); - tool.fill_xyz(5, 0, 0, 5, 0, 14, GridElement::Solid); + tool.fill_xyz(5, 0, 1, 5, 0, 14, GridElement::Solid); tool.fill_xyz(0, 0, 0, 4, 0, 15, GridElement::Water { amount: MAX_FLUID_AMOUNT }); } @@ -116,10 +116,10 @@ pub fn cube(position: Vec3,index: usize,mesh: Handle) -> impl Scene { } } -pub fn update_cubes(query: Query<(&mut Mesh3d,&mut Visibility,&mut MeshMaterial3d,&TileWatch)>, materials: Res, stages: Res, grid: Res) { +pub fn update_cubes(query: Query<(&mut Mesh3d,&mut Visibility,&mut MeshMaterial3d,&TileWatch, &mut Transform)>, materials: Res, stages: Res, grid: Res) { if grid.is_changed() == false { return; } - for (mut mesh,mut visibility, mut material, index) in query { + for (mut mesh,mut visibility, mut material, index, mut transform) in query { let state = grid.data[index.0]; match state { @@ -129,7 +129,10 @@ pub fn update_cubes(query: Query<(&mut Mesh3d,&mut Visibility,&mut MeshMaterial3 crate::grid::GridElement::Water { amount } => { *visibility = Visibility::Visible; material.0 = materials.water.clone(); - let stage = ((amount as f32)/(MAX_FLUID_AMOUNT as f32) * (FLUID_FULLNES_STAGES-1) as f32).round() as usize; + let stage = ((amount as f32)/(MAX_FLUID_AMOUNT as f32) * (FLUID_FULLNES_STAGES-1) as f32).ceil() as usize; + + transform.translation = grid.deindexify(index.0 as u32).as_vec3() - vec3(0.,((FLUID_FULLNES_STAGES-1-stage) as f32 / (FLUID_FULLNES_STAGES as f32))/2.,0.); + mesh.0 = stages.0[stage].clone(); }, crate::grid::GridElement::Solid => { diff --git a/src/view/mod.rs b/src/view/mod.rs index fe3f2e2..7e80b34 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -2,6 +2,8 @@ use bevy::prelude::*; use game::GameViewPlugin; +use crate::view::ui::UIPlugin; + pub mod ui; pub mod game; @@ -9,6 +11,6 @@ pub struct ViewPlugin; impl Plugin for ViewPlugin { fn build(&self, app: &mut App) { - app.add_plugins(GameViewPlugin); + app.add_plugins((GameViewPlugin,UIPlugin)); } } diff --git a/src/view/ui.rs b/src/view/ui.rs index e69de29..131d2b3 100644 --- a/src/view/ui.rs +++ b/src/view/ui.rs @@ -0,0 +1,143 @@ +use bevy::feathers::{FeathersPlugins, containers::*, controls::*, dark_theme::create_dark_theme, display::*, palette, rounded_corners::RoundedCorners, theme::UiTheme}; +use bevy::ui_widgets::{Activate}; +use bevy::prelude::*; + +use crate::grid::{Grid, GridElement}; +use crate::simulation::SimulationStatus; + +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; + +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() + ] + } +} + +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 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 == 1 {1} 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 >= 128 {128} else {current_frequency * 2}; + time.set_timestep_hz(current_frequency as f64); + label.0 = format!("{} hz", current_frequency); + }), + ] + } +}