diff --git a/docs/emgui_bg.wasm b/docs/emgui_bg.wasm index 3856f3f0..1855cb8e 100644 Binary files a/docs/emgui_bg.wasm and b/docs/emgui_bg.wasm differ diff --git a/docs/frontend.js b/docs/frontend.js index fd62a684..8423748c 100644 --- a/docs/frontend.js +++ b/docs/frontend.js @@ -76,11 +76,7 @@ function js_gui(input) { }); return commands; } -function paint_gui(canvas, mouse_pos) { - var input = { - mouse_pos: mouse_pos, - screen_size: { x: canvas.width, y: canvas.height } - }; +function paint_gui(canvas, input) { var commands = rust_gui(input); for (var _i = 0, commands_1 = commands; _i < commands_1.length; _i++) { var cmd = commands_1[_i]; @@ -88,6 +84,15 @@ function paint_gui(canvas, mouse_pos) { } } // ---------------------------------------------------------------------------- +var g_mouse_pos = { x: -1000.0, y: -1000.0 }; +var g_mouse_down = false; +function get_input(canvas) { + return { + mouse_down: g_mouse_down, + mouse_pos: g_mouse_pos, + screen_size: { x: canvas.width, y: canvas.height } + }; +} function mouse_pos_from_event(canvas, evt) { var rect = canvas.getBoundingClientRect(); return { @@ -98,12 +103,22 @@ function mouse_pos_from_event(canvas, evt) { function initialize() { var canvas = document.getElementById("canvas"); canvas.addEventListener("mousemove", function (evt) { - var mouse_pos = mouse_pos_from_event(canvas, evt); - paint_gui(canvas, mouse_pos); + g_mouse_pos = mouse_pos_from_event(canvas, evt); + paint_gui(canvas, get_input(canvas)); + }, false); + canvas.addEventListener("mouseleave", function (evt) { + g_mouse_pos = { x: -1000.0, y: -1000.0 }; + paint_gui(canvas, get_input(canvas)); }, false); canvas.addEventListener("mousedown", function (evt) { - var mouse_pos = mouse_pos_from_event(canvas, evt); - paint_gui(canvas, mouse_pos); + g_mouse_pos = mouse_pos_from_event(canvas, evt); + g_mouse_down = true; + paint_gui(canvas, get_input(canvas)); }, false); - paint_gui(canvas, { x: 0, y: 0 }); + canvas.addEventListener("mouseup", function (evt) { + g_mouse_pos = mouse_pos_from_event(canvas, evt); + g_mouse_down = false; + paint_gui(canvas, get_input(canvas)); + }, false); + paint_gui(canvas, get_input(canvas)); } diff --git a/docs/frontend.ts b/docs/frontend.ts index d1251498..ee6f3a05 100644 --- a/docs/frontend.ts +++ b/docs/frontend.ts @@ -104,10 +104,16 @@ function paintCommand(canvas, cmd: PaintCmd) { // ---------------------------------------------------------------------------- -interface Input { +/// What the integration gives to the gui. +interface RawInput { + /// Is the button currently down? + mouse_down: boolean; + + /// Current position of the mouse in points. mouse_pos: Vec2; + + /// Size of the screen in points. screen_size: Vec2; - // TODO: mouse down etc } // ---------------------------------------------------------------------------- @@ -127,13 +133,13 @@ wasm_bindgen("./emgui_bg.wasm") .then(wasm_loaded) .catch(console.error); -function rust_gui(input: Input): PaintCmd[] { +function rust_gui(input: RawInput): PaintCmd[] { return JSON.parse(wasm_bindgen.show_gui(JSON.stringify(input))); } // ---------------------------------------------------------------------------- -function js_gui(input: Input): PaintCmd[] { +function js_gui(input: RawInput): PaintCmd[] { const commands = []; commands.push({ @@ -152,11 +158,7 @@ function js_gui(input: Input): PaintCmd[] { return commands; } -function paint_gui(canvas, mouse_pos) { - const input = { - mouse_pos, - screen_size: { x: canvas.width, y: canvas.height }, - }; +function paint_gui(canvas, input: RawInput) { const commands = rust_gui(input); for (const cmd of commands) { @@ -166,6 +168,17 @@ function paint_gui(canvas, mouse_pos) { // ---------------------------------------------------------------------------- +let g_mouse_pos = { x: -1000.0, y: -1000.0 }; +let g_mouse_down = false; + +function get_input(canvas): RawInput { + return { + mouse_down: g_mouse_down, + mouse_pos: g_mouse_pos, + screen_size: { x: canvas.width, y: canvas.height }, + }; +} + function mouse_pos_from_event(canvas, evt): Vec2 { const rect = canvas.getBoundingClientRect(); return { @@ -180,8 +193,17 @@ function initialize() { canvas.addEventListener( "mousemove", (evt) => { - const mouse_pos = mouse_pos_from_event(canvas, evt); - paint_gui(canvas, mouse_pos); + g_mouse_pos = mouse_pos_from_event(canvas, evt); + paint_gui(canvas, get_input(canvas)); + }, + false, + ); + + canvas.addEventListener( + "mouseleave", + (evt) => { + g_mouse_pos = { x: -1000.0, y: -1000.0 }; + paint_gui(canvas, get_input(canvas)); }, false, ); @@ -189,11 +211,22 @@ function initialize() { canvas.addEventListener( "mousedown", (evt) => { - const mouse_pos = mouse_pos_from_event(canvas, evt); - paint_gui(canvas, mouse_pos); + g_mouse_pos = mouse_pos_from_event(canvas, evt); + g_mouse_down = true; + paint_gui(canvas, get_input(canvas)); }, false, ); - paint_gui(canvas, { x: 0, y: 0 }); + canvas.addEventListener( + "mouseup", + (evt) => { + g_mouse_pos = mouse_pos_from_event(canvas, evt); + g_mouse_down = false; + paint_gui(canvas, get_input(canvas)); + }, + false, + ); + + paint_gui(canvas, get_input(canvas)); } diff --git a/docs/index.html b/docs/index.html index 13f8a303..2308071f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -41,4 +41,3 @@ - diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 00000000..e9e885d7 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,28 @@ +use gui::Gui; + +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; + } + if gui.button("Or click me instead!").clicked { + self.count += 1; + } + + gui.label(format!( + "The buttons have been clicked {} times", + self.count + )); + + let commands_json = serde_json::to_string_pretty(&gui.paint_commands()).unwrap(); + gui.label(format!("All paint commands:\n{}", commands_json)); + } +} diff --git a/src/gui.rs b/src/gui.rs new file mode 100644 index 00000000..2f268a90 --- /dev/null +++ b/src/gui.rs @@ -0,0 +1,100 @@ +use types::*; + +#[derive(Default)] +pub struct InteractInfo { + pub hovering: bool, + pub clicked: bool, +} + +// TODO: implement Gui on this so we can add children to a widget +// pub struct Widget {} + +pub struct Gui { + commands: Vec, + input: GuiInput, + + cursor: Vec2, +} + +impl Gui { + pub fn new(input: GuiInput) -> Self { + Gui { + commands: vec![PaintCmd::Clear { + fill_style: "#44444400".to_string(), + }], + 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) -> &[PaintCmd] { + &self.commands + } + + fn rect(&mut self, rect: Rect, fill_style: String) -> InteractInfo { + let hovering = rect.contains(self.input.mouse_pos); + let clicked = hovering && self.input.mouse_clicked; + let ii = InteractInfo { hovering, clicked }; + self.commands.push(PaintCmd::RoundedRect { + fill_style, + pos: rect.pos, + corner_radius: 5.0, + size: rect.size, + }); + ii + } + + fn text>(&mut self, pos: Vec2, text: S) { + self.commands.push(PaintCmd::Text { + fill_style: "#ffffffbb".to_string(), + font: "14px Palatino".to_string(), + pos, + text: text.into(), + text_align: TextAlign::Start, + }); + } + + // ------------------------------------------------------------------------ + + pub fn button>(&mut self, text: S) -> InteractInfo { + let rect = Rect { + pos: self.cursor, + size: Vec2 { x: 200.0, y: 32.0 }, // TODO: get from some settings + }; + let hovering = rect.contains(self.input.mouse_pos); + let fill_style = if hovering { + "#444444ff".to_string() + } else { + "#222222ff".to_string() + }; + let ii = self.rect(rect, fill_style); + self.text( + Vec2 { + x: rect.pos.x + 4.0, + y: rect.center().y + 14.0 / 2.0, + }, + text, + ); + self.cursor.y += rect.size.y + 16.0; + ii + } + + pub fn label>(&mut self, text: S) { + for line in text.into().split("\n") { + let pos = self.cursor; + self.text(pos, line); + self.cursor.y += 16.0; + } + self.cursor.y += 16.0; // Padding + } + + // ------------------------------------------------------------------------ +} diff --git a/src/lib.rs b/src/lib.rs index 00d3383f..34140abb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,10 +11,12 @@ use std::sync::Mutex; use wasm_bindgen::prelude::*; -mod types; - use types::*; +pub mod app; +pub mod gui; +pub mod types; + /* // Fast compilation, slow code: @@ -30,133 +32,21 @@ fn foo(x: &dyn T); fn foo(x: &Trait); */ -#[derive(Default)] -pub struct InteractInfo { - pub is_hovering: bool, -} - -// TODO: implement Gui on this so we can add children to a widget -// pub struct Widget {} - -pub struct Gui { - commands: Vec, - input: Input, -} - -impl Gui { - pub fn new(input: Input) -> Self { - Gui { - commands: vec![PaintCmd::Clear { - fill_style: "#44444400".to_string(), - }], - input, - } - } - - pub fn input(&self) -> &Input { - &self.input - } - - pub fn into_commands(self) -> Vec { - self.commands - } - - pub fn rect(&mut self, rect: Rect) -> InteractInfo { - let ii = InteractInfo { - is_hovering: rect.contains(&self.input.mouse_pos), - }; - self.commands.push(PaintCmd::RoundedRect { - fill_style: "#ffffff10".to_string(), - pos: rect.pos, - corner_radius: 40.0, - size: rect.size, - }); - ii - } - - pub fn text(&mut self, pos: Vec2, text: String) { - self.commands.push(PaintCmd::Text { - fill_style: "#11ff00".to_string(), - font: "14px Palatino".to_string(), - pos, - text, - text_align: TextAlign::Start, - }); - } -} - -struct App { - count: i32, -} - -impl App { - fn new() -> Self { - App { count: 0 } - } - - fn show_gui(&mut self, gui: &mut Gui, input: &Input) { - gui.rect(Rect { - pos: Vec2 { x: 0.0, y: 0.0 }, - size: input.screen_size, - }); - - gui.rect(Rect { - pos: Vec2 { x: 50.0, y: 50.0 }, - size: Vec2 { - x: (input.screen_size.x - 100.0) / 3.0, - y: (input.screen_size.y - 100.0), - }, - }); - - let is_hovering = gui - .rect(Rect { - pos: Vec2 { x: 100.0, y: 100.0 }, - size: Vec2 { x: 200.0, y: 200.0 }, - }).is_hovering; - - if is_hovering { - self.count += 1; - } - - gui.text( - Vec2 { x: 100.0, y: 350.0 }, - format!( - "Mouse pos: {} {}, is_hovering: {}", - input.mouse_pos.x, input.mouse_pos.y, is_hovering - ), - ); - - let m = input.mouse_pos; - let hw = 32.0; - gui.rect(Rect { - pos: Vec2 { - x: m.x - hw, - y: m.y - hw, - }, - size: Vec2 { - x: 2.0 * hw, - y: 2.0 * hw, - }, - }); - - gui.text( - Vec2 { x: 100.0, y: 400.0 }, - format!("Count: {}", self.count), - ); - } -} - #[wasm_bindgen] -pub fn show_gui(input_json: &str) -> String { +pub fn show_gui(raw_input_json: &str) -> String { + // TODO: faster interface than JSON + let raw_input: RawInput = serde_json::from_str(raw_input_json).unwrap(); + lazy_static::lazy_static! { - static ref APP: Mutex = Mutex::new(App::new()); + static ref APP: Mutex = Mutex::new(app::App::new()); + static ref LAST_INPUT: Mutex = Default::default(); } - // TODO: faster interface than JSON - let input: Input = serde_json::from_str(input_json).unwrap(); + let gui_input = GuiInput::from_last_and_new(&LAST_INPUT.lock().unwrap(), &raw_input); + *LAST_INPUT.lock().unwrap() = raw_input; - let mut gui = Gui::new(input); - APP.lock().unwrap().show_gui(&mut gui, &input); + let mut gui = gui::Gui::new(gui_input); + APP.lock().unwrap().show_gui(&mut gui); let commands = gui.into_commands(); serde_json::to_string(&commands).unwrap() } diff --git a/src/types.rs b/src/types.rs index c66535fb..d25efe8a 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,30 +1,79 @@ -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)] pub struct Vec2 { pub x: f32, pub y: f32, } -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[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 { + 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, + } + } } -#[derive(Clone, Copy, Debug, Deserialize)] -pub struct Input { - pub screen_size: Vec2, +// ---------------------------------------------------------------------------- + +/// What the integration gives to the gui. +#[derive(Clone, Copy, Debug, Default, Deserialize)] +pub struct RawInput { + /// Is the button currently down? + pub mouse_down: bool, + + /// Current position of the mouse in points. pub mouse_pos: Vec2, + + /// Size of the screen in points. + pub screen_size: Vec2, } +/// What the gui maintains +#[derive(Clone, Copy, Debug)] +pub struct GuiInput { + /// Is the button currently down? + pub mouse_down: bool, + + /// The mouse went from !down to down + pub mouse_clicked: bool, + + /// The mouse went from down to !down + pub mouse_released: bool, + + /// Current position of the mouse in points. + pub mouse_pos: Vec2, + + /// Size of the screen in points. + pub screen_size: Vec2, +} + +impl GuiInput { + pub fn from_last_and_new(last: &RawInput, new: &RawInput) -> GuiInput { + GuiInput { + mouse_down: new.mouse_down, + mouse_clicked: !last.mouse_down && new.mouse_down, + mouse_released: last.mouse_down && !new.mouse_down, + mouse_pos: new.mouse_pos, + screen_size: new.screen_size, + } + } +} + +// ---------------------------------------------------------------------------- + #[derive(Clone, Copy, Debug, Serialize)] #[serde(rename_all = "snake_case")] pub enum TextAlign {