From 348fc40c33e2d3d3a36448c65b7539ec2f9337b8 Mon Sep 17 00:00:00 2001 From: Mark Date: Fri, 2 Feb 2024 22:32:03 -0800 Subject: [PATCH] Reworked UI manager --- crates/galactica/src/main.rs | 4 +- crates/render/src/gpustate.rs | 58 ++- crates/render/src/renderinput.rs | 4 +- crates/render/src/renderscene/landed.rs | 2 +- .../render/src/renderscene/system/system.rs | 2 +- crates/render/src/renderstate.rs | 6 +- crates/render/src/ui/manager.rs | 329 ++++++++++++------ crates/render/src/ui/mod.rs | 6 +- .../src/ui/scenes/flying/fpsindicator.rs | 59 ---- crates/render/src/ui/scenes/flying/mod.rs | 6 - crates/render/src/ui/scenes/flying/radar.rs | 265 -------------- crates/render/src/ui/scenes/flying/scene.rs | 45 --- crates/render/src/ui/scenes/flying/status.rs | 96 ----- crates/render/src/ui/scenes/landed.rs | 135 ------- crates/render/src/ui/scenes/mod.rs | 7 - crates/render/src/ui/scenes/outfitter.rs | 129 ------- crates/render/src/ui/util/mod.rs | 172 +-------- crates/render/src/ui/util/sprite.rs | 179 +++++----- crates/render/src/ui/util/textarea.rs | 102 ------ crates/render/src/ui/util/textbox.rs | 91 +++++ 20 files changed, 441 insertions(+), 1256 deletions(-) delete mode 100644 crates/render/src/ui/scenes/flying/fpsindicator.rs delete mode 100644 crates/render/src/ui/scenes/flying/mod.rs delete mode 100644 crates/render/src/ui/scenes/flying/radar.rs delete mode 100644 crates/render/src/ui/scenes/flying/scene.rs delete mode 100644 crates/render/src/ui/scenes/flying/status.rs delete mode 100644 crates/render/src/ui/scenes/landed.rs delete mode 100644 crates/render/src/ui/scenes/mod.rs delete mode 100644 crates/render/src/ui/scenes/outfitter.rs delete mode 100644 crates/render/src/ui/util/textarea.rs create mode 100644 crates/render/src/ui/util/textbox.rs diff --git a/crates/galactica/src/main.rs b/crates/galactica/src/main.rs index c2fef48..e5d9934 100644 --- a/crates/galactica/src/main.rs +++ b/crates/galactica/src/main.rs @@ -183,7 +183,7 @@ fn try_main() -> Result<()> { | ShipState::Flying { .. } => { if was_landed { was_landed = false; - gpu.set_scene(&content, RenderScenes::System); + gpu.set_scene(RenderScenes::System); } Some(*o.rigidbody.translation()) @@ -192,7 +192,7 @@ fn try_main() -> Result<()> { ShipState::Landed { target } => { if !was_landed { was_landed = true; - gpu.set_scene(&content, RenderScenes::Landed); + gpu.set_scene(RenderScenes::Landed); } let b = content.get_system_object(*target); diff --git a/crates/render/src/gpustate.rs b/crates/render/src/gpustate.rs index 3aa3220..b9d1d3a 100644 --- a/crates/render/src/gpustate.rs +++ b/crates/render/src/gpustate.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use anyhow::Result; use bytemuck; use galactica_content::Content; @@ -13,10 +15,7 @@ use crate::{ shaderprocessor::preprocess_shader, starfield::Starfield, texturearray::TextureArray, - ui::{ - scenes::{UiFlyingScene, UiLandedScene}, - UiManager, UiScenes, - }, + ui::{UiManager, UiScene}, RenderInput, RenderScenes, RenderState, VertexBuffers, }; @@ -40,7 +39,7 @@ impl GPUState { /// Make a new GPUState that draws on `window` pub async fn new( window: winit::window::Window, - ct: &Content, + ct: Rc, scene: RenderScenes, ) -> Result { let window_size = window.inner_size(); @@ -106,11 +105,11 @@ impl GPUState { surface.configure(&device, &config); } - let vertex_buffers = VertexBuffers::new(&device, ct); + let vertex_buffers = VertexBuffers::new(&device, &ct); // Load uniforms let global_uniform = GlobalUniform::new(&device); - let texture_array = TextureArray::new(&device, &queue, ct)?; + let texture_array = TextureArray::new(&device, &queue, &ct)?; // Make sure these match the indices in each shader let bind_group_layouts = &[ @@ -217,23 +216,10 @@ impl GPUState { .build(); let mut starfield = Starfield::new(); - starfield.regenerate(ct); - - let mut state = RenderState { - queue, - window, - window_size, - window_aspect, - global_uniform, - vertex_buffers, - text_atlas, - text_cache, - text_font_system, - text_renderer, - }; + starfield.regenerate(&ct); return Ok(Self { - ui: UiManager::new(ct, &mut state), + ui: UiManager::new(ct), device, config, surface, @@ -244,7 +230,19 @@ impl GPUState { ui_pipeline, radialbar_pipeline, scene, - state, + + state: RenderState { + queue, + window, + window_size, + window_aspect, + global_uniform, + vertex_buffers, + text_atlas, + text_cache, + text_font_system, + text_renderer, + }, }); } } @@ -255,18 +253,14 @@ impl GPUState { &self.state.window } - /// Change the current scene - pub fn set_scene(&mut self, ct: &Content, scene: RenderScenes) { + /// Change the current scenection + pub fn set_scene(&mut self, scene: RenderScenes) { debug!("switching to {:?}", scene); match scene { - RenderScenes::Landed => self - .ui - .set_scene(UiScenes::Landed(UiLandedScene::new(ct, &mut self.state))), - RenderScenes::System => self - .ui - .set_scene(UiScenes::Flying(UiFlyingScene::new(ct, &mut self.state))), - } + RenderScenes::Landed => self.ui.set_scene(&mut self.state, UiScene::Landed).unwrap(), + RenderScenes::System => self.ui.set_scene(&mut self.state, UiScene::Flying).unwrap(), + }; self.scene = scene; } diff --git a/crates/render/src/renderinput.rs b/crates/render/src/renderinput.rs index a017651..c0bd69e 100644 --- a/crates/render/src/renderinput.rs +++ b/crates/render/src/renderinput.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use galactica_content::{Content, SystemHandle}; use galactica_playeragent::PlayerAgent; use galactica_system::phys::PhysImage; @@ -29,7 +31,7 @@ pub struct RenderInput<'a> { pub time_since_last_run: f32, /// Game content - pub ct: &'a Content, + pub ct: Rc, /// Time we spent in each part of the game loop pub timing: Timing, diff --git a/crates/render/src/renderscene/landed.rs b/crates/render/src/renderscene/landed.rs index 746b6c0..7e2a0ac 100644 --- a/crates/render/src/renderscene/landed.rs +++ b/crates/render/src/renderscene/landed.rs @@ -40,7 +40,7 @@ impl RenderScene for LandedScene { }); // Create sprite instances - g.ui.draw(&input, &mut g.state); + g.ui.draw(&input, &mut g.state)?; // These should match the indices in each shader, // and should each have a corresponding bind group layout. diff --git a/crates/render/src/renderscene/system/system.rs b/crates/render/src/renderscene/system/system.rs index d3cea6e..d02343e 100644 --- a/crates/render/src/renderscene/system/system.rs +++ b/crates/render/src/renderscene/system/system.rs @@ -54,7 +54,7 @@ impl RenderScene for SystemScene { Self::push_ships(g, &input, (clip_ne, clip_sw)); Self::push_projectiles(g, &input, (clip_ne, clip_sw)); Self::push_effects(g, &input, (clip_ne, clip_sw)); - g.ui.draw(&input, &mut g.state); + g.ui.draw(&input, &mut g.state)?; // These should match the indices in each shader, // and should each have a corresponding bind group layout. diff --git a/crates/render/src/renderstate.rs b/crates/render/src/renderstate.rs index ce63e0d..4d103cd 100644 --- a/crates/render/src/renderstate.rs +++ b/crates/render/src/renderstate.rs @@ -1,7 +1,5 @@ use galactica_content::Content; -use galactica_util::constants::{ - OBJECT_SPRITE_INSTANCE_LIMIT, RADIALBAR_SPRITE_INSTANCE_LIMIT, UI_SPRITE_INSTANCE_LIMIT, -}; +use galactica_util::constants::{OBJECT_SPRITE_INSTANCE_LIMIT, UI_SPRITE_INSTANCE_LIMIT}; use glyphon::{FontSystem, SwashCache, TextAtlas, TextRenderer}; use std::rc::Rc; use wgpu::BufferAddress; @@ -154,6 +152,7 @@ impl RenderState { self.vertex_buffers.object_counter as u32 } + /* pub fn push_radialbar_buffer(&mut self, instance: RadialBarInstance) { // Enforce buffer limit if self.vertex_buffers.radialbar_counter as u64 > RADIALBAR_SPRITE_INSTANCE_LIMIT { @@ -168,6 +167,7 @@ impl RenderState { ); self.vertex_buffers.radialbar_counter += 1; } + */ pub fn get_radialbar_counter(&self) -> u32 { self.vertex_buffers.radialbar_counter as u32 diff --git a/crates/render/src/ui/manager.rs b/crates/render/src/ui/manager.rs index 7dce037..83bd33e 100644 --- a/crates/render/src/ui/manager.rs +++ b/crates/render/src/ui/manager.rs @@ -1,146 +1,265 @@ -use std::fmt::Debug; - +use anyhow::Result; use galactica_content::Content; use glyphon::TextArea; -use log::debug; +use log::{debug, error, trace}; +use rhai::{Array, Dynamic, Engine, Scope, AST}; +use std::{cell::RefCell, collections::HashSet, fmt::Debug, rc::Rc}; -use super::scenes::{UiFlyingScene, UiLandedScene, UiOutfitterScene}; -use crate::{RenderInput, RenderState}; +use super::{ + api::{self, SceneAction, SpriteElement, TextBoxBuilder}, + util::{Sprite, TextBox}, +}; +use crate::{ + ui::api::{SpriteBuilder, State}, + RenderInput, RenderState, +}; -/// Output from a ui scene step -pub struct UiSceneStepResult { - /// If Some, switch to this scene - pub new_scene: Option, +#[derive(Debug, Copy, Clone)] +pub enum MouseEvent { + Click, + Release, + Enter, + Leave, + None, } -pub trait UiScene<'this> -where - Self: 'this, -{ - /// Draw this scene - fn draw(&mut self, input: &RenderInput, state: &mut RenderState); - - /// Update this scene's state for this frame. - /// Handles clicks, keys, etc. - fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult; - - /// Add all textareas in this scene to `v` - fn get_textareas( - &'this self, - v: &mut Vec>, - input: &RenderInput, - state: &RenderState, - ); -} - -pub(crate) enum UiScenes { - Landed(UiLandedScene), - Flying(UiFlyingScene), - Outfitter(UiOutfitterScene), -} - -impl Debug for UiScenes { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl MouseEvent { + pub fn is_enter(&self) -> bool { match self { - Self::Flying(_) => write!(f, "UiScenes::Flying"), - Self::Landed(_) => write!(f, "UiScenes::Landed"), - Self::Outfitter(_) => write!(f, "UiScenes::Outfitter"), - } - } -} - -/* -impl UiScenes { - fn is_flying(&self) -> bool { - match self { - Self::Flying(_) => true, + Self::Enter => true, _ => false, } } - fn is_landed(&self) -> bool { + pub fn is_click(&self) -> bool { match self { - Self::Landed(_) => true, - _ => false, - } - } - - fn is_outfitter(&self) -> bool { - match self { - Self::Outfitter(_) => true, + Self::Click => true, _ => false, } } } -*/ -impl<'a> UiScene<'a> for UiScenes { - fn draw(&mut self, input: &RenderInput, state: &mut RenderState) { - match self { - Self::Flying(s) => s.draw(input, state), - Self::Landed(s) => s.draw(input, state), - Self::Outfitter(s) => s.draw(input, state), - } - } +#[derive(Debug, Copy, Clone)] +pub(crate) enum UiScene { + Landed, + Flying, + Outfitter, +} - fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult { +impl ToString for UiScene { + fn to_string(&self) -> String { match self { - Self::Flying(s) => s.step(input, state), - Self::Landed(s) => s.step(input, state), - Self::Outfitter(s) => s.step(input, state), - } - } - - fn get_textareas( - &'a self, - v: &mut Vec>, - input: &RenderInput, - state: &RenderState, - ) { - match self { - Self::Flying(s) => s.get_textareas(v, input, state), - Self::Landed(s) => s.get_textareas(v, input, state), - Self::Outfitter(s) => s.get_textareas(v, input, state), + Self::Flying => "flying".to_string(), + Self::Landed => "landed".to_string(), + Self::Outfitter => "outfitter".to_string(), } } } -pub struct UiManager { - current_scene: UiScenes, +enum UiElement { + Sprite(Rc>), + Text(TextBox), +} + +impl UiElement { + pub fn new_sprite(sprite: Sprite) -> Self { + Self::Sprite(Rc::new(RefCell::new(sprite))) + } + + pub fn new_text(text: TextBox) -> Self { + Self::Text(text) + } + + pub fn sprite(&self) -> Option>> { + match self { + Self::Sprite(s) => Some(s.clone()), + _ => None, + } + } + + pub fn text(&self) -> Option<&TextBox> { + match self { + Self::Text(t) => Some(t), + _ => None, + } + } +} + +pub(crate) struct UiManager { + current_scene: UiScene, + engine: Engine, + scope: Scope<'static>, + elements: Vec, + ct: Rc, } impl UiManager { - pub fn new(ct: &Content, state: &mut RenderState) -> Self { + pub fn new(ct: Rc) -> Self { + let scope = Scope::new(); + + let mut engine = Engine::new(); + api::register_into_engine(&mut engine); + Self { - current_scene: UiScenes::Flying(UiFlyingScene::new(ct, state)), + ct, + current_scene: UiScene::Flying, + engine, + scope, + elements: Vec::new(), + } + } + + pub fn get_scene_ast(ct: &Content, scene: UiScene) -> &AST { + match scene { + UiScene::Landed => &ct.get_config().ui_landed_scene, + UiScene::Flying => &ct.get_config().ui_flying_scene, + UiScene::Outfitter => &ct.get_config().ui_outfitter_scene, } } /// Change the current scene - pub fn set_scene(&mut self, scene: UiScenes) { + pub fn set_scene(&mut self, state: &mut RenderState, scene: UiScene) -> Result<()> { debug!("switching to {:?}", scene); self.current_scene = scene; + + self.scope.clear(); + self.elements.clear(); + let mut used_names = HashSet::new(); + + trace!("running init for `{}`", self.current_scene.to_string()); + + let builders: Array = self.engine.call_fn( + &mut self.scope, + Self::get_scene_ast(&self.ct, self.current_scene), + "init", + (State { + planet_landscape: "ui::landscape::test".to_string(), + planet_name: "Earth".to_string(), + },), + )?; + + trace!("found {:?} builders", builders.len()); + + for v in builders { + if v.is::() { + let s = v.cast::(); + if used_names.contains(&s.name) { + error!( + "UI scene `{}` re-uses element name `{}`", + self.current_scene.to_string(), + s.name + ); + } else { + used_names.insert(s.name.clone()); + } + self.elements.push(UiElement::new_sprite(Sprite::new( + &self.ct, s.name, s.sprite, s.mask, s.rect, + ))); + } else if v.is::() { + let t = v.cast::(); + if used_names.contains(&t.name) { + error!( + "UI scene `{}` re-uses element name `{}`", + self.current_scene.to_string(), + t.name + ); + } else { + used_names.insert(t.name.clone()); + } + let mut b = TextBox::new( + state, + t.name, + t.font_size, + t.line_height, + t.font, + t.justify, + t.rect, + ); + b.set_text(state, &t.text); + self.elements.push(UiElement::new_text(b)); + } else { + // TODO: better message + error!("bad type in builder array") + } + } + return Ok(()); } /// Draw all ui elements - pub fn draw(&mut self, input: &RenderInput, state: &mut RenderState) { - loop { - let r = self.current_scene.step(input, state); - if let Some(new_scene) = r.new_scene { - debug!("{:?} changed scene", self.current_scene); - self.set_scene(new_scene) - } else { - break; + pub fn draw(&mut self, input: &RenderInput, state: &mut RenderState) -> Result<()> { + let mut iter = self + .elements + .iter() + .map(|x| x.sprite()) + .filter(|x| x.is_some()) + .map(|x| x.unwrap()); + + let action: SceneAction = loop { + let e = match iter.next() { + Some(e) => e, + None => break SceneAction::None, + }; + + let mut x = (*e).borrow_mut(); + let m = x.check_mouse(input, state); + x.step(input, state); + x.push_to_buffer(input, state); + drop(x); + // we MUST drop here, since script calls mutate the sprite RefCell + + let action: Dynamic = match m { + MouseEvent::None => Dynamic::from(SceneAction::None), + + MouseEvent::Release | MouseEvent::Click => self.engine.call_fn( + &mut self.scope, + Self::get_scene_ast(&self.ct, self.current_scene), + "click", + (SpriteElement::new(self.ct.clone(), e.clone()), m.is_click()), + )?, + + MouseEvent::Leave | MouseEvent::Enter => self.engine.call_fn( + &mut self.scope, + Self::get_scene_ast(&self.ct, self.current_scene), + "hover", + (SpriteElement::new(self.ct.clone(), e.clone()), m.is_enter()), + )?, + }; + + if let Some(action) = action.try_cast::() { + match action { + SceneAction::None => {} + _ => { + break action; + } + } } + }; + + drop(iter); + + match action { + SceneAction::None => {} + SceneAction::SceneOutfitter => self.set_scene(state, UiScene::Outfitter)?, + SceneAction::SceneLanded => self.set_scene(state, UiScene::Landed)?, } - self.current_scene.draw(input, state); - } - - /// Textareas to show while player is flying - pub fn get_textareas(&self, input: &RenderInput, state: &RenderState) -> Vec