diff --git a/src/app.rs b/src/app.rs index 0ed72856..a108386a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3,6 +3,7 @@ use crate::gui::Gui; #[derive(Default)] pub struct App { count: i32, + slider_value: f32, } impl App { @@ -10,14 +11,10 @@ impl App { if gui.button("Click me").clicked { self.count += 1; } - if gui.button("Or click me instead!").clicked { - self.count += 1; - } - gui.label(format!( - "The buttons have been clicked {} times", - self.count - )); + gui.label(format!("The button have been clicked {} times", self.count)); + + gui.slider_f32("Slider", &mut self.slider_value, 0.0, 10.0); let commands_json = format!("{:#?}", gui.gui_commands()); gui.label(format!("All gui commands: {}", commands_json)); diff --git a/src/gui.rs b/src/gui.rs index 1a8b80cb..e01d0e92 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,4 +1,4 @@ -use crate::types::*; +use crate::{math::*, types::*}; // TODO: implement Gui on this so we can add children to a widget // pub struct Widget {} @@ -37,18 +37,7 @@ impl Gui { size: Vec2 { x: 200.0, y: 32.0 }, // TODO: get from some settings }; - let hovered = rect.contains(self.input.mouse_pos); - let clicked = hovered && self.input.mouse_clicked; - if clicked { - self.state.active_id = Some(id); - } - let active = self.state.active_id == Some(id); - - let interact = InteractInfo { - hovered, - clicked, - active, - }; + let interact = self.interactive_rect(id, &rect); self.commands.push(GuiCmd::Rect { interact, @@ -78,8 +67,58 @@ impl Gui { self.cursor.y += 16.0; // Padding } + pub fn slider_f32>( + &mut self, + label: S, + value: &mut f32, + min: f32, + max: f32, + ) -> InteractInfo { + let label: String = label.into(); + let id = self.get_id(&label); + let rect = Rect { + pos: self.cursor, + size: Vec2 { x: 200.0, y: 24.0 }, // TODO: get from some settings + }; + let interact = self.interactive_rect(id, &rect); + + debug_assert!(min <= max); + + if interact.active { + *value = remap_clamp(self.input.mouse_pos.x, rect.min().x, rect.max().x, min, max); + } + + self.commands.push(GuiCmd::Slider { + interact, + label, + max, + min, + rect, + value: *value, + }); + + self.cursor.y += rect.size.y + 16.0; + + interact + } + // ------------------------------------------------------------------------ + fn interactive_rect(&mut self, id: Id, rect: &Rect) -> InteractInfo { + let hovered = rect.contains(self.input.mouse_pos); + let clicked = hovered && self.input.mouse_clicked; + if clicked { + self.state.active_id = Some(id); + } + let active = self.state.active_id == Some(id); + + InteractInfo { + hovered, + clicked, + active, + } + } + fn get_id(&self, id_str: &str) -> Id { use std::hash::Hasher; let mut hasher = std::collections::hash_map::DefaultHasher::new(); diff --git a/src/lib.rs b/src/lib.rs index 54ec0542..25317656 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,20 +1,23 @@ +#![deny(warnings)] + extern crate lazy_static; extern crate serde; extern crate serde_json; extern crate wasm_bindgen; extern crate web_sys; -#[macro_use] +#[macro_use] // TODO: get rid of this extern crate serde_derive; use std::sync::Mutex; use wasm_bindgen::prelude::*; -use crate::types::*; +use crate::{math::Vec2, types::*}; pub mod app; pub mod gui; +pub mod math; pub mod style; pub mod types; diff --git a/src/math.rs b/src/math.rs new file mode 100644 index 00000000..1dce4c1e --- /dev/null +++ b/src/math.rs @@ -0,0 +1,90 @@ +#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)] +pub struct Vec2 { + pub x: f32, + pub y: f32, +} + +impl std::ops::Add for Vec2 { + type Output = Vec2; + fn add(self, rhs: Vec2) -> Vec2 { + Vec2 { + x: self.x + rhs.x, + y: self.y + rhs.y, + } + } +} + +impl std::ops::Sub for Vec2 { + type Output = Vec2; + fn sub(self, rhs: Vec2) -> Vec2 { + Vec2 { + x: self.x - rhs.x, + y: self.y - rhs.y, + } + } +} + +impl std::ops::Mul for Vec2 { + type Output = Vec2; + fn mul(self, factor: f32) -> Vec2 { + Vec2 { + x: self.x * factor, + y: self.y * factor, + } + } +} + +pub fn vec2(x: f32, y: f32) -> Vec2 { + Vec2 { x, y } +} + +#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)] +pub struct Rect { + pub pos: Vec2, + pub size: Vec2, +} + +impl Rect { + pub fn from_center_size(center: Vec2, size: Vec2) -> Self { + Rect { + pos: center - size * 0.5, + size, + } + } + + pub fn contains(&self, p: Vec2) -> bool { + self.pos.x <= p.x + && p.x <= self.pos.x + self.size.x + && self.pos.y <= p.y + && p.y <= self.pos.y + self.size.y + } + + pub fn center(&self) -> Vec2 { + Vec2 { + x: self.pos.x + self.size.x / 2.0, + y: self.pos.y + self.size.y / 2.0, + } + } + + pub fn min(&self) -> Vec2 { + self.pos + } + pub fn max(&self) -> Vec2 { + self.pos + self.size + } +} + +pub fn lerp(t: f32, min: f32, max: f32) -> f32 { + (1.0 - t) * min + t * max +} + +pub fn remap_clamp(from: f32, from_min: f32, from_max: f32, to_min: f32, to_max: f32) -> f32 { + let t = if from <= from_min { + 0.0 + } else if from >= from_max { + 1.0 + } else { + (from - from_min) / (from_max - from_min) + }; + lerp(t, to_min, to_max) +} diff --git a/src/style.rs b/src/style.rs index 458c11db..7d330ea3 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,7 +1,7 @@ -use crate::types::*; +use crate::{math::*, types::*}; /// TODO: a Style struct which defines colors etc -fn translate_cmd(cmd: GuiCmd) -> PaintCmd { +fn translate_cmd(out_commands: &mut Vec, cmd: GuiCmd) { match cmd { GuiCmd::Rect { rect, @@ -16,12 +16,12 @@ fn translate_cmd(cmd: GuiCmd) -> PaintCmd { } else { "#222222ff".to_string() }; - PaintCmd::RoundedRect { + out_commands.push(PaintCmd::RoundedRect { corner_radius: 5.0, fill_style, pos: rect.pos, size: rect.size, - } + }); } }, GuiCmd::Text { @@ -34,17 +34,66 @@ fn translate_cmd(cmd: GuiCmd) -> PaintCmd { TextStyle::Button => "#ffffffbb".to_string(), TextStyle::Label => "#ffffffbb".to_string(), }; - PaintCmd::Text { + out_commands.push(PaintCmd::Text { fill_style, font: "14px Palatino".to_string(), pos, text, text_align, - } + }); + } + GuiCmd::Slider { + interact, + label, + max, + min, + rect, + value, + } => { + let thin_rect = Rect::from_center_size(rect.center(), vec2(rect.size.x, 8.0)); + + let marker_center_x = remap_clamp(value, min, max, rect.min().x, rect.max().x); + + let marker_rect = + Rect::from_center_size(vec2(marker_center_x, rect.center().y), vec2(16.0, 16.0)); + + let marker_fill_style = if interact.active { + "#888888ff".to_string() + } else if interact.hovered { + "#444444ff".to_string() + } else { + "#222222ff".to_string() + }; + + out_commands.push(PaintCmd::RoundedRect { + corner_radius: 2.0, + fill_style: "#111111ff".to_string(), + pos: thin_rect.pos, + size: thin_rect.size, + }); + + out_commands.push(PaintCmd::RoundedRect { + corner_radius: 3.0, + fill_style: marker_fill_style, + pos: marker_rect.pos, + size: marker_rect.size, + }); + + out_commands.push(PaintCmd::Text { + fill_style: "#ffffffbb".to_string(), + font: "14px Palatino".to_string(), + pos: rect.center(), + text: format!("{}: {:.3}", label, value), + text_align: TextAlign::Center, + }); } } } pub fn into_paint_commands(gui_commands: &[GuiCmd]) -> Vec { - gui_commands.iter().cloned().map(translate_cmd).collect() + let mut paint_commands = vec![]; + for gui_cmd in gui_commands { + translate_cmd(&mut paint_commands, gui_cmd.clone()) + } + paint_commands } diff --git a/src/types.rs b/src/types.rs index 7cead124..86c8109f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,30 +1,4 @@ -#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)] -pub struct Vec2 { - pub x: f32, - pub y: f32, -} - -#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)] -pub struct Rect { - pub pos: Vec2, - pub size: Vec2, -} - -impl Rect { - pub fn contains(&self, p: Vec2) -> bool { - self.pos.x <= p.x - && p.x <= self.pos.x + self.size.x - && self.pos.y <= p.y - && p.y <= self.pos.y + self.size.y - } - - pub fn center(&self) -> Vec2 { - Vec2 { - x: self.pos.x + self.size.x / 2.0, - y: self.pos.y + self.size.y / 2.0, - } - } -} +use crate::math::{Rect, Vec2}; // ---------------------------------------------------------------------------- @@ -141,6 +115,14 @@ pub enum GuiCmd { text_align: TextAlign, style: TextStyle, }, + Slider { + interact: InteractInfo, + label: String, + max: f32, + min: f32, + rect: Rect, + value: f32, + }, } // ----------------------------------------------------------------------------