diff --git a/crates/render/src/ui/util/mod.rs b/crates/render/src/ui/util/mod.rs new file mode 100644 index 0000000..ba6365e --- /dev/null +++ b/crates/render/src/ui/util/mod.rs @@ -0,0 +1,18 @@ +mod spriteimage; +mod spritetextarea; + +pub(super) use spriteimage::SpriteImage; +pub(super) use spritetextarea::SpriteTextArea; + +use nalgebra::{Point2, Vector2}; + +/// Represents a rectangular region inside a sprite. +pub(crate) struct SpriteRect { + /// The position of the top-left corner of this rectangle, in fractional units. + /// (0.0 is left edge of sprite, 1.0 is right edge) + pub pos: Point2, + + /// The width and height of this rectangle, in fractional units. + /// 1.0 will be as tall as the sprite, 0.5 will be half as tall + pub dim: Vector2, +} diff --git a/crates/render/src/ui/util/spriteimage.rs b/crates/render/src/ui/util/spriteimage.rs new file mode 100644 index 0000000..af122bb --- /dev/null +++ b/crates/render/src/ui/util/spriteimage.rs @@ -0,0 +1,60 @@ +use galactica_content::SpriteHandle; +use galactica_util::to_radians; +use nalgebra::{Point2, Vector2}; + +use super::SpriteRect; +use crate::{vertexbuffer::types::UiInstance, PositionAnchor, RenderState}; + +pub struct SpriteImage { + parent: SpriteHandle, + parent_position: Point2, + parent_size: f32, + + inner: SpriteHandle, + mask: SpriteHandle, + rect: SpriteRect, +} + +impl SpriteImage { + pub fn new( + parent: SpriteHandle, + parent_position: Point2, + parent_size: f32, + inner: SpriteHandle, + mask: SpriteHandle, + rect: SpriteRect, + ) -> Self { + return Self { + parent, + parent_position, + parent_size, + inner, + mask, + rect, + }; + } + + /// Add this image to the gpu sprite buffer + pub fn push_to_buffer(&self, state: &mut RenderState) { + let h = self.parent_size; + let w = self.parent.aspect * h; + + let zero = Point2::new( + self.parent_position.x - (w / 2.0), + self.parent_position.y + (h / 2.0), + ); + + let pos = zero + Vector2::new(self.rect.pos.x * w, -self.rect.pos.y * h); + let dim = Vector2::new(self.rect.dim.x * w, self.rect.dim.y * h); + + state.push_ui_buffer(UiInstance { + anchor: PositionAnchor::CNw.to_int(), + position: pos.into(), + angle: to_radians(90.0), + size: dim.y, + color: [1.0, 1.0, 1.0, 1.0], + sprite_index: self.inner.get_index(), + mask_index: [1, self.mask.get_index()], + }); + } +} diff --git a/crates/render/src/ui/util/spritetextarea.rs b/crates/render/src/ui/util/spritetextarea.rs new file mode 100644 index 0000000..b403879 --- /dev/null +++ b/crates/render/src/ui/util/spritetextarea.rs @@ -0,0 +1,106 @@ +use galactica_content::SpriteHandle; +use glyphon::{cosmic_text::Align, Attrs, Buffer, Color, Metrics, Shaping, TextArea, TextBounds}; +use nalgebra::{Point2, Vector2}; + +use super::SpriteRect; +use crate::RenderState; + +/// Represents a text area inside a sprite. +pub(crate) struct SpriteTextArea { + /// Parent sprite + sprite: SpriteHandle, + + /// Position of parent sprite's center, in logical pixels, + /// with 0, 0 at the center of the screen + sprite_position: Point2, + + /// Height of parent sprite, in logical pixels + sprite_size: f32, + + /// Bounds of text area + rect: SpriteRect, + + /// Text buffer + buffer: Buffer, + + /// Text color + color: Color, + + /// Text alignment + align: Align, +} + +impl SpriteTextArea { + pub fn new( + state: &mut RenderState, + sprite: SpriteHandle, + sprite_position: Point2, + sprite_size: f32, + rect: SpriteRect, + text_metrics: Metrics, + color: Color, + align: Align, + ) -> Self { + let mut s = Self { + buffer: Buffer::new(&mut state.text_font_system, text_metrics), + sprite_size: f32::NAN, + sprite, + sprite_position, + rect, + align, + color, + }; + s.set_size(state, sprite_size); + return s; + } + + pub fn set_size(&mut self, state: &mut RenderState, sprite_size: f32) { + self.sprite_size = sprite_size; + self.buffer.set_size( + &mut state.text_font_system, + (self.rect.dim.x * self.sprite_size) * state.window.scale_factor() as f32, + (self.rect.dim.y * self.sprite_size * self.sprite.aspect) + * state.window.scale_factor() as f32, + ); + } + + pub fn set_text(&mut self, state: &mut RenderState, text: &str, attrs: Attrs) { + self.buffer + .set_text(&mut state.text_font_system, text, attrs, Shaping::Advanced); + + for l in &mut self.buffer.lines { + l.set_align(Some(self.align)); + } + self.buffer.shape_until_scroll(&mut state.text_font_system); + } + + pub fn get_textarea(&self, state: &RenderState) -> TextArea { + let h = self.sprite_size; + let w = self.sprite.aspect * h; + + // Glypon works with physical pixels, so we must convert + let fac = state.window.scale_factor() as f32; + + // All the units below are in logical pixels + let zero = Vector2::new( + (state.window_size.width as f32 / (2.0 * fac)) - (w / 2.0) + self.sprite_position.x, + (state.window_size.height as f32 / (2.0 * fac)) - (h / 2.0) - self.sprite_position.y, + ); + let corner_ne = zero + Vector2::new(self.rect.pos.x * w, self.rect.pos.y * h); + let corner_sw = corner_ne + Vector2::new(self.rect.dim.x * w, self.rect.dim.y * h); + + TextArea { + buffer: &self.buffer, + top: corner_ne.y * fac, + left: corner_ne.x * fac, + scale: 1.0, + bounds: TextBounds { + top: (corner_ne.y * fac) as i32, + bottom: (corner_sw.y * fac) as i32, + left: (corner_ne.x * fac) as i32, + right: (corner_sw.x * fac) as i32, + }, + default_color: self.color, + } + } +}