diff --git a/src/app.rs b/src/app.rs index c2975462..c6389c5e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,4 +1,8 @@ -use crate::{gui::Gui, math::*, types::*}; +use crate::{layout::Layout, math::*, types::*}; + +pub trait GuiSettings { + fn show_gui(&mut self, gui: &mut Layout); +} pub struct App { checked: bool, @@ -25,8 +29,8 @@ impl Default for App { } } -impl App { - pub fn show_gui(&mut self, gui: &mut Gui) { +impl GuiSettings for App { + fn show_gui(&mut self, gui: &mut Layout) { gui.checkbox("checkbox", &mut self.checked); if gui @@ -71,7 +75,28 @@ impl App { }), }])); - let commands_json = format!("{:#?}", gui.gui_commands()); - gui.label(format!("All gui commands: {}", commands_json)); + gui.label("LayoutOptions:"); + let mut layout_options = gui.layout_options; + layout_options.show_gui(gui); + gui.layout_options = layout_options; + } +} + +impl GuiSettings for crate::layout::LayoutOptions { + fn show_gui(&mut self, gui: &mut Layout) { + if gui.button("Reset LayoutOptions").clicked { + *self = Default::default(); + } + gui.slider_f32("item_spacing.x", &mut self.item_spacing.x, 0.0, 10.0); + gui.slider_f32("item_spacing.y", &mut self.item_spacing.y, 0.0, 10.0); + gui.slider_f32("width", &mut self.width, 0.0, 1000.0); + gui.slider_f32("button_height", &mut self.button_height, 0.0, 60.0); + gui.slider_f32( + "checkbox_radio_height", + &mut self.checkbox_radio_height, + 0.0, + 60.0, + ); + gui.slider_f32("slider_height", &mut self.slider_height, 0.0, 60.0); } } diff --git a/src/emgui.rs b/src/emgui.rs new file mode 100644 index 00000000..ace896b0 --- /dev/null +++ b/src/emgui.rs @@ -0,0 +1,28 @@ +use crate::{layout, math::*, style, types::*}; + +/// Encapsulates input, layout and painting for ease of use. +#[derive(Clone, Debug, Default)] +pub struct Emgui { + pub last_input: RawInput, + pub layout: layout::Layout, + pub style: style::Style, +} + +impl Emgui { + pub fn new_frame(&mut self, new_input: RawInput) { + let gui_input = GuiInput::from_last_and_new(&self.last_input, &new_input); + self.last_input = new_input; + + // TODO: this should be nicer + self.layout.commands.clear(); + self.layout.cursor = vec2(32.0, 32.0); + self.layout.input = gui_input; + if !gui_input.mouse_down { + self.layout.state.active_id = None; + } + } + + pub fn paint(&mut self) -> Vec { + style::into_paint_commands(self.layout.gui_commands(), &self.style) + } +} diff --git a/src/gui.rs b/src/layout.rs similarity index 65% rename from src/gui.rs rename to src/layout.rs index df38201b..2ee5670d 100644 --- a/src/gui.rs +++ b/src/layout.rs @@ -1,24 +1,59 @@ use crate::{math::*, types::*}; -// TODO: implement Gui on this so we can add children to a widget -// pub struct Widget {} +// ---------------------------------------------------------------------------- -type Id = u64; +#[derive(Clone, Copy, Debug, Serialize)] +pub struct LayoutOptions { + // Horizontal and vertical spacing between widgets + pub item_spacing: Vec2, + + /// Default width of buttons, sliders etc + pub width: f32, + + /// Height of a button + pub button_height: f32, + + /// Height of a checkbox and radio button + pub checkbox_radio_height: f32, + + /// Height of a slider + pub slider_height: f32, +} + +impl Default for LayoutOptions { + fn default() -> Self { + LayoutOptions { + item_spacing: Vec2 { x: 8.0, y: 4.0 }, + width: 200.0, + button_height: 24.0, + checkbox_radio_height: 24.0, + slider_height: 32.0, + } + } +} + +// ---------------------------------------------------------------------------- #[derive(Clone, Copy, Debug, Default)] -pub struct GuiState { +pub struct State { /// The widget being interacted with (e.g. dragged, in case of a slider). pub active_id: Option, } -pub struct Gui { +// ---------------------------------------------------------------------------- + +type Id = u64; + +#[derive(Clone, Debug, Default)] +pub struct Layout { pub commands: Vec, pub cursor: Vec2, pub input: GuiInput, - pub state: GuiState, + pub layout_options: LayoutOptions, + pub state: State, } -impl Gui { +impl Layout { pub fn input(&self) -> &GuiInput { &self.input } @@ -34,7 +69,10 @@ impl Gui { let id = self.get_id(&text); let rect = Rect { pos: self.cursor, - size: Vec2 { x: 176.0, y: 24.0 }, // TODO: get from some settings + size: Vec2 { + x: self.layout_options.width, + y: self.layout_options.button_height, + }, }; let interact = self.interactive_rect(id, &rect); @@ -45,7 +83,7 @@ impl Gui { text, }); - self.cursor.y += rect.size.y + 16.0; + self.cursor.y += rect.size.y + self.layout_options.item_spacing.y; interact } @@ -54,7 +92,10 @@ impl Gui { 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 + size: Vec2 { + x: self.layout_options.width, + y: self.layout_options.checkbox_radio_height, + }, }; let interact = self.interactive_rect(id, &rect); @@ -69,7 +110,7 @@ impl Gui { text: label, }); - self.cursor.y += rect.size.y + 16.0; + self.cursor.y += rect.size.y + self.layout_options.item_spacing.y; interact } @@ -79,7 +120,7 @@ impl Gui { self.text(self.cursor, TextStyle::Label, line); self.cursor.y += 16.0; } - self.cursor.y += 16.0; // Padding + self.cursor.y += self.layout_options.item_spacing.y; } /// A radio button @@ -88,7 +129,10 @@ impl Gui { 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 + size: Vec2 { + x: self.layout_options.width, + y: self.layout_options.checkbox_radio_height, + }, }; let interact = self.interactive_rect(id, &rect); @@ -100,7 +144,7 @@ impl Gui { text: label, }); - self.cursor.y += rect.size.y + 16.0; + self.cursor.y += rect.size.y + self.layout_options.item_spacing.y; interact } @@ -115,7 +159,10 @@ impl Gui { 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 + size: Vec2 { + x: self.layout_options.width, + y: self.layout_options.slider_height, + }, }; let interact = self.interactive_rect(id, &rect); @@ -134,7 +181,7 @@ impl Gui { value: *value, }); - self.cursor.y += rect.size.y + 16.0; + self.cursor.y += rect.size.y + self.layout_options.item_spacing.y; interact } diff --git a/src/lib.rs b/src/lib.rs index 25317656..c8e384f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,16 +13,16 @@ use std::sync::Mutex; use wasm_bindgen::prelude::*; -use crate::{math::Vec2, types::*}; +use crate::types::*; pub mod app; -pub mod gui; +pub mod emgui; +pub mod layout; pub mod math; pub mod style; pub mod types; /* - // Fast compilation, slow code: fn foo(x: &dyn Trait); @@ -43,26 +43,15 @@ pub fn show_gui(raw_input_json: &str) -> String { lazy_static::lazy_static! { static ref APP: Mutex = Default::default(); - static ref LAST_INPUT: Mutex = Default::default(); - static ref GUI_STATE: Mutex = Default::default(); + static ref EMGUI: Mutex = Default::default(); } - let gui_input = GuiInput::from_last_and_new(&LAST_INPUT.lock().unwrap(), &raw_input); - *LAST_INPUT.lock().unwrap() = raw_input; + let mut emgui = EMGUI.lock().unwrap(); + emgui.new_frame(raw_input); - let mut gui = gui::Gui { - commands: Vec::new(), - cursor: Vec2 { x: 32.0, y: 32.0 }, - input: gui_input, - state: *GUI_STATE.lock().unwrap(), - }; - if !gui_input.mouse_down { - gui.state.active_id = None; - } - APP.lock().unwrap().show_gui(&mut gui); + use crate::app::GuiSettings; + APP.lock().unwrap().show_gui(&mut emgui.layout); - *GUI_STATE.lock().unwrap() = gui.state; - - let commands = style::into_paint_commands(gui.gui_commands()); + let commands = emgui.paint(); serde_json::to_string(&commands).unwrap() } diff --git a/src/math.rs b/src/math.rs index 1dce4c1e..47193884 100644 --- a/src/math.rs +++ b/src/math.rs @@ -45,6 +45,10 @@ pub struct Rect { } impl Rect { + pub fn from_min_size(min: Vec2, size: Vec2) -> Self { + Rect { pos: min, size } + } + pub fn from_center_size(center: Vec2, size: Vec2) -> Self { Rect { pos: center - size * 0.5, @@ -74,7 +78,7 @@ impl Rect { } } -pub fn lerp(t: f32, min: f32, max: f32) -> f32 { +pub fn lerp(min: f32, max: f32, t: f32) -> f32 { (1.0 - t) * min + t * max } @@ -86,5 +90,5 @@ pub fn remap_clamp(from: f32, from_min: f32, from_max: f32, to_min: f32, to_max: } else { (from - from_min) / (from_max - from_min) }; - lerp(t, to_min, to_max) + lerp(to_min, to_max, t) } diff --git a/src/style.rs b/src/style.rs index f68cb630..9141ed44 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,7 +1,19 @@ use crate::{math::*, types::*}; +#[derive(Clone, Copy, Debug)] +pub struct Style { + /// For stuff like checkmarks in check boxes + pub line_width: f32, +} + +impl Default for Style { + fn default() -> Style { + Style { line_width: 2.0 } + } +} + /// TODO: a Style struct which defines colors etc -fn translate_cmd(out_commands: &mut Vec, cmd: GuiCmd) { +fn translate_cmd(out_commands: &mut Vec, style: &Style, cmd: GuiCmd) { match cmd { GuiCmd::PaintCommands(mut commands) => out_commands.append(&mut commands), GuiCmd::Button { @@ -29,7 +41,7 @@ fn translate_cmd(out_commands: &mut Vec, cmd: GuiCmd) { font: "14px Palatino".to_string(), pos: Vec2 { x: rect.center().x, - y: rect.center().y + 14.0 / 2.0, + y: rect.center().y + 6.0, }, text, text_align: TextAlign::Center, @@ -79,7 +91,7 @@ fn translate_cmd(out_commands: &mut Vec, cmd: GuiCmd) { vec2(smaller_rect.max().x, smaller_rect.min().y), ], style: stroke_style.clone(), - width: 4.0, + width: style.line_width, }); } @@ -88,7 +100,7 @@ fn translate_cmd(out_commands: &mut Vec, cmd: GuiCmd) { font: "14px Palatino".to_string(), pos: Vec2 { x: box_rect.max().x + 4.0, - y: rect.center().y + 14.0 / 2.0, + y: rect.center().y + 5.0, }, text, text_align: TextAlign::Start, @@ -153,12 +165,17 @@ fn translate_cmd(out_commands: &mut Vec, cmd: GuiCmd) { rect, value, } => { - let thin_rect = Rect::from_center_size(rect.center(), vec2(rect.size.x, 8.0)); + let thin_rect = Rect::from_min_size( + vec2(rect.min().x, lerp(rect.min().y, rect.max().y, 2.0 / 3.0)), + 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_rect = Rect::from_center_size( + vec2(marker_center_x, thin_rect.center().y), + vec2(16.0, 16.0), + ); let marker_fill_style = if interact.active { "#888888ff".to_string() @@ -187,7 +204,10 @@ fn translate_cmd(out_commands: &mut Vec, cmd: GuiCmd) { out_commands.push(PaintCmd::Text { fill_style: "#ffffffbb".to_string(), font: "14px Palatino".to_string(), - pos: rect.min(), + pos: vec2( + rect.min().x, + lerp(rect.min().y, rect.max().y, 1.0 / 3.0) + 6.0, + ), text: format!("{}: {:.3}", label, value), text_align: TextAlign::Start, }); @@ -204,7 +224,7 @@ fn translate_cmd(out_commands: &mut Vec, cmd: GuiCmd) { out_commands.push(PaintCmd::Text { fill_style, font: "14px Palatino".to_string(), - pos, + pos: pos + vec2(0.0, 7.0), // TODO: FIXME text, text_align, }); @@ -212,10 +232,10 @@ fn translate_cmd(out_commands: &mut Vec, cmd: GuiCmd) { } } -pub fn into_paint_commands(gui_commands: &[GuiCmd]) -> Vec { +pub fn into_paint_commands(gui_commands: &[GuiCmd], style: &Style) -> Vec { let mut paint_commands = vec![]; for gui_cmd in gui_commands { - translate_cmd(&mut paint_commands, gui_cmd.clone()) + translate_cmd(&mut paint_commands, style, gui_cmd.clone()) } paint_commands } diff --git a/src/types.rs b/src/types.rs index 0a9e0161..f2f96b22 100644 --- a/src/types.rs +++ b/src/types.rs @@ -16,7 +16,7 @@ pub struct RawInput { } /// What the gui maintains -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct GuiInput { /// Is the button currently down? pub mouse_down: bool, @@ -48,28 +48,6 @@ impl GuiInput { // ---------------------------------------------------------------------------- -/// Names taken from Dear ImGui -#[derive(Clone, Copy, Debug, Default, Serialize)] -pub struct LayoutOptions { - // Horizontal and vertical spacing between widgets - item_spacing: Vec2, - - /// Padding within a framed rectangle (used by most widgets) - frame_padding: Vec2, -} - -impl LayoutOptions { - pub fn new() -> Self { - // Values taken from Dear ImGui - LayoutOptions { - item_spacing: Vec2 { x: 8.0, y: 4.0 }, - frame_padding: Vec2 { x: 4.0, y: 3.0 }, - } - } -} - -// ---------------------------------------------------------------------------- - #[derive(Clone, Copy, Debug, Default, Serialize)] pub struct InteractInfo { pub hovered: bool,