From 0dbd3e370e48a9078512727b6f0add31d06bb4ca Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Fri, 6 May 2022 23:54:07 +0300 Subject: [PATCH 01/36] Rusty MouseData API --- examples/coordinates.rs | 10 +-- packages/html/Cargo.toml | 1 + packages/html/src/events.rs | 151 ++++++++++++++++++++++++++++++++++ packages/html/src/geometry.rs | 13 +++ packages/html/src/lib.rs | 1 + packages/tui/src/hooks.rs | 2 + 6 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 packages/html/src/geometry.rs diff --git a/examples/coordinates.rs b/examples/coordinates.rs index 0c45c2e3d..24ee5da62 100644 --- a/examples/coordinates.rs +++ b/examples/coordinates.rs @@ -9,7 +9,7 @@ fn main() { fn app(cx: Scope) -> Element { let page_coordinates = use_state(&cx, || "".to_string()); let screen_coordinates = use_state(&cx, || "".to_string()); - let offset_coordinates = use_state(&cx, || "".to_string()); + let element_coordinates = use_state(&cx, || "".to_string()); let container_style = r#" display: flex; @@ -25,9 +25,9 @@ fn app(cx: Scope) -> Element { let update_mouse_position = move |event: UiEvent| { let mouse_data = event.data; - page_coordinates.set(format!("{:?}", (mouse_data.page_x, mouse_data.page_y))); - screen_coordinates.set(format!("{:?}", (mouse_data.screen_x, mouse_data.screen_y))); - offset_coordinates.set(format!("{:?}", (mouse_data.offset_x, mouse_data.offset_y))); + page_coordinates.set(format!("{:?}", mouse_data.page_coordinates())); + screen_coordinates.set(format!("{:?}", mouse_data.screen_coordinates())); + element_coordinates.set(format!("{:?}", mouse_data.element_coordinates())); // Note: client coordinates are also available, but they would be the same as the page coordinates in this example, because there is no scrolling. }; @@ -42,7 +42,7 @@ fn app(cx: Scope) -> Element { } div {"Page coordinates: {page_coordinates}"}, div {"Screen coordinates: {screen_coordinates}"}, - div {"Offset coordinates: {offset_coordinates}"}, + div {"Element coordinates: {element_coordinates}"}, } )) } diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 5daaa1407..fd1df29da 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -15,6 +15,7 @@ dioxus-core = { path = "../core", version = "^0.2.1" } serde = { version = "1", features = ["derive"], optional = true } serde_repr = { version = "0.1", optional = true } wasm-bindgen = { version = "0.2.79", optional = true } +euclid = "0.22.7" [dependencies.web-sys] optional = true diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index cfa901a2e..dfa0b49da 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -3,6 +3,7 @@ use dioxus_core::exports::bumpalo; use dioxus_core::*; pub mod on { + use crate::geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint}; use std::collections::HashMap; use super::*; @@ -498,8 +499,10 @@ pub mod on { #[derive(Debug, Clone)] pub struct MouseData { /// True if the alt key was down when the mouse event was fired. + #[deprecated(since = "0.3.0", note = "use modifiers() instead")] pub alt_key: bool, /// The button number that was pressed (if applicable) when the mouse event was fired. + #[deprecated(since = "0.3.0", note = "use trigger_button() instead")] pub button: i16, /// Indicates which buttons are pressed on the mouse (or other input device) when a mouse event is triggered. /// @@ -510,40 +513,188 @@ pub mod on { /// - 4: Auxiliary button (usually the mouse wheel button or middle button) /// - 8: 4th button (typically the "Browser Back" button) /// - 16 : 5th button (typically the "Browser Forward" button) + #[deprecated(since = "0.3.0", note = "use held_buttons() instead")] pub buttons: u16, /// The horizontal coordinate within the application's viewport at which the event occurred (as opposed to the coordinate within the page). /// /// For example, clicking on the left edge of the viewport will always result in a mouse event with a clientX value of 0, regardless of whether the page is scrolled horizontally. + #[deprecated(since = "0.3.0", note = "use client_coordinates() instead")] pub client_x: i32, /// The vertical coordinate within the application's viewport at which the event occurred (as opposed to the coordinate within the page). /// /// For example, clicking on the top edge of the viewport will always result in a mouse event with a clientY value of 0, regardless of whether the page is scrolled vertically. + #[deprecated(since = "0.3.0", note = "use client_coordinates() instead")] pub client_y: i32, /// True if the control key was down when the mouse event was fired. + #[deprecated(since = "0.3.0", note = "use modifiers() instead")] pub ctrl_key: bool, /// True if the meta key was down when the mouse event was fired. + #[deprecated(since = "0.3.0", note = "use modifiers() instead")] pub meta_key: bool, /// The offset in the X coordinate of the mouse pointer between that event and the padding edge of the target node. + #[deprecated(since = "0.3.0", note = "use element_coordinates() instead")] pub offset_x: i32, /// The offset in the Y coordinate of the mouse pointer between that event and the padding edge of the target node. + #[deprecated(since = "0.3.0", note = "use element_coordinates() instead")] pub offset_y: i32, /// The X (horizontal) coordinate (in pixels) of the mouse, relative to the left edge of the entire document. This includes any portion of the document not currently visible. /// /// Being based on the edge of the document as it is, this property takes into account any horizontal scrolling of the page. For example, if the page is scrolled such that 200 pixels of the left side of the document are scrolled out of view, and the mouse is clicked 100 pixels inward from the left edge of the view, the value returned by pageX will be 300. + #[deprecated(since = "0.3.0", note = "use page_coordinates() instead")] pub page_x: i32, /// The Y (vertical) coordinate in pixels of the event relative to the whole document. /// /// See `page_x`. + #[deprecated(since = "0.3.0", note = "use page_coordinates() instead")] pub page_y: i32, /// The X coordinate of the mouse pointer in global (screen) coordinates. + #[deprecated(since = "0.3.0", note = "use screen_coordinates() instead")] pub screen_x: i32, /// The Y coordinate of the mouse pointer in global (screen) coordinates. + #[deprecated(since = "0.3.0", note = "use screen_coordinates() instead")] pub screen_y: i32, /// True if the shift key was down when the mouse event was fired. + #[deprecated(since = "0.3.0", note = "use modifiers() instead")] pub shift_key: bool, // fn get_modifier_state(&self, key_code: &str) -> bool; } + impl MouseData { + /// The event's coordinates relative to the application's viewport (as opposed to the coordinate within the page). + // + // For example, clicking in the top left corner of the viewport will always result in a mouse event with client coordinates (0., 0.), regardless of whether the page is scrolled horizontally. + pub fn client_coordinates(&self) -> ClientPoint { + #[allow(deprecated)] + ClientPoint::new(self.client_x.into(), self.client_y.into()) + } + + /// The event's coordinates relative to the padding edge of the target element + /// + /// For example, clicking in the top left corner of an element will result in element coordinates (0., 0.) + pub fn element_coordinates(&self) -> ElementPoint { + #[allow(deprecated)] + ElementPoint::new(self.offset_x.into(), self.offset_y.into()) + } + + /// The event's coordinates relative to the entire document. This includes any portion of the document not currently visible. + /// + /// For example, if the page is scrolled 200 pixels to the right and 300 pixels down, clicking in the top left corner of the viewport would result in page coordinates (200., 300.) + pub fn page_coordinates(&self) -> PagePoint { + #[allow(deprecated)] + PagePoint::new(self.page_x.into(), self.page_y.into()) + } + + /// The event's coordinates relative to the entire screen. This takes into account the window's offset. + pub fn screen_coordinates(&self) -> ScreenPoint { + #[allow(deprecated)] + ScreenPoint::new(self.screen_x.into(), self.screen_y.into()) + } + + /// The set of modifier keys which were pressed when the event occurred + pub fn modifiers(&self) -> ModifierSet { + #[allow(deprecated)] + ModifierSet { + has_alt: self.alt_key, + has_ctrl: self.ctrl_key, + has_meta: self.meta_key, + has_shift: self.shift_key, + } + } + + /// The set of mouse buttons which were held when the event occurred. + pub fn held_buttons(&self) -> MouseButtonSet { + #[allow(deprecated)] + MouseButtonSet { + has_primary: self.buttons & 0b1 != 0, + has_secondary: self.buttons & 0b10 != 0, + has_auxiliary: self.buttons & 0b100 != 0, + has_fourth: self.buttons & 0b1000 != 0, + has_fifth: self.buttons & 0b10000 != 0, + } + } + + /// The mouse button that triggered the event + /// + // todo the following is kind of bad; should we just return None when the trigger_button is unreliable (and frankly irrelevant)? i guess we would need the event_type here + /// This is only guaranteed to indicate which button was pressed during events caused by pressing or releasing a button. As such, it is not reliable for events such as mouseenter, mouseleave, mouseover, mouseout, or mousemove. For example, a value of MouseButton::Primary may also indicate that no button was pressed. + pub fn trigger_button(&self) -> MouseButton { + #[allow(deprecated)] + let button_code = self.button; + + match button_code { + 0 => MouseButton::Primary, + // not a typo; auxiliary and secondary are swapped unlike in the `buttons` field. + // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + 1 => MouseButton::Auxiliary, + 2 => MouseButton::Secondary, + 3 => MouseButton::Fourth, + 4 => MouseButton::Fifth, + other => MouseButton::Other(other), + } + } + } + + pub struct ModifierSet { + has_alt: bool, + has_ctrl: bool, + has_meta: bool, + has_shift: bool, + } + + impl ModifierSet { + pub fn has_alt(&self) -> bool { + self.has_alt + } + pub fn has_ctrl(&self) -> bool { + self.has_ctrl + } + pub fn has_meta(&self) -> bool { + self.has_meta + } + pub fn has_shift(&self) -> bool { + self.has_shift + } + + pub fn is_empty(&self) -> bool { + !(self.has_alt || self.has_ctrl || self.has_meta || self.has_shift) + } + } + + pub struct MouseButtonSet { + has_primary: bool, + has_secondary: bool, + has_auxiliary: bool, + has_fourth: bool, + has_fifth: bool, + } + + impl MouseButtonSet { + pub fn has_primary(&self) -> bool { + self.has_primary + } + pub fn has_secondary(&self) -> bool { + self.has_secondary + } + pub fn has_auxiliary(&self) -> bool { + self.has_auxiliary + } + pub fn has_fourth(&self) -> bool { + self.has_fourth + } + pub fn has_fifth(&self) -> bool { + self.has_fifth + } + } + + pub enum MouseButton { + Primary, + Secondary, + Auxiliary, + Fourth, + Fifth, + Other(i16), + } + pub type PointerEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] diff --git a/packages/html/src/geometry.rs b/packages/html/src/geometry.rs new file mode 100644 index 000000000..9260c12ae --- /dev/null +++ b/packages/html/src/geometry.rs @@ -0,0 +1,13 @@ +pub use euclid::*; + +pub struct ScreenSpace; +pub type ScreenPoint = Point2D; + +pub struct ClientSpace; +pub type ClientPoint = Point2D; + +pub struct ElementSpace; +pub type ElementPoint = Point2D; + +pub struct PageSpace; +pub type PagePoint = Point2D; \ No newline at end of file diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index 1440fd9ae..4247bf39b 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -18,6 +18,7 @@ mod events; mod global_attributes; #[cfg(feature = "wasm-bind")] mod web_sys_bind; +mod geometry; pub use elements::*; pub use events::*; diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 04d320fcb..29ce634be 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -94,6 +94,7 @@ impl InnerInputState { // stores current input state and transforms events based on that state fn apply_event(&mut self, evt: &mut EventCore) { + #[allow(deprecated)] match evt.1 { // limitations: only two buttons may be held at once EventData::Mouse(ref mut m) => match &mut self.mouse { @@ -606,6 +607,7 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> { // The `offset`, `page` and `screen` coordinates are inconsistent with the MDN definition, as they are relative to the viewport (client), not the target element/page/screen, respectively. // todo? // But then, MDN defines them in terms of pixels, yet crossterm provides only row/column, and it might not be possible to get pixels. So we can't get 100% consistency anyway. + #[allow(deprecated)] EventData::Mouse(MouseData { alt_key: alt, button: button_state, From 3c447d7266158d12f6aa733dee30922ea64de08b Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 00:49:47 +0300 Subject: [PATCH 02/36] Documentation and examples Also derive useful traits --- examples/{coordinates.rs => mouse_event.rs} | 8 ++++++ packages/html/src/events.rs | 30 +++++++++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) rename examples/{coordinates.rs => mouse_event.rs} (81%) diff --git a/examples/coordinates.rs b/examples/mouse_event.rs similarity index 81% rename from examples/coordinates.rs rename to examples/mouse_event.rs index 24ee5da62..698de1c68 100644 --- a/examples/coordinates.rs +++ b/examples/mouse_event.rs @@ -10,6 +10,8 @@ fn app(cx: Scope) -> Element { let page_coordinates = use_state(&cx, || "".to_string()); let screen_coordinates = use_state(&cx, || "".to_string()); let element_coordinates = use_state(&cx, || "".to_string()); + let buttons = use_state(&cx, || "".to_string()); + let modifiers = use_state(&cx, || "".to_string()); let container_style = r#" display: flex; @@ -30,6 +32,9 @@ fn app(cx: Scope) -> Element { element_coordinates.set(format!("{:?}", mouse_data.element_coordinates())); // Note: client coordinates are also available, but they would be the same as the page coordinates in this example, because there is no scrolling. + + buttons.set(format!("{:?}", mouse_data.held_buttons())); + modifiers.set(format!("{:?}", mouse_data.modifiers())); }; cx.render(rsx! ( @@ -39,10 +44,13 @@ fn app(cx: Scope) -> Element { div { style: "{rect_style}", onmousemove: update_mouse_position, + prevent_default: "mousedown", } div {"Page coordinates: {page_coordinates}"}, div {"Screen coordinates: {screen_coordinates}"}, div {"Element coordinates: {element_coordinates}"}, + div {"{buttons}"}, + div {"{modifiers}"}, } )) } diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index dfa0b49da..98cd5b323 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -561,8 +561,8 @@ pub mod on { impl MouseData { /// The event's coordinates relative to the application's viewport (as opposed to the coordinate within the page). - // - // For example, clicking in the top left corner of the viewport will always result in a mouse event with client coordinates (0., 0.), regardless of whether the page is scrolled horizontally. + /// + /// For example, clicking in the top left corner of the viewport will always result in a mouse event with client coordinates (0., 0.), regardless of whether the page is scrolled horizontally. pub fn client_coordinates(&self) -> ClientPoint { #[allow(deprecated)] ClientPoint::new(self.client_x.into(), self.client_y.into()) @@ -634,6 +634,9 @@ pub mod on { } } + /// A set of modifier keys + #[derive(Debug, Clone)] + #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct ModifierSet { has_alt: bool, has_ctrl: bool, @@ -642,24 +645,33 @@ pub mod on { } impl ModifierSet { + /// Whether the set includes the alt key pub fn has_alt(&self) -> bool { self.has_alt } + /// Whether the set includes the control key pub fn has_ctrl(&self) -> bool { self.has_ctrl } + /// Whether the set includes the meta (windows/command) key + pub fn has_meta(&self) -> bool { self.has_meta } + /// Whether the set includes the shift key pub fn has_shift(&self) -> bool { self.has_shift } + /// True if the set contains no keys pub fn is_empty(&self) -> bool { !(self.has_alt || self.has_ctrl || self.has_meta || self.has_shift) } } + /// A set of mouse buttons + #[derive(Debug, Clone)] + #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct MouseButtonSet { has_primary: bool, has_secondary: bool, @@ -669,29 +681,43 @@ pub mod on { } impl MouseButtonSet { + /// Whether the set includes the primary button (typically the left button) pub fn has_primary(&self) -> bool { self.has_primary } + /// Whether the set includes the secondary button (typically the right button) pub fn has_secondary(&self) -> bool { self.has_secondary } + /// Whether the set includes the auxiliary button (typically the middle button) pub fn has_auxiliary(&self) -> bool { self.has_auxiliary } + /// Whether the set includes the fourth button (typically the "Browser Back" button) pub fn has_fourth(&self) -> bool { self.has_fourth } + /// Whether the set includes the fifth button (typically the "Browser Forward" button) pub fn has_fifth(&self) -> bool { self.has_fifth } } + /// A mouse button type (such as Primary/Secondary) + #[derive(Debug, Clone)] + #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub enum MouseButton { + /// Primary button (typically the left button) Primary, + /// Secondary button (typically the right button) Secondary, + /// Auxiliary button (typically the middle button) Auxiliary, + /// Fourth button (typically the "Browser Back" button) Fourth, + /// Fifth button (typically the "Browser Forward" button) Fifth, + /// A button with an unknown code Other(i16), } From 6a5a3f47cce98d4000037e9e64fbe48c3062e931 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 00:59:12 +0300 Subject: [PATCH 03/36] Remove `allow(deprecated)` to bring back the warnings --- packages/html/src/lib.rs | 2 +- packages/tui/src/hooks.rs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index 4247bf39b..bff22ec5e 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -15,10 +15,10 @@ mod elements; mod events; +pub mod geometry; mod global_attributes; #[cfg(feature = "wasm-bind")] mod web_sys_bind; -mod geometry; pub use elements::*; pub use events::*; diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 29ce634be..04d320fcb 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -94,7 +94,6 @@ impl InnerInputState { // stores current input state and transforms events based on that state fn apply_event(&mut self, evt: &mut EventCore) { - #[allow(deprecated)] match evt.1 { // limitations: only two buttons may be held at once EventData::Mouse(ref mut m) => match &mut self.mouse { @@ -607,7 +606,6 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> { // The `offset`, `page` and `screen` coordinates are inconsistent with the MDN definition, as they are relative to the viewport (client), not the target element/page/screen, respectively. // todo? // But then, MDN defines them in terms of pixels, yet crossterm provides only row/column, and it might not be possible to get pixels. So we can't get 100% consistency anyway. - #[allow(deprecated)] EventData::Mouse(MouseData { alt_key: alt, button: button_state, From 2c48ae271fc411e91006bcc87039ad2fb5d93b15 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 01:01:38 +0300 Subject: [PATCH 04/36] rustfmt --- packages/html/src/geometry.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/html/src/geometry.rs b/packages/html/src/geometry.rs index 9260c12ae..d456efa57 100644 --- a/packages/html/src/geometry.rs +++ b/packages/html/src/geometry.rs @@ -10,4 +10,4 @@ pub struct ElementSpace; pub type ElementPoint = Point2D; pub struct PageSpace; -pub type PagePoint = Point2D; \ No newline at end of file +pub type PagePoint = Point2D; From 6ba067a617b53bb7558370f6e5a86f62571764e5 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 09:31:44 +0300 Subject: [PATCH 05/36] Use EnumSet for modifier and button sets --- packages/html/Cargo.toml | 1 + packages/html/src/events.rs | 169 ++++++++++++++++------------------ packages/html/src/geometry.rs | 54 ++++++++++- 3 files changed, 131 insertions(+), 93 deletions(-) diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index fd1df29da..956f9966f 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -16,6 +16,7 @@ serde = { version = "1", features = ["derive"], optional = true } serde_repr = { version = "0.1", optional = true } wasm-bindgen = { version = "0.2.79", optional = true } euclid = "0.22.7" +enumset = "1.0.11" [dependencies.web-sys] optional = true diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 98cd5b323..898246377 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -1,9 +1,11 @@ use bumpalo::boxed::Box as BumpBox; use dioxus_core::exports::bumpalo; use dioxus_core::*; +use enumset::EnumSetType; pub mod on { - use crate::geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint}; + use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; + use enumset::EnumSet; use std::collections::HashMap; use super::*; @@ -592,25 +594,54 @@ pub mod on { /// The set of modifier keys which were pressed when the event occurred pub fn modifiers(&self) -> ModifierSet { + let mut set = EnumSet::empty(); + #[allow(deprecated)] - ModifierSet { - has_alt: self.alt_key, - has_ctrl: self.ctrl_key, - has_meta: self.meta_key, - has_shift: self.shift_key, + { + if self.alt_key { + set |= Modifier::Alt; + } + if self.ctrl_key { + set |= Modifier::Ctrl; + } + if self.meta_key { + set |= Modifier::Meta; + } + if self.shift_key { + set |= Modifier::Shift; + } } + + set } /// The set of mouse buttons which were held when the event occurred. pub fn held_buttons(&self) -> MouseButtonSet { + let mut set = EnumSet::empty(); + + // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons #[allow(deprecated)] - MouseButtonSet { - has_primary: self.buttons & 0b1 != 0, - has_secondary: self.buttons & 0b10 != 0, - has_auxiliary: self.buttons & 0b100 != 0, - has_fourth: self.buttons & 0b1000 != 0, - has_fifth: self.buttons & 0b10000 != 0, + { + if self.buttons & 0b1 != 0 { + set |= MouseButton::Primary; + } + if self.buttons & 0b10 != 0 { + set |= MouseButton::Secondary; + } + if self.buttons & 0b100 != 0 { + set |= MouseButton::Auxiliary; + } + if self.buttons & 0b1000 != 0 { + set |= MouseButton::Fourth; + } + if self.buttons & 0b10000 != 0 { + set |= MouseButton::Fifth; + } + if self.buttons & (!0b11111) != 0 { + set |= MouseButton::Unknown; + } } + set } /// The mouse button that triggered the event @@ -619,92 +650,24 @@ pub mod on { /// This is only guaranteed to indicate which button was pressed during events caused by pressing or releasing a button. As such, it is not reliable for events such as mouseenter, mouseleave, mouseover, mouseout, or mousemove. For example, a value of MouseButton::Primary may also indicate that no button was pressed. pub fn trigger_button(&self) -> MouseButton { #[allow(deprecated)] - let button_code = self.button; - - match button_code { - 0 => MouseButton::Primary, - // not a typo; auxiliary and secondary are swapped unlike in the `buttons` field. - // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button - 1 => MouseButton::Auxiliary, - 2 => MouseButton::Secondary, - 3 => MouseButton::Fourth, - 4 => MouseButton::Fifth, - other => MouseButton::Other(other), - } + MouseButton::from_web_code(self.button) } } - /// A set of modifier keys - #[derive(Debug, Clone)] - #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - pub struct ModifierSet { - has_alt: bool, - has_ctrl: bool, - has_meta: bool, - has_shift: bool, + // note: EnumSetType also derives Copy and Clone for some reason + #[derive(EnumSetType, Debug)] + pub enum Modifier { + Alt, + Ctrl, + Meta, + Shift, } - impl ModifierSet { - /// Whether the set includes the alt key - pub fn has_alt(&self) -> bool { - self.has_alt - } - /// Whether the set includes the control key - pub fn has_ctrl(&self) -> bool { - self.has_ctrl - } - /// Whether the set includes the meta (windows/command) key - - pub fn has_meta(&self) -> bool { - self.has_meta - } - /// Whether the set includes the shift key - pub fn has_shift(&self) -> bool { - self.has_shift - } - - /// True if the set contains no keys - pub fn is_empty(&self) -> bool { - !(self.has_alt || self.has_ctrl || self.has_meta || self.has_shift) - } - } - - /// A set of mouse buttons - #[derive(Debug, Clone)] - #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - pub struct MouseButtonSet { - has_primary: bool, - has_secondary: bool, - has_auxiliary: bool, - has_fourth: bool, - has_fifth: bool, - } - - impl MouseButtonSet { - /// Whether the set includes the primary button (typically the left button) - pub fn has_primary(&self) -> bool { - self.has_primary - } - /// Whether the set includes the secondary button (typically the right button) - pub fn has_secondary(&self) -> bool { - self.has_secondary - } - /// Whether the set includes the auxiliary button (typically the middle button) - pub fn has_auxiliary(&self) -> bool { - self.has_auxiliary - } - /// Whether the set includes the fourth button (typically the "Browser Back" button) - pub fn has_fourth(&self) -> bool { - self.has_fourth - } - /// Whether the set includes the fifth button (typically the "Browser Forward" button) - pub fn has_fifth(&self) -> bool { - self.has_fifth - } - } + pub type ModifierSet = EnumSet; /// A mouse button type (such as Primary/Secondary) - #[derive(Debug, Clone)] + // note: EnumSetType also derives Copy and Clone for some reason + #[derive(EnumSetType, Debug)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub enum MouseButton { /// Primary button (typically the left button) @@ -718,9 +681,31 @@ pub mod on { /// Fifth button (typically the "Browser Forward" button) Fifth, /// A button with an unknown code - Other(i16), + Unknown, } + impl MouseButton { + /// Constructs a MouseButton for the specified button code + /// + /// E.g. 0 => Primary; 1 => Auxiliary + /// + /// Unknown codes get mapped to MouseButton::Unknown. + pub fn from_web_code(code: i16) -> Self { + match code { + 0 => MouseButton::Primary, + // not a typo; auxiliary and secondary are swapped unlike in the `buttons` field. + // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + 1 => MouseButton::Auxiliary, + 2 => MouseButton::Secondary, + 3 => MouseButton::Fourth, + 4 => MouseButton::Fifth, + _ => MouseButton::Unknown, + } + } + } + + pub type MouseButtonSet = EnumSet; + pub type PointerEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] diff --git a/packages/html/src/geometry.rs b/packages/html/src/geometry.rs index d456efa57..38c497427 100644 --- a/packages/html/src/geometry.rs +++ b/packages/html/src/geometry.rs @@ -1,13 +1,65 @@ -pub use euclid::*; +pub use euclid; +use euclid::*; +/// Coordinate space relative to the screen pub struct ScreenSpace; pub type ScreenPoint = Point2D; +/// Coordinate space relative to the viewport pub struct ClientSpace; pub type ClientPoint = Point2D; +/// Coordinate space relative to an element pub struct ElementSpace; pub type ElementPoint = Point2D; +/// Coordinate space relative to the page pub struct PageSpace; pub type PagePoint = Point2D; + +/// Coordinates of a point in the app's interface +pub struct Coordinates { + screen: ScreenPoint, + client: ClientPoint, + element: ElementPoint, + page: PagePoint, +} + +impl Coordinates { + /// Construct new coordinates with the specified screen-, client-, element- and page-relative points + pub fn new( + screen: ScreenPoint, + client: ClientPoint, + element: ElementPoint, + page: PagePoint, + ) -> Self { + Self { + screen, + client, + element, + page, + } + } + /// Coordinates relative to the entire screen. This takes into account the window's offset. + pub fn screen(&self) -> ScreenPoint { + self.screen + } + /// Coordinates relative to the application's viewport (as opposed to the coordinate within the page). + /// + /// For example, clicking in the top left corner of the viewport will always result in a mouse event with client coordinates (0., 0.), regardless of whether the page is scrolled horizontally. + pub fn client(&self) -> ClientPoint { + self.client + } + /// Coordinates relative to the padding edge of the target element + /// + /// For example, clicking in the top left corner of an element will result in element coordinates (0., 0.) + pub fn element(&self) -> ElementPoint { + self.element + } + /// Coordinates relative to the entire document. This includes any portion of the document not currently visible. + /// + /// For example, if the page is scrolled 200 pixels to the right and 300 pixels down, clicking in the top left corner of the viewport would result in page coordinates (200., 300.) + pub fn page(&self) -> PagePoint { + self.page + } +} From 2ef332085c852fe86069062903dd71ae55e7759a Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 09:39:53 +0300 Subject: [PATCH 06/36] Refactor: move input types to separate module --- packages/html/src/events.rs | 56 ++---------------------------------- packages/html/src/input.rs | 57 +++++++++++++++++++++++++++++++++++++ packages/html/src/lib.rs | 2 ++ 3 files changed, 61 insertions(+), 54 deletions(-) create mode 100644 packages/html/src/input.rs diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 898246377..12c946eeb 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -1,10 +1,10 @@ use bumpalo::boxed::Box as BumpBox; use dioxus_core::exports::bumpalo; use dioxus_core::*; -use enumset::EnumSetType; pub mod on { - use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; + use crate::geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint}; + use crate::input::{Modifier, ModifierSet, MouseButton, MouseButtonSet}; use enumset::EnumSet; use std::collections::HashMap; @@ -654,58 +654,6 @@ pub mod on { } } - // note: EnumSetType also derives Copy and Clone for some reason - #[derive(EnumSetType, Debug)] - pub enum Modifier { - Alt, - Ctrl, - Meta, - Shift, - } - - pub type ModifierSet = EnumSet; - - /// A mouse button type (such as Primary/Secondary) - // note: EnumSetType also derives Copy and Clone for some reason - #[derive(EnumSetType, Debug)] - #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - pub enum MouseButton { - /// Primary button (typically the left button) - Primary, - /// Secondary button (typically the right button) - Secondary, - /// Auxiliary button (typically the middle button) - Auxiliary, - /// Fourth button (typically the "Browser Back" button) - Fourth, - /// Fifth button (typically the "Browser Forward" button) - Fifth, - /// A button with an unknown code - Unknown, - } - - impl MouseButton { - /// Constructs a MouseButton for the specified button code - /// - /// E.g. 0 => Primary; 1 => Auxiliary - /// - /// Unknown codes get mapped to MouseButton::Unknown. - pub fn from_web_code(code: i16) -> Self { - match code { - 0 => MouseButton::Primary, - // not a typo; auxiliary and secondary are swapped unlike in the `buttons` field. - // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button - 1 => MouseButton::Auxiliary, - 2 => MouseButton::Secondary, - 3 => MouseButton::Fourth, - 4 => MouseButton::Fifth, - _ => MouseButton::Unknown, - } - } - } - - pub type MouseButtonSet = EnumSet; - pub type PointerEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] diff --git a/packages/html/src/input.rs b/packages/html/src/input.rs new file mode 100644 index 000000000..06787023b --- /dev/null +++ b/packages/html/src/input.rs @@ -0,0 +1,57 @@ +use enumset::{EnumSet, EnumSetType}; + +// note: EnumSetType also derives Copy and Clone for some reason +/// A modifier key, such as Alt or Ctrl +#[derive(EnumSetType, Debug)] +pub enum Modifier { + Alt, + Ctrl, + /// The meta key (windows key, or command key) + Meta, + Shift, +} + +/// A set of modifier keys +pub type ModifierSet = EnumSet; + +/// A mouse button type (such as Primary/Secondary) +// note: EnumSetType also derives Copy and Clone for some reason +#[derive(EnumSetType, Debug)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +pub enum MouseButton { + /// Primary button (typically the left button) + Primary, + /// Secondary button (typically the right button) + Secondary, + /// Auxiliary button (typically the middle button) + Auxiliary, + /// Fourth button (typically the "Browser Back" button) + Fourth, + /// Fifth button (typically the "Browser Forward" button) + Fifth, + /// A button with an unknown code + Unknown, +} + +impl MouseButton { + /// Constructs a MouseButton for the specified button code + /// + /// E.g. 0 => Primary; 1 => Auxiliary + /// + /// Unknown codes get mapped to MouseButton::Unknown. + pub fn from_web_code(code: i16) -> Self { + match code { + 0 => MouseButton::Primary, + // not a typo; auxiliary and secondary are swapped unlike in the `buttons` field. + // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + 1 => MouseButton::Auxiliary, + 2 => MouseButton::Secondary, + 3 => MouseButton::Fourth, + 4 => MouseButton::Fifth, + _ => MouseButton::Unknown, + } + } +} + +/// A set of mouse buttons +pub type MouseButtonSet = EnumSet; diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index bff22ec5e..3de940e25 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -17,9 +17,11 @@ mod elements; mod events; pub mod geometry; mod global_attributes; +mod input; #[cfg(feature = "wasm-bind")] mod web_sys_bind; pub use elements::*; pub use events::*; pub use global_attributes::*; +pub use input::*; From 3fec31fe50966d3a8f7f1df8b12f8e03e5caf364 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 10:05:52 +0300 Subject: [PATCH 07/36] Use keyboard_types instead of bespoke ModifierSet --- packages/html/Cargo.toml | 1 + packages/html/src/events.rs | 19 +++++++++++-------- packages/html/src/geometry.rs | 8 ++++++++ packages/html/src/input.rs | 16 +++------------- packages/html/src/lib.rs | 3 +-- 5 files changed, 24 insertions(+), 23 deletions(-) diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 956f9966f..ac5cd340f 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -17,6 +17,7 @@ serde_repr = { version = "0.1", optional = true } wasm-bindgen = { version = "0.2.79", optional = true } euclid = "0.22.7" enumset = "1.0.11" +keyboard-types = "0.6.2" [dependencies.web-sys] optional = true diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 12c946eeb..414639523 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -3,9 +3,12 @@ use dioxus_core::exports::bumpalo; use dioxus_core::*; pub mod on { + //! Input events and associated data + use crate::geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint}; - use crate::input::{Modifier, ModifierSet, MouseButton, MouseButtonSet}; + use crate::input::{MouseButton, MouseButtonSet}; use enumset::EnumSet; + use keyboard_types::Modifiers; use std::collections::HashMap; use super::*; @@ -593,26 +596,26 @@ pub mod on { } /// The set of modifier keys which were pressed when the event occurred - pub fn modifiers(&self) -> ModifierSet { - let mut set = EnumSet::empty(); + pub fn modifiers(&self) -> Modifiers { + let mut modifiers = Modifiers::empty(); #[allow(deprecated)] { if self.alt_key { - set |= Modifier::Alt; + modifiers.insert(Modifiers::ALT); } if self.ctrl_key { - set |= Modifier::Ctrl; + modifiers.insert(Modifiers::CONTROL); } if self.meta_key { - set |= Modifier::Meta; + modifiers.insert(Modifiers::META); } if self.shift_key { - set |= Modifier::Shift; + modifiers.insert(Modifiers::SHIFT); } } - set + modifiers } /// The set of mouse buttons which were held when the event occurred. diff --git a/packages/html/src/geometry.rs b/packages/html/src/geometry.rs index 38c497427..5a7c8112e 100644 --- a/packages/html/src/geometry.rs +++ b/packages/html/src/geometry.rs @@ -1,20 +1,28 @@ +//! Geometry primitives for representing e.g. mouse events + +/// A re-export of euclid, which we use for geometry primitives pub use euclid; + use euclid::*; /// Coordinate space relative to the screen pub struct ScreenSpace; +/// A point in ScreenSpace pub type ScreenPoint = Point2D; /// Coordinate space relative to the viewport pub struct ClientSpace; +/// A point in ClientSpace pub type ClientPoint = Point2D; /// Coordinate space relative to an element pub struct ElementSpace; +/// A point in ElementSpace pub type ElementPoint = Point2D; /// Coordinate space relative to the page pub struct PageSpace; +/// A point in PageSpace pub type PagePoint = Point2D; /// Coordinates of a point in the app's interface diff --git a/packages/html/src/input.rs b/packages/html/src/input.rs index 06787023b..7c722b384 100644 --- a/packages/html/src/input.rs +++ b/packages/html/src/input.rs @@ -1,18 +1,8 @@ +//! Data structures representing user input, such as modifier keys and mouse buttons use enumset::{EnumSet, EnumSetType}; -// note: EnumSetType also derives Copy and Clone for some reason -/// A modifier key, such as Alt or Ctrl -#[derive(EnumSetType, Debug)] -pub enum Modifier { - Alt, - Ctrl, - /// The meta key (windows key, or command key) - Meta, - Shift, -} - -/// A set of modifier keys -pub type ModifierSet = EnumSet; +/// A re-export of keyboard_types +pub use keyboard_types; /// A mouse button type (such as Primary/Secondary) // note: EnumSetType also derives Copy and Clone for some reason diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index 3de940e25..705a076e4 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -17,11 +17,10 @@ mod elements; mod events; pub mod geometry; mod global_attributes; -mod input; +pub mod input; #[cfg(feature = "wasm-bind")] mod web_sys_bind; pub use elements::*; pub use events::*; pub use global_attributes::*; -pub use input::*; From 6b2753bd4f189630bd271e6e20e85762e1bbcaf0 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 10:12:08 +0300 Subject: [PATCH 08/36] Update example --- examples/mouse_event.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/mouse_event.rs b/examples/mouse_event.rs index 698de1c68..165d9445c 100644 --- a/examples/mouse_event.rs +++ b/examples/mouse_event.rs @@ -49,8 +49,8 @@ fn app(cx: Scope) -> Element { div {"Page coordinates: {page_coordinates}"}, div {"Screen coordinates: {screen_coordinates}"}, div {"Element coordinates: {element_coordinates}"}, - div {"{buttons}"}, - div {"{modifiers}"}, + div {"Buttons: {buttons}"}, + div {"Modifiers: {modifiers}"}, } )) } From c35dddc203f0011fb1183864caeb98e5c380e375 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 10:31:05 +0300 Subject: [PATCH 09/36] Create constructor for MouseData --- packages/html/src/events.rs | 71 +++++++++++++++++++++++------------- packages/html/src/input.rs | 73 +++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 26 deletions(-) diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 414639523..4f2507292 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -5,8 +5,10 @@ use dioxus_core::*; pub mod on { //! Input events and associated data - use crate::geometry::{ClientPoint, ElementPoint, PagePoint, ScreenPoint}; - use crate::input::{MouseButton, MouseButtonSet}; + use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; + use crate::input::{ + decode_mouse_button_set, encode_mouse_button_set, MouseButton, MouseButtonSet, + }; use enumset::EnumSet; use keyboard_types::Modifiers; use std::collections::HashMap; @@ -502,6 +504,9 @@ pub mod on { pub type MouseEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] + /// Data associated with a mouse event + /// + /// Do not use the deprecated fields; they may change or become private in the future. pub struct MouseData { /// True if the alt key was down when the mouse event was fired. #[deprecated(since = "0.3.0", note = "use modifiers() instead")] @@ -565,6 +570,43 @@ pub mod on { } impl MouseData { + pub fn new( + coordinates: Coordinates, + trigger_button: MouseButton, + held_buttons: MouseButtonSet, + modifiers: Modifiers, + ) -> Self { + let alt_key = modifiers.contains(Modifiers::ALT); + let ctrl_key = modifiers.contains(Modifiers::CONTROL); + let meta_key = modifiers.contains(Modifiers::META); + let shift_key = modifiers.contains(Modifiers::SHIFT); + + let [client_x, client_y]: [i32; 2] = coordinates.client().cast().into(); + let [offset_x, offset_y]: [i32; 2] = coordinates.element().cast().into(); + let [page_x, page_y]: [i32; 2] = coordinates.page().cast().into(); + let [screen_x, screen_y]: [i32; 2] = coordinates.screen().cast().into(); + + #[allow(deprecated)] + Self { + alt_key, + ctrl_key, + meta_key, + shift_key, + + button: trigger_button.into_web_code(), + buttons: encode_mouse_button_set(held_buttons), + + client_x, + client_y, + offset_x, + offset_y, + page_x, + page_y, + screen_x, + screen_y, + } + } + /// The event's coordinates relative to the application's viewport (as opposed to the coordinate within the page). /// /// For example, clicking in the top left corner of the viewport will always result in a mouse event with client coordinates (0., 0.), regardless of whether the page is scrolled horizontally. @@ -620,31 +662,8 @@ pub mod on { /// The set of mouse buttons which were held when the event occurred. pub fn held_buttons(&self) -> MouseButtonSet { - let mut set = EnumSet::empty(); - - // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons #[allow(deprecated)] - { - if self.buttons & 0b1 != 0 { - set |= MouseButton::Primary; - } - if self.buttons & 0b10 != 0 { - set |= MouseButton::Secondary; - } - if self.buttons & 0b100 != 0 { - set |= MouseButton::Auxiliary; - } - if self.buttons & 0b1000 != 0 { - set |= MouseButton::Fourth; - } - if self.buttons & 0b10000 != 0 { - set |= MouseButton::Fifth; - } - if self.buttons & (!0b11111) != 0 { - set |= MouseButton::Unknown; - } - } - set + decode_mouse_button_set(self.buttons) } /// The mouse button that triggered the event diff --git a/packages/html/src/input.rs b/packages/html/src/input.rs index 7c722b384..b5bc73a44 100644 --- a/packages/html/src/input.rs +++ b/packages/html/src/input.rs @@ -41,7 +41,80 @@ impl MouseButton { _ => MouseButton::Unknown, } } + + /// Converts MouseButton into the corresponding button code + /// + /// MouseButton::Unknown will get mapped to -1 + pub fn into_web_code(self) -> i16 { + match self { + MouseButton::Primary => 0, + // not a typo; auxiliary and secondary are swapped unlike in the `buttons` field. + // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + MouseButton::Auxiliary => 1, + MouseButton::Secondary => 2, + MouseButton::Fourth => 3, + MouseButton::Fifth => 4, + MouseButton::Unknown => -1, + } + } } /// A set of mouse buttons pub type MouseButtonSet = EnumSet; + +pub fn decode_mouse_button_set(code: u16) -> MouseButtonSet { + let mut set = EnumSet::empty(); + + // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + #[allow(deprecated)] + { + if code & 0b1 != 0 { + set |= MouseButton::Primary; + } + if code & 0b10 != 0 { + set |= MouseButton::Secondary; + } + if code & 0b100 != 0 { + set |= MouseButton::Auxiliary; + } + if code & 0b1000 != 0 { + set |= MouseButton::Fourth; + } + if code & 0b10000 != 0 { + set |= MouseButton::Fifth; + } + if code & (!0b11111) != 0 { + set |= MouseButton::Unknown; + } + } + + set +} + +pub fn encode_mouse_button_set(set: MouseButtonSet) -> u16 { + let mut code = 0; + + // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + { + if set.contains(MouseButton::Primary) { + code |= 0b1; + } + if set.contains(MouseButton::Secondary) { + code |= 0b10; + } + if set.contains(MouseButton::Auxiliary) { + code |= 0b100; + } + if set.contains(MouseButton::Fourth) { + code |= 0b1000; + } + if set.contains(MouseButton::Fifth) { + code |= 0b10000; + } + if set.contains(MouseButton::Unknown) { + code |= 0b100000; + } + } + + code +} From 9c6cd42ca24119358a69653e1934fe0bae9221bc Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 10:49:49 +0300 Subject: [PATCH 10/36] TUI: use constructor instead of deprecated fields --- packages/html/src/events.rs | 12 ++++--- packages/tui/src/hooks.rs | 65 +++++++++++++++++++++---------------- 2 files changed, 44 insertions(+), 33 deletions(-) diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 4f2507292..fa1791e60 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -9,7 +9,6 @@ pub mod on { use crate::input::{ decode_mouse_button_set, encode_mouse_button_set, MouseButton, MouseButtonSet, }; - use enumset::EnumSet; use keyboard_types::Modifiers; use std::collections::HashMap; @@ -570,9 +569,12 @@ pub mod on { } impl MouseData { + /// Construct MouseData with the specified properties + /// + /// Note: the current implementation truncates coordinates. In the future, when we change the internal representation, it may also support a fractional part. pub fn new( coordinates: Coordinates, - trigger_button: MouseButton, + trigger_button: Option, held_buttons: MouseButtonSet, modifiers: Modifiers, ) -> Self { @@ -593,7 +595,7 @@ pub mod on { meta_key, shift_key, - button: trigger_button.into_web_code(), + button: trigger_button.map_or(0, |b| b.into_web_code()), buttons: encode_mouse_button_set(held_buttons), client_x, @@ -670,9 +672,9 @@ pub mod on { /// // todo the following is kind of bad; should we just return None when the trigger_button is unreliable (and frankly irrelevant)? i guess we would need the event_type here /// This is only guaranteed to indicate which button was pressed during events caused by pressing or releasing a button. As such, it is not reliable for events such as mouseenter, mouseleave, mouseover, mouseout, or mousemove. For example, a value of MouseButton::Primary may also indicate that no button was pressed. - pub fn trigger_button(&self) -> MouseButton { + pub fn trigger_button(&self) -> Option { #[allow(deprecated)] - MouseButton::from_web_code(self.button) + Some(MouseButton::from_web_code(self.button)) } } diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 04d320fcb..722c2598c 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -4,6 +4,10 @@ use crossterm::event::{ use dioxus_core::*; use fxhash::{FxHashMap, FxHashSet}; +use dioxus_html::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; +use dioxus_html::input::keyboard_types::Modifiers; +use dioxus_html::input::MouseButton as DioxusMouseButton; +use dioxus_html::input::MouseButtonSet as DioxusMouseButtons; use dioxus_html::{on::*, KeyCode}; use std::{ any::Any, @@ -588,40 +592,45 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> { let ctrl = m.modifiers.contains(KeyModifiers::CONTROL); let meta = false; - let get_mouse_data = |b| { - let buttons = match b { - None => 0, - Some(MouseButton::Left) => 1, - Some(MouseButton::Right) => 2, - Some(MouseButton::Middle) => 4, - }; - let button_state = match b { - None => 0, - Some(MouseButton::Left) => 0, - Some(MouseButton::Middle) => 1, - Some(MouseButton::Right) => 2, + let get_mouse_data = |crossterm_button: Option| { + let button = crossterm_button.map(|b| match b { + MouseButton::Left => DioxusMouseButton::Primary, + MouseButton::Right => DioxusMouseButton::Secondary, + MouseButton::Middle => DioxusMouseButton::Auxiliary, + }); + + let button_set = match button { + None => DioxusMouseButtons::empty(), + Some(button) => DioxusMouseButtons::only(button), }; + // from https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent // The `offset`, `page` and `screen` coordinates are inconsistent with the MDN definition, as they are relative to the viewport (client), not the target element/page/screen, respectively. // todo? // But then, MDN defines them in terms of pixels, yet crossterm provides only row/column, and it might not be possible to get pixels. So we can't get 100% consistency anyway. - EventData::Mouse(MouseData { - alt_key: alt, - button: button_state, - buttons, - client_x: x, - client_y: y, - ctrl_key: ctrl, - meta_key: meta, - offset_x: x, - offset_y: y, - page_x: x, - page_y: y, - screen_x: x, - screen_y: y, - shift_key: shift, - }) + let coordinates = Coordinates::new( + ScreenPoint::new(x, y), + ClientPoint::new(x, y), + ElementPoint::new(x, y), + PagePoint::new(x, y), + ); + + let mut modifiers = Modifiers::empty(); + if shift { + modifiers.insert(Modifiers::SHIFT); + } + if ctrl { + modifiers.insert(Modifiers::CONTROL); + } + if meta { + modifiers.insert(Modifiers::META); + } + if alt { + modifiers.insert(Modifiers::ALT); + } + + EventData::Mouse(MouseData::new(coordinates, button, button_set, modifiers)) }; let get_wheel_data = |up| { From 285a6d080befee2eddb3948fdb73c679450f4ccc Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 15:32:19 +0300 Subject: [PATCH 11/36] Add coordinates to tui_hover example --- examples/tui_hover.rs | 29 ++++++++++++++++++++++++++++- packages/tui/src/hooks.rs | 25 +++++++++++++++++++++---- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/examples/tui_hover.rs b/examples/tui_hover.rs index 2e6f91643..2de409f17 100644 --- a/examples/tui_hover.rs +++ b/examples/tui_hover.rs @@ -1,6 +1,7 @@ use std::{convert::TryInto, sync::Arc}; use dioxus::{events::MouseData, prelude::*}; +use dioxus_core::UiEvent; fn main() { dioxus::tui::launch(app); @@ -26,6 +27,24 @@ fn app(cx: Scope) -> Element { let q3_color_str = to_str(q3_color); let q4_color_str = to_str(q4_color); + let page_coordinates = use_state(&cx, || "".to_string()); + let element_coordinates = use_state(&cx, || "".to_string()); + let buttons = use_state(&cx, || "".to_string()); + let modifiers = use_state(&cx, || "".to_string()); + + let update_data = move |event: UiEvent| { + let mouse_data = event.data; + + page_coordinates.set(format!("{:?}", mouse_data.page_coordinates())); + element_coordinates.set(format!("{:?}", mouse_data.element_coordinates())); + + // Note: client coordinates are also available, but they would be the same as the page coordinates in this example, because there is no scrolling. + // There are also screen coordinates, but they are currently the same as client coordinates due to technical limitations + + buttons.set(format!("{:?}", mouse_data.held_buttons())); + modifiers.set(format!("{:?}", mouse_data.modifiers())); + }; + cx.render(rsx! { div { width: "100%", @@ -48,6 +67,7 @@ fn app(cx: Scope) -> Element { onmouseup: move |m| q1_color.set([get_brightness(m.data), 0, 0]), onwheel: move |w| q1_color.set([q1_color[0] + (10.0*w.delta_y) as i32, 0, 0]), onmouseleave: move |_| q1_color.set([200; 3]), + onmousemove: update_data, "click me" } div { @@ -61,6 +81,7 @@ fn app(cx: Scope) -> Element { onmouseup: move |m| q2_color.set([get_brightness(m.data); 3]), onwheel: move |w| q2_color.set([q2_color[0] + (10.0*w.delta_y) as i32;3]), onmouseleave: move |_| q2_color.set([200; 3]), + onmousemove: update_data, "click me" } } @@ -80,6 +101,7 @@ fn app(cx: Scope) -> Element { onmouseup: move |m| q3_color.set([0, get_brightness(m.data), 0]), onwheel: move |w| q3_color.set([0, q3_color[1] + (10.0*w.delta_y) as i32, 0]), onmouseleave: move |_| q3_color.set([200; 3]), + onmousemove: update_data, "click me" } div { @@ -93,9 +115,14 @@ fn app(cx: Scope) -> Element { onmouseup: move |m| q4_color.set([0, 0, get_brightness(m.data)]), onwheel: move |w| q4_color.set([0, 0, q4_color[2] + (10.0*w.delta_y) as i32]), onmouseleave: move |_| q4_color.set([200; 3]), + onmousemove: update_data, "click me" } - } + }, + div {"Page coordinates: {page_coordinates}"}, + div {"Element coordinates: {element_coordinates}"}, + div {"Buttons: {buttons}"}, + div {"Modifiers: {modifiers}"}, } }) } diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 077787666..350b836a4 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -16,6 +16,7 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; +use stretch2::geometry::Point; use stretch2::{prelude::Layout, Stretch}; use crate::{Dom, Node}; @@ -225,10 +226,26 @@ impl InnerInputState { } fn prepare_mouse_data(mouse_data: &MouseData, layout: &Layout) -> MouseData { - let mut data = mouse_data.clone(); - data.offset_x = data.client_x - layout.location.x as i32; - data.offset_y = data.client_y - layout.location.y as i32; - data + let Point { x, y } = layout.location; + let node_origin = ClientPoint::new(x.into(), y.into()); + + let new_client_coordinates = (mouse_data.client_coordinates() - node_origin) + .to_point() + .cast_unit(); + + let coordinates = Coordinates::new( + mouse_data.screen_coordinates(), + mouse_data.client_coordinates(), + new_client_coordinates, + mouse_data.page_coordinates(), + ); + + MouseData::new( + coordinates, + mouse_data.trigger_button(), + mouse_data.held_buttons(), + mouse_data.modifiers(), + ) } if let Some(mouse) = &self.mouse { From 81d3c990bfea22ff02e459c874c81555dfbecfcf Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 16:11:48 +0300 Subject: [PATCH 12/36] Simplify mouse button tracking delete the shitcodes --- examples/tui_hover.rs | 2 +- packages/html/src/events.rs | 9 +++ packages/tui/src/hooks.rs | 108 +++++++++++++++--------------------- 3 files changed, 55 insertions(+), 64 deletions(-) diff --git a/examples/tui_hover.rs b/examples/tui_hover.rs index 2de409f17..136213c25 100644 --- a/examples/tui_hover.rs +++ b/examples/tui_hover.rs @@ -13,7 +13,7 @@ fn app(cx: Scope) -> Element { } fn get_brightness(m: Arc) -> i32 { - let b: i32 = m.buttons.count_ones().try_into().unwrap(); + let b: i32 = m.held_buttons().len().try_into().unwrap(); 127 * b } diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index fa1791e60..64c9ecf68 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -639,6 +639,15 @@ pub mod on { ScreenPoint::new(self.screen_x.into(), self.screen_y.into()) } + pub fn coordinates(&self) -> Coordinates { + Coordinates::new( + self.screen_coordinates(), + self.client_coordinates(), + self.element_coordinates(), + self.page_coordinates(), + ) + } + /// The set of modifier keys which were pressed when the event occurred pub fn modifiers(&self) -> Modifiers { let mut modifiers = Modifiers::empty(); diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 350b836a4..c8d890d83 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -6,8 +6,8 @@ use fxhash::{FxHashMap, FxHashSet}; use dioxus_html::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; use dioxus_html::input::keyboard_types::Modifiers; -use dioxus_html::input::MouseButton as DioxusMouseButton; use dioxus_html::input::MouseButtonSet as DioxusMouseButtons; +use dioxus_html::input::{MouseButton as DioxusMouseButton, MouseButtonSet}; use dioxus_html::{on::*, KeyCode}; use std::{ any::Any, @@ -79,7 +79,7 @@ impl EventData { const MAX_REPEAT_TIME: Duration = Duration::from_millis(100); pub struct InnerInputState { - mouse: Option<(MouseData, Vec)>, + mouse: Option, wheel: Option, last_key_pressed: Option<(KeyboardData, Instant)>, screen: Option<(u16, u16)>, @@ -101,54 +101,38 @@ impl InnerInputState { fn apply_event(&mut self, evt: &mut EventCore) { match evt.1 { // limitations: only two buttons may be held at once - EventData::Mouse(ref mut m) => match &mut self.mouse { - Some(state) => { - let mut buttons = state.0.buttons; - state.0 = m.clone(); - match evt.0 { - // this code only runs when there are no buttons down - "mouseup" => { - buttons = 0; - state.1 = Vec::new(); - } - "mousedown" => { - if state.1.contains(&m.buttons) { - // if we already pressed a button and there is another button released the button crossterm sends is the button remaining - if state.1.len() > 1 { - evt.0 = "mouseup"; - state.1 = vec![m.buttons]; - } - // otherwise some other button was pressed. In testing it was consistantly this mapping - else { - match m.buttons { - 0x01 => state.1.push(0x02), - 0x02 => state.1.push(0x01), - 0x04 => state.1.push(0x01), - _ => (), - } - } - } else { - state.1.push(m.buttons); - } + EventData::Mouse(ref mut m) => { + let mut held_buttons = match &self.mouse { + Some(previous_data) => previous_data.held_buttons(), + None => MouseButtonSet::empty(), + }; - buttons = state.1.iter().copied().reduce(|a, b| a | b).unwrap(); - } - _ => (), + match evt.0 { + "mousedown" => { + held_buttons.insert( + m.trigger_button() + .expect("No trigger button for mousedown event"), + ); } - state.0.buttons = buttons; - m.buttons = buttons; + "mouseup" => { + held_buttons.remove( + m.trigger_button() + .expect("No trigger button for mouseup event"), + ); + } + _ => {} } - None => { - self.mouse = Some(( - m.clone(), - if m.buttons == 0 { - Vec::new() - } else { - vec![m.buttons] - }, - )); - } - }, + + let new_mouse_data = MouseData::new( + m.coordinates(), + m.trigger_button(), + held_buttons, + m.modifiers(), + ); + + self.mouse = Some(new_mouse_data.clone()); + *m = new_mouse_data; + } EventData::Wheel(ref w) => self.wheel = Some(w.clone()), EventData::Screen(ref s) => self.screen = Some(*s), EventData::Keyboard(ref mut k) => { @@ -171,7 +155,7 @@ impl InnerInputState { layout: &Stretch, dom: &mut Dom, ) { - let previous_mouse = self.mouse.as_ref().map(|m| (m.0.clone(), m.1.clone())); + let previous_mouse = self.mouse.clone(); self.wheel = None; @@ -188,7 +172,7 @@ impl InnerInputState { fn resolve_mouse_events( &self, - previous_mouse: Option<(MouseData, Vec)>, + previous_mouse: Option, resolved_events: &mut Vec, layout: &Stretch, dom: &mut Dom, @@ -248,19 +232,16 @@ impl InnerInputState { ) } - if let Some(mouse) = &self.mouse { - let new_pos = (mouse.0.screen_x, mouse.0.screen_y); - let old_pos = previous_mouse - .as_ref() - .map(|m| (m.0.screen_x, m.0.screen_y)); + if let Some(mouse_data) = &self.mouse { + let new_pos = (mouse_data.screen_x, mouse_data.screen_y); + let old_pos = previous_mouse.as_ref().map(|m| (m.screen_x, m.screen_y)); // the a mouse button is pressed if a button was not down and is now down let pressed = - (mouse.0.buttons & !previous_mouse.as_ref().map(|m| m.0.buttons).unwrap_or(0)) > 0; + (mouse_data.buttons & !previous_mouse.as_ref().map(|m| m.buttons).unwrap_or(0)) > 0; // the a mouse button is pressed if a button was down and is now not down let released = - (!mouse.0.buttons & previous_mouse.map(|m| m.0.buttons).unwrap_or(0)) > 0; + (!mouse_data.buttons & previous_mouse.map(|m| m.buttons).unwrap_or(0)) > 0; let wheel_delta = self.wheel.as_ref().map_or(0.0, |w| w.delta_y); - let mouse_data = &mouse.0; let wheel_data = &self.wheel; { @@ -603,11 +584,6 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> { MouseButton::Middle => DioxusMouseButton::Auxiliary, }); - let button_set = match button { - None => DioxusMouseButtons::empty(), - Some(button) => DioxusMouseButtons::only(button), - }; - // from https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent // The `page` and `screen` coordinates are inconsistent with the MDN definition, as they are relative to the viewport (client), not the target element/page/screen, respectively. @@ -635,7 +611,13 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> { modifiers.insert(Modifiers::ALT); } - EventData::Mouse(MouseData::new(coordinates, button, button_set, modifiers)) + // held mouse buttons get set later by maintaining state, as crossterm does not provide them + EventData::Mouse(MouseData::new( + coordinates, + button, + DioxusMouseButtons::empty(), + modifiers, + )) }; let get_wheel_data = |up| { From df59d5148ed7d79dc39f872018e24e3b8139f8f8 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 16:27:34 +0300 Subject: [PATCH 13/36] TUI mouse press&release: Replace usage of deprecated fields with accessors --- packages/tui/src/hooks.rs | 49 +++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index c8d890d83..1dcddf5f4 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -4,6 +4,7 @@ use crossterm::event::{ use dioxus_core::*; use fxhash::{FxHashMap, FxHashSet}; +use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D}; use dioxus_html::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; use dioxus_html::input::keyboard_types::Modifiers; use dioxus_html::input::MouseButtonSet as DioxusMouseButtons; @@ -16,7 +17,7 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; -use stretch2::geometry::Point; +use stretch2::geometry::{Point, Size}; use stretch2::{prelude::Layout, Stretch}; use crate::{Dom, Node}; @@ -177,11 +178,12 @@ impl InnerInputState { layout: &Stretch, dom: &mut Dom, ) { - fn layout_contains_point(layout: &Layout, point: (i32, i32)) -> bool { - layout.location.x as i32 <= point.0 - && layout.location.x as i32 + layout.size.width as i32 >= point.0 - && layout.location.y as i32 <= point.1 - && layout.location.y as i32 + layout.size.height as i32 >= point.1 + fn layout_contains_point(layout: &Layout, point: ScreenPoint) -> bool { + let Point { x, y } = layout.location; + let Size { width, height } = layout.size; + + let layout_rect = Rect::new(Point2D::new(x, y), Size2D::new(width, height)); + layout_rect.contains(point.cast()) } fn try_create_event( @@ -233,14 +235,19 @@ impl InnerInputState { } if let Some(mouse_data) = &self.mouse { - let new_pos = (mouse_data.screen_x, mouse_data.screen_y); - let old_pos = previous_mouse.as_ref().map(|m| (m.screen_x, m.screen_y)); - // the a mouse button is pressed if a button was not down and is now down - let pressed = - (mouse_data.buttons & !previous_mouse.as_ref().map(|m| m.buttons).unwrap_or(0)) > 0; - // the a mouse button is pressed if a button was down and is now not down - let released = - (!mouse_data.buttons & previous_mouse.map(|m| m.buttons).unwrap_or(0)) > 0; + let new_pos = mouse_data.screen_coordinates(); + let old_pos = previous_mouse.as_ref().map(|m| m.screen_coordinates()); + + // a mouse button is pressed if a button was not down and is now down + let previous_buttons = previous_mouse + .map_or(MouseButtonSet::empty(), |previous_data| { + previous_data.held_buttons() + }); + let was_pressed = !(mouse_data.held_buttons() - previous_buttons).is_empty(); + + // a mouse button is released if a button was down and is now not down + let was_released = !(previous_buttons - mouse_data.held_buttons()).is_empty(); + let wheel_delta = self.wheel.as_ref().map_or(0.0, |w| w.delta_y); let wheel_data = &self.wheel; @@ -316,7 +323,7 @@ impl InnerInputState { } // mousedown - if pressed { + if was_pressed { let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("mousedown") { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); @@ -342,7 +349,7 @@ impl InnerInputState { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, new_pos); - if currently_contains && released { + if currently_contains && was_released { try_create_event( "mouseup", Arc::new(prepare_mouse_data(mouse_data, node_layout)), @@ -362,7 +369,10 @@ impl InnerInputState { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, new_pos); - if currently_contains && released && mouse_data.button == 0 { + if currently_contains + && was_released + && mouse_data.trigger_button() == Some(DioxusMouseButton::Primary) + { try_create_event( "click", Arc::new(prepare_mouse_data(mouse_data, node_layout)), @@ -382,7 +392,10 @@ impl InnerInputState { let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap(); let currently_contains = layout_contains_point(node_layout, new_pos); - if currently_contains && released && mouse_data.button == 2 { + if currently_contains + && was_released + && mouse_data.trigger_button() == Some(DioxusMouseButton::Secondary) + { try_create_event( "contextmenu", Arc::new(prepare_mouse_data(mouse_data, node_layout)), From 5bddafb9439d40a3f67e4c4a9a587cdd9024554e Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Sat, 7 May 2022 18:28:15 +0300 Subject: [PATCH 14/36] Update tui_keys example and web_sys to use new accessor methods --- examples/tui_keys.rs | 16 +++++---- packages/html/src/web_sys_bind/events.rs | 43 +++++++++++++++--------- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/examples/tui_keys.rs b/examples/tui_keys.rs index 4fde8c2c0..11a96a9b2 100644 --- a/examples/tui_keys.rs +++ b/examples/tui_keys.rs @@ -1,5 +1,7 @@ use dioxus::events::WheelEvent; use dioxus::prelude::*; +use dioxus_html::geometry::ScreenPoint; +use dioxus_html::input::MouseButtonSet; use dioxus_html::on::{KeyboardEvent, MouseEvent}; use dioxus_html::KeyCode; @@ -9,9 +11,9 @@ fn main() { fn app(cx: Scope) -> Element { let key = use_state(&cx, || "".to_string()); - let mouse = use_state(&cx, || (0, 0)); + let mouse = use_state(&cx, || ScreenPoint::zero()); let count = use_state(&cx, || 0); - let buttons = use_state(&cx, || 0); + let buttons = use_state(&cx, || MouseButtonSet::empty()); let mouse_clicked = use_state(&cx, || false); cx.render(rsx! { @@ -36,21 +38,21 @@ fn app(cx: Scope) -> Element { count.set(count + evt.data.delta_y as i64); }, ondrag: move |evt: MouseEvent| { - mouse.set((evt.data.screen_x, evt.data.screen_y)); + mouse.set(evt.data.screen_coordinates()); }, onmousedown: move |evt: MouseEvent| { - mouse.set((evt.data.screen_x, evt.data.screen_y)); - buttons.set(evt.data.buttons); + mouse.set(evt.data.screen_coordinates()); + buttons.set(evt.data.held_buttons()); mouse_clicked.set(true); }, onmouseup: move |evt: MouseEvent| { - buttons.set(evt.data.buttons); + buttons.set(evt.data.held_buttons()); mouse_clicked.set(false); }, "count: {count:?}", "key: {key}", - "mouse buttons: {buttons:b}", + "mouse buttons: {buttons:?}", "mouse pos: {mouse:?}", "mouse button pressed: {mouse_clicked}" } diff --git a/packages/html/src/web_sys_bind/events.rs b/packages/html/src/web_sys_bind/events.rs index d43130193..0bf1179e4 100644 --- a/packages/html/src/web_sys_bind/events.rs +++ b/packages/html/src/web_sys_bind/events.rs @@ -1,8 +1,11 @@ +use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; +use crate::input::{decode_mouse_button_set, MouseButton}; use crate::on::{ AnimationData, CompositionData, KeyboardData, MouseData, PointerData, TouchData, TransitionData, WheelData, }; use crate::KeyCode; +use keyboard_types::Modifiers; use wasm_bindgen::JsCast; use web_sys::{ AnimationEvent, CompositionEvent, Event, KeyboardEvent, MouseEvent, PointerEvent, TouchEvent, @@ -71,22 +74,32 @@ impl From<&KeyboardEvent> for KeyboardData { impl From<&MouseEvent> for MouseData { fn from(e: &MouseEvent) -> Self { - Self { - alt_key: e.alt_key(), - button: e.button(), - buttons: e.buttons(), - client_x: e.client_x(), - client_y: e.client_y(), - ctrl_key: e.ctrl_key(), - meta_key: e.meta_key(), - offset_x: e.offset_x(), - offset_y: e.offset_y(), - screen_x: e.screen_x(), - screen_y: e.screen_y(), - shift_key: e.shift_key(), - page_x: e.page_x(), - page_y: e.page_y(), + let mut modifiers = Modifiers::empty(); + + if e.alt_key() { + modifiers.insert(Modifiers::ALT); } + if e.ctrl_key() { + modifiers.insert(Modifiers::CONTROL); + } + if e.meta_key() { + modifiers.insert(Modifiers::META); + } + if e.shift_key() { + modifiers.insert(Modifiers::SHIFT); + } + + MouseData::new( + Coordinates::new( + ScreenPoint::new(e.screen_x().into(), e.screen_y().into()), + ClientPoint::new(e.client_x().into(), e.client_y().into()), + ElementPoint::new(e.offset_x().into(), e.offset_y().into()), + PagePoint::new(e.page_x().into(), e.page_y().into()), + ), + Some(MouseButton::from_web_code(e.button().into())), + decode_mouse_button_set(e.buttons()), + modifiers, + ) } } From 95573d1f547628ef1d634438778b10d54f237bac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rowin=20Hern=C3=A1ndez?= Date: Mon, 9 May 2022 18:48:50 +0100 Subject: [PATCH 15/36] Update useref.rs defualt -> default --- packages/hooks/src/useref.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hooks/src/useref.rs b/packages/hooks/src/useref.rs index 5fa8eb9f0..6a384a5ff 100644 --- a/packages/hooks/src/useref.rs +++ b/packages/hooks/src/useref.rs @@ -24,7 +24,7 @@ use std::{ /// val.write().insert(1, "hello".to_string()); /// ``` /// -/// You can avoid this defualt behavior with `write_silent` +/// You can avoid this default behavior with `write_silent` /// /// ``` /// // with `write_silent`, the component will not be re-rendered From b7e9dca87cd9b8b566966403cd6a7ce9bdaa67de Mon Sep 17 00:00:00 2001 From: YuKun Liu <41265098+mrxiaozhuox@users.noreply.github.com> Date: Wed, 11 May 2022 14:09:31 +0800 Subject: [PATCH 16/36] fix: add eval script failed log --- packages/desktop/src/desktop_context.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 0b37910b0..93aad7ee8 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -193,9 +193,11 @@ pub(super) fn handler( DevTool => {} - Eval(code) => webview - .evaluate_script(code.as_str()) - .expect("eval shouldn't panic"), + Eval(code) => { + if let Err(e) = webview.evaluate_script(code.as_str()) { + log::error!("Eval script error: {e}"); + } + } } } From 8aadeff9d207f57f09b45d95e6b4d4190f32ef1e Mon Sep 17 00:00:00 2001 From: YuKun Liu <41265098+mrxiaozhuox@users.noreply.github.com> Date: Wed, 11 May 2022 14:29:31 +0800 Subject: [PATCH 17/36] fix: add eval script failed log --- packages/desktop/src/desktop_context.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 93aad7ee8..6479e825a 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -195,7 +195,8 @@ pub(super) fn handler( Eval(code) => { if let Err(e) = webview.evaluate_script(code.as_str()) { - log::error!("Eval script error: {e}"); + // we can't panic this error. + log::warn!("Eval script error: {e}"); } } } From 4f55fcb84ab088a544ade666cc04f58f3c9cd9e9 Mon Sep 17 00:00:00 2001 From: YuKun Liu <41265098+mrxiaozhuox@users.noreply.github.com> Date: Wed, 11 May 2022 14:49:46 +0800 Subject: [PATCH 18/36] feat: use_window add `set_zoom_level` --- packages/desktop/src/desktop_context.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 6479e825a..39fd5fd16 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -115,6 +115,11 @@ impl DesktopContext { let _ = self.proxy.send_event(SetDecorations(decoration)); } + /// set window zoom level + pub fn set_zoom_level(&self, scale_factor: f64) { + let _ = self.proxy.send_event(SetZoomLevel(scale_factor)); + } + /// opens DevTool window pub fn devtool(&self) { let _ = self.proxy.send_event(DevTool); @@ -148,6 +153,8 @@ pub enum UserWindowEvent { SetTitle(String), SetDecorations(bool), + SetZoomLevel(f64), + DevTool, Eval(String), @@ -191,6 +198,8 @@ pub(super) fn handler( SetTitle(content) => window.set_title(&content), SetDecorations(state) => window.set_decorations(state), + SetZoomLevel(scale_factor) => webview.zoom(scale_factor), + DevTool => {} Eval(code) => { From 77a92a1cf622306ee3bfcd259ccf8d8a0bc996ea Mon Sep 17 00:00:00 2001 From: YuKun Liu <41265098+mrxiaozhuox@users.noreply.github.com> Date: Wed, 11 May 2022 14:56:37 +0800 Subject: [PATCH 19/36] feat: use_window add `print` & `devtool` --- packages/desktop/src/desktop_context.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 39fd5fd16..3d4bf0b26 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -120,6 +120,11 @@ impl DesktopContext { let _ = self.proxy.send_event(SetZoomLevel(scale_factor)); } + /// launch print modal + pub fn print(&self) { + let _ = self.proxy.send_event(Print); + } + /// opens DevTool window pub fn devtool(&self) { let _ = self.proxy.send_event(DevTool); @@ -155,6 +160,7 @@ pub enum UserWindowEvent { SetZoomLevel(f64), + Print, DevTool, Eval(String), @@ -200,7 +206,13 @@ pub(super) fn handler( SetZoomLevel(scale_factor) => webview.zoom(scale_factor), - DevTool => {} + Print => { + if let Err(e) = webview.print() { + // we can't panic this error. + log::warn!("Open print modal failed: {e}"); + } + } + DevTool => webview.open_devtools(), Eval(code) => { if let Err(e) = webview.evaluate_script(code.as_str()) { From 765baf6e48c67d6445f1cf3b4004f9217d9cd03a Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Wed, 11 May 2022 13:47:58 +0300 Subject: [PATCH 20/36] Rename module `input` to avoid naming conflict with element of the same name Not sure if this was supposed to be allowed, but it was causing https://github.com/rust-lang/rust/issues/96934 --- examples/tui_keys.rs | 2 +- packages/html/src/events.rs | 2 +- packages/html/src/{input.rs => input_data.rs} | 0 packages/html/src/lib.rs | 2 +- packages/html/src/web_sys_bind/events.rs | 2 +- packages/tui/src/hooks.rs | 6 +++--- 6 files changed, 7 insertions(+), 7 deletions(-) rename packages/html/src/{input.rs => input_data.rs} (100%) diff --git a/examples/tui_keys.rs b/examples/tui_keys.rs index 11a96a9b2..138eec2fe 100644 --- a/examples/tui_keys.rs +++ b/examples/tui_keys.rs @@ -1,7 +1,7 @@ use dioxus::events::WheelEvent; use dioxus::prelude::*; use dioxus_html::geometry::ScreenPoint; -use dioxus_html::input::MouseButtonSet; +use dioxus_html::input_data::MouseButtonSet; use dioxus_html::on::{KeyboardEvent, MouseEvent}; use dioxus_html::KeyCode; diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 64c9ecf68..6d7cbe06e 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -6,7 +6,7 @@ pub mod on { //! Input events and associated data use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; - use crate::input::{ + use crate::input_data::{ decode_mouse_button_set, encode_mouse_button_set, MouseButton, MouseButtonSet, }; use keyboard_types::Modifiers; diff --git a/packages/html/src/input.rs b/packages/html/src/input_data.rs similarity index 100% rename from packages/html/src/input.rs rename to packages/html/src/input_data.rs diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index 705a076e4..cb356af7e 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -17,7 +17,7 @@ mod elements; mod events; pub mod geometry; mod global_attributes; -pub mod input; +pub mod input_data; #[cfg(feature = "wasm-bind")] mod web_sys_bind; diff --git a/packages/html/src/web_sys_bind/events.rs b/packages/html/src/web_sys_bind/events.rs index 0bf1179e4..a69ac5fef 100644 --- a/packages/html/src/web_sys_bind/events.rs +++ b/packages/html/src/web_sys_bind/events.rs @@ -1,5 +1,5 @@ use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; -use crate::input::{decode_mouse_button_set, MouseButton}; +use crate::input_data::{decode_mouse_button_set, MouseButton}; use crate::on::{ AnimationData, CompositionData, KeyboardData, MouseData, PointerData, TouchData, TransitionData, WheelData, diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 74af56bef..5481d6b1c 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -6,9 +6,9 @@ use fxhash::{FxHashMap, FxHashSet}; use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D}; use dioxus_html::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; -use dioxus_html::input::keyboard_types::Modifiers; -use dioxus_html::input::MouseButtonSet as DioxusMouseButtons; -use dioxus_html::input::{MouseButton as DioxusMouseButton, MouseButtonSet}; +use dioxus_html::input_data::keyboard_types::Modifiers; +use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons; +use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet}; use dioxus_html::{on::*, KeyCode}; use std::{ any::Any, From 77c22161b9ef9f1755c17fb3fea4293ce1c27ebe Mon Sep 17 00:00:00 2001 From: YuKun Liu <41265098+mrxiaozhuox@users.noreply.github.com> Date: Thu, 12 May 2022 00:03:42 +0800 Subject: [PATCH 21/36] example: add `window_zoom` example --- examples/window_zoom.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 examples/window_zoom.rs diff --git a/examples/window_zoom.rs b/examples/window_zoom.rs new file mode 100644 index 000000000..792ab6de5 --- /dev/null +++ b/examples/window_zoom.rs @@ -0,0 +1,23 @@ +use dioxus::desktop::use_window; +use dioxus::prelude::*; + +fn main() { + dioxus::desktop::launch(app); +} + +fn app(cx: Scope) -> Element { + let window = use_window(&cx); + + let level = use_state(&cx, || 1.0); + + window.set_zoom_level(*level.get()); + cx.render(rsx! { + input { + r#type: "number", + value: "{level_display}", + oninput: |e| { + level.set(e.value.parse::().unwrap_or_default()) + } + } + }) +} From 31a2ded2b9ad37f2ce4bc9492f4fea0afb6e9d53 Mon Sep 17 00:00:00 2001 From: YuKun Liu <41265098+mrxiaozhuox@users.noreply.github.com> Date: Thu, 12 May 2022 00:05:59 +0800 Subject: [PATCH 22/36] example: add `window_zoom` example --- examples/window_zoom.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/window_zoom.rs b/examples/window_zoom.rs index 792ab6de5..3d185c78c 100644 --- a/examples/window_zoom.rs +++ b/examples/window_zoom.rs @@ -14,7 +14,7 @@ fn app(cx: Scope) -> Element { cx.render(rsx! { input { r#type: "number", - value: "{level_display}", + value: "{level}", oninput: |e| { level.set(e.value.parse::().unwrap_or_default()) } From 3f4897cd1c63f2c5d0600c9f86a62a90beb4bd95 Mon Sep 17 00:00:00 2001 From: YuKun Liu <41265098+mrxiaozhuox@users.noreply.github.com> Date: Thu, 12 May 2022 08:41:30 +0800 Subject: [PATCH 23/36] example: update example --- examples/window_zoom.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/window_zoom.rs b/examples/window_zoom.rs index 3d185c78c..cfb831550 100644 --- a/examples/window_zoom.rs +++ b/examples/window_zoom.rs @@ -9,14 +9,14 @@ fn app(cx: Scope) -> Element { let window = use_window(&cx); let level = use_state(&cx, || 1.0); - - window.set_zoom_level(*level.get()); cx.render(rsx! { input { r#type: "number", value: "{level}", oninput: |e| { - level.set(e.value.parse::().unwrap_or_default()) + let num = e.value.parse::().unwrap_or(1.0); + level.set(num); + window.set_zoom_level(num); } } }) From 328bab0f5bb8bdb5305a42daf51adf118b5a30d5 Mon Sep 17 00:00:00 2001 From: duanzhou Date: Wed, 18 May 2022 00:30:36 +0800 Subject: [PATCH 24/36] Fix: enable `dioxus-core` `serialize` feature in `dioxuse-web` --- packages/web/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 387349ec5..a3001a6b2 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -11,7 +11,7 @@ documentation = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react", "wasm"] [dependencies] -dioxus-core = { path = "../core", version = "^0.2.1" } +dioxus-core = { path = "../core", version = "^0.2.1", features = ["serialize"] } dioxus-html = { path = "../html", version = "^0.2.1", features = ["wasm-bind"] } dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1", features = [ "web" From b964f7c802c7d20da5075a0e37acc9c7b0fb1be0 Mon Sep 17 00:00:00 2001 From: YuKun Liu Date: Sat, 21 May 2022 10:11:07 +0800 Subject: [PATCH 25/36] Update README.md --- packages/router/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/router/README.md b/packages/router/README.md index d0c036bc9..b7372c271 100644 --- a/packages/router/README.md +++ b/packages/router/README.md @@ -43,9 +43,9 @@ | Examples | - Guide (0.1.8) + Guide (0.2.4) | - Guide (Master) + Guide (Master) From 8346525db52e73aeae0eedb6a239ec3e17335c5a Mon Sep 17 00:00:00 2001 From: YuKun Liu Date: Sat, 21 May 2022 23:33:17 +0800 Subject: [PATCH 26/36] Update README.md --- packages/router/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/router/README.md b/packages/router/README.md index b7372c271..ffd04ef0e 100644 --- a/packages/router/README.md +++ b/packages/router/README.md @@ -43,7 +43,7 @@ | Examples | - Guide (0.2.4) + Guide (Latest) | Guide (Master) From 2cdd142c3e8c2dde5c904208b411ed3bf575b1a9 Mon Sep 17 00:00:00 2001 From: ZingerLittleBee <6970999@gmail.com> Date: Mon, 23 May 2022 00:56:08 +0800 Subject: [PATCH 27/36] fix: desktop project name in docs --- docs/reference/src/platforms/desktop.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/src/platforms/desktop.md b/docs/reference/src/platforms/desktop.md index 18393f132..985fbe6d9 100644 --- a/docs/reference/src/platforms/desktop.md +++ b/docs/reference/src/platforms/desktop.md @@ -11,7 +11,7 @@ Getting Set up with Dioxus-Desktop is quite easy. Make sure you have Rust and Ca ```shell $ cargo new --bin demo -$ cd app +$ cd demo ``` Add Dioxus with the `desktop` feature: From db1866090a4a1521801e997e9c80feb601fac207 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 22 May 2022 20:44:32 -0500 Subject: [PATCH 28/36] fix broken doc links --- packages/core-macro/src/rsx/component.rs | 3 +-- packages/core/src/diff.rs | 16 ++++++++-------- packages/core/src/events.rs | 2 +- packages/core/src/nodes.rs | 10 +++++----- packages/core/src/util.rs | 2 +- packages/html/src/events.rs | 2 +- packages/native-core/src/real_dom.rs | 4 ++-- packages/native-core/src/state.rs | 10 +++++----- packages/web/src/lib.rs | 2 +- 9 files changed, 25 insertions(+), 26 deletions(-) diff --git a/packages/core-macro/src/rsx/component.rs b/packages/core-macro/src/rsx/component.rs index dfdda74b8..5b0a38b87 100644 --- a/packages/core-macro/src/rsx/component.rs +++ b/packages/core-macro/src/rsx/component.rs @@ -1,7 +1,6 @@ -//! Parse components into the VComponent VNode +//! Parse components into the VNode::Component variant //! ========================================== //! -//! This parsing path emerges from [`AmbiguousElement`] which supports validation of the vcomponent format. //! We can be reasonably sure that whatever enters this parsing path is in the right format. //! This feature must support //! - [x] Namespaced components diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 3d8b9082d..72711a992 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -1,26 +1,26 @@ #![warn(clippy::pedantic)] #![allow(clippy::cast_possible_truncation)] -//! This module contains the stateful [`DiffState`] and all methods to diff [`VNodes`], their properties, and their children. +//! This module contains the stateful [`DiffState`] and all methods to diff [`VNode`]s, their properties, and their children. //! //! The [`DiffState`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set -//! of mutations for the [`RealDom`] to apply. +//! of mutations for the renderer to apply. //! //! ## Notice: //! //! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support -//! Components, Fragments, Suspense, [`SubTree`] memoization, incremental diffing, cancellation, [`NodeRefs`], pausing, priority +//! Components, Fragments, Suspense, SubTree memoization, incremental diffing, cancellation, pausing, priority //! scheduling, and additional batching operations. //! //! ## Implementation Details: //! //! ### IDs for elements //! -------------------- -//! All nodes are addressed by their IDs. The [`RealDom`] provides an imperative interface for making changes to these nodes. +//! All nodes are addressed by their IDs. //! We don't necessarily require that DOM changes happen instantly during the diffing process, so the implementor may choose //! to batch nodes if it is more performant for their application. The element IDs are indices into the internal element //! array. The expectation is that implementors will use the ID as an index into a Vec of real nodes, allowing for passive -//! garbage collection as the [`VirtualDOM`] replaces old nodes. +//! garbage collection as the [crate::VirtualDom] replaces old nodes. //! //! When new vnodes are created through `cx.render`, they won't know which real node they correspond to. During diffing, //! we always make sure to copy over the ID. If we don't do this properly, the [`ElementId`] will be populated incorrectly @@ -30,7 +30,7 @@ //! -------------------- //! Fragments (nodes without a parent) are supported through a combination of "replace with" and anchor vnodes. Fragments //! can be particularly challenging when they are empty, so the anchor node lets us "reserve" a spot for the empty -//! fragment to be replaced with when it is no longer empty. This is guaranteed by logic in the [`NodeFactory`] - it is +//! fragment to be replaced with when it is no longer empty. This is guaranteed by logic in the [crate::innerlude::NodeFactory] - it is //! impossible to craft a fragment with 0 elements - they must always have at least a single placeholder element. Adding //! "dummy" nodes _is_ inefficient, but it makes our diffing algorithm faster and the implementation is completely up to //! the platform. @@ -44,13 +44,13 @@ //! into a promise-like value. React will then work on the next "ready" fiber, checking back on the previous fiber once //! it has finished its new work. In Dioxus, we use a similar approach, but try to completely render the tree before //! switching sub-fibers. Instead, each future is submitted into a futures-queue and the node is manually loaded later on. -//! Due to the frequent calls to [`yield_now`] we can get the pure "fetch-as-you-render" behavior of React Fiber. +//! Due to the frequent calls to [crate::virtual_dom::VirtualDom::work_with_deadline] we can get the pure "fetch-as-you-render" behavior of React Fiber. //! //! We're able to use this approach because we use placeholder nodes - futures that aren't ready still get submitted to //! DOM, but as a placeholder. //! //! Right now, the "suspense" queue is intertwined with hooks. In the future, we should allow any future to drive attributes -//! and contents, without the need for the [`use_suspense`] hook. In the interim, this is the quickest way to get Suspense working. +//! and contents, without the need for a use_suspense hook. In the interim, this is the quickest way to get Suspense working. //! //! ## Subtree Memoization //! ----------------------- diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 22889a96b..0b24f5563 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -150,7 +150,7 @@ impl AnyEvent { /// You should prefer to use the name of the event directly, rather than /// the UiEvent generic type. /// -/// For the HTML crate, this would include [`MouseEvent`], [`FormEvent`] etc. +/// For the HTML crate, this would include MouseEvent, FormEvent etc. pub struct UiEvent { /// The internal data of the event /// This is wrapped in an Arc so that it can be sent across threads diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 66c25f1db..1b19aa878 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -550,7 +550,7 @@ impl<'a> NodeFactory<'a> { })) } - /// Create a new [`VNode::VElement`] + /// Create a new [`VNode::Element`] pub fn element( &self, el: impl DioxusElement, @@ -569,7 +569,7 @@ impl<'a> NodeFactory<'a> { ) } - /// Create a new [`VNode::VElement`] without the trait bound + /// Create a new [`VNode::Element`] without the trait bound /// /// IE pass in "div" instead of `div` pub fn raw_element( @@ -637,7 +637,7 @@ impl<'a> NodeFactory<'a> { } } - /// Create a new [`VNode::VComponent`] + /// Create a new [`VNode::Component`] pub fn component

( &self, component: fn(Scope<'a, P>) -> Element, @@ -684,7 +684,7 @@ impl<'a> NodeFactory<'a> { } } - /// Create a new [`VNode::VFragment`] from a root of the rsx! call + /// Create a new [`VNode::Fragment`] from a root of the rsx! call pub fn fragment_root<'b, 'c>( self, node_iter: impl IntoIterator + 'c> + 'b, @@ -705,7 +705,7 @@ impl<'a> NodeFactory<'a> { } } - /// Create a new [`VNode::VFragment`] from any iterator + /// Create a new [`VNode::Fragment`] from any iterator pub fn fragment_from_iter<'b, 'c>( self, node_iter: impl IntoIterator + 'c> + 'b, diff --git a/packages/core/src/util.rs b/packages/core/src/util.rs index a6a524e27..5d311f177 100644 --- a/packages/core/src/util.rs +++ b/packages/core/src/util.rs @@ -1,7 +1,7 @@ use crate::innerlude::*; /// An iterator that only yields "real" [`Element`]s. IE only Elements that are -/// not [`VNode::VComponent`] or [`VNode::VFragment`], . +/// not [`VNode::Component`] or [`VNode::Fragment`], . pub struct ElementIdIterator<'a> { vdom: &'a VirtualDom, diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 6d7cbe06e..beac81167 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -447,7 +447,7 @@ pub mod on { /// Get the key code as an enum Variant. /// /// This is intended for things like arrow keys, escape keys, function keys, and other non-international keys. - /// To match on unicode sequences, use the [`KeyboardEvent::key`] method - this will return a string identifier instead of a limited enum. + /// To match on unicode sequences, use the [`KeyboardData::key`] method - this will return a string identifier instead of a limited enum. /// /// /// ## Example diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 4d6964572..ca8da730a 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -15,7 +15,7 @@ use crate::{ /// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers. /// The render state passes from parent to children and or accumulates state from children to parents. -/// To get started implement [PushedDownState] and or [BubbledUpState] and call [RealDom::apply_mutations] to update the dom and [RealDom::update_state] to update the state of the nodes. +/// To get started implement [crate::state::ParentDepState], [crate::state::NodeDepState], or [crate::state::ChildDepState] and call [RealDom::apply_mutations] to update the dom and [RealDom::update_state] to update the state of the nodes. #[derive(Debug)] pub struct RealDom { root: usize, @@ -712,7 +712,7 @@ impl IndexMut for RealDom { } } -/// The node is stored client side and stores only basic data about the node. For more complete information about the node see [`domNode::element`]. +/// The node is stored client side and stores only basic data about the node. #[derive(Debug, Clone)] pub struct Node { /// The id of the node this node was created from. diff --git a/packages/native-core/src/state.rs b/packages/native-core/src/state.rs index b55f217d0..7583e3ede 100644 --- a/packages/native-core/src/state.rs +++ b/packages/native-core/src/state.rs @@ -44,10 +44,10 @@ pub(crate) fn union_ordered_iter( } /// This state is derived from children. For example a node's size could be derived from the size of children. -/// Called when the current node's node properties are modified, a child's [BubbledUpState] is modified or a child is removed. +/// Called when the current node's node properties are modified, a child's [ChildDepState] is modified or a child is removed. /// Called at most once per update. pub trait ChildDepState { - /// The context is passed to the [PushedDownState::reduce] when it is pushed down. + /// The context is passed to the [ChildDepState::reduce] when it is pushed down. /// This is sometimes nessisary for lifetime purposes. type Ctx; /// This must be either a [ChildDepState] or [NodeDepState] @@ -64,10 +64,10 @@ pub trait ChildDepState { } /// This state that is passed down to children. For example text properties (`` `` ``) would be passed to children. -/// Called when the current node's node properties are modified or a parrent's [PushedDownState] is modified. +/// Called when the current node's node properties are modified or a parrent's [ParentDepState] is modified. /// Called at most once per update. pub trait ParentDepState { - /// The context is passed to the [PushedDownState::reduce] when it is pushed down. + /// The context is passed to the [ParentDepState::reduce] when it is pushed down. /// This is sometimes nessisary for lifetime purposes. type Ctx; /// This must be either a [ParentDepState] or [NodeDepState] @@ -77,7 +77,7 @@ pub trait ParentDepState { } /// This state that is upadated lazily. For example any propertys that do not effect other parts of the dom like bg-color. -/// Called when the current node's node properties are modified or a parrent's [PushedDownState] is modified. +/// Called when the current node's node properties are modified or a sibling's [NodeDepState] is modified. /// Called at most once per update. pub trait NodeDepState { type Ctx; diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index da3955d1b..d66d8bc3f 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -26,7 +26,7 @@ // main thread. // // React solves this problem by breaking up the rendering process into a "diff" phase and a "render" phase. In Dioxus, -// the diff phase is non-blocking, using "yield_now" to allow the browser to process other events. When the diff phase +// the diff phase is non-blocking, using "work_with_deadline" to allow the browser to process other events. When the diff phase // is finally complete, the VirtualDOM will return a set of "Mutations" for this crate to apply. // // Here, we schedule the "diff" phase during the browser's idle period, achieved by calling RequestIdleCallback and then From 43d750c329a7070af7a5b7ca15844ff8d280df6c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 22 May 2022 21:21:28 -0500 Subject: [PATCH 29/36] fix clippy --- packages/core/src/diff.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 72711a992..71c9f1289 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -9,7 +9,7 @@ //! ## Notice: //! //! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support -//! Components, Fragments, Suspense, SubTree memoization, incremental diffing, cancellation, pausing, priority +//! Components, Fragments, Suspense, `SubTree` memoization, incremental diffing, cancellation, pausing, priority //! scheduling, and additional batching operations. //! //! ## Implementation Details: @@ -20,7 +20,7 @@ //! We don't necessarily require that DOM changes happen instantly during the diffing process, so the implementor may choose //! to batch nodes if it is more performant for their application. The element IDs are indices into the internal element //! array. The expectation is that implementors will use the ID as an index into a Vec of real nodes, allowing for passive -//! garbage collection as the [crate::VirtualDom] replaces old nodes. +//! garbage collection as the [`crate::VirtualDom`] replaces old nodes. //! //! When new vnodes are created through `cx.render`, they won't know which real node they correspond to. During diffing, //! we always make sure to copy over the ID. If we don't do this properly, the [`ElementId`] will be populated incorrectly @@ -30,7 +30,7 @@ //! -------------------- //! Fragments (nodes without a parent) are supported through a combination of "replace with" and anchor vnodes. Fragments //! can be particularly challenging when they are empty, so the anchor node lets us "reserve" a spot for the empty -//! fragment to be replaced with when it is no longer empty. This is guaranteed by logic in the [crate::innerlude::NodeFactory] - it is +//! fragment to be replaced with when it is no longer empty. This is guaranteed by logic in the [`crate::innerlude::NodeFactory`] - it is //! impossible to craft a fragment with 0 elements - they must always have at least a single placeholder element. Adding //! "dummy" nodes _is_ inefficient, but it makes our diffing algorithm faster and the implementation is completely up to //! the platform. @@ -44,13 +44,13 @@ //! into a promise-like value. React will then work on the next "ready" fiber, checking back on the previous fiber once //! it has finished its new work. In Dioxus, we use a similar approach, but try to completely render the tree before //! switching sub-fibers. Instead, each future is submitted into a futures-queue and the node is manually loaded later on. -//! Due to the frequent calls to [crate::virtual_dom::VirtualDom::work_with_deadline] we can get the pure "fetch-as-you-render" behavior of React Fiber. +//! Due to the frequent calls to [`crate::virtual_dom::VirtualDom::work_with_deadline`] we can get the pure "fetch-as-you-render" behavior of React Fiber. //! //! We're able to use this approach because we use placeholder nodes - futures that aren't ready still get submitted to //! DOM, but as a placeholder. //! //! Right now, the "suspense" queue is intertwined with hooks. In the future, we should allow any future to drive attributes -//! and contents, without the need for a use_suspense hook. In the interim, this is the quickest way to get Suspense working. +//! and contents, without the need for a `use_suspense` hook. In the interim, this is the quickest way to get Suspense working. //! //! ## Subtree Memoization //! ----------------------- From 60e74b2ec0d70a9ae1850340748a8fde66c396ed Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Mon, 23 May 2022 09:35:47 +0300 Subject: [PATCH 30/36] Add missing event types to web (copied from desktop) Related: #416 --- packages/web/src/dom.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 94d66460f..f321398dc 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -305,6 +305,8 @@ fn event_name_from_typ(typ: &str) -> &'static str { "keypress" => "keypress", "keyup" => "keyup", "focus" => "focus", + "focusout" => "focusout", + "focusin" => "focusin", "blur" => "blur", "change" => "change", "input" => "input", @@ -314,6 +316,7 @@ fn event_name_from_typ(typ: &str) -> &'static str { "click" => "click", "contextmenu" => "contextmenu", "doubleclick" => "doubleclick", + "dblclick" => "dblclick", "drag" => "drag", "dragend" => "dragend", "dragenter" => "dragenter", @@ -374,8 +377,8 @@ fn event_name_from_typ(typ: &str) -> &'static str { "volumechange" => "volumechange", "waiting" => "waiting", "toggle" => "toggle", - _ => { - panic!("unsupported event type") + a => { + panic!("unsupported event type {:?}", a); } } } From f792ed7a1771b4fd283ba1ae2499b716563125f4 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Mon, 23 May 2022 09:56:45 +0300 Subject: [PATCH 31/36] Update virtual_event_from_websys_event based on `make_synthetic_event` in the `desktop` crate --- packages/web/src/dom.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index f321398dc..ebcdefbcc 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -178,7 +178,7 @@ fn virtual_event_from_websys_event( }) } "keydown" | "keypress" | "keyup" => Arc::new(KeyboardData::from(event)), - "focus" | "blur" => Arc::new(FocusData {}), + "focus" | "blur" | "focusout" | "focusin" => Arc::new(FocusData {}), // todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy // don't have a good solution with the serialized event problem @@ -258,9 +258,9 @@ fn virtual_event_from_websys_event( Arc::new(FormData { value, values }) } - "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit" - | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter" - | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => { + "click" | "contextmenu" | "dblclick" | "doubleclick" | "drag" | "dragend" | "dragenter" + | "dragexit" | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" + | "mouseenter" | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => { Arc::new(MouseData::from(event)) } "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture" From 6520b24d6746522f1a7a060b2de2066b0c40341c Mon Sep 17 00:00:00 2001 From: Alejandro Osornio <50227494+AOx0@users.noreply.github.com> Date: Wed, 1 Jun 2022 15:52:43 -0500 Subject: [PATCH 32/36] Update index.md + Fix outdated documentation --- docs/guide/src/interactivity/index.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/guide/src/interactivity/index.md b/docs/guide/src/interactivity/index.md index 02c39b5b1..670ca5a40 100644 --- a/docs/guide/src/interactivity/index.md +++ b/docs/guide/src/interactivity/index.md @@ -64,7 +64,7 @@ The most common hook you'll use for storing state is `use_state`. `use_state` pr ```rust fn App(cx: Scope)-> Element { - let (post, set_post) = use_state(&cx, || { + let post = use_state(&cx, || { PostData { id: Uuid::new_v4(), score: 10, @@ -112,11 +112,11 @@ For example, let's say we provide a button to generate a new post. Whenever the ```rust fn App(cx: Scope)-> Element { - let (post, set_post) = use_state(&cx, || PostData::new()); + let post = use_state(&cx, || PostData::new()); cx.render(rsx!{ button { - onclick: move |_| set_post(PostData::random()) + onclick: move |_| post.set(PostData::random()) "Generate a random post" } Post { props: &post } @@ -141,19 +141,19 @@ We can use tasks in our components to build a tiny stopwatch that ticks every se ```rust fn App(cx: Scope)-> Element { - let (elapsed, set_elapsed) = use_state(&cx, || 0); - - use_future(&cx, || { - to_owned![set_elapsed]; // explicitly capture this hook for use in async + let elapsed = use_state(&cx, || 0); + + use_future(&cx, (), |()| { + to_owned![elapsed]; // explicitly capture this hook for use in async async move { loop { - TimeoutFuture::from_ms(1000).await; - set_elapsed.modify(|i| i + 1) + gloo_timers::future::TimeoutFuture::new(1_000).await; + elapsed.modify(|i| i + 1) } } }); - rsx!(cx, div { "Current stopwatch time: {sec_elapsed}" }) + rsx!(cx, div { "Current stopwatch time: {elapsed}" }) } ``` From 2433b9d305a6baade4e55088a35064dc8b86aae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E6=97=8B?= Date: Wed, 1 Jun 2022 23:07:54 +0800 Subject: [PATCH 33/36] docs: examples of mistakes docs: add hard_diff example missing parameter --- packages/core/src/properties.rs | 2 +- packages/core/src/virtual_dom.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/properties.rs b/packages/core/src/properties.rs index e81fc52a2..e138acaa6 100644 --- a/packages/core/src/properties.rs +++ b/packages/core/src/properties.rs @@ -33,7 +33,7 @@ impl<'a, const A: bool> FragmentBuilder<'a, A> { /// fn App(cx: Scope) -> Element { /// cx.render(rsx!{ /// CustomCard { -/// h1 {}2 +/// h1 {} /// p {} /// } /// }) diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 8d3fd1789..2b1de0767 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -583,7 +583,7 @@ impl VirtualDom { /// /// *value.borrow_mut() = "goodbye"; /// - /// let edits = dom.diff(); + /// let edits = dom.hard_diff(ScopeId(0)); /// ``` pub fn hard_diff(&mut self, scope_id: ScopeId) -> Mutations { let mut diff_machine = DiffState::new(&self.scopes); From 09b1d379d7cf9aec17d39988378df5fedf3ff37b Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 9 Jun 2022 16:55:53 +0800 Subject: [PATCH 34/36] fix(desktop): makes release build working --- packages/desktop/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index e61120d64..8c3317843 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -20,7 +20,7 @@ serde = "1.0.136" serde_json = "1.0.79" thiserror = "1.0.30" log = "0.4.14" -wry = { version = "0.16.0" } +wry = { version = "0.16.0", features = ["devtools"] } futures-channel = "0.3.21" tokio = { version = "1.16.1", features = [ "sync", From 38de26d0e4e1ea826de334e097130b5741b1ea27 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 9 Jun 2022 10:31:11 -0500 Subject: [PATCH 35/36] remove attributes correctly --- packages/interpreter/src/interpreter.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index a908ad74c..0c129efda 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -140,7 +140,9 @@ export class Interpreter { RemoveAttribute(root, field, ns) { const name = field; const node = this.nodes[root]; - if (ns !== null || ns !== undefined) { + if (ns == "style") { + node.style.removeProperty(name); + } else if (ns !== null || ns !== undefined) { node.removeAttributeNS(ns, name); } else if (name === "value") { node.value = ""; From 030f6d58fb2b699861f327d8e05543fe0a0097d9 Mon Sep 17 00:00:00 2001 From: CGQAQ Date: Fri, 10 Jun 2022 06:04:54 +0800 Subject: [PATCH 36/36] conditional devtools --- packages/desktop/Cargo.toml | 2 +- packages/desktop/src/desktop_context.rs | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 8c3317843..e61120d64 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -20,7 +20,7 @@ serde = "1.0.136" serde_json = "1.0.79" thiserror = "1.0.30" log = "0.4.14" -wry = { version = "0.16.0", features = ["devtools"] } +wry = { version = "0.16.0" } futures-channel = "0.3.21" tokio = { version = "1.16.1", features = [ "sync", diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 3d4bf0b26..d6d436a09 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -212,7 +212,12 @@ pub(super) fn handler( log::warn!("Open print modal failed: {e}"); } } - DevTool => webview.open_devtools(), + DevTool => { + #[cfg(debug_assertions)] + webview.open_devtools(); + #[cfg(not(debug_assertions))] + log::warn!("Devtools are disabled in release builds"); + } Eval(code) => { if let Err(e) = webview.evaluate_script(code.as_str()) {