diff --git a/lint.sh b/lint.sh index d6c8f3a4..c6b68d02 100755 --- a/lint.sh +++ b/lint.sh @@ -3,3 +3,6 @@ set -eu echo "Lint and clean up typescript:" tslint --fix docs/*.ts + +echo "Cargo clippy" +cargo clippy diff --git a/src/app.rs b/src/app.rs index dfeb96f7..0ed72856 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,14 +1,11 @@ use crate::gui::Gui; +#[derive(Default)] pub struct App { count: i32, } impl App { - pub fn new() -> Self { - App { count: 0 } - } - pub fn show_gui(&mut self, gui: &mut Gui) { if gui.button("Click me").clicked { self.count += 1; @@ -22,7 +19,7 @@ impl App { self.count )); - let commands_json = serde_json::to_string_pretty(&gui.paint_commands()).unwrap(); - gui.label(format!("All paint commands:\n{}", commands_json)); + 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 39ecd3c4..1a8b80cb 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -3,63 +3,58 @@ use crate::types::*; // TODO: implement Gui on this so we can add children to a widget // pub struct Widget {} -pub struct Gui { - commands: Vec, - input: GuiInput, +type Id = u64; - cursor: Vec2, +#[derive(Clone, Copy, Debug, Default)] +pub struct GuiState { + /// The widget being interacted with (e.g. dragged, in case of a slider). + pub active_id: Option, +} + +pub struct Gui { + pub commands: Vec, + pub cursor: Vec2, + pub input: GuiInput, + pub state: GuiState, } impl Gui { - pub fn new(input: GuiInput) -> Self { - Gui { - commands: vec![], - input, - cursor: Vec2 { x: 32.0, y: 32.0 }, - } - } - pub fn input(&self) -> &GuiInput { &self.input } - pub fn into_commands(self) -> Vec { - self.commands - } - - pub fn paint_commands(&self) -> &[GuiCmd] { + pub fn gui_commands(&self) -> &[GuiCmd] { &self.commands } - fn rect(&mut self, rect: Rect, style: RectStyle) -> InteractInfo { - let hovered = rect.contains(self.input.mouse_pos); - let clicked = hovered && self.input.mouse_clicked; - let interact = InteractInfo { hovered, clicked }; - self.commands.push(GuiCmd::Rect { - interact, - rect, - style, - }); - interact - } - - fn text>(&mut self, pos: Vec2, style: TextStyle, text: S) { - self.commands.push(GuiCmd::Text { - pos, - style, - text: text.into(), - text_align: TextAlign::Start, - }); - } - // ------------------------------------------------------------------------ pub fn button>(&mut self, text: S) -> InteractInfo { + let text: String = text.into(); + let id = self.get_id(&text); let rect = Rect { pos: self.cursor, size: Vec2 { x: 200.0, y: 32.0 }, // TODO: get from some settings }; - let interact = self.rect(rect, RectStyle::Button); + + 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, + }; + + self.commands.push(GuiCmd::Rect { + interact, + rect, + style: RectStyle::Button, + }); // TODO: clip-rect of text self.text( @@ -75,7 +70,8 @@ impl Gui { } pub fn label>(&mut self, text: S) { - for line in text.into().split("\n") { + let text: String = text.into(); + for line in text.split('\n') { self.text(self.cursor, TextStyle::Label, line); self.cursor.y += 16.0; } @@ -83,4 +79,20 @@ impl Gui { } // ------------------------------------------------------------------------ + + fn get_id(&self, id_str: &str) -> Id { + use std::hash::Hasher; + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + hasher.write(id_str.as_bytes()); + hasher.finish() + } + + fn text>(&mut self, pos: Vec2, style: TextStyle, text: S) { + self.commands.push(GuiCmd::Text { + pos, + style, + text: text.into(), + text_align: TextAlign::Start, + }); + } } diff --git a/src/lib.rs b/src/lib.rs index f6b6b930..54ec0542 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,16 +39,27 @@ pub fn show_gui(raw_input_json: &str) -> String { let raw_input: RawInput = serde_json::from_str(raw_input_json).unwrap(); lazy_static::lazy_static! { - static ref APP: Mutex = Mutex::new(app::App::new()); + static ref APP: Mutex = Default::default(); static ref LAST_INPUT: Mutex = Default::default(); + static ref GUI_STATE: 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 gui = gui::Gui::new(gui_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); - let commands = gui.into_commands(); - let commands = style::into_paint_commands(commands); + + *GUI_STATE.lock().unwrap() = gui.state; + + let commands = style::into_paint_commands(gui.gui_commands()); serde_json::to_string(&commands).unwrap() } diff --git a/src/style.rs b/src/style.rs index a4a004a3..458c11db 100644 --- a/src/style.rs +++ b/src/style.rs @@ -9,7 +9,9 @@ fn translate_cmd(cmd: GuiCmd) -> PaintCmd { interact, } => match style { RectStyle::Button => { - let fill_style = if interact.hovered { + let fill_style = if interact.active { + "#888888ff".to_string() + } else if interact.hovered { "#444444ff".to_string() } else { "#222222ff".to_string() @@ -43,6 +45,6 @@ fn translate_cmd(cmd: GuiCmd) -> PaintCmd { } } -pub fn into_paint_commands(gui_commands: Vec) -> Vec { - gui_commands.into_iter().map(translate_cmd).collect() +pub fn into_paint_commands(gui_commands: &[GuiCmd]) -> Vec { + gui_commands.iter().cloned().map(translate_cmd).collect() } diff --git a/src/types.rs b/src/types.rs index f0f1cbdf..7cead124 100644 --- a/src/types.rs +++ b/src/types.rs @@ -74,10 +74,37 @@ 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, + + /// The mouse went got pressed on this thing this frame pub clicked: bool, + + /// The mouse is interacting with this thing (e.g. dragging it) + pub active: bool, } #[derive(Clone, Copy, Debug, Serialize)]