diff --git a/games/rstnode/rst-client/src/color.rs b/games/rstnode/rst-client/src/color.rs index 4fbd7519dcd..65c32974ac8 100644 --- a/games/rstnode/rst-client/src/color.rs +++ b/games/rstnode/rst-client/src/color.rs @@ -1,7 +1,32 @@ use ggez::graphics::Color as EzColor; use rst_core::data::Color; - pub fn to(Color(r, g, b): Color) -> EzColor { EzColor::from_rgb(r, g, b) } + +/// A utility for manipulating colours +pub trait ColorUtils { + fn darken(&self, factor: u8) -> Self; + fn brighten(&self, factor: u8) -> Self; +} + +impl ColorUtils for EzColor { + fn darken(&self, factor: u8) -> Self { + Self { + r: self.r / factor as f32, + g: self.g / factor as f32, + b: self.b / factor as f32, + a: self.a, + } + } + + fn brighten(&self, factor: u8) -> Self { + Self { + r: self.r * factor as f32, + g: self.g * factor as f32, + b: self.b * factor as f32, + a: self.a, + } + } +} diff --git a/games/rstnode/rst-client/src/editor/mod.rs b/games/rstnode/rst-client/src/editor/mod.rs new file mode 100644 index 00000000000..ba786701753 --- /dev/null +++ b/games/rstnode/rst-client/src/editor/mod.rs @@ -0,0 +1,50 @@ +use crate::{assets::Assets, input::InputHandle, ui::Button, viewport::Viewport, GameSettings}; +use ggez::{ + event::{EventHandler, MouseButton}, + graphics::{self, Color}, + Context, GameResult, +}; + +pub struct EditorState { + assets: Assets, + settings: GameSettings, + input: InputHandle, + vp: Viewport, + btn: Button, +} + +impl EditorState { + pub fn new(settings: GameSettings, assets: Assets) -> Self { + info!("Initialising map editor state"); + Self { + assets, + settings, + vp: Viewport::new(), + input: InputHandle::new(), + btn: Button::new( + (25.0, 25.0).into(), + (250.0, 125.0).into(), + Some("Create Node".into()), + Color::from_rgb(50, 50, 50), + ), + } + } +} + +impl EventHandler for EditorState { + fn update(&mut self, ctx: &mut Context) -> GameResult<()> { + Ok(()) + } + + fn mouse_button_down_event(&mut self, ctx: &mut Context, btn: MouseButton, x: f32, y: f32) { + self.btn.mouse_button_down_event(ctx, btn, x, y) + } + + fn draw(&mut self, ctx: &mut Context) -> GameResult<()> { + graphics::clear(ctx, graphics::Color::from_rgb(15, 15, 15)); + + self.btn.draw(ctx)?; + + graphics::present(ctx) + } +} diff --git a/games/rstnode/rst-client/src/ui/button.rs b/games/rstnode/rst-client/src/ui/button.rs new file mode 100644 index 00000000000..9d51b0271cb --- /dev/null +++ b/games/rstnode/rst-client/src/ui/button.rs @@ -0,0 +1,109 @@ +use crate::color::ColorUtils; +use ggez::graphics::{self, Align, Color, DrawMode, DrawParam, Drawable, MeshBuilder, Rect, Text}; +use ggez::{ + event::{EventHandler, MouseButton}, + Context, GameResult, +}; +use rst_core::data::Pos; +use std::collections::HashMap; + +/// A button with next that can be clicked to run some code +pub struct Button { + pos: Pos, + size: Pos, + text: Option, + color_base: Color, + color_trim: Color, + cbs: HashMap ()>>, +} + +impl Button { + /// Create a new button + pub fn new( + pos: Pos, + size: Pos, + text: impl Into>, + color: impl Into, + ) -> Self { + let color = color.into(); + + Self { + pos, + size, + text: text.into(), + color_base: color.clone(), + color_trim: color.brighten(2), + cbs: HashMap::new(), + } + } + + /// Register an on-click listening closure + pub fn on_click () + 'static>(&mut self, btn: MouseButton, cb: C) { + self.cbs.insert(btn, Box::new(cb)); + } + + /// Create a button with text which auto-infers its required size + pub fn auto(pos: Pos, text: String, color: impl Into) -> Self { + todo!() + } + + fn boundry_check(&self, x: f32, y: f32) -> bool { + self.pos.x < x + && (self.pos.x + self.size.x) > x + && self.pos.y < y + && (self.pos.y + self.size.y) > y + } +} + +impl EventHandler for Button { + fn update(&mut self, ctx: &mut Context) -> GameResult<()> { + Ok(()) + } + + fn mouse_button_down_event(&mut self, _ctx: &mut Context, btn: MouseButton, x: f32, y: f32) { + if self.boundry_check(x, y) { + trace!("Triggering button state"); + std::mem::swap(&mut self.color_base, &mut self.color_trim); + if let Some(ref mut callback) = self.cbs.get_mut(&btn) { + callback(); + } + } + } + + fn draw(&mut self, ctx: &mut Context) -> GameResult<()> { + let mut mb = MeshBuilder::new(); + let bounds = Rect { + x: self.pos.x, + y: self.pos.y, + w: self.size.x, + h: self.size.y, + }; + + // Create the basic building blocks + let rect = mb + .rectangle(DrawMode::fill(), bounds, self.color_base.clone())? + .build(ctx)?; + let frame = mb + .rectangle(DrawMode::stroke(2.0), bounds, self.color_trim.clone())? + .build(ctx)?; + + rect.draw(ctx, DrawParam::new())?; + frame.draw(ctx, DrawParam::new())?; + + if let Some(ref text) = self.text { + let Pos { x, y } = self.pos; + let Pos { x: w, y: h } = self.size; + + let mut text = Text::new(text.clone()); + text.draw( + ctx, + DrawParam::new().dest([ + x + (w / 2.0 - text.width(ctx) / 2.0), + y + (h / 2.0 - text.height(ctx) / 2.0), + ]), + )?; + } + + Ok(()) + } +} diff --git a/games/rstnode/rst-client/src/ui/mod.rs b/games/rstnode/rst-client/src/ui/mod.rs new file mode 100644 index 00000000000..449f04efc1e --- /dev/null +++ b/games/rstnode/rst-client/src/ui/mod.rs @@ -0,0 +1,4 @@ +//! Use ggez to render various UI elements and component clusters + +mod button; +pub use button::*;