diff --git a/crates/render/src/ui/api/mod.rs b/crates/render/src/ui/api/mod.rs new file mode 100644 index 0000000..d448399 --- /dev/null +++ b/crates/render/src/ui/api/mod.rs @@ -0,0 +1,38 @@ +mod rect; +mod sprite; +mod spritebuilder; +mod state; +mod textboxbuilder; +mod util; + +use rhai::{exported_module, Engine}; + +pub fn register_into_engine(engine: &mut Engine) { + engine + .build_type::() + .build_type::() + .build_type::() + .build_type::() + .build_type::() + .register_type_with_name::("SpriteAnchor") + .register_static_module("SpriteAnchor", exported_module!(spriteanchor_mod).into()) + .register_type_with_name::("TextBoxFont") + .register_static_module( + "TextBoxFont", + exported_module!(util::textboxfont_mod).into(), + ) + .register_type_with_name::("TextBoxJustify") + .register_static_module( + "TextBoxJustify", + exported_module!(textboxjustify_mod).into(), + ) + .register_type_with_name::("SceneAction") + .register_static_module("SceneAction", exported_module!(sceneaction_mod).into()); +} + +pub use rect::*; +pub use sprite::*; +pub use spritebuilder::*; +pub use state::*; +pub use textboxbuilder::*; +pub use util::*; diff --git a/crates/render/src/ui/api/rect.rs b/crates/render/src/ui/api/rect.rs new file mode 100644 index 0000000..9c4a413 --- /dev/null +++ b/crates/render/src/ui/api/rect.rs @@ -0,0 +1,126 @@ +use nalgebra::{Point2, Vector2}; +use rhai::{CustomType, TypeBuilder}; +use winit::dpi::LogicalSize; + +use super::SpriteAnchor; +use crate::{RenderInput, RenderState}; + +#[derive(Debug, Clone)] +pub struct Rect { + pub pos: Point2, + pub dim: Vector2, + pub anchor_self: SpriteAnchor, + pub anchor_parent: SpriteAnchor, +} + +impl Rect { + pub fn new( + x: f32, + y: f32, + w: f32, + h: f32, + anchor_self: SpriteAnchor, + anchor_parent: SpriteAnchor, + ) -> Self { + Self { + pos: Point2::new(x, y), + dim: Vector2::new(w, h), + anchor_self, + anchor_parent, + } + } + + pub fn to_centered(&self, state: &RenderState, ui_scale: f32) -> CenteredRect { + let w: LogicalSize = state.window_size.to_logical(state.window.scale_factor()); + let w = Vector2::new(w.width, w.height); + + let mut pos = self.pos * ui_scale; + let dim = self.dim * ui_scale; + + // Origin + match self.anchor_parent { + SpriteAnchor::Center => {} + + SpriteAnchor::NorthWest => { + pos += Vector2::new(-w.x, w.y) / 2.0; + } + + SpriteAnchor::SouthWest => { + pos += Vector2::new(-w.x, -w.y) / 2.0; + } + + SpriteAnchor::NorthEast => { + pos += Vector2::new(w.x, w.y) / 2.0; + } + + SpriteAnchor::SouthEast => { + pos += Vector2::new(w.x, -w.y) / 2.0; + } + } + + // Offset for self dimensions + match self.anchor_self { + SpriteAnchor::Center => {} + + SpriteAnchor::NorthWest => { + pos += Vector2::new(dim.x, -dim.y) / 2.0; + } + + SpriteAnchor::NorthEast => { + pos += Vector2::new(-dim.x, -dim.y) / 2.0; + } + + SpriteAnchor::SouthWest => { + pos += Vector2::new(dim.x, dim.y) / 2.0; + } + + SpriteAnchor::SouthEast => { + pos += Vector2::new(-dim.x, dim.y) / 2.0; + } + }; + + return CenteredRect { pos, dim }; + } +} + +impl CustomType for Rect { + fn build(mut builder: TypeBuilder) { + builder.with_name("Rect").with_fn("Rect", Self::new); + } +} + +/// Represents a rectangular region, in absolute coordinates relative to the screen center. +#[derive(Debug, Clone, Copy)] +pub(crate) struct CenteredRect { + /// 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, +} + +impl CenteredRect { + pub fn contains_point(&self, pt: Point2) -> bool { + let ne = self.pos + Vector2::new(-self.dim.x, self.dim.y) / 2.0; + let sw = self.pos + Vector2::new(self.dim.x, -self.dim.y) / 2.0; + return (pt.y < ne.y && pt.y > sw.y) && (pt.x > ne.x && pt.x < sw.x); + } + + pub fn contains_mouse(&self, input: &RenderInput, state: &RenderState) -> bool { + let fac = state.window.scale_factor() as f32; + let window_size = Vector2::new( + state.window_size.width as f32 / fac, + state.window_size.height as f32 / fac, + ); + + let pos = input.player.input.get_mouse_pos(); + let mouse_pos = Point2::new( + pos.x / fac - window_size.x / 2.0, + window_size.y / 2.0 - pos.y / fac, + ); + + return self.contains_point(mouse_pos); + } +} diff --git a/crates/render/src/ui/api/sprite.rs b/crates/render/src/ui/api/sprite.rs new file mode 100644 index 0000000..d005390 --- /dev/null +++ b/crates/render/src/ui/api/sprite.rs @@ -0,0 +1,61 @@ +use galactica_content::{resolve_edge_as_edge, Content}; +use log::error; +use rhai::{CustomType, TypeBuilder}; +use std::{cell::RefCell, rc::Rc}; + +use crate::ui::util::Sprite; + +#[derive(Debug, Clone)] +pub struct SpriteElement { + pub sprite: Rc>, + pub ct: Rc, +} + +unsafe impl Send for SpriteElement {} +unsafe impl Sync for SpriteElement {} + +impl SpriteElement { + pub fn new(ct: Rc, sprite: Rc>) -> Self { + Self { ct, sprite } + } + + // This MUST be &mut, or rhai won't recognize it. + pub fn has_name(&mut self, s: String) -> bool { + self.sprite.as_ref().borrow().name == s + } + + pub fn take_edge(&mut self, edge_name: &str, duration: f32) { + let sprite_handle = self.sprite.as_ref().borrow().anim.get_sprite(); + let sprite = self.ct.get_sprite(sprite_handle); + + let edge = resolve_edge_as_edge(edge_name, duration, |x| { + sprite.get_section_handle_by_name(x) + }); + let edge = match edge { + Err(x) => { + error!( + "UI script reference invalid section `{}` in sprite `{}`, skipping", + edge_name, sprite.name + ); + error!("error: {:?}", x); + return; + } + Ok(s) => s, + }; + + self.sprite + .as_ref() + .borrow_mut() + .anim + .jump_to(&self.ct, edge); + } +} + +impl CustomType for SpriteElement { + fn build(mut builder: TypeBuilder) { + builder + .with_name("SpriteElement") + .with_fn("has_name", Self::has_name) + .with_fn("take_edge", Self::take_edge); + } +} diff --git a/crates/render/src/ui/api/spritebuilder.rs b/crates/render/src/ui/api/spritebuilder.rs new file mode 100644 index 0000000..f2f4781 --- /dev/null +++ b/crates/render/src/ui/api/spritebuilder.rs @@ -0,0 +1,35 @@ +use rhai::{CustomType, TypeBuilder}; + +use super::Rect; + +#[derive(Debug, Clone)] +pub struct SpriteBuilder { + pub name: String, + pub rect: Rect, + pub sprite: String, + pub mask: Option, +} + +impl SpriteBuilder { + pub fn new(name: String, sprite: String, rect: Rect) -> Self { + Self { + name, + rect, + sprite, + mask: None, + } + } + + pub fn set_mask(&mut self, mask: String) { + self.mask = Some(mask); + } +} + +impl CustomType for SpriteBuilder { + fn build(mut builder: TypeBuilder) { + builder + .with_name("SpriteBuilder") + .with_fn("SpriteBuilder", Self::new) + .with_fn("set_mask", Self::set_mask); + } +} diff --git a/crates/render/src/ui/api/state.rs b/crates/render/src/ui/api/state.rs new file mode 100644 index 0000000..4d8cf71 --- /dev/null +++ b/crates/render/src/ui/api/state.rs @@ -0,0 +1,7 @@ +use rhai::{CustomType, TypeBuilder}; + +#[derive(Debug, Clone, CustomType)] +pub struct State { + pub planet_landscape: String, + pub planet_name: String, +} diff --git a/crates/render/src/ui/api/textboxbuilder.rs b/crates/render/src/ui/api/textboxbuilder.rs new file mode 100644 index 0000000..1c2dafe --- /dev/null +++ b/crates/render/src/ui/api/textboxbuilder.rs @@ -0,0 +1,48 @@ +use rhai::{CustomType, TypeBuilder}; + +use super::{Rect, TextBoxFont, TextBoxJustify}; + +#[derive(Debug, Clone)] +pub struct TextBoxBuilder { + pub name: String, + pub font_size: f32, + pub line_height: f32, + pub font: TextBoxFont, + pub justify: TextBoxJustify, + pub rect: Rect, + pub text: String, +} + +impl TextBoxBuilder { + pub fn new( + name: String, + font_size: f32, + line_height: f32, + font: TextBoxFont, + justify: TextBoxJustify, + rect: Rect, + ) -> Self { + Self { + name, + font_size, + line_height, + font, + justify, + rect, + text: String::new(), + } + } + + pub fn set_text(&mut self, text: String) { + self.text = text + } +} + +impl CustomType for TextBoxBuilder { + fn build(mut builder: TypeBuilder) { + builder + .with_name("TextBoxBuilder") + .with_fn("TextBoxBuilder", Self::new) + .with_fn("set_text", Self::set_text); + } +} diff --git a/crates/render/src/ui/api/util.rs b/crates/render/src/ui/api/util.rs new file mode 100644 index 0000000..9429951 --- /dev/null +++ b/crates/render/src/ui/api/util.rs @@ -0,0 +1,81 @@ +use rhai::plugin::*; + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum SpriteAnchor { + Center, + NorthWest, + SouthWest, + NorthEast, + SouthEast, +} + +#[export_module] +pub mod spriteanchor_mod { + #[allow(non_upper_case_globals)] + pub const Center: SpriteAnchor = SpriteAnchor::Center; + + #[allow(non_upper_case_globals)] + pub const NorthWest: SpriteAnchor = SpriteAnchor::NorthWest; + + #[allow(non_upper_case_globals)] + pub const NorthEast: SpriteAnchor = SpriteAnchor::NorthEast; + + #[allow(non_upper_case_globals)] + pub const SouthWest: SpriteAnchor = SpriteAnchor::SouthWest; + + #[allow(non_upper_case_globals)] + pub const SouthEast: SpriteAnchor = SpriteAnchor::SouthEast; +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum TextBoxFont { + Serif, + SansSerif, + Monospace, +} + +#[export_module] +pub mod textboxfont_mod { + #[allow(non_upper_case_globals)] + pub const Serif: TextBoxFont = TextBoxFont::Serif; + + #[allow(non_upper_case_globals)] + pub const SansSerif: TextBoxFont = TextBoxFont::SansSerif; + + #[allow(non_upper_case_globals)] + pub const Monospace: TextBoxFont = TextBoxFont::Monospace; +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum TextBoxJustify { + Center, + Left, +} + +#[export_module] +pub mod textboxjustify_mod { + #[allow(non_upper_case_globals)] + pub const Center: TextBoxJustify = TextBoxJustify::Center; + + #[allow(non_upper_case_globals)] + pub const Left: TextBoxJustify = TextBoxJustify::Left; +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum SceneAction { + None, + SceneOutfitter, + SceneLanded, +} + +#[export_module] +pub mod sceneaction_mod { + #[allow(non_upper_case_globals)] + pub const None: SceneAction = SceneAction::None; + + #[allow(non_upper_case_globals)] + pub const SceneOutfitter: SceneAction = SceneAction::SceneOutfitter; + + #[allow(non_upper_case_globals)] + pub const SceneLanded: SceneAction = SceneAction::SceneLanded; +}