diff --git a/content/ui/flying.rhai b/content/ui/flying.rhai index d14bbd1..a38bcc6 100644 --- a/content/ui/flying.rhai +++ b/content/ui/flying.rhai @@ -1,3 +1,44 @@ -fn init(state) { return []; } +fn init(state) { + + + let ring = SpriteBuilder( + "ring", + "ui::status", + Rect( + -5.0, -5.0, 100.0, 100.0, + SpriteAnchor::NorthEast, + SpriteAnchor::NorthEast + ) + ); + + let shield = RadialBuilder( + "shield", 2.5, + Color(0.3, 0.6, 0.8, 1.0), + Rect( + -9.5, -9.5, 91.0, 91.0, + SpriteAnchor::NorthEast, + SpriteAnchor::NorthEast + ) + ); + shield.set_progress(0.2); + + let hull = RadialBuilder( + "hull", 2.5, + Color(0.8, 0.7, 0.5, 1.0), + Rect( + -13.5, -13.5, 83.0, 83.0, + SpriteAnchor::NorthEast, + SpriteAnchor::NorthEast + ) + ); + hull.set_progress(0.4); + + return [ + ring, + shield, + hull + ]; +} + fn hover(element, hover_state) {} fn click(element, click_state) {} \ No newline at end of file diff --git a/crates/galactica/src/main.rs b/crates/galactica/src/main.rs index e5d9934..be41771 100644 --- a/crates/galactica/src/main.rs +++ b/crates/galactica/src/main.rs @@ -121,11 +121,8 @@ fn try_main() -> Result<()> { let event_loop = EventLoop::new(); let window = WindowBuilder::new().build(&event_loop).unwrap(); - let mut gpu = pollster::block_on(galactica_render::GPUState::new( - window, - content.clone(), - RenderScenes::System, - ))?; + let mut gpu = pollster::block_on(galactica_render::GPUState::new(window, content.clone()))?; + gpu.set_scene(RenderScenes::System); gpu.init(&content); // TODO: don't clone content diff --git a/crates/render/src/gpustate.rs b/crates/render/src/gpustate.rs index b9d1d3a..b57e538 100644 --- a/crates/render/src/gpustate.rs +++ b/crates/render/src/gpustate.rs @@ -37,11 +37,7 @@ pub struct GPUState { impl GPUState { /// Make a new GPUState that draws on `window` - pub async fn new( - window: winit::window::Window, - ct: Rc, - scene: RenderScenes, - ) -> Result { + pub async fn new(window: winit::window::Window, ct: Rc) -> Result { let window_size = window.inner_size(); let window_aspect = window_size.width as f32 / window_size.height as f32; @@ -229,7 +225,7 @@ impl GPUState { starfield_pipeline, ui_pipeline, radialbar_pipeline, - scene, + scene: RenderScenes::Landed, state: RenderState { queue, diff --git a/crates/render/src/renderstate.rs b/crates/render/src/renderstate.rs index 4d103cd..ce63e0d 100644 --- a/crates/render/src/renderstate.rs +++ b/crates/render/src/renderstate.rs @@ -1,5 +1,7 @@ use galactica_content::Content; -use galactica_util::constants::{OBJECT_SPRITE_INSTANCE_LIMIT, UI_SPRITE_INSTANCE_LIMIT}; +use galactica_util::constants::{ + OBJECT_SPRITE_INSTANCE_LIMIT, RADIALBAR_SPRITE_INSTANCE_LIMIT, UI_SPRITE_INSTANCE_LIMIT, +}; use glyphon::{FontSystem, SwashCache, TextAtlas, TextRenderer}; use std::rc::Rc; use wgpu::BufferAddress; @@ -152,7 +154,6 @@ 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 { @@ -167,7 +168,6 @@ 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/api/color.rs b/crates/render/src/ui/api/color.rs new file mode 100644 index 0000000..6f05d2a --- /dev/null +++ b/crates/render/src/ui/api/color.rs @@ -0,0 +1,25 @@ +use nalgebra::Vector4; +use rhai::{CustomType, TypeBuilder}; + +#[derive(Debug, Clone)] +pub struct Color { + pub val: Vector4, +} + +impl Color { + pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self { + Self { + val: Vector4::new(r, g, b, a), + } + } + + pub fn as_array(&self) -> [f32; 4] { + [self.val.x, self.val.y, self.val.z, self.val.w] + } +} + +impl CustomType for Color { + fn build(mut builder: TypeBuilder) { + builder.with_name("Color").with_fn("Color", Self::new); + } +} diff --git a/crates/render/src/ui/api/mod.rs b/crates/render/src/ui/api/mod.rs index d448399..0e865bd 100644 --- a/crates/render/src/ui/api/mod.rs +++ b/crates/render/src/ui/api/mod.rs @@ -1,3 +1,5 @@ +mod color; +mod radialbuilder; mod rect; mod sprite; mod spritebuilder; @@ -11,6 +13,8 @@ pub fn register_into_engine(engine: &mut Engine) { engine .build_type::() .build_type::() + .build_type::() + .build_type::() .build_type::() .build_type::() .build_type::() @@ -30,6 +34,8 @@ pub fn register_into_engine(engine: &mut Engine) { .register_static_module("SceneAction", exported_module!(sceneaction_mod).into()); } +pub use color::*; +pub use radialbuilder::*; pub use rect::*; pub use sprite::*; pub use spritebuilder::*; diff --git a/crates/render/src/ui/api/radialbuilder.rs b/crates/render/src/ui/api/radialbuilder.rs new file mode 100644 index 0000000..0df357c --- /dev/null +++ b/crates/render/src/ui/api/radialbuilder.rs @@ -0,0 +1,39 @@ +use nalgebra::clamp; +use rhai::{CustomType, ImmutableString, TypeBuilder}; + +use super::{color::Color, Rect}; + +#[derive(Debug, Clone)] +pub struct RadialBuilder { + pub name: ImmutableString, + pub rect: Rect, + + pub stroke: f32, + pub color: Color, + pub progress: f32, +} + +impl RadialBuilder { + pub fn new(name: ImmutableString, stroke: f32, color: Color, rect: Rect) -> Self { + Self { + name, + rect, + stroke, + color, + progress: 0.0, + } + } + + pub fn set_progress(&mut self, progress: f32) { + self.progress = clamp(progress, 0.0, 1.0); + } +} + +impl CustomType for RadialBuilder { + fn build(mut builder: TypeBuilder) { + builder + .with_name("RadialBuilder") + .with_fn("RadialBuilder", Self::new) + .with_fn("set_progress", Self::set_progress); + } +} diff --git a/crates/render/src/ui/api/spritebuilder.rs b/crates/render/src/ui/api/spritebuilder.rs index f2f4781..92ddf59 100644 --- a/crates/render/src/ui/api/spritebuilder.rs +++ b/crates/render/src/ui/api/spritebuilder.rs @@ -1,17 +1,17 @@ -use rhai::{CustomType, TypeBuilder}; +use rhai::{CustomType, ImmutableString, TypeBuilder}; use super::Rect; #[derive(Debug, Clone)] pub struct SpriteBuilder { - pub name: String, + pub name: ImmutableString, pub rect: Rect, - pub sprite: String, - pub mask: Option, + pub sprite: ImmutableString, + pub mask: Option, } impl SpriteBuilder { - pub fn new(name: String, sprite: String, rect: Rect) -> Self { + pub fn new(name: ImmutableString, sprite: ImmutableString, rect: Rect) -> Self { Self { name, rect, @@ -20,7 +20,7 @@ impl SpriteBuilder { } } - pub fn set_mask(&mut self, mask: String) { + pub fn set_mask(&mut self, mask: ImmutableString) { self.mask = Some(mask); } } diff --git a/crates/render/src/ui/api/textboxbuilder.rs b/crates/render/src/ui/api/textboxbuilder.rs index 1c2dafe..5b0946f 100644 --- a/crates/render/src/ui/api/textboxbuilder.rs +++ b/crates/render/src/ui/api/textboxbuilder.rs @@ -1,21 +1,21 @@ -use rhai::{CustomType, TypeBuilder}; +use rhai::{CustomType, ImmutableString, TypeBuilder}; use super::{Rect, TextBoxFont, TextBoxJustify}; #[derive(Debug, Clone)] pub struct TextBoxBuilder { - pub name: String, + pub name: ImmutableString, pub font_size: f32, pub line_height: f32, pub font: TextBoxFont, pub justify: TextBoxJustify, pub rect: Rect, - pub text: String, + pub text: ImmutableString, } impl TextBoxBuilder { pub fn new( - name: String, + name: ImmutableString, font_size: f32, line_height: f32, font: TextBoxFont, @@ -29,11 +29,11 @@ impl TextBoxBuilder { font, justify, rect, - text: String::new(), + text: ImmutableString::new(), } } - pub fn set_text(&mut self, text: String) { + pub fn set_text(&mut self, text: ImmutableString) { self.text = text } } diff --git a/crates/render/src/ui/manager.rs b/crates/render/src/ui/manager.rs index 83bd33e..e400978 100644 --- a/crates/render/src/ui/manager.rs +++ b/crates/render/src/ui/manager.rs @@ -7,10 +7,13 @@ use std::{cell::RefCell, collections::HashSet, fmt::Debug, rc::Rc}; use super::{ api::{self, SceneAction, SpriteElement, TextBoxBuilder}, - util::{Sprite, TextBox}, + util::{RadialBar, TextBox}, }; use crate::{ - ui::api::{SpriteBuilder, State}, + ui::{ + api::{RadialBuilder, SpriteBuilder, State}, + util::Sprite, + }, RenderInput, RenderState, }; @@ -58,6 +61,7 @@ impl ToString for UiScene { enum UiElement { Sprite(Rc>), + RadialBar(Rc>), Text(TextBox), } @@ -66,6 +70,10 @@ impl UiElement { Self::Sprite(Rc::new(RefCell::new(sprite))) } + pub fn new_radialbar(bar: RadialBar) -> Self { + Self::RadialBar(Rc::new(RefCell::new(bar))) + } + pub fn new_text(text: TextBox) -> Self { Self::Text(text) } @@ -83,6 +91,13 @@ impl UiElement { _ => None, } } + + pub fn radialbar(&self) -> Option>> { + match self { + Self::RadialBar(r) => Some(r.clone()), + _ => None, + } + } } pub(crate) struct UiManager { @@ -143,32 +158,55 @@ impl UiManager { for v in builders { if v.is::() { let s = v.cast::(); - if used_names.contains(&s.name) { + if used_names.contains(s.name.as_str()) { error!( "UI scene `{}` re-uses element name `{}`", self.current_scene.to_string(), s.name ); } else { - used_names.insert(s.name.clone()); + used_names.insert(s.name.to_string()); } self.elements.push(UiElement::new_sprite(Sprite::new( - &self.ct, s.name, s.sprite, s.mask, s.rect, + &self.ct, + s.name.to_string(), + s.sprite.to_string(), + s.mask.map(|x| x.to_string()), + s.rect, + ))); + } else if v.is::() { + let r = v.cast::(); + if used_names.contains(r.name.as_str()) { + error!( + "UI scene `{}` re-uses element name `{}`", + self.current_scene.to_string(), + r.name + ); + } else { + used_names.insert(r.name.to_string()); + } + self.elements.push(UiElement::new_radialbar(RadialBar::new( + &self.ct, + r.name.to_string(), + r.stroke, + r.color, + r.rect, + r.progress, ))); } else if v.is::() { let t = v.cast::(); - if used_names.contains(&t.name) { + if used_names.contains(t.name.as_str()) { error!( "UI scene `{}` re-uses element name `{}`", self.current_scene.to_string(), t.name ); } else { - used_names.insert(t.name.clone()); + used_names.insert(t.name.to_string()); } let mut b = TextBox::new( state, - t.name, + t.name.to_string(), t.font_size, t.line_height, t.font, @@ -187,12 +225,7 @@ impl UiManager { /// Draw all ui elements 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 mut iter = self.elements.iter(); let action: SceneAction = loop { let e = match iter.next() { @@ -200,29 +233,40 @@ impl UiManager { 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 = { + if let Some(e) = e.sprite() { + 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), + 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::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()), - )?, + 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()), + )?, + } + } else if let Some(e) = e.radialbar() { + let mut x = (*e).borrow_mut(); + x.step(input, state); + x.push_to_buffer(input, state); + Dynamic::from(SceneAction::None) + } else { + Dynamic::from(SceneAction::None) + } }; if let Some(action) = action.try_cast::() { diff --git a/crates/render/src/ui/util/mod.rs b/crates/render/src/ui/util/mod.rs index 04ed9a4..142a322 100644 --- a/crates/render/src/ui/util/mod.rs +++ b/crates/render/src/ui/util/mod.rs @@ -1,5 +1,7 @@ +mod radialbar; mod sprite; mod textbox; +pub use radialbar::*; pub use sprite::*; pub use textbox::*; diff --git a/crates/render/src/ui/util/radialbar.rs b/crates/render/src/ui/util/radialbar.rs new file mode 100644 index 0000000..b5dc582 --- /dev/null +++ b/crates/render/src/ui/util/radialbar.rs @@ -0,0 +1,51 @@ +use galactica_content::Content; +use std::f32::consts::TAU; + +use super::super::api::Rect; +use crate::{ + ui::api::Color, vertexbuffer::types::RadialBarInstance, PositionAnchor, RenderInput, + RenderState, +}; + +#[derive(Debug, Clone)] +pub struct RadialBar { + pub name: String, + pub rect: Rect, + pub stroke: f32, + pub color: Color, + pub progress: f32, +} + +impl RadialBar { + pub fn new( + _ct: &Content, + name: String, + stroke: f32, + color: Color, + rect: Rect, + progress: f32, + ) -> Self { + Self { + name, + rect, + stroke, + color, + progress, + } + } + + pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) { + let rect = self.rect.to_centered(state, input.ct.get_config().ui_scale); + + state.push_radialbar_buffer(RadialBarInstance { + position: [rect.pos.x, rect.pos.y], + anchor: PositionAnchor::CC.to_int(), + diameter: rect.dim.x.min(rect.dim.y), + stroke: self.stroke * input.ct.get_config().ui_scale, + color: self.color.as_array(), + angle: self.progress * TAU, + }); + } + + pub fn step(&mut self, _input: &RenderInput, _state: &mut RenderState) {} +}