From d7e4fcda801aea7d455d1f281afc520375c97879 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Thu, 12 May 2022 10:54:38 +0300 Subject: [PATCH 01/30] Implement idiomatic accessors for WheelData --- examples/{mouse_event.rs => event_mouse.rs} | 0 examples/event_wheel.rs | 39 +++++++++++++ packages/html/src/events.rs | 56 ++++++++++++++++++- packages/html/src/geometry.rs | 61 +++++++++++++++++++++ packages/tui/src/hooks.rs | 20 +++---- 5 files changed, 165 insertions(+), 11 deletions(-) rename examples/{mouse_event.rs => event_mouse.rs} (100%) create mode 100644 examples/event_wheel.rs diff --git a/examples/mouse_event.rs b/examples/event_mouse.rs similarity index 100% rename from examples/mouse_event.rs rename to examples/event_mouse.rs diff --git a/examples/event_wheel.rs b/examples/event_wheel.rs new file mode 100644 index 000000000..763c64d02 --- /dev/null +++ b/examples/event_wheel.rs @@ -0,0 +1,39 @@ +use dioxus::prelude::*; +use dioxus_core::UiEvent; +use dioxus_html::on::WheelData; + +fn main() { + dioxus::desktop::launch(app); +} + +fn app(cx: Scope) -> Element { + let delta = use_state(&cx, || "".to_string()); + + let container_style = r#" + display: flex; + flex-direction: column; + align-items: center; + "#; + let rect_style = r#" + background: deepskyblue; + height: 50vh; + width: 50vw; + "#; + + let handle_event = move |event: UiEvent| { + let wheel_data = event.data; + delta.set(format!("{:?}", wheel_data.delta())); + }; + + cx.render(rsx! ( + div { + style: "{container_style}", + "Scroll mouse wheel over rectangle:", + div { + style: "{rect_style}", + onwheel: handle_event, + } + div {"Delta: {delta}"}, + } + )) +} diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 6d7cbe06e..38ae75407 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -5,10 +5,14 @@ use dioxus_core::*; pub mod on { //! Input events and associated data - use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; + use crate::geometry::{ + ClientPoint, Coordinates, ElementPoint, LinesVector, PagePoint, PagesVector, PixelsVector, + ScreenPoint, WheelDelta, + }; use crate::input_data::{ decode_mouse_button_set, encode_mouse_button_set, MouseButton, MouseButtonSet, }; + use euclid::UnknownUnit; use keyboard_types::Modifiers; use std::collections::HashMap; @@ -740,12 +744,62 @@ pub mod on { #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] pub struct WheelData { + #[deprecated(since = "0.3.0", note = "use delta() instead")] pub delta_mode: u32, + #[deprecated(since = "0.3.0", note = "use delta() instead")] pub delta_x: f64, + #[deprecated(since = "0.3.0", note = "use delta() instead")] pub delta_y: f64, + #[deprecated(since = "0.3.0", note = "use delta() instead")] pub delta_z: f64, } + impl WheelData { + pub fn new(delta: WheelDelta) -> Self { + let (delta_mode, vector) = match delta { + WheelDelta::Pixels(v) => (0, v.cast_unit::()), + WheelDelta::Lines(v) => (1, v.cast_unit::()), + WheelDelta::Pages(v) => (2, v.cast_unit::()), + }; + + #[allow(deprecated)] + WheelData { + delta_mode, + delta_x: vector.x, + delta_y: vector.y, + delta_z: vector.z, + } + } + + pub fn from_web_attributes( + delta_mode: u32, + delta_x: f64, + delta_y: f64, + delta_z: f64, + ) -> Self { + #[allow(deprecated)] + Self { + delta_mode, + delta_x, + delta_y, + delta_z, + } + } + + #[allow(deprecated)] + pub fn delta(&self) -> WheelDelta { + let x = self.delta_x; + let y = self.delta_y; + let z = self.delta_z; + match self.delta_mode { + 0 => WheelDelta::Pixels(PixelsVector::new(x, y, z)), + 1 => WheelDelta::Lines(LinesVector::new(x, y, z)), + 2 => WheelDelta::Pages(PagesVector::new(x, y, z)), + _ => panic!("Invalid delta mode, {:?}", self.delta_mode), + } + } + } + pub type MediaEvent = 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 5a7c8112e..97ab068d3 100644 --- a/packages/html/src/geometry.rs +++ b/packages/html/src/geometry.rs @@ -25,6 +25,67 @@ pub struct PageSpace; /// A point in PageSpace pub type PagePoint = Point2D; +/// A pixel unit: one unit corresponds to 1 pixel +pub struct Pixels; +/// A vector expressed in Pixels +pub type PixelsVector = Vector3D; + +/// A unit in terms of Lines +/// +/// One unit is relative to the size of one line +pub struct Lines; +/// A vector expressed in Lines +pub type LinesVector = Vector3D; + +/// A unit in terms of Screens: +/// +/// One unit is relative to the size of a page +pub struct Pages; +/// A vector expressed in Pages +pub type PagesVector = Vector3D; + +/// A vector representing the amount the mouse wheel was moved +/// +/// This may be expressed in Pixels, Lines or Pages +#[derive(Copy, Clone, Debug)] +pub enum WheelDelta { + /// Movement in Pixels + Pixels(PixelsVector), + /// Movement in Lines + Lines(LinesVector), + /// Movement in Pages + Pages(PagesVector), +} + +impl WheelDelta { + /// Convenience function for constructing a WheelDelta with pixel units + pub fn pixels(x: f64, y: f64, z: f64) -> Self { + WheelDelta::Pixels(PixelsVector::new(x, y, z)) + } + + /// Convenience function for constructing a WheelDelta with line units + pub fn lines(x: f64, y: f64, z: f64) -> Self { + WheelDelta::Lines(LinesVector::new(x, y, z)) + } + + /// Convenience function for constructing a WheelDelta with page units + pub fn pages(x: f64, y: f64, z: f64) -> Self { + WheelDelta::Pages(PagesVector::new(x, y, z)) + } + + pub fn is_zero(&self) -> bool { + self.strip_units() == Vector3D::new(0., 0., 0.) + } + + pub fn strip_units(&self) -> Vector3D { + match self { + WheelDelta::Pixels(v) => v.cast_unit(), + WheelDelta::Lines(v) => v.cast_unit(), + WheelDelta::Pages(v) => v.cast_unit(), + } + } +} + /// Coordinates of a point in the app's interface pub struct Coordinates { screen: ScreenPoint, diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 5481d6b1c..bb32c1375 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -5,7 +5,9 @@ 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::geometry::{ + ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint, WheelDelta, +}; 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}; @@ -248,7 +250,10 @@ impl InnerInputState { // 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 was_scrolled = self + .wheel + .as_ref() + .map_or(false, |data| !data.delta().is_zero()); let wheel_data = &self.wheel; { @@ -412,7 +417,7 @@ impl InnerInputState { { // wheel if let Some(w) = wheel_data { - if wheel_delta != 0.0 { + if was_scrolled { let mut will_bubble = FxHashSet::default(); for node in dom.get_listening_sorted("wheel") { let node_layout = @@ -638,13 +643,8 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> { }; let get_wheel_data = |up| { - // from https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent - EventData::Wheel(WheelData { - delta_mode: 0x01, - delta_x: 0.0, - delta_y: if up { -1.0 } else { 1.0 }, - delta_z: 0.0, - }) + let y = if up { -1.0 } else { 1.0 }; + EventData::Wheel(WheelData::new(WheelDelta::lines(0., y, 0.))) }; match m.kind { From 1bb8b04d875f10871af4a4136ca4a05df2c2156f Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Thu, 12 May 2022 11:03:51 +0300 Subject: [PATCH 02/30] Update examples to avoid deprecated fields --- examples/tui_border.rs | 2 +- examples/tui_hover.rs | 8 ++++---- examples/tui_keys.rs | 2 +- examples/tui_text.rs | 2 +- packages/html/src/web_sys_bind/events.rs | 7 +------ 5 files changed, 8 insertions(+), 13 deletions(-) diff --git a/examples/tui_border.rs b/examples/tui_border.rs index 041df4a8b..3e1c01849 100644 --- a/examples/tui_border.rs +++ b/examples/tui_border.rs @@ -14,7 +14,7 @@ fn app(cx: Scope) -> Element { justify_content: "center", align_items: "center", background_color: "hsl(248, 53%, 58%)", - onwheel: move |w| radius.modify(|r| (r + w.delta_y as i8).abs()), + onwheel: move |w| radius.modify(|r| (r + w.delta().strip_units().y as i8).abs()), border_style: "solid none solid double", border_width: "thick", diff --git a/examples/tui_hover.rs b/examples/tui_hover.rs index 136213c25..5c3b2d832 100644 --- a/examples/tui_hover.rs +++ b/examples/tui_hover.rs @@ -65,7 +65,7 @@ fn app(cx: Scope) -> Element { onmouseenter: move |m| q1_color.set([get_brightness(m.data), 0, 0]), onmousedown: move |m| q1_color.set([get_brightness(m.data), 0, 0]), 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]), + onwheel: move |w| q1_color.set([q1_color[0] + (10.0*w.delta().strip_units().y) as i32, 0, 0]), onmouseleave: move |_| q1_color.set([200; 3]), onmousemove: update_data, "click me" @@ -79,7 +79,7 @@ fn app(cx: Scope) -> Element { onmouseenter: move |m| q2_color.set([get_brightness(m.data); 3]), onmousedown: move |m| q2_color.set([get_brightness(m.data); 3]), 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]), + onwheel: move |w| q2_color.set([q2_color[0] + (10.0*w.delta().strip_units().y) as i32;3]), onmouseleave: move |_| q2_color.set([200; 3]), onmousemove: update_data, "click me" @@ -99,7 +99,7 @@ fn app(cx: Scope) -> Element { onmouseenter: move |m| q3_color.set([0, get_brightness(m.data), 0]), onmousedown: move |m| q3_color.set([0, get_brightness(m.data), 0]), 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]), + onwheel: move |w| q3_color.set([0, q3_color[1] + (10.0*w.delta().strip_units().y) as i32, 0]), onmouseleave: move |_| q3_color.set([200; 3]), onmousemove: update_data, "click me" @@ -113,7 +113,7 @@ fn app(cx: Scope) -> Element { onmouseenter: move |m| q4_color.set([0, 0, get_brightness(m.data)]), onmousedown: move |m| q4_color.set([0, 0, get_brightness(m.data)]), 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]), + onwheel: move |w| q4_color.set([0, 0, q4_color[2] + (10.0*w.delta().strip_units().y) as i32]), onmouseleave: move |_| q4_color.set([200; 3]), onmousemove: update_data, "click me" diff --git a/examples/tui_keys.rs b/examples/tui_keys.rs index 138eec2fe..b4a7ce5d1 100644 --- a/examples/tui_keys.rs +++ b/examples/tui_keys.rs @@ -35,7 +35,7 @@ fn app(cx: Scope) -> Element { key.set(format!("{:?} repeating: {:?}", evt.key, evt.repeat)); }, onwheel: move |evt: WheelEvent| { - count.set(count + evt.data.delta_y as i64); + count.set(count + evt.data.delta().strip_units().y as i64); }, ondrag: move |evt: MouseEvent| { mouse.set(evt.data.screen_coordinates()); diff --git a/examples/tui_text.rs b/examples/tui_text.rs index b8f04d99a..a2992a8a0 100644 --- a/examples/tui_text.rs +++ b/examples/tui_text.rs @@ -12,7 +12,7 @@ fn app(cx: Scope) -> Element { width: "100%", height: "100%", flex_direction: "column", - onwheel: move |evt| alpha.set((**alpha + evt.data.delta_y as i64).min(100).max(0)), + onwheel: move |evt| alpha.set((**alpha + evt.data.delta().strip_units().y as i64).min(100).max(0)), p { background_color: "black", diff --git a/packages/html/src/web_sys_bind/events.rs b/packages/html/src/web_sys_bind/events.rs index a69ac5fef..fbbb34d9e 100644 --- a/packages/html/src/web_sys_bind/events.rs +++ b/packages/html/src/web_sys_bind/events.rs @@ -146,12 +146,7 @@ impl From<&PointerEvent> for PointerData { impl From<&WheelEvent> for WheelData { fn from(e: &WheelEvent) -> Self { - Self { - delta_x: e.delta_x(), - delta_y: e.delta_y(), - delta_z: e.delta_z(), - delta_mode: e.delta_mode(), - } + WheelData::from_web_attributes(e.delta_mode(), e.delta_x(), e.delta_y(), e.delta_z()) } } From addb0703b10a477616729075d633079490bdfbda Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Thu, 12 May 2022 11:12:34 +0300 Subject: [PATCH 03/30] Write docs for wheel events --- packages/html/src/events.rs | 3 +++ packages/html/src/geometry.rs | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 38ae75407..4cb7c6fb1 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -755,6 +755,7 @@ pub mod on { } impl WheelData { + /// Construct a new WheelData with the specified wheel movement delta pub fn new(delta: WheelDelta) -> Self { let (delta_mode, vector) = match delta { WheelDelta::Pixels(v) => (0, v.cast_unit::()), @@ -771,6 +772,7 @@ pub mod on { } } + /// Construct from the attributes of the web wheel event pub fn from_web_attributes( delta_mode: u32, delta_x: f64, @@ -786,6 +788,7 @@ pub mod on { } } + /// The amount of wheel movement #[allow(deprecated)] pub fn delta(&self) -> WheelDelta { let x = self.delta_x; diff --git a/packages/html/src/geometry.rs b/packages/html/src/geometry.rs index 97ab068d3..027922a85 100644 --- a/packages/html/src/geometry.rs +++ b/packages/html/src/geometry.rs @@ -48,6 +48,7 @@ pub type PagesVector = Vector3D; /// /// This may be expressed in Pixels, Lines or Pages #[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub enum WheelDelta { /// Movement in Pixels Pixels(PixelsVector), @@ -73,10 +74,18 @@ impl WheelDelta { WheelDelta::Pages(PagesVector::new(x, y, z)) } + /// Returns true iff there is no wheel movement + /// + /// i.e. the x, y and z delta is zero (disregards units) pub fn is_zero(&self) -> bool { self.strip_units() == Vector3D::new(0., 0., 0.) } + /// A Vector3D proportional to the amount scrolled + /// + /// Note that this disregards the 3 possible units: this could be expressed in terms of pixels, lines, or pages. + /// + /// In most cases, to properly handle scrolling, you should handle all 3 possible enum variants instead of stripping units. Otherwise, if you assume that the units will always be pixels, the user may experience some unexpectedly slow scrolling if their mouse/OS sends values expressed in lines or pages. pub fn strip_units(&self) -> Vector3D { match self { WheelDelta::Pixels(v) => v.cast_unit(), From 7ee02bc0d8804b25a01f99157ad9c9137cada033 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Thu, 12 May 2022 11:38:27 +0300 Subject: [PATCH 04/30] Enable `euclid/serde` when needed --- packages/html/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index ac5cd340f..046ab9acc 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -40,5 +40,5 @@ features = [ [features] default = [] -serialize = ["serde", "serde_repr"] +serialize = ["serde", "serde_repr", "euclid/serde"] wasm-bind = ["web-sys", "wasm-bindgen"] From ed34e339fcfa9dd266a82778659f4ce90494298e Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Thu, 12 May 2022 14:10:25 +0300 Subject: [PATCH 05/30] Tui: construct keyboard data with new api --- packages/html/Cargo.toml | 2 +- packages/html/src/events.rs | 144 +++++++++---- packages/html/src/input_data.rs | 23 +++ packages/tui/src/hooks.rs | 348 ++++++++++++++++++++------------ 4 files changed, 342 insertions(+), 175 deletions(-) diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 046ab9acc..8464189ea 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -40,5 +40,5 @@ features = [ [features] default = [] -serialize = ["serde", "serde_repr", "euclid/serde"] +serialize = ["serde", "serde_repr", "euclid/serde", "keyboard-types/serde"] wasm-bind = ["web-sys", "wasm-bindgen"] diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 4cb7c6fb1..373e46414 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -10,11 +10,15 @@ pub mod on { ScreenPoint, WheelDelta, }; use crate::input_data::{ - decode_mouse_button_set, encode_mouse_button_set, MouseButton, MouseButtonSet, + decode_key_location, decode_mouse_button_set, encode_key_location, encode_mouse_button_set, + MouseButton, MouseButtonSet, }; use euclid::UnknownUnit; - use keyboard_types::Modifiers; + use keyboard_types::Key::Alt; + use keyboard_types::{Code, Key, Location, Modifiers}; use std::collections::HashMap; + use std::convert::TryInto; + use std::str::FromStr; use super::*; macro_rules! event_directory { @@ -425,69 +429,128 @@ pub mod on { #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] pub struct KeyboardData { + #[deprecated( + since = "0.3.0", + note = "This may not work in all environments. Use key() instead." + )] pub char_code: u32, /// Identify which "key" was entered. - /// - /// This is the best method to use for all languages. They key gets mapped to a String sequence which you can match on. - /// The key isn't an enum because there are just so many context-dependent keys. - /// - /// A full list on which keys to use is available at: - /// - /// - /// # Example - /// - /// ```rust, ignore - /// match event.key().as_str() { - /// "Esc" | "Escape" => {} - /// "ArrowDown" => {} - /// "ArrowLeft" => {} - /// _ => {} - /// } - /// ``` - /// + #[deprecated(since = "0.3.0", note = "use key() instead")] pub key: String, /// 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. - /// - /// - /// ## Example - /// - /// ```rust, ignore - /// use dioxus::KeyCode; - /// match event.key_code() { - /// KeyCode::Escape => {} - /// KeyCode::LeftArrow => {} - /// KeyCode::RightArrow => {} - /// _ => {} - /// } - /// ``` - /// + #[deprecated( + since = "0.3.0", + note = "This may not work in all environments. Use code() instead." + )] pub key_code: KeyCode, + /// the physical key on the keyboard + code: Code, + /// Indicate if the `alt` modifier key was pressed during this keyboard event + #[deprecated(since = "0.3.0", note = "use modifiers() instead")] pub alt_key: bool, /// Indicate if the `ctrl` modifier key was pressed during this keyboard event + #[deprecated(since = "0.3.0", note = "use modifiers() instead")] pub ctrl_key: bool, /// Indicate if the `meta` modifier key was pressed during this keyboard event + #[deprecated(since = "0.3.0", note = "use modifiers() instead")] pub meta_key: bool, /// Indicate if the `shift` modifier key was pressed during this keyboard event + #[deprecated(since = "0.3.0", note = "use modifiers() instead")] pub shift_key: bool, - pub locale: String, - + #[deprecated(since = "0.3.0", note = "use location() instead")] pub location: usize, + #[deprecated(since = "0.3.0", note = "use is_auto_repeating() instead")] pub repeat: bool, + #[deprecated(since = "0.3.0", note = "use code() or key() instead")] pub which: usize, - // get_modifier_state: bool, + } + + impl KeyboardData { + pub fn new( + key: Key, + code: Code, + location: Location, + is_auto_repeating: bool, + modifiers: Modifiers, + ) -> Self { + #[allow(deprecated)] + KeyboardData { + char_code: key.legacy_charcode(), + key: key.to_string(), + key_code: KeyCode::from_raw_code( + key.legacy_keycode() + .try_into() + .expect("could not convert keycode to u8"), + ), + code, + alt_key: modifiers.contains(Modifiers::ALT), + ctrl_key: modifiers.contains(Modifiers::CONTROL), + meta_key: modifiers.contains(Modifiers::META), + shift_key: modifiers.contains(Modifiers::SHIFT), + location: encode_key_location(location), + repeat: is_auto_repeating, + which: key + .legacy_charcode() + .try_into() + .expect("could not convert charcode to usize"), + } + } + + /// The value of the key pressed by the user, taking into consideration the state of modifier keys such as Shift as well as the keyboard locale and layout. + pub fn key(&self) -> Key { + #[allow(deprecated)] + FromStr::from_str(&self.key).expect("could not parse") + } + + /// A physical key on the keyboard (as opposed to the character generated by pressing the key). In other words, this property returns a value that isn't altered by keyboard layout or the state of the modifier keys. + pub fn code(&self) -> Code { + self.code + } + + /// The set of modifier keys which were pressed when the event occurred + pub fn modifiers(&self) -> Modifiers { + let mut modifiers = Modifiers::empty(); + + #[allow(deprecated)] + { + if self.alt_key { + modifiers.insert(Modifiers::ALT); + } + if self.ctrl_key { + modifiers.insert(Modifiers::CONTROL); + } + if self.meta_key { + modifiers.insert(Modifiers::META); + } + if self.shift_key { + modifiers.insert(Modifiers::SHIFT); + } + } + + modifiers + } + + /// The location of the key on the keyboard or other input device. + pub fn location(&self) -> Location { + #[allow(deprecated)] + decode_key_location(self.location) + } + + /// `true` iff the key is being held down such that it is automatically repeating. + pub fn is_auto_repeating(&self) -> bool { + #[allow(deprecated)] + self.repeat + } } pub type FocusEvent = UiEvent; @@ -845,6 +908,7 @@ pub mod on { )] #[derive(Clone, Copy, Debug, PartialEq)] #[repr(u8)] +#[deprecated(since = "0.3.0", note = "use keyboard_types::Code instead")] pub enum KeyCode { // That key has no keycode, = 0 // break, = 3 diff --git a/packages/html/src/input_data.rs b/packages/html/src/input_data.rs index b5bc73a44..6b994d5f0 100644 --- a/packages/html/src/input_data.rs +++ b/packages/html/src/input_data.rs @@ -3,6 +3,7 @@ use enumset::{EnumSet, EnumSetType}; /// A re-export of keyboard_types pub use keyboard_types; +use keyboard_types::Location; /// A mouse button type (such as Primary/Secondary) // note: EnumSetType also derives Copy and Clone for some reason @@ -118,3 +119,25 @@ pub fn encode_mouse_button_set(set: MouseButtonSet) -> u16 { code } + +pub fn decode_key_location(code: usize) -> Location { + match code { + 0 => Location::Standard, + 1 => Location::Left, + 2 => Location::Right, + 3 => Location::Numpad, + // keyboard_types doesn't yet support mobile/joystick locations + 4 | 5 => Location::Standard, + // unknown location; Standard seems better than panicking + _ => Location::Standard, + } +} + +pub fn encode_key_location(location: Location) -> usize { + match location { + Location::Standard => 0, + Location::Left => 1, + Location::Right => 2, + Location::Numpad => 3, + } +} diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index bb32c1375..b8b076730 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -8,10 +8,11 @@ use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D}; use dioxus_html::geometry::{ ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint, WheelDelta, }; -use dioxus_html::input_data::keyboard_types::Modifiers; +use dioxus_html::input_data::keyboard_types::{Code, Key, Location, 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::str::FromStr; use std::{ any::Any, cell::RefCell, @@ -663,142 +664,221 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> { } fn translate_key_event(event: crossterm::event::KeyEvent) -> Option { - let (code, key_str); - if let TermKeyCode::Char(c) = event.code { - code = match c { + let key = key_from_crossterm_key_code(event.code); + // crossterm does not provide code. we make a guess as to which key might have been pressed + // this is probably garbage if the user has a custom keyboard layout + let code = guess_code_from_crossterm_key_code(event.code)?; + let modifiers = modifiers_from_crossterm_modifiers(event.modifiers); + + Some(EventData::Keyboard(KeyboardData::new( + key, + code, + Location::Standard, + false, + modifiers, + ))) +} + +/// The crossterm key_code nicely represents the meaning of the key and we can mostly convert it without any issues +/// +/// Exceptions: +/// BackTab and Null are converted to Key::Unidentified +fn key_from_crossterm_key_code(key_code: TermKeyCode) -> Key { + match key_code { + TermKeyCode::Backspace => Key::Backspace, + TermKeyCode::Enter => Key::Enter, + TermKeyCode::Left => Key::ArrowLeft, + TermKeyCode::Right => Key::ArrowRight, + TermKeyCode::Up => Key::ArrowUp, + TermKeyCode::Down => Key::ArrowDown, + TermKeyCode::Home => Key::Home, + TermKeyCode::End => Key::End, + TermKeyCode::PageUp => Key::PageUp, + TermKeyCode::PageDown => Key::PageDown, + TermKeyCode::Tab => Key::Tab, + // ? no corresponding Key + TermKeyCode::BackTab => Key::Unidentified, + TermKeyCode::Delete => Key::Delete, + TermKeyCode::Insert => Key::Insert, + TermKeyCode::F(1) => Key::F1, + TermKeyCode::F(2) => Key::F2, + TermKeyCode::F(3) => Key::F3, + TermKeyCode::F(4) => Key::F4, + TermKeyCode::F(5) => Key::F5, + TermKeyCode::F(6) => Key::F6, + TermKeyCode::F(7) => Key::F7, + TermKeyCode::F(8) => Key::F8, + TermKeyCode::F(9) => Key::F9, + TermKeyCode::F(10) => Key::F10, + TermKeyCode::F(11) => Key::F11, + TermKeyCode::F(12) => Key::F12, + TermKeyCode::F(13) => Key::F13, + TermKeyCode::F(14) => Key::F14, + TermKeyCode::F(15) => Key::F15, + TermKeyCode::F(16) => Key::F16, + TermKeyCode::F(17) => Key::F17, + TermKeyCode::F(18) => Key::F18, + TermKeyCode::F(19) => Key::F19, + TermKeyCode::F(20) => Key::F20, + TermKeyCode::F(21) => Key::F21, + TermKeyCode::F(22) => Key::F22, + TermKeyCode::F(23) => Key::F23, + TermKeyCode::F(24) => Key::F24, + TermKeyCode::F(other) => { + panic!("Unexpected function key: {other:?}") + } + TermKeyCode::Char(c) => Key::Character(c.to_string()), + TermKeyCode::Null => Key::Unidentified, + TermKeyCode::Esc => Key::Escape, + } +} + +// Crossterm does not provide a way to get the `code` (physical key on keyboard) +// So we make a guess based on their `key_code`, but this is probably going to break on anything other than a very standard european keyboard +// It may look fine, but it's a horrible hack. +fn guess_code_from_crossterm_key_code(key_code: TermKeyCode) -> Option { + let code = match key_code { + TermKeyCode::Backspace => Code::Backspace, + TermKeyCode::Enter => Code::Enter, + TermKeyCode::Left => Code::ArrowLeft, + TermKeyCode::Right => Code::ArrowRight, + TermKeyCode::Up => Code::ArrowUp, + TermKeyCode::Down => Code::ArrowDown, + TermKeyCode::Home => Code::Home, + TermKeyCode::End => Code::End, + TermKeyCode::PageUp => Code::PageUp, + TermKeyCode::PageDown => Code::PageDown, + TermKeyCode::Tab => Code::Tab, + // ? no corresponding Code + TermKeyCode::BackTab => Code::Tab, + TermKeyCode::Delete => Code::Delete, + TermKeyCode::Insert => Code::Insert, + TermKeyCode::F(1) => Code::F1, + TermKeyCode::F(2) => Code::F2, + TermKeyCode::F(3) => Code::F3, + TermKeyCode::F(4) => Code::F4, + TermKeyCode::F(5) => Code::F5, + TermKeyCode::F(6) => Code::F6, + TermKeyCode::F(7) => Code::F7, + TermKeyCode::F(8) => Code::F8, + TermKeyCode::F(9) => Code::F9, + TermKeyCode::F(10) => Code::F10, + TermKeyCode::F(11) => Code::F11, + TermKeyCode::F(12) => Code::F12, + TermKeyCode::F(13) => Code::F13, + TermKeyCode::F(14) => Code::F14, + TermKeyCode::F(15) => Code::F15, + TermKeyCode::F(16) => Code::F16, + TermKeyCode::F(17) => Code::F17, + TermKeyCode::F(18) => Code::F18, + TermKeyCode::F(19) => Code::F19, + TermKeyCode::F(20) => Code::F20, + TermKeyCode::F(21) => Code::F21, + TermKeyCode::F(22) => Code::F22, + TermKeyCode::F(23) => Code::F23, + TermKeyCode::F(24) => Code::F24, + TermKeyCode::F(other) => { + panic!("Unexpected function key: {other:?}") + } + // this is a horrible way for crossterm to represent keys but we have to deal with it + TermKeyCode::Char(c) => match c { 'A'..='Z' | 'a'..='z' => match c.to_ascii_uppercase() { - 'A' => KeyCode::A, - 'B' => KeyCode::B, - 'C' => KeyCode::C, - 'D' => KeyCode::D, - 'E' => KeyCode::E, - 'F' => KeyCode::F, - 'G' => KeyCode::G, - 'H' => KeyCode::H, - 'I' => KeyCode::I, - 'J' => KeyCode::J, - 'K' => KeyCode::K, - 'L' => KeyCode::L, - 'M' => KeyCode::M, - 'N' => KeyCode::N, - 'O' => KeyCode::O, - 'P' => KeyCode::P, - 'Q' => KeyCode::Q, - 'R' => KeyCode::R, - 'S' => KeyCode::S, - 'T' => KeyCode::T, - 'U' => KeyCode::U, - 'V' => KeyCode::V, - 'W' => KeyCode::W, - 'X' => KeyCode::X, - 'Y' => KeyCode::Y, - 'Z' => KeyCode::Z, - _ => return None, + 'A' => Code::KeyA, + 'B' => Code::KeyB, + 'C' => Code::KeyC, + 'D' => Code::KeyD, + 'E' => Code::KeyE, + 'F' => Code::KeyF, + 'G' => Code::KeyG, + 'H' => Code::KeyH, + 'I' => Code::KeyI, + 'J' => Code::KeyJ, + 'K' => Code::KeyK, + 'L' => Code::KeyL, + 'M' => Code::KeyM, + 'N' => Code::KeyN, + 'O' => Code::KeyO, + 'P' => Code::KeyP, + 'Q' => Code::KeyQ, + 'R' => Code::KeyR, + 'S' => Code::KeyS, + 'T' => Code::KeyT, + 'U' => Code::KeyU, + 'V' => Code::KeyV, + 'W' => Code::KeyW, + 'X' => Code::KeyX, + 'Y' => Code::KeyY, + 'Z' => Code::KeyZ, + _ => unreachable!(), }, - ' ' => KeyCode::Space, - '[' => KeyCode::OpenBracket, - '{' => KeyCode::OpenBracket, - ']' => KeyCode::CloseBraket, - '}' => KeyCode::CloseBraket, - ';' => KeyCode::Semicolon, - ':' => KeyCode::Semicolon, - ',' => KeyCode::Comma, - '<' => KeyCode::Comma, - '.' => KeyCode::Period, - '>' => KeyCode::Period, - '1' => KeyCode::Num1, - '2' => KeyCode::Num2, - '3' => KeyCode::Num3, - '4' => KeyCode::Num4, - '5' => KeyCode::Num5, - '6' => KeyCode::Num6, - '7' => KeyCode::Num7, - '8' => KeyCode::Num8, - '9' => KeyCode::Num9, - '0' => KeyCode::Num0, - '!' => KeyCode::Num1, - '@' => KeyCode::Num2, - '#' => KeyCode::Num3, - '$' => KeyCode::Num4, - '%' => KeyCode::Num5, - '^' => KeyCode::Num6, - '&' => KeyCode::Num7, - '*' => KeyCode::Num8, - '(' => KeyCode::Num9, - ')' => KeyCode::Num0, + ' ' => Code::Space, + '[' | '{' => Code::BracketLeft, + ']' | '}' => Code::BracketRight, + ';' => Code::Semicolon, + ':' => Code::Semicolon, + ',' => Code::Comma, + '<' => Code::Comma, + '.' => Code::Period, + '>' => Code::Period, + '1' => Code::Digit1, + '2' => Code::Digit2, + '3' => Code::Digit3, + '4' => Code::Digit4, + '5' => Code::Digit5, + '6' => Code::Digit6, + '7' => Code::Digit7, + '8' => Code::Digit8, + '9' => Code::Digit9, + '0' => Code::Digit0, + '!' => Code::Digit1, + '@' => Code::Digit2, + '#' => Code::Digit3, + '$' => Code::Digit4, + '%' => Code::Digit5, + '^' => Code::Digit6, + '&' => Code::Digit7, + '*' => Code::Digit8, + '(' => Code::Digit9, + ')' => Code::Digit0, // numpad charicter are ambiguous to tui - // '*' => KeyCode::Multiply, - // '/' => KeyCode::Divide, - // '-' => KeyCode::Subtract, - // '+' => KeyCode::Add, - '+' => KeyCode::EqualSign, - '-' => KeyCode::Dash, - '_' => KeyCode::Dash, - '\'' => KeyCode::SingleQuote, - '"' => KeyCode::SingleQuote, - '\\' => KeyCode::BackSlash, - '|' => KeyCode::BackSlash, - '/' => KeyCode::ForwardSlash, - '?' => KeyCode::ForwardSlash, - '=' => KeyCode::EqualSign, - '`' => KeyCode::GraveAccent, - '~' => KeyCode::GraveAccent, + // '*' => Code::Multiply, + // '/' => Code::Divide, + // '-' => Code::Subtract, + // '+' => Code::Add, + '+' => Code::Equal, + '-' | '_' => Code::Minus, + '\'' => Code::Quote, + '"' => Code::Quote, + '\\' => Code::Backslash, + '|' => Code::Backslash, + '/' => Code::Slash, + '?' => Code::Slash, + '=' => Code::Equal, + '`' => Code::Backquote, + '~' => Code::Backquote, _ => return None, - }; - key_str = c.to_string(); - } else { - code = match event.code { - TermKeyCode::Esc => KeyCode::Escape, - TermKeyCode::Backspace => KeyCode::Backspace, - TermKeyCode::Enter => KeyCode::Enter, - TermKeyCode::Left => KeyCode::LeftArrow, - TermKeyCode::Right => KeyCode::RightArrow, - TermKeyCode::Up => KeyCode::UpArrow, - TermKeyCode::Down => KeyCode::DownArrow, - TermKeyCode::Home => KeyCode::Home, - TermKeyCode::End => KeyCode::End, - TermKeyCode::PageUp => KeyCode::PageUp, - TermKeyCode::PageDown => KeyCode::PageDown, - TermKeyCode::Tab => KeyCode::Tab, - TermKeyCode::Delete => KeyCode::Delete, - TermKeyCode::Insert => KeyCode::Insert, - TermKeyCode::F(fn_num) => match fn_num { - 1 => KeyCode::F1, - 2 => KeyCode::F2, - 3 => KeyCode::F3, - 4 => KeyCode::F4, - 5 => KeyCode::F5, - 6 => KeyCode::F6, - 7 => KeyCode::F7, - 8 => KeyCode::F8, - 9 => KeyCode::F9, - 10 => KeyCode::F10, - 11 => KeyCode::F11, - 12 => KeyCode::F12, - _ => return None, - }, - TermKeyCode::BackTab => return None, - TermKeyCode::Null => return None, - _ => return None, - }; - key_str = if let KeyCode::BackSlash = code { - "\\".to_string() - } else { - format!("{code:?}") - } + }, + TermKeyCode::Null => return None, + TermKeyCode::Esc => Code::Escape, }; - // from https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent - Some(EventData::Keyboard(KeyboardData { - char_code: code.raw_code(), - key: key_str, - key_code: code, - alt_key: event.modifiers.contains(KeyModifiers::ALT), - ctrl_key: event.modifiers.contains(KeyModifiers::CONTROL), - meta_key: false, - shift_key: event.modifiers.contains(KeyModifiers::SHIFT), - locale: Default::default(), - location: 0x00, - repeat: Default::default(), - which: Default::default(), - })) + + Some(code) +} + +fn modifiers_from_crossterm_modifiers(src: KeyModifiers) -> Modifiers { + let mut modifiers = Modifiers::empty(); + + if src.contains(KeyModifiers::SHIFT) { + modifiers.insert(Modifiers::SHIFT); + } + + if src.contains(KeyModifiers::ALT) { + modifiers.insert(Modifiers::ALT); + } + + if src.contains(KeyModifiers::CONTROL) { + modifiers.insert(Modifiers::CONTROL); + } + + modifiers } From aaf9d4665f8285fbb4cdc5115f3547736ec7a02b Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Thu, 12 May 2022 14:36:52 +0300 Subject: [PATCH 06/30] Update interpreter and example --- examples/calculator.rs | 50 ++++++++++++++----------- examples/tui_keys.rs | 27 ++++++++----- packages/html/src/events.rs | 2 - packages/interpreter/src/interpreter.js | 2 + packages/tui/src/hooks.rs | 17 +++++---- 5 files changed, 57 insertions(+), 41 deletions(-) diff --git a/examples/calculator.rs b/examples/calculator.rs index f63b8a754..75da8f1d3 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -5,6 +5,7 @@ This calculator version uses React-style state management. All state is held as use dioxus::events::*; use dioxus::prelude::*; +use dioxus_html::input_data::keyboard_types::Key; fn main() { use dioxus::desktop::tao::dpi::LogicalSize; @@ -29,33 +30,38 @@ fn app(cx: Scope) -> Element { let input_operator = move |key: &str| val.make_mut().push_str(key); + let handle_key_down_event = move |evt: KeyboardEvent| match evt.key() { + Key::Backspace => { + if !val.len() != 0 { + val.make_mut().pop(); + } + } + Key::Character(character) => match character.as_str() { + "+" => input_operator("+"), + "-" => input_operator("-"), + "/" => input_operator("/"), + "*" => input_operator("*"), + "0" => input_digit(0), + "1" => input_digit(1), + "2" => input_digit(2), + "3" => input_digit(3), + "4" => input_digit(4), + "5" => input_digit(5), + "6" => input_digit(6), + "7" => input_digit(7), + "8" => input_digit(8), + "9" => input_digit(9), + _ => {} + }, + _ => {} + }; + cx.render(rsx!( style { [include_str!("./assets/calculator.css")] } div { id: "wrapper", div { class: "app", div { class: "calculator", - onkeydown: move |evt| match evt.key_code { - KeyCode::Add => input_operator("+"), - KeyCode::Subtract => input_operator("-"), - KeyCode::Divide => input_operator("/"), - KeyCode::Multiply => input_operator("*"), - KeyCode::Num0 => input_digit(0), - KeyCode::Num1 => input_digit(1), - KeyCode::Num2 => input_digit(2), - KeyCode::Num3 => input_digit(3), - KeyCode::Num4 => input_digit(4), - KeyCode::Num5 => input_digit(5), - KeyCode::Num6 => input_digit(6), - KeyCode::Num7 => input_digit(7), - KeyCode::Num8 => input_digit(8), - KeyCode::Num9 => input_digit(9), - KeyCode::Backspace => { - if !val.len() != 0 { - val.make_mut().pop(); - } - } - _ => {} - }, + onkeydown: handle_key_down_event, div { class: "calculator-display", [val.to_string()] } div { class: "calculator-keypad", div { class: "input-keys", diff --git a/examples/tui_keys.rs b/examples/tui_keys.rs index b4a7ce5d1..f53818234 100644 --- a/examples/tui_keys.rs +++ b/examples/tui_keys.rs @@ -1,6 +1,7 @@ use dioxus::events::WheelEvent; use dioxus::prelude::*; use dioxus_html::geometry::ScreenPoint; +use dioxus_html::input_data::keyboard_types::Code; use dioxus_html::input_data::MouseButtonSet; use dioxus_html::on::{KeyboardEvent, MouseEvent}; use dioxus_html::KeyCode; @@ -16,6 +17,21 @@ fn app(cx: Scope) -> Element { let buttons = use_state(&cx, || MouseButtonSet::empty()); let mouse_clicked = use_state(&cx, || false); + let key_down_handler = move |evt: KeyboardEvent| { + match evt.data.code() { + Code::ArrowLeft => count.set(count + 1), + Code::ArrowRight => count.set(count - 1), + Code::ArrowUp => count.set(count + 10), + Code::ArrowDown => count.set(count - 10), + _ => {} + } + key.set(format!( + "{:?} repeating: {:?}", + evt.key(), + evt.is_auto_repeating() + )); + }; + cx.render(rsx! { div { width: "100%", @@ -24,16 +40,7 @@ fn app(cx: Scope) -> Element { justify_content: "center", align_items: "center", flex_direction: "column", - onkeydown: move |evt: KeyboardEvent| { - match evt.data.key_code { - KeyCode::LeftArrow => count.set(count + 1), - KeyCode::RightArrow => count.set(count - 1), - KeyCode::UpArrow => count.set(count + 10), - KeyCode::DownArrow => count.set(count - 10), - _ => {}, - } - key.set(format!("{:?} repeating: {:?}", evt.key, evt.repeat)); - }, + onkeydown: key_down_handler, onwheel: move |evt: WheelEvent| { count.set(count + evt.data.delta().strip_units().y as i64); }, diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 373e46414..114317428 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -14,7 +14,6 @@ pub mod on { MouseButton, MouseButtonSet, }; use euclid::UnknownUnit; - use keyboard_types::Key::Alt; use keyboard_types::{Code, Key, Location, Modifiers}; use std::collections::HashMap; use std::convert::TryInto; @@ -908,7 +907,6 @@ pub mod on { )] #[derive(Clone, Copy, Debug, PartialEq)] #[repr(u8)] -#[deprecated(since = "0.3.0", note = "use keyboard_types::Code instead")] pub enum KeyCode { // That key has no keycode, = 0 // break, = 3 diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index a908ad74c..c1b70ff2a 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -334,6 +334,7 @@ export function serialize_event(event) { location, repeat, which, + code, } = event; return { char_code: charCode, @@ -347,6 +348,7 @@ export function serialize_event(event) { repeat: repeat, which: which, locale: "locale", + code, }; } case "focus": diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index b8b076730..877e0b524 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -11,8 +11,7 @@ use dioxus_html::geometry::{ use dioxus_html::input_data::keyboard_types::{Code, Key, Location, 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::str::FromStr; +use dioxus_html::on::*; use std::{ any::Any, cell::RefCell, @@ -140,14 +139,18 @@ impl InnerInputState { EventData::Wheel(ref w) => self.wheel = Some(w.clone()), EventData::Screen(ref s) => self.screen = Some(*s), EventData::Keyboard(ref mut k) => { - let repeat = self + let is_repeating = self .last_key_pressed .as_ref() - .filter(|k2| k2.0.key == k.key && k2.1.elapsed() < MAX_REPEAT_TIME) + // heuristic for guessing which presses are auto-repeating. not necessarily accurate + .filter(|k2| k2.0.key() == k.key() && k2.1.elapsed() < MAX_REPEAT_TIME) .is_some(); - k.repeat = repeat; - let new = k.clone(); - self.last_key_pressed = Some((new, Instant::now())); + if is_repeating { + let new = k.clone(); + self.last_key_pressed = Some((new, Instant::now())); + + *k = KeyboardData::new(k.key(), k.code(), k.location(), true, k.modifiers()); + } } } } From 45980f9a1e92cf47bbd489ad412a3d50d7da9047 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Thu, 12 May 2022 15:00:43 +0300 Subject: [PATCH 07/30] Update examples to avoid deprecated API --- .../src/advanced-guides/custom-renderer.md | 1 - docs/reference/src/guide/custom_renderer.md | 1 - examples/pattern_model.rs | 37 +++++++++++-------- examples/todomvc.rs | 7 ++-- examples/tui_keys.rs | 1 - packages/html/src/web_sys_bind/events.rs | 1 - packages/interpreter/src/interpreter.js | 1 - packages/liveview/src/interpreter.js | 1 - packages/web/src/olddom.rs | 1 - 9 files changed, 25 insertions(+), 26 deletions(-) diff --git a/docs/guide/src/advanced-guides/custom-renderer.md b/docs/guide/src/advanced-guides/custom-renderer.md index d2aa40bc9..a8bec614c 100644 --- a/docs/guide/src/advanced-guides/custom-renderer.md +++ b/docs/guide/src/advanced-guides/custom-renderer.md @@ -204,7 +204,6 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent { fn ctrl_key(&self) -> bool { self.0.ctrl_key() } fn key(&self) -> String { self.0.key() } fn key_code(&self) -> usize { self.0.key_code() } - fn locale(&self) -> String { self.0.locale() } fn location(&self) -> usize { self.0.location() } fn meta_key(&self) -> bool { self.0.meta_key() } fn repeat(&self) -> bool { self.0.repeat() } diff --git a/docs/reference/src/guide/custom_renderer.md b/docs/reference/src/guide/custom_renderer.md index 9a73dd7f7..35187e2e3 100644 --- a/docs/reference/src/guide/custom_renderer.md +++ b/docs/reference/src/guide/custom_renderer.md @@ -206,7 +206,6 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent { ctrl_key: event.ctrl_key(), meta_key: event.meta_key(), shift_key: event.shift_key(), - locale: "".to_string(), location: event.location(), repeat: event.repeat(), which: event.which(), diff --git a/examples/pattern_model.rs b/examples/pattern_model.rs index a7b821750..c88bc78fb 100644 --- a/examples/pattern_model.rs +++ b/examples/pattern_model.rs @@ -20,6 +20,7 @@ use dioxus::desktop::wry::application::dpi::LogicalSize; use dioxus::events::*; use dioxus::prelude::*; +use dioxus_html::input_data::keyboard_types::Key; fn main() { dioxus::desktop::launch_cfg(app, |cfg| { @@ -212,22 +213,26 @@ impl Calculator { self.waiting_for_operand = true; } fn handle_keydown(&mut self, evt: KeyboardEvent) { - match evt.key_code { - KeyCode::Backspace => self.backspace(), - KeyCode::Num0 => self.input_digit(0), - KeyCode::Num1 => self.input_digit(1), - KeyCode::Num2 => self.input_digit(2), - KeyCode::Num3 => self.input_digit(3), - KeyCode::Num4 => self.input_digit(4), - KeyCode::Num5 => self.input_digit(5), - KeyCode::Num6 => self.input_digit(6), - KeyCode::Num7 => self.input_digit(7), - KeyCode::Num8 => self.input_digit(8), - KeyCode::Num9 => self.input_digit(9), - KeyCode::Add => self.operator = Some(Operator::Add), - KeyCode::Subtract => self.operator = Some(Operator::Sub), - KeyCode::Divide => self.operator = Some(Operator::Div), - KeyCode::Multiply => self.operator = Some(Operator::Mul), + match evt.key() { + Key::Backspace => self.backspace(), + Key::Character(c) => match c.as_str() { + "0" => self.input_digit(0), + "1" => self.input_digit(1), + "2" => self.input_digit(2), + "3" => self.input_digit(3), + "4" => self.input_digit(4), + "5" => self.input_digit(5), + "6" => self.input_digit(6), + "7" => self.input_digit(7), + "8" => self.input_digit(8), + "9" => self.input_digit(9), + "+" => self.operator = Some(Operator::Add), + "-" => self.operator = Some(Operator::Sub), + "/" => self.operator = Some(Operator::Div), + "*" => self.operator = Some(Operator::Mul), + _ => {} + }, + _ => {} } } diff --git a/examples/todomvc.rs b/examples/todomvc.rs index 99cf67a69..cf9a49410 100644 --- a/examples/todomvc.rs +++ b/examples/todomvc.rs @@ -1,4 +1,5 @@ use dioxus::prelude::*; +use dioxus_elements::input_data::keyboard_types::Key; fn main() { dioxus::desktop::launch(app); @@ -56,7 +57,7 @@ pub fn app(cx: Scope<()>) -> Element { autofocus: "true", oninput: move |evt| draft.set(evt.value.clone()), onkeydown: move |evt| { - if evt.key == "Enter" && !draft.is_empty() { + if evt.key() == Key::Enter && !draft.is_empty() { todos.make_mut().insert( **todo_id, TodoItem { @@ -148,8 +149,8 @@ pub fn todo_entry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element { autofocus: "true", onfocusout: move |_| is_editing.set(false), onkeydown: move |evt| { - match evt.key.as_str() { - "Enter" | "Escape" | "Tab" => is_editing.set(false), + match evt.key() { + Key::Enter | Key::Escape | Key::Tab => is_editing.set(false), _ => {} } }, diff --git a/examples/tui_keys.rs b/examples/tui_keys.rs index f53818234..2417a3077 100644 --- a/examples/tui_keys.rs +++ b/examples/tui_keys.rs @@ -4,7 +4,6 @@ use dioxus_html::geometry::ScreenPoint; use dioxus_html::input_data::keyboard_types::Code; use dioxus_html::input_data::MouseButtonSet; use dioxus_html::on::{KeyboardEvent, MouseEvent}; -use dioxus_html::KeyCode; fn main() { dioxus::tui::launch(app); diff --git a/packages/html/src/web_sys_bind/events.rs b/packages/html/src/web_sys_bind/events.rs index fbbb34d9e..4a9f086b3 100644 --- a/packages/html/src/web_sys_bind/events.rs +++ b/packages/html/src/web_sys_bind/events.rs @@ -62,7 +62,6 @@ impl From<&KeyboardEvent> for KeyboardData { key: e.key(), key_code: KeyCode::from_raw_code(e.key_code() as u8), ctrl_key: e.ctrl_key(), - locale: "not implemented".to_string(), location: e.location() as usize, meta_key: e.meta_key(), repeat: e.repeat(), diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index c1b70ff2a..f1060266c 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -347,7 +347,6 @@ export function serialize_event(event) { location: location, repeat: repeat, which: which, - locale: "locale", code, }; } diff --git a/packages/liveview/src/interpreter.js b/packages/liveview/src/interpreter.js index 383f33a5c..f1eec3fde 100644 --- a/packages/liveview/src/interpreter.js +++ b/packages/liveview/src/interpreter.js @@ -358,7 +358,6 @@ function serialize_event(event) { location: location, repeat: repeat, which: which, - locale: "locale", }; } case "focus": diff --git a/packages/web/src/olddom.rs b/packages/web/src/olddom.rs index 8654ced90..d6035d5ab 100644 --- a/packages/web/src/olddom.rs +++ b/packages/web/src/olddom.rs @@ -521,7 +521,6 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc Date: Thu, 12 May 2022 18:24:13 +0300 Subject: [PATCH 08/30] Update web_sys to avoid deprecated API --- packages/html/src/web_sys_bind/events.rs | 43 ++++++++++++++++-------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/packages/html/src/web_sys_bind/events.rs b/packages/html/src/web_sys_bind/events.rs index 4a9f086b3..e7bdcf5d7 100644 --- a/packages/html/src/web_sys_bind/events.rs +++ b/packages/html/src/web_sys_bind/events.rs @@ -1,11 +1,12 @@ use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint}; -use crate::input_data::{decode_mouse_button_set, MouseButton}; +use crate::input_data::{decode_key_location, decode_mouse_button_set, MouseButton}; use crate::on::{ AnimationData, CompositionData, KeyboardData, MouseData, PointerData, TouchData, TransitionData, WheelData, }; -use crate::KeyCode; -use keyboard_types::Modifiers; +use keyboard_types::{Code, Key, Modifiers}; +use std::convert::TryInto; +use std::str::FromStr; use wasm_bindgen::JsCast; use web_sys::{ AnimationEvent, CompositionEvent, Event, KeyboardEvent, MouseEvent, PointerEvent, TouchEvent, @@ -56,18 +57,32 @@ impl From<&CompositionEvent> for CompositionData { impl From<&KeyboardEvent> for KeyboardData { fn from(e: &KeyboardEvent) -> Self { - Self { - alt_key: e.alt_key(), - char_code: e.char_code(), - key: e.key(), - key_code: KeyCode::from_raw_code(e.key_code() as u8), - ctrl_key: e.ctrl_key(), - location: e.location() as usize, - meta_key: e.meta_key(), - repeat: e.repeat(), - shift_key: e.shift_key(), - which: e.which() as usize, + 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); + } + + Self::new( + Key::from_str(&e.key()).expect("could not parse key"), + Code::from_str(&e.code()).expect("could not parse code"), + decode_key_location( + e.location() + .try_into() + .expect("could not convert location to u32"), + ), + e.repeat(), + modifiers, + ) } } From 6932924f57e74b75658d04f152424268365b7d9e Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Tue, 28 Jun 2022 21:17:46 +0300 Subject: [PATCH 09/30] Comment on crossterm key event conversion --- packages/tui/src/hooks.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 1443719a0..d91667d97 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -770,7 +770,7 @@ fn translate_key_event(event: crossterm::event::KeyEvent) -> Option { /// The crossterm key_code nicely represents the meaning of the key and we can mostly convert it without any issues /// /// Exceptions: -/// BackTab and Null are converted to Key::Unidentified +/// BackTab is converted to Key::Tab, and Null is converted to Key::Unidentified fn key_from_crossterm_key_code(key_code: TermKeyCode) -> Key { match key_code { TermKeyCode::Backspace => Key::Backspace, @@ -785,7 +785,7 @@ fn key_from_crossterm_key_code(key_code: TermKeyCode) -> Key { TermKeyCode::PageDown => Key::PageDown, TermKeyCode::Tab => Key::Tab, // ? no corresponding Key - TermKeyCode::BackTab => Key::Unidentified, + TermKeyCode::BackTab => Key::Tab, TermKeyCode::Delete => Key::Delete, TermKeyCode::Insert => Key::Insert, TermKeyCode::F(1) => Key::F1, @@ -823,7 +823,7 @@ fn key_from_crossterm_key_code(key_code: TermKeyCode) -> Key { // Crossterm does not provide a way to get the `code` (physical key on keyboard) // So we make a guess based on their `key_code`, but this is probably going to break on anything other than a very standard european keyboard -// It may look fine, but it's a horrible hack. +// It may look fine, but it's a horrible hack. But there's nothing better we can do. fn guess_code_from_crossterm_key_code(key_code: TermKeyCode) -> Option { let code = match key_code { TermKeyCode::Backspace => Code::Backspace, @@ -837,7 +837,7 @@ fn guess_code_from_crossterm_key_code(key_code: TermKeyCode) -> Option { TermKeyCode::PageUp => Code::PageUp, TermKeyCode::PageDown => Code::PageDown, TermKeyCode::Tab => Code::Tab, - // ? no corresponding Code + // ? Apparently you get BackTab by pressing Tab TermKeyCode::BackTab => Code::Tab, TermKeyCode::Delete => Code::Delete, TermKeyCode::Insert => Code::Insert, @@ -897,7 +897,7 @@ fn guess_code_from_crossterm_key_code(key_code: TermKeyCode) -> Option { 'X' => Code::KeyX, 'Y' => Code::KeyY, 'Z' => Code::KeyZ, - _ => unreachable!(), + _ => unreachable!("Exhaustively checked all characters in range A..Z"), }, ' ' => Code::Space, '[' | '{' => Code::BracketLeft, @@ -928,7 +928,8 @@ fn guess_code_from_crossterm_key_code(key_code: TermKeyCode) -> Option { '*' => Code::Digit8, '(' => Code::Digit9, ')' => Code::Digit0, - // numpad charicter are ambiguous to tui + // numpad characters are ambiguous; we don't know which key was really pressed + // it could be also: // '*' => Code::Multiply, // '/' => Code::Divide, // '-' => Code::Subtract, From bfcfe2ca5f0e176b0887987d5d424a759037781f Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Tue, 28 Jun 2022 23:10:44 +0300 Subject: [PATCH 10/30] Remove use of deprecated functions --- packages/tui/src/hooks.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index d91667d97..bb5f81dfb 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -11,7 +11,7 @@ use dioxus_html::geometry::{ use dioxus_html::input_data::keyboard_types::{Code, Key, Location, Modifiers}; use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons; use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet}; -use dioxus_html::{event_bubbles, on::*, KeyCode}; +use dioxus_html::{event_bubbles, on::*}; use std::{ any::Any, cell::{RefCell, RefMut}, @@ -172,8 +172,10 @@ impl InnerInputState { let old_focus = self.focus_state.last_focused_id; evts.retain(|e| match &e.1 { - EventData::Keyboard(k) => match k.key_code { - KeyCode::Tab => !self.focus_state.progress(dom, !k.shift_key), + EventData::Keyboard(k) => match k.code() { + Code::Tab => !self + .focus_state + .progress(dom, !k.modifiers().contains(Modifiers::SHIFT)), _ => true, }, _ => true, From 6a6a028afca781a613eaf5e29b7bc2fee5ea9c46 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Tue, 28 Jun 2022 23:36:06 +0300 Subject: [PATCH 11/30] Create new example for all mouse events --- examples/all_events.rs | 61 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 examples/all_events.rs diff --git a/examples/all_events.rs b/examples/all_events.rs new file mode 100644 index 000000000..9a5a585ca --- /dev/null +++ b/examples/all_events.rs @@ -0,0 +1,61 @@ +use dioxus::prelude::*; +use dioxus_core::UiEvent; +use dioxus_html::on::MouseData; +use std::sync::Arc; + +fn main() { + dioxus::desktop::launch(app); +} + +#[derive(Debug)] +enum Event { + MouseMove(Arc), + MouseClick(Arc), + MouseDoubleClick(Arc), + MouseDown(Arc), + MouseUp(Arc), +} + +const MAX_EVENTS: usize = 4; + +fn app(cx: Scope) -> Element { + let container_style = r#" + display: flex; + flex-direction: column; + align-items: center; + "#; + let rect_style = r#" + background: deepskyblue; + height: 50vh; + width: 50vw; + "#; + + let events = use_ref(&cx, || Vec::new()); + + let events_lock = events.read(); + let first_index = events_lock.len().saturating_sub(MAX_EVENTS); + let events_rendered = events_lock[first_index..] + .iter() + .map(|event| cx.render(rsx!(div{"{event:?}"}))); + + let log_event = move |event: Event| { + events.write().push(event); + }; + + cx.render(rsx! ( + div { + style: "{container_style}", + "Hover over to display coordinates:", + div { + style: "{rect_style}", + onmousemove: move |event| log_event(Event::MouseMove(event.data)), + onclick: move |event| log_event(Event::MouseClick(event.data)), + ondblclick: move |event| log_event(Event::MouseDoubleClick(event.data)), + onmousedown: move |event| log_event(Event::MouseDown(event.data)), + onmouseup: move |event| log_event(Event::MouseUp(event.data)), + prevent_default: "mousedown", + } + }, + events_rendered, + )) +} From c090741c60f847077f91babd75e2c23847a93a1b Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Tue, 28 Jun 2022 23:46:24 +0300 Subject: [PATCH 12/30] Improve event formatting --- examples/all_events.rs | 6 +++--- packages/html/src/events.rs | 14 +++++++++++++- packages/html/src/geometry.rs | 1 + 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/examples/all_events.rs b/examples/all_events.rs index 9a5a585ca..5c6d56e37 100644 --- a/examples/all_events.rs +++ b/examples/all_events.rs @@ -36,7 +36,7 @@ fn app(cx: Scope) -> Element { let first_index = events_lock.len().saturating_sub(MAX_EVENTS); let events_rendered = events_lock[first_index..] .iter() - .map(|event| cx.render(rsx!(div{"{event:?}"}))); + .map(|event| cx.render(rsx!(div {"{event:?}"}))); let log_event = move |event: Event| { events.write().push(event); @@ -45,7 +45,6 @@ fn app(cx: Scope) -> Element { cx.render(rsx! ( div { style: "{container_style}", - "Hover over to display coordinates:", div { style: "{rect_style}", onmousemove: move |event| log_event(Event::MouseMove(event.data)), @@ -53,9 +52,10 @@ fn app(cx: Scope) -> Element { ondblclick: move |event| log_event(Event::MouseDoubleClick(event.data)), onmousedown: move |event| log_event(Event::MouseDown(event.data)), onmouseup: move |event| log_event(Event::MouseUp(event.data)), + // prevent selection prevent_default: "mousedown", } + div { events_rendered }, }, - events_rendered, )) } diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 3bd84ac0e..dbf6dd2ca 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -17,6 +17,7 @@ pub mod on { use keyboard_types::{Code, Key, Location, Modifiers}; use std::collections::HashMap; use std::convert::TryInto; + use std::fmt::{Debug, Formatter}; use std::str::FromStr; use super::*; @@ -568,7 +569,7 @@ pub mod on { pub type MouseEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug, Clone)] + #[derive(Clone)] /// Data associated with a mouse event /// /// Do not use the deprecated fields; they may change or become private in the future. @@ -753,6 +754,17 @@ pub mod on { } } + impl Debug for MouseData { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MouseData") + .field("coordinates", &self.coordinates()) + .field("modifiers", &self.modifiers()) + .field("held_buttons", &self.held_buttons()) + .field("trigger_button", &self.trigger_button()) + .finish() + } + } + 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 027922a85..990ca5b5e 100644 --- a/packages/html/src/geometry.rs +++ b/packages/html/src/geometry.rs @@ -96,6 +96,7 @@ impl WheelDelta { } /// Coordinates of a point in the app's interface +#[derive(Debug)] pub struct Coordinates { screen: ScreenPoint, client: ClientPoint, From 8f8a614a5cca00c16949c7b2ac81ae367e9a29e7 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Tue, 28 Jun 2022 23:58:07 +0300 Subject: [PATCH 13/30] Add keyboard events to event example --- examples/all_events.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/examples/all_events.rs b/examples/all_events.rs index 5c6d56e37..513a92f68 100644 --- a/examples/all_events.rs +++ b/examples/all_events.rs @@ -1,6 +1,6 @@ use dioxus::prelude::*; use dioxus_core::UiEvent; -use dioxus_html::on::MouseData; +use dioxus_html::on::{KeyboardData, MouseData}; use std::sync::Arc; fn main() { @@ -14,6 +14,9 @@ enum Event { MouseDoubleClick(Arc), MouseDown(Arc), MouseUp(Arc), + KeyDown(Arc), + KeyUp(Arc), + KeyPress(Arc), } const MAX_EVENTS: usize = 4; @@ -47,13 +50,18 @@ fn app(cx: Scope) -> Element { style: "{container_style}", div { style: "{rect_style}", + // focusing is necessary to catch keyboard events + tabindex: "0", + onmousemove: move |event| log_event(Event::MouseMove(event.data)), onclick: move |event| log_event(Event::MouseClick(event.data)), ondblclick: move |event| log_event(Event::MouseDoubleClick(event.data)), onmousedown: move |event| log_event(Event::MouseDown(event.data)), onmouseup: move |event| log_event(Event::MouseUp(event.data)), - // prevent selection - prevent_default: "mousedown", + + onkeydown: move |event| log_event(Event::KeyDown(event.data)), + onkeyup: move |event| log_event(Event::KeyUp(event.data)), + onkeypress: move |event| log_event(Event::KeyPress(event.data)), } div { events_rendered }, }, From a46ff936006d4b355c1812dbae9cc182bdbf296e Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Wed, 29 Jun 2022 01:09:17 +0300 Subject: [PATCH 14/30] Improve keyboard event debug formatting --- packages/html/src/events.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index dbf6dd2ca..b4e81726e 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -427,7 +427,7 @@ pub mod on { pub type KeyboardEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug, Clone)] + #[derive(Clone)] pub struct KeyboardData { #[deprecated( since = "0.3.0", @@ -553,6 +553,18 @@ pub mod on { } } + impl Debug for KeyboardData { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("KeyboardData") + .field("key", &self.key()) + .field("code", &self.code()) + .field("modifiers", &self.modifiers()) + .field("location", &self.location()) + .field("is_auto_repeating", &self.is_auto_repeating()) + .finish() + } + } + pub type FocusEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] From f30387597abf4825897c4e5549207c961130657b Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Wed, 29 Jun 2022 01:18:09 +0300 Subject: [PATCH 15/30] Add wheel events to event example; make interface prettier --- examples/all_events.rs | 13 ++++++++++++- packages/html/src/events.rs | 10 +++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/examples/all_events.rs b/examples/all_events.rs index 513a92f68..713473dc5 100644 --- a/examples/all_events.rs +++ b/examples/all_events.rs @@ -1,6 +1,6 @@ use dioxus::prelude::*; use dioxus_core::UiEvent; -use dioxus_html::on::{KeyboardData, MouseData}; +use dioxus_html::on::{KeyboardData, MouseData, WheelData}; use std::sync::Arc; fn main() { @@ -14,6 +14,9 @@ enum Event { MouseDoubleClick(Arc), MouseDown(Arc), MouseUp(Arc), + + Wheel(Arc), + KeyDown(Arc), KeyUp(Arc), KeyPress(Arc), @@ -31,6 +34,10 @@ fn app(cx: Scope) -> Element { background: deepskyblue; height: 50vh; width: 50vw; + color: white; + padding: 20px; + margin: 20px; + text-aligh: center; "#; let events = use_ref(&cx, || Vec::new()); @@ -59,9 +66,13 @@ fn app(cx: Scope) -> Element { onmousedown: move |event| log_event(Event::MouseDown(event.data)), onmouseup: move |event| log_event(Event::MouseUp(event.data)), + onwheel: move |event| log_event(Event::Wheel(event.data)), + onkeydown: move |event| log_event(Event::KeyDown(event.data)), onkeyup: move |event| log_event(Event::KeyUp(event.data)), onkeypress: move |event| log_event(Event::KeyPress(event.data)), + + "Hover, click, type or scroll to see the info down below" } div { events_rendered }, }, diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index b4e81726e..d2fa8d3ba 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -828,7 +828,7 @@ pub mod on { pub type WheelEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug, Clone)] + #[derive(Clone)] pub struct WheelData { #[deprecated(since = "0.3.0", note = "use delta() instead")] pub delta_mode: u32, @@ -889,6 +889,14 @@ pub mod on { } } + impl Debug for WheelData { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("WheelData") + .field("delta", &self.delta()) + .finish() + } + } + pub type MediaEvent = UiEvent; #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] From 0cdaeb1a8db6c8aa985b4da50a0f51d2f89e3557 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Wed, 29 Jun 2022 01:24:33 +0300 Subject: [PATCH 16/30] Delete redundant examples (all events now demoed in single simple example) --- examples/all_events.rs | 10 ++++++-- examples/event_mouse.rs | 56 ----------------------------------------- examples/event_wheel.rs | 39 ---------------------------- examples/events.rs | 26 ------------------- 4 files changed, 8 insertions(+), 123 deletions(-) delete mode 100644 examples/event_mouse.rs delete mode 100644 examples/event_wheel.rs delete mode 100644 examples/events.rs diff --git a/examples/all_events.rs b/examples/all_events.rs index 713473dc5..540d87500 100644 --- a/examples/all_events.rs +++ b/examples/all_events.rs @@ -1,6 +1,6 @@ use dioxus::prelude::*; use dioxus_core::UiEvent; -use dioxus_html::on::{KeyboardData, MouseData, WheelData}; +use dioxus_html::on::{FocusData, KeyboardData, MouseData, WheelData}; use std::sync::Arc; fn main() { @@ -20,9 +20,12 @@ enum Event { KeyDown(Arc), KeyUp(Arc), KeyPress(Arc), + + FocusIn(Arc), + FocusOut(Arc), } -const MAX_EVENTS: usize = 4; +const MAX_EVENTS: usize = 8; fn app(cx: Scope) -> Element { let container_style = r#" @@ -72,6 +75,9 @@ fn app(cx: Scope) -> Element { onkeyup: move |event| log_event(Event::KeyUp(event.data)), onkeypress: move |event| log_event(Event::KeyPress(event.data)), + onfocusin: move |event| log_event(Event::FocusIn(event.data)), + onfocusout: move |event| log_event(Event::FocusOut(event.data)), + "Hover, click, type or scroll to see the info down below" } div { events_rendered }, diff --git a/examples/event_mouse.rs b/examples/event_mouse.rs deleted file mode 100644 index 165d9445c..000000000 --- a/examples/event_mouse.rs +++ /dev/null @@ -1,56 +0,0 @@ -use dioxus::prelude::*; -use dioxus_core::UiEvent; -use dioxus_html::on::MouseData; - -fn main() { - dioxus::desktop::launch(app); -} - -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; - flex-direction: column; - align-items: center; - "#; - let rect_style = r#" - background: deepskyblue; - height: 50vh; - width: 50vw; - "#; - - let update_mouse_position = move |event: UiEvent| { - let mouse_data = event.data; - - 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. - - buttons.set(format!("{:?}", mouse_data.held_buttons())); - modifiers.set(format!("{:?}", mouse_data.modifiers())); - }; - - cx.render(rsx! ( - div { - style: "{container_style}", - "Hover over to display coordinates:", - 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: {buttons}"}, - div {"Modifiers: {modifiers}"}, - } - )) -} diff --git a/examples/event_wheel.rs b/examples/event_wheel.rs deleted file mode 100644 index 763c64d02..000000000 --- a/examples/event_wheel.rs +++ /dev/null @@ -1,39 +0,0 @@ -use dioxus::prelude::*; -use dioxus_core::UiEvent; -use dioxus_html::on::WheelData; - -fn main() { - dioxus::desktop::launch(app); -} - -fn app(cx: Scope) -> Element { - let delta = use_state(&cx, || "".to_string()); - - let container_style = r#" - display: flex; - flex-direction: column; - align-items: center; - "#; - let rect_style = r#" - background: deepskyblue; - height: 50vh; - width: 50vw; - "#; - - let handle_event = move |event: UiEvent| { - let wheel_data = event.data; - delta.set(format!("{:?}", wheel_data.delta())); - }; - - cx.render(rsx! ( - div { - style: "{container_style}", - "Scroll mouse wheel over rectangle:", - div { - style: "{rect_style}", - onwheel: handle_event, - } - div {"Delta: {delta}"}, - } - )) -} diff --git a/examples/events.rs b/examples/events.rs deleted file mode 100644 index 0a82e9bd7..000000000 --- a/examples/events.rs +++ /dev/null @@ -1,26 +0,0 @@ -use dioxus::prelude::*; - -fn main() { - dioxus::desktop::launch(app); -} - -fn app(cx: Scope) -> Element { - cx.render(rsx! { - div { - button { - ondblclick: move |_| { - // - println!("double clicked!"); - }, - "Click me!" - } - input { - onfocusin: move |_| { - // - println!("blurred!"); - }, - "onblur": "console.log('blurred!')" - } - } - }) -} From 6aa800018b3b73e93794aba60f6a33750578279f Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Wed, 29 Jun 2022 01:52:03 +0300 Subject: [PATCH 17/30] Create similar event demo for TUI --- examples/tui_all_events.rs | 85 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 examples/tui_all_events.rs diff --git a/examples/tui_all_events.rs b/examples/tui_all_events.rs new file mode 100644 index 000000000..732b62532 --- /dev/null +++ b/examples/tui_all_events.rs @@ -0,0 +1,85 @@ +use dioxus::prelude::*; +use dioxus_html::on::{FocusData, KeyboardData, MouseData, WheelData}; +use std::sync::Arc; + +fn main() { + dioxus::tui::launch(app); +} + +#[derive(Debug)] +enum Event { + MouseMove(Arc), + MouseClick(Arc), + MouseDoubleClick(Arc), + MouseDown(Arc), + MouseUp(Arc), + + Wheel(Arc), + + KeyDown(Arc), + KeyUp(Arc), + KeyPress(Arc), + + FocusIn(Arc), + FocusOut(Arc), +} + +const MAX_EVENTS: usize = 8; + +fn app(cx: Scope) -> Element { + let events = use_ref(&cx, || Vec::new()); + + let events_lock = events.read(); + let first_index = events_lock.len().saturating_sub(MAX_EVENTS); + let events_rendered = events_lock[first_index..].iter().map(|event| { + // TUI panics if text overflows (https://github.com/DioxusLabs/dioxus/issues/371) + // temporary hack: just trim the strings (and make sure viewport is big enough) + // todo: remove + let mut trimmed = format!("{event:?}"); + trimmed.truncate(200); + cx.render(rsx!(p { "{trimmed}" })) + }); + + let log_event = move |event: Event| { + events.write().push(event); + }; + + cx.render(rsx! { + div { + width: "100%", + height: "100%", + flex_direction: "column", + div { + width: "80%", + height: "50%", + border_width: "1px", + justify_content: "center", + align_items: "center", + background_color: "hsl(248, 53%, 58%)", + + onmousemove: move |event| log_event(Event::MouseMove(event.data)), + onclick: move |event| log_event(Event::MouseClick(event.data)), + ondblclick: move |event| log_event(Event::MouseDoubleClick(event.data)), + onmousedown: move |event| log_event(Event::MouseDown(event.data)), + onmouseup: move |event| log_event(Event::MouseUp(event.data)), + + onwheel: move |event| log_event(Event::Wheel(event.data)), + + onkeydown: move |event| log_event(Event::KeyDown(event.data)), + onkeyup: move |event| log_event(Event::KeyUp(event.data)), + onkeypress: move |event| log_event(Event::KeyPress(event.data)), + + onfocusin: move |event| log_event(Event::FocusIn(event.data)), + onfocusout: move |event| log_event(Event::FocusOut(event.data)), + + "Hover, click, type or scroll to see the info down below" + }, + div { + width: "80%", + height: "50%", + flex_direction: "column", + events_rendered, + }, + }, + }) +} From cae0c1409d9ea12a280171c4a7fc1da1721f46a4 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Wed, 29 Jun 2022 02:13:01 +0300 Subject: [PATCH 18/30] Fix some warnings: avoid deprecated fields --- examples/all_events.rs | 1 - examples/tui_buttons.rs | 5 +++-- packages/router/tests/web_router.rs | 1 + packages/tui/tests/events.rs | 5 +++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/all_events.rs b/examples/all_events.rs index 540d87500..5430bdbf5 100644 --- a/examples/all_events.rs +++ b/examples/all_events.rs @@ -1,5 +1,4 @@ use dioxus::prelude::*; -use dioxus_core::UiEvent; use dioxus_html::on::{FocusData, KeyboardData, MouseData, WheelData}; use std::sync::Arc; diff --git a/examples/tui_buttons.rs b/examples/tui_buttons.rs index 31d2e1ce0..f0c4b3948 100644 --- a/examples/tui_buttons.rs +++ b/examples/tui_buttons.rs @@ -1,4 +1,5 @@ -use dioxus::{events::KeyCode, prelude::*}; +use dioxus::prelude::*; +use dioxus_html::input_data::keyboard_types::Code; fn main() { dioxus::tui::launch(app); @@ -27,7 +28,7 @@ fn Button(cx: Scope) -> Element { background_color: "{color}", tabindex: "{cx.props.layer}", onkeydown: |e| { - if let KeyCode::Space = e.data.key_code{ + if let Code::Space = e.data.code() { toggle.modify(|f| !f); } }, diff --git a/packages/router/tests/web_router.rs b/packages/router/tests/web_router.rs index 40b3a764f..cddc542c1 100644 --- a/packages/router/tests/web_router.rs +++ b/packages/router/tests/web_router.rs @@ -1,4 +1,5 @@ #![cfg(target_arch = "wasm32")] +#![allow(non_snake_case)] use dioxus_core::prelude::*; use dioxus_core_macro::*; diff --git a/packages/tui/tests/events.rs b/packages/tui/tests/events.rs index ab6b70b2e..fe31f4c5e 100644 --- a/packages/tui/tests/events.rs +++ b/packages/tui/tests/events.rs @@ -4,6 +4,7 @@ use dioxus_core::*; use dioxus_core_macro::*; use dioxus_hooks::*; use dioxus_html as dioxus_elements; +use dioxus_html::input_data::keyboard_types::Code; use dioxus_tui::TuiContext; use std::future::Future; use std::pin::Pin; @@ -60,7 +61,7 @@ fn key_down() { width: "100%", height: "100%", onkeydown: move |evt| { - assert_eq!(evt.data.key_code, dioxus_html::KeyCode::A); + assert_eq!(evt.data.code(), Code::KeyA); tui_ctx.quit(); }, } @@ -290,7 +291,7 @@ fn wheel() { width: "100%", height: "100%", onwheel: move |evt| { - assert!(evt.data.delta_y > 0.0); + assert!(evt.data.delta().strip_units().y > 0.0); tui_ctx.quit(); }, } From ca60b01c4929fcaf28479ef36080ebf59ce48d42 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Wed, 29 Jun 2022 02:17:00 +0300 Subject: [PATCH 19/30] Fix remaining warnings (unrelated) --- packages/router/tests/web_router.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/router/tests/web_router.rs b/packages/router/tests/web_router.rs index cddc542c1..1cfde67ac 100644 --- a/packages/router/tests/web_router.rs +++ b/packages/router/tests/web_router.rs @@ -6,7 +6,6 @@ use dioxus_core_macro::*; use dioxus_html as dioxus_elements; use dioxus_router::*; use gloo_utils::document; -use serde::{Deserialize, Serialize}; use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); @@ -50,7 +49,7 @@ fn simple_test() { } fn BlogPost(cx: Scope) -> Element { - let id = use_route(&cx).parse_segment::("id")?; + let _id = use_route(&cx).parse_segment::("id")?; cx.render(rsx! { div { @@ -61,5 +60,5 @@ fn simple_test() { main(); - let element = gloo_utils::document(); + let _ = document(); } From a9f286c52b2252a03db68323c0655404fd676f93 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Wed, 29 Jun 2022 07:44:19 +0300 Subject: [PATCH 20/30] Fix bug in TUI key repeat handling --- packages/tui/src/hooks.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index bb5f81dfb..59aa7a7e3 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -146,14 +146,16 @@ impl InnerInputState { .last_key_pressed .as_ref() // heuristic for guessing which presses are auto-repeating. not necessarily accurate - .filter(|k2| k2.0.key() == k.key() && k2.1.elapsed() < MAX_REPEAT_TIME) + .filter(|(last_data, last_instant)| { + last_data.key() == k.key() && last_instant.elapsed() < MAX_REPEAT_TIME + }) .is_some(); - if is_repeating { - let new = k.clone(); - self.last_key_pressed = Some((new, Instant::now())); + if is_repeating { *k = KeyboardData::new(k.key(), k.code(), k.location(), true, k.modifiers()); } + + self.last_key_pressed = Some((k.clone(), Instant::now())); } } } From 58a495351a1b10405f9145f8da43c02553a7b6e6 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 29 Jun 2022 09:09:47 -0500 Subject: [PATCH 21/30] check that an node is an element before removing listeners --- packages/interpreter/src/interpreter.js | 88 ++++++++++++------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 759e83c9f..4483d202a 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -15,43 +15,45 @@ class ListenerMap { this.root = root; } - createBubbling(event_name, handler) { - if (this.global[event_name] === undefined) { - this.global[event_name] = {}; - this.global[event_name].active = 1; - this.global[event_name].callback = handler; - this.root.addEventListener(event_name, handler); - } else { - this.global[event_name].active++; + create(event_name, element, handler, bubbles) { + if (bubbles) { + if (this.global[event_name] === undefined) { + this.global[event_name] = {}; + this.global[event_name].active = 1; + this.global[event_name].callback = handler; + this.root.addEventListener(event_name, handler); + } else { + this.global[event_name].active++; + } + } + else { + const id = element.getAttribute("data-dioxus-id"); + if (!this.local[id]) { + this.local[id] = {}; + } + this.local[id][event_name] = handler; + element.addEventListener(event_name, handler); } } - createNonBubbling(event_name, element, handler) { - const id = element.getAttribute("data-dioxus-id"); - if (!this.local[id]) { - this.local[id] = {}; + remove(element, event_name, bubbles) { + if (bubbles) { + this.global[event_name].active--; + if (this.global[event_name].active === 0) { + this.root.removeEventListener(event_name, this.global[event_name].callback); + delete this.global[event_name]; + } } - this.local[id][event_name] = handler; - element.addEventListener(event_name, handler); - } - - removeBubbling(event_name) { - this.global[event_name].active--; - if (this.global[event_name].active === 0) { - this.root.removeEventListener(event_name, this.global[event_name].callback); - delete this.global[event_name]; + else { + const id = element.getAttribute("data-dioxus-id"); + delete this.local[id][event_name]; + if (this.local[id].length === 0) { + delete this.local[id]; + } + element.removeEventListener(event_name, handler); } } - removeNonBubbling(element, event_name) { - const id = element.getAttribute("data-dioxus-id"); - delete this.local[id][event_name]; - if (this.local[id].length === 0) { - delete this.local[id]; - } - element.removeEventListener(event_name, handler); - } - removeAllNonBubbling(element) { const id = element.getAttribute("data-dioxus-id"); delete this.local[id]; @@ -93,7 +95,9 @@ export class Interpreter { ReplaceWith(root_id, m) { let root = this.nodes[root_id]; let els = this.stack.splice(this.stack.length - m); - this.listeners.removeAllNonBubbling(root); + if (is_element_node(root.nodeType)) { + this.listeners.removeAllNonBubbling(root); + } root.replaceWith(...els); } InsertAfter(root, n) { @@ -108,8 +112,10 @@ export class Interpreter { } Remove(root) { let node = this.nodes[root]; - this.listeners.removeAllNonBubbling(node); if (node !== undefined) { + if (is_element_node(node)) { + this.listeners.removeAllNonBubbling(node); + } node.remove(); } } @@ -137,22 +143,12 @@ export class Interpreter { NewEventListener(event_name, root, handler, bubbles) { const element = this.nodes[root]; element.setAttribute("data-dioxus-id", `${root}`); - if (bubbles) { - this.listeners.createBubbling(event_name, handler); - } - else { - this.listeners.createNonBubbling(event_name, element, handler); - } + this.listeners.create(event_name, element, handler, bubbles); } RemoveEventListener(root, event_name, bubbles) { const element = this.nodes[root]; element.removeAttribute(`data-dioxus-id`); - if (bubbles) { - this.listeners.removeBubbling(event_name) - } - else { - this.listeners.removeNonBubbling(element, event_name); - } + this.listeners.remove(element, event_name, bubbles); } SetText(root, text) { this.nodes[root].textContent = text; @@ -660,6 +656,10 @@ const bool_attrs = { truespeed: true, }; +function is_element_node(node) { + return node.nodeType == 1; +} + function event_bubbles(event) { switch (event) { case "copy": From 175e921ee6dad50f7a8233ba2722d02ed8be56cf Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Thu, 30 Jun 2022 15:46:02 +0300 Subject: [PATCH 22/30] Remove link to state management in Get Started section It was dead, and didn't feel like it belonged there. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 5e87587e3..9c0a6e69a 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,6 @@ If you know React, then you already know Dioxus. Desktop SSR Mobile - State From f5fe4f8fd968dc1acc537cb48d95f1e3a354b981 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Thu, 30 Jun 2022 17:10:28 +0300 Subject: [PATCH 23/30] Re-write examples Readme Delete useless table with mostly dead links. Categorize examples for easy browsing. Move missing examples to bottom of page. --- examples/README.md | 321 +++++++++++++++++++++------------------------ 1 file changed, 152 insertions(+), 169 deletions(-) diff --git a/examples/README.md b/examples/README.md index e1897db8b..2c07885bc 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,189 +1,172 @@ # Examples -Most of these examples are run through webview so you don't need the Dioxus CLI installed to preview the functionality. +These examples are fully-fledged mini Dioxus apps. -These examples are fully-fledged micro apps. They can be ran with the `cargo run --example XYZ` +You can run them with `cargo run --example EXAMPLE_NAME`. Example: -| Example | What it does | Status | -| --------------------------------------------------- | ------------------------------------------- | ------ | -| [The basics](./basics.rs) | A few basic examples to preview Dioxus | 🛠 | -| [fine grained reactivity](./signals.rs) | Escape `diffing` by writing values directly | 🛠 | -| [Global State Management](./statemanagement.rs) | Share state between components | 🛠 | -| [Virtual Refs]() | Cross-platform imperative elements | 🛠 | -| [Inline Styles](./inline-styles.rs) | Define styles for elements inline | 🛠 | -| [Conditional Rendering](./conditional-rendering.rs) | Hide/Show elements using conditionals | ✅ | - -These examples are not necessarily meant to be run, but rather serve as a reference for the given functionality. - -| Example | What it does | Status | -| --------------------------------------------------- | ----------------------------------------------- | ------ | -| [The basics](./basics.rs) | A few basic examples to preview Dioxus | 🛠 | -| [fine grained reactivity](./signals.rs) | Escape `diffing` by writing values directly | 🛠 | -| [Global State Management](./statemanagement.rs) | Share state between components | 🛠 | -| [Virtual Refs]() | Cross-platform imperative elements | 🛠 | -| [Inline Styles](./inline-styles.rs) | Define styles for elements inline | 🛠 | -| [Conditional Rendering](./conditional-rendering.rs) | Hide/Show elements using conditionals | ✅ | -| [Maps/Iterators](./iterators.rs) | Use iterators in the rsx! macro | ✅ | -| [Render To string](./tostring.rs) | Render a mounted virtualdom to a string | 🛠 | -| [Component Children](./children.rs) | Pass children into child components | 🛠 | -| [Function Driven children]() | Pass functions to make VNodes | 🛠 | -| [Memoization & Borrowed Data](./memo.rs) | Suppress renders, borrow from parents | ✅ | -| [Fragments](./fragments.rs) | Support root-less element groups | ✅ | -| [Null/None Components](./empty.rs) | Return nothing! | 🛠 | -| [Spread Pattern for props](./spreadpattern.rs) | Manually specify and override props | ✅ | -| [Controlled Inputs](./controlled-inputs.rs) | this does | 🛠 | -| [Custom Elements]() | Define custom elements | 🛠 | -| [Web Components]() | Custom elements to interface with WebComponents | 🛠 | -| [Testing And debugging]() | this does | 🛠 | -| [Asynchronous Data]() | Using suspense to wait for data | 🛠 | -| [Fiber/Scheduled Rendering]() | this does | 🛠 | -| [CSS Compiled Styles]() | this does | 🛠 | -| [Anti-patterns](./antipatterns.rs) | A collection of discouraged patterns | ✅ | -| [Complete rsx reference](./rsx_usage.rs) | A complete reference for all rsx! usage | ✅ | -| [Event Listeners](./listener.rs) | Attach closures to events on elements | ✅ | -| [Inline Props](./inlineprops.rs) | Using the `#[inline_props]` macro | ✅ | -| [Eval](./eval.rs) | Evaluate dynamic JavaScript code | ✅ | - - -## Show me some examples! - -In our collection of examples, guides, and tutorials, we have: -- The book (an introductory course) -- The guide (an in-depth analysis of everything in Dioxus) -- The reference (a collection of examples with heavy documentation) -- The general examples -- The platform-specific examples (web, ssr, desktop, mobile, server) - -Here's what a few common tasks look like in Dioxus: - -Nested components with children and internal state: -```rust -fn App(cx: Scope) -> Element { - cx.render(rsx!( Toggle { "Toggle me" } )) -} - -#[derive(PartialEq, Props)] -struct ToggleProps { children: Element } - -fn Toggle(cx: Scope) -> Element { - let mut toggled = use_state(&cx, || false); - cx.render(rsx!{ - div { - &cx.props.children - button { onclick: move |_| toggled.set(true), - toggled.and_then(|| "On").or_else(|| "Off") - } - } - }) -} +```shell +cargo run --example hello_world ``` -Controlled inputs: -```rust -fn App(cx: Scope) -> Element { - let value = use_state(&cx, String::new); - cx.render(rsx!( - input { - "type": "text", - value: "{value}", - oninput: move |evt| value.set(evt.value.clone()) - } - )) -} -``` +(Most of these examples are run through webview, so you don't need the Dioxus CLI installed) -Lists and Conditional rendering: -```rust -fn App(cx: Scope) -> Element { - let list = (0..10).map(|i| { - rsx!(li { key: "{i}", "Value: {i}" }) - }); +## Basic Features - let title = match list.len() { - 0 => rsx!("Not enough"), - _ => rsx!("Plenty!"), - }; +[hello_world](./hello_world.rs) - Most basic example - if should_show { - cx.render(rsx!( - title, - ul { list } - )) - } else { - None - } -} -``` +[readme](./readme.rs) - Counter example from the Readme -Tiny components: -```rust -static App: Component = |cx| rsx!(cx, div {"hello world!"}); -``` +[custom_assets](./custom_assets.rs) - Include images -Borrowed prop contents: -```rust -fn App(cx: Scope) -> Element { - let name = use_state(&cx, || String::from("example")); - rsx!(cx, Child { title: name.as_str() }) -} +[custom_element](./custom_element.rs) - Render webcomponents -#[derive(Props)] -struct ChildProps<'a> { title: &'a str } +[custom_html](./custom_html.rs) - Customize wrapper HTML -fn Child(cx: Scope) -> Element { - rsx!(cx, "Hello {cx.props.title}") -} -``` +[eval](./eval.rs) - Evaluate JS expressions -Global State -```rust -struct GlobalState { name: String } +### RSX -fn App(cx: Scope) -> Element { - use_provide_shared_state(cx, || GlobalState { name: String::from("Toby") }) - rsx!(cx, Leaf {}) -} +[rsx_usage](./rsx_usage.rs) - Demo of all RSX features -fn Leaf(cx: Scope) -> Element { - let state = use_consume_shared_state::(cx)?; - rsx!(cx, "Hello {state.name}") -} -``` +[xss_safety](./xss_safety.rs) - You can include text without worrying about injections by default -Router (inspired by Yew-Router) -```rust -#[derive(PartialEq, Clone, Hash, Eq, Routable)] -enum Route { - #[at("/")] - Home, - #[at("/post/{id}")] - Post(id) -} +### Props -fn App(cx: Scope) -> Element { - let route = use_router(cx, Route::parse); - cx.render(rsx!(div { - match route { - Route::Home => rsx!( Home {} ), - Route::Post(id) => rsx!( Post { id: id }) - } - })) -} -``` +[borrowed](./borrowed.rs) - Borrowed props -Suspense -```rust -fn App(cx: Scope) -> Element { - let doggo = use_suspense(cx, - || async { reqwest::get("https://dog.ceo/api/breeds/image/random").await.unwrap().json::().await.unwrap() }, - |response| cx.render(rsx!( img { src: "{response.message}" })) - ); +[inlineprops](./inlineprops.rs) - Demo of `inline_props` macro - cx.render(rsx!{ - div { - "One doggo coming right up:", - doggo - } - }) -} -``` +[optional_props](./optional_props.rs) - Optional props + +### CSS + +[all_css](./all_css.rs) - You can specify any CSS attribute + +[tailwind](./tailwind.rs) - You can use a library for styling + +## Input Handling + +[all_events](./all_events.rs) - Basic event handling demo + +[filedragdrop](./filedragdrop.rs) - Handle dropped files + +[form](./form.rs) - Handle form submission + +[inputs](./inputs.rs) - Input values + +[nested_listeners](./nested_listeners.rs) - Nested handlers and bubbling + +[textarea](textarea.rs) - Text area input + +### State Management + +[fermi](./fermi.rs) - Fermi library for state management + +[pattern_reducer](./pattern_reducer.rs) - The reducer pattern with `use_state` + +[rsx_compile_fail](./rsx_compile_fail.rs) + +### Async + +[login_form](./login_form.rs) - Login endpoint example + +[suspense](./suspense.rs) - Render placeholders while data is loading + +[tasks](./tasks.rs) - Continuously run future + +[order](./order.rs) + +### SVG + +[svg_basic](./svg_basic.rs) + +[svg](./svg.rs) + +### Performance + +[framework_benchmark](./framework_benchmark.rs) - Renders a huge list + +[heavy_compute](./heavy_compute.rs) - How to deal with expensive operations + +## Server-side rendering + +[ssr](./ssr.rs) - Rendering RSX server-side + +[hydration](./hydration.rs) - Pre-rendering with hydration + +## Common Patterns + +[disabled](./disabled.rs) - Disable buttons conditionally + +[error_handle](./error_handle.rs) - Handle errors with early return + +## Routing + +[flat_router](./flat_router.rs) - Basic, flat route example + +[router](./router.rs) - Router example + +[link](./link.rs) - Internal, external and custom links + +## Platform Features + +[window_event](./window_event.rs) - Window decorations, fullscreen, minimization, etc. + +[window_zoom](./window_zoom.rs) – Zoom in or out + +## Example Apps + +[calculator](./calculator.rs) - Simple calculator + +[pattern_model](./pattern_model.rs) - Simple calculator, but using a custom struct as the model + +[crm](./crm.rs) - Toy multi-page customer management app + +[dog_app](./dog_app.rs) - Accesses dog API + +[file_explorer](./file_explorer.rs) - File browser that uses `use_ref` to interact with the model + +[todomvc](./todomvc.rs) - Todo task list example + +## Terminal UI + +[tui_border](./tui_border.rs) + +[tui_buttons](./tui_buttons.rs) + +[tui_color_test](./tui_color_test.rs) + +[tui_components](./tui_components.rs) + +[tui_frame](./tui_frame.rs) + +[tui_hover](./tui_hover.rs) + +[tui_keys](./tui_keys.rs) + +[tui_list](./tui_list.rs) + +[tui_margin](./tui_margin.rs) + +[tui_quadrants](./tui_quadrants.rs) + +[tui_readme](./tui_readme.rs) + +[tui_task](./tui_task.rs) + +[tui_text](./tui_text.rs) + +# TODO +Missing Features +- Fine-grained reactivity +- Refs - imperative handles to elements +- Function-driven children: Pass functions to make VNodes + +Missing examples +- Shared state +- Root-less element groups +- Spread props +- Custom elements +- Component Children: Pass children into child components +- Render To string: Render a mounted virtualdom to a string +- Testing and Debugging \ No newline at end of file From 0ab2f38314cb0af1b727b5192f279e232fba73bb Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Thu, 30 Jun 2022 22:39:34 +0300 Subject: [PATCH 24/30] Guide: remove empty and almost empty pages (Avoids confusion and improves navigation) --- docs/guide/src/SUMMARY.md | 10 ++-------- docs/guide/src/async/fetching.md | 2 -- docs/guide/src/async/sockets.md | 1 - docs/guide/src/state/channels.md | 1 - docs/guide/src/tutorial/advanced_guides.md | 1 - docs/guide/src/tutorial/components.md | 3 --- docs/guide/src/tutorial/state.md | 3 --- docs/guide/src/tutorial/structure.md | 4 ---- docs/guide/src/tutorial/styling.md | 4 ---- 9 files changed, 2 insertions(+), 27 deletions(-) delete mode 100644 docs/guide/src/async/fetching.md delete mode 100644 docs/guide/src/async/sockets.md delete mode 100644 docs/guide/src/state/channels.md delete mode 100644 docs/guide/src/tutorial/advanced_guides.md delete mode 100644 docs/guide/src/tutorial/components.md delete mode 100644 docs/guide/src/tutorial/state.md delete mode 100644 docs/guide/src/tutorial/structure.md delete mode 100644 docs/guide/src/tutorial/styling.md diff --git a/docs/guide/src/SUMMARY.md b/docs/guide/src/SUMMARY.md index 8060bbe0b..12a0b60cf 100644 --- a/docs/guide/src/SUMMARY.md +++ b/docs/guide/src/SUMMARY.md @@ -32,18 +32,12 @@ - [Working with Async](async/index.md) - [UseFuture](async/use_future.md) - [UseCoroutine](async/coroutines.md) - - - + - [Updating State](async/loading_state.md) + - [Tasks](async/asynctasks.md) diff --git a/docs/guide/src/async/fetching.md b/docs/guide/src/async/fetching.md deleted file mode 100644 index f4cd29a36..000000000 --- a/docs/guide/src/async/fetching.md +++ /dev/null @@ -1,2 +0,0 @@ -# Fetching - diff --git a/docs/guide/src/async/sockets.md b/docs/guide/src/async/sockets.md deleted file mode 100644 index 7627a1570..000000000 --- a/docs/guide/src/async/sockets.md +++ /dev/null @@ -1 +0,0 @@ -# WebSockets diff --git a/docs/guide/src/state/channels.md b/docs/guide/src/state/channels.md deleted file mode 100644 index a3c35c63e..000000000 --- a/docs/guide/src/state/channels.md +++ /dev/null @@ -1 +0,0 @@ -# Channels diff --git a/docs/guide/src/tutorial/advanced_guides.md b/docs/guide/src/tutorial/advanced_guides.md deleted file mode 100644 index 53d2cb54f..000000000 --- a/docs/guide/src/tutorial/advanced_guides.md +++ /dev/null @@ -1 +0,0 @@ -# Advanced Guides diff --git a/docs/guide/src/tutorial/components.md b/docs/guide/src/tutorial/components.md deleted file mode 100644 index 82bae6cd7..000000000 --- a/docs/guide/src/tutorial/components.md +++ /dev/null @@ -1,3 +0,0 @@ -# Defining Components - -This section is currently under construction! 🏗 diff --git a/docs/guide/src/tutorial/state.md b/docs/guide/src/tutorial/state.md deleted file mode 100644 index 6026c7546..000000000 --- a/docs/guide/src/tutorial/state.md +++ /dev/null @@ -1,3 +0,0 @@ -# Defining State - -This section is currently under construction! 🏗 diff --git a/docs/guide/src/tutorial/structure.md b/docs/guide/src/tutorial/structure.md deleted file mode 100644 index eaf8afe43..000000000 --- a/docs/guide/src/tutorial/structure.md +++ /dev/null @@ -1,4 +0,0 @@ -# Structuring our app - - -This section is currently under construction! 🏗 diff --git a/docs/guide/src/tutorial/styling.md b/docs/guide/src/tutorial/styling.md deleted file mode 100644 index b25db14d2..000000000 --- a/docs/guide/src/tutorial/styling.md +++ /dev/null @@ -1,4 +0,0 @@ -# Styling - - -This section is currently under construction! 🏗 From a6e8852cedd4fa9616de6d18ac45e645b5b23cbd Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 30 Jun 2022 15:41:45 -0400 Subject: [PATCH 25/30] polish: make clippy happy on use_hook --- packages/core/src/scopes.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index ba6afcfe2..c54e88bdd 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -835,10 +835,7 @@ impl ScopeState { /// } /// ``` #[allow(clippy::mut_from_ref)] - pub fn use_hook<'src, State: 'static>( - &'src self, - initializer: impl FnOnce(usize) -> State, - ) -> &'src mut State { + pub fn use_hook(&self, initializer: impl FnOnce(usize) -> State) -> &mut State { let mut vals = self.hook_vals.borrow_mut(); let hook_len = vals.len(); From 48d96869d3abfd19f2bd16f3b8d920c552a66d7d Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Thu, 30 Jun 2022 22:49:19 +0300 Subject: [PATCH 26/30] Reference: remove empty and almost empty pages (Avoids confusion and improves navigation) --- docs/reference/src/SUMMARY.md | 32 +------------------ docs/reference/src/depth/components.md | 1 - docs/reference/src/depth/memoization.md | 1 - docs/reference/src/depth/performance.md | 1 - docs/reference/src/depth/props.md | 1 - docs/reference/src/depth/rsx.md | 1 - docs/reference/src/depth/testing.md | 1 - docs/reference/src/depth/topics.md | 1 - docs/reference/src/guide/bundline.md | 1 - docs/reference/src/guide/components.md | 1 - docs/reference/src/guide/custom_elements.md | 1 - docs/reference/src/guide/memoization.md | 1 - docs/reference/src/guide/performance.md | 1 - docs/reference/src/guide/props.md | 1 - docs/reference/src/guide/rsx.md | 1 - .../src/guide/server_side_components.md | 1 - docs/reference/src/guide/testing.md | 1 - 17 files changed, 1 insertion(+), 47 deletions(-) delete mode 100644 docs/reference/src/depth/components.md delete mode 100644 docs/reference/src/depth/memoization.md delete mode 100644 docs/reference/src/depth/performance.md delete mode 100644 docs/reference/src/depth/props.md delete mode 100644 docs/reference/src/depth/rsx.md delete mode 100644 docs/reference/src/depth/testing.md delete mode 100644 docs/reference/src/depth/topics.md delete mode 100644 docs/reference/src/guide/bundline.md delete mode 100644 docs/reference/src/guide/components.md delete mode 100644 docs/reference/src/guide/custom_elements.md delete mode 100644 docs/reference/src/guide/memoization.md delete mode 100644 docs/reference/src/guide/performance.md delete mode 100644 docs/reference/src/guide/props.md delete mode 100644 docs/reference/src/guide/rsx.md delete mode 100644 docs/reference/src/guide/server_side_components.md delete mode 100644 docs/reference/src/guide/testing.md diff --git a/docs/reference/src/SUMMARY.md b/docs/reference/src/SUMMARY.md index 3918268d3..9f9371b12 100644 --- a/docs/reference/src/SUMMARY.md +++ b/docs/reference/src/SUMMARY.md @@ -11,35 +11,5 @@ - [Advanced Guides](guide/index.md) - [RSX in Depth](guide/rsx_in_depth.md) - - [Components](guide/components.md) - - [Props](guide/props.md) - - [Memoization](guide/memoization.md) - - [Performance](guide/performance.md) - - [Testing](guide/testing.md) - - [Building Elements with NodeFactory](guide/rsx.md) - - [Custom Elements](guide/custom_elements.md) - [Custom Renderer](guide/custom_renderer.md) - - [Server-side components](guide/server_side_components.md) - - [Bundling and Distributing](guide/bundline.md) - - [Hot Reloading Rsx](guide/hot_reloading.md) - -- [Reference Guide](reference/reference.md) - - [Anti-patterns](reference/anti.md) - - [Children](reference/children.md) - - [Conditional Rendering](reference/conditional.md) - - [Controlled Inputs](reference/controlled.md) - - [Custom Elements](reference/custom.md) - - [Empty Components](reference/empty.md) - - [Error Handling](reference/error.md) - - [Fragments](reference/fragments.md) - - [Global CSS](reference/global.md) - - [Inline Styles](reference/inline.md) - - [Iterators](reference/iterators.md) - - [Listeners](reference/listeners.md) - - [Memoization](reference/memoization.md) - - [Node Refs](reference/node.md) - - [Spread Pattern](reference/spread.md) - - [State Management](reference/state.md) - - [Suspense](reference/suspense.md) - - [task](reference/task.md) - - [Testing](reference/testing.md) + - [Hot Reloading Rsx](guide/hot_reloading.md) \ No newline at end of file diff --git a/docs/reference/src/depth/components.md b/docs/reference/src/depth/components.md deleted file mode 100644 index 0eac4eb52..000000000 --- a/docs/reference/src/depth/components.md +++ /dev/null @@ -1 +0,0 @@ -# Components diff --git a/docs/reference/src/depth/memoization.md b/docs/reference/src/depth/memoization.md deleted file mode 100644 index 3ed477f68..000000000 --- a/docs/reference/src/depth/memoization.md +++ /dev/null @@ -1 +0,0 @@ -# Memoization diff --git a/docs/reference/src/depth/performance.md b/docs/reference/src/depth/performance.md deleted file mode 100644 index 6babb314a..000000000 --- a/docs/reference/src/depth/performance.md +++ /dev/null @@ -1 +0,0 @@ -# Performance diff --git a/docs/reference/src/depth/props.md b/docs/reference/src/depth/props.md deleted file mode 100644 index d4b69bda7..000000000 --- a/docs/reference/src/depth/props.md +++ /dev/null @@ -1 +0,0 @@ -# Props diff --git a/docs/reference/src/depth/rsx.md b/docs/reference/src/depth/rsx.md deleted file mode 100644 index 1f349a7ef..000000000 --- a/docs/reference/src/depth/rsx.md +++ /dev/null @@ -1 +0,0 @@ -# RSX diff --git a/docs/reference/src/depth/testing.md b/docs/reference/src/depth/testing.md deleted file mode 100644 index f00b526a9..000000000 --- a/docs/reference/src/depth/testing.md +++ /dev/null @@ -1 +0,0 @@ -# Testing diff --git a/docs/reference/src/depth/topics.md b/docs/reference/src/depth/topics.md deleted file mode 100644 index 0cf65091f..000000000 --- a/docs/reference/src/depth/topics.md +++ /dev/null @@ -1 +0,0 @@ -# Topics in Depth diff --git a/docs/reference/src/guide/bundline.md b/docs/reference/src/guide/bundline.md deleted file mode 100644 index 963cf5490..000000000 --- a/docs/reference/src/guide/bundline.md +++ /dev/null @@ -1 +0,0 @@ -# Bundling and Distributing diff --git a/docs/reference/src/guide/components.md b/docs/reference/src/guide/components.md deleted file mode 100644 index 0eac4eb52..000000000 --- a/docs/reference/src/guide/components.md +++ /dev/null @@ -1 +0,0 @@ -# Components diff --git a/docs/reference/src/guide/custom_elements.md b/docs/reference/src/guide/custom_elements.md deleted file mode 100644 index 3b24ad9fe..000000000 --- a/docs/reference/src/guide/custom_elements.md +++ /dev/null @@ -1 +0,0 @@ -# Custom Elements diff --git a/docs/reference/src/guide/memoization.md b/docs/reference/src/guide/memoization.md deleted file mode 100644 index 3ed477f68..000000000 --- a/docs/reference/src/guide/memoization.md +++ /dev/null @@ -1 +0,0 @@ -# Memoization diff --git a/docs/reference/src/guide/performance.md b/docs/reference/src/guide/performance.md deleted file mode 100644 index 6babb314a..000000000 --- a/docs/reference/src/guide/performance.md +++ /dev/null @@ -1 +0,0 @@ -# Performance diff --git a/docs/reference/src/guide/props.md b/docs/reference/src/guide/props.md deleted file mode 100644 index d4b69bda7..000000000 --- a/docs/reference/src/guide/props.md +++ /dev/null @@ -1 +0,0 @@ -# Props diff --git a/docs/reference/src/guide/rsx.md b/docs/reference/src/guide/rsx.md deleted file mode 100644 index 189f5934c..000000000 --- a/docs/reference/src/guide/rsx.md +++ /dev/null @@ -1 +0,0 @@ -# Building Elements with NodeFactory diff --git a/docs/reference/src/guide/server_side_components.md b/docs/reference/src/guide/server_side_components.md deleted file mode 100644 index a8c4f6182..000000000 --- a/docs/reference/src/guide/server_side_components.md +++ /dev/null @@ -1 +0,0 @@ -# Server-side components diff --git a/docs/reference/src/guide/testing.md b/docs/reference/src/guide/testing.md deleted file mode 100644 index f00b526a9..000000000 --- a/docs/reference/src/guide/testing.md +++ /dev/null @@ -1 +0,0 @@ -# Testing From 69dd699b999f7672897ba10312a0716ceef1bf27 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Thu, 30 Jun 2022 22:58:34 +0300 Subject: [PATCH 27/30] Remove outdated example; Add note on performance benchmark --- examples/README.md | 8 ++++++-- examples/order.rs | 28 ---------------------------- 2 files changed, 6 insertions(+), 30 deletions(-) delete mode 100644 examples/order.rs diff --git a/examples/README.md b/examples/README.md index 2c07885bc..732b19ba8 100644 --- a/examples/README.md +++ b/examples/README.md @@ -74,8 +74,6 @@ cargo run --example hello_world [tasks](./tasks.rs) - Continuously run future -[order](./order.rs) - ### SVG [svg_basic](./svg_basic.rs) @@ -86,6 +84,12 @@ cargo run --example hello_world [framework_benchmark](./framework_benchmark.rs) - Renders a huge list +> Note: The benchmark should be run in release mode: +> +>```shell +> cargo run --example framework_benchmark --release +>``` + [heavy_compute](./heavy_compute.rs) - How to deal with expensive operations ## Server-side rendering diff --git a/examples/order.rs b/examples/order.rs deleted file mode 100644 index fee6f9184..000000000 --- a/examples/order.rs +++ /dev/null @@ -1,28 +0,0 @@ -#![allow(non_snake_case)] - -//! This example proves that instantly resolving futures don't cause issues - -use dioxus::prelude::*; - -fn main() { - dioxus::desktop::launch(App); -} - -fn App(cx: Scope) -> Element { - cx.render(rsx!(Demo {})) -} - -fn Demo(cx: Scope) -> Element { - let fut1 = use_future(&cx, (), |_| async move { - std::thread::sleep(std::time::Duration::from_millis(100)); - 10 - }); - - cx.render(match fut1.value() { - Some(value) => { - let content = format!("content : {:?}", value); - rsx!(div{ "{content}" }) - } - None => rsx!(div{"computing!"}), - }) -} From a8be789761d273fcfe0f543087f98eecb4a58eec Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Thu, 30 Jun 2022 23:15:36 +0300 Subject: [PATCH 28/30] Remove unneeded lifetimes This makes clippy (and me) happy --- packages/hooks/src/use_shared_state.rs | 2 +- packages/hooks/src/useref.rs | 5 +---- packages/hooks/src/usestate.rs | 12 ++++++------ 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index 78ac671c1..2d9f9eb60 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -60,7 +60,7 @@ impl ProvidedStateInner { /// /// /// -pub fn use_context<'a, T: 'static>(cx: &'a ScopeState) -> Option> { +pub fn use_context(cx: &ScopeState) -> Option> { let state = cx.use_hook(|_| { let scope_id = cx.scope_id(); let root = cx.consume_context::>(); diff --git a/packages/hooks/src/useref.rs b/packages/hooks/src/useref.rs index 6a384a5ff..73ef99a59 100644 --- a/packages/hooks/src/useref.rs +++ b/packages/hooks/src/useref.rs @@ -110,10 +110,7 @@ use std::{ /// } /// }) /// ``` -pub fn use_ref<'a, T: 'static>( - cx: &'a ScopeState, - initialize_refcell: impl FnOnce() -> T, -) -> &'a UseRef { +pub fn use_ref(cx: &ScopeState, initialize_refcell: impl FnOnce() -> T) -> &UseRef { let hook = cx.use_hook(|_| UseRef { update: cx.schedule_update(), value: Rc::new(RefCell::new(initialize_refcell())), diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index 60dc172b3..68440ca52 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -30,10 +30,10 @@ use std::{ /// )) /// } /// ``` -pub fn use_state<'a, T: 'static>( - cx: &'a ScopeState, +pub fn use_state( + cx: &ScopeState, initial_state_fn: impl FnOnce() -> T, -) -> &'a UseState { +) -> &UseState { let hook = cx.use_hook(move |_| { let current_val = Rc::new(initial_state_fn()); let update_callback = cx.schedule_update(); @@ -304,13 +304,13 @@ impl Clone for UseState { } } -impl<'a, T: 'static + Display> std::fmt::Display for UseState { +impl std::fmt::Display for UseState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.current_val) } } -impl<'a, T: std::fmt::Binary> std::fmt::Binary for UseState { +impl std::fmt::Binary for UseState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:b}", self.current_val.as_ref()) } @@ -341,7 +341,7 @@ impl Debug for UseState { } } -impl<'a, T> std::ops::Deref for UseState { +impl std::ops::Deref for UseState { type Target = T; fn deref(&self) -> &Self::Target { From cd7ee47e40d7e0e4b08a0bfcdc58f16d33a2ae96 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 30 Jun 2022 16:35:33 -0400 Subject: [PATCH 29/30] fix: make clippy happier with rust 1.63 --- packages/hooks/src/use_shared_state.rs | 2 +- packages/hooks/src/useref.rs | 5 +---- packages/hooks/src/usestate.rs | 12 ++++++------ 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index 78ac671c1..2d9f9eb60 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -60,7 +60,7 @@ impl ProvidedStateInner { /// /// /// -pub fn use_context<'a, T: 'static>(cx: &'a ScopeState) -> Option> { +pub fn use_context(cx: &ScopeState) -> Option> { let state = cx.use_hook(|_| { let scope_id = cx.scope_id(); let root = cx.consume_context::>(); diff --git a/packages/hooks/src/useref.rs b/packages/hooks/src/useref.rs index 6a384a5ff..73ef99a59 100644 --- a/packages/hooks/src/useref.rs +++ b/packages/hooks/src/useref.rs @@ -110,10 +110,7 @@ use std::{ /// } /// }) /// ``` -pub fn use_ref<'a, T: 'static>( - cx: &'a ScopeState, - initialize_refcell: impl FnOnce() -> T, -) -> &'a UseRef { +pub fn use_ref(cx: &ScopeState, initialize_refcell: impl FnOnce() -> T) -> &UseRef { let hook = cx.use_hook(|_| UseRef { update: cx.schedule_update(), value: Rc::new(RefCell::new(initialize_refcell())), diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index 60dc172b3..68440ca52 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -30,10 +30,10 @@ use std::{ /// )) /// } /// ``` -pub fn use_state<'a, T: 'static>( - cx: &'a ScopeState, +pub fn use_state( + cx: &ScopeState, initial_state_fn: impl FnOnce() -> T, -) -> &'a UseState { +) -> &UseState { let hook = cx.use_hook(move |_| { let current_val = Rc::new(initial_state_fn()); let update_callback = cx.schedule_update(); @@ -304,13 +304,13 @@ impl Clone for UseState { } } -impl<'a, T: 'static + Display> std::fmt::Display for UseState { +impl std::fmt::Display for UseState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.current_val) } } -impl<'a, T: std::fmt::Binary> std::fmt::Binary for UseState { +impl std::fmt::Binary for UseState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:b}", self.current_val.as_ref()) } @@ -341,7 +341,7 @@ impl Debug for UseState { } } -impl<'a, T> std::ops::Deref for UseState { +impl std::ops::Deref for UseState { type Target = T; fn deref(&self) -> &Self::Target { From f0655a11adddeed2e4ef5a72d600fdafd411ad6c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 1 Jul 2022 09:01:15 -0500 Subject: [PATCH 30/30] panic on errors in hot-reload if no error handler socket is connected --- packages/desktop/src/controller.rs | 2 + .../rsx_interpreter/src/captuered_context.rs | 4 +- packages/rsx_interpreter/src/error.rs | 17 ++++ packages/rsx_interpreter/src/interperter.rs | 13 ++- packages/rsx_interpreter/src/lib.rs | 2 + packages/web/src/lib.rs | 98 ++++++++++--------- 6 files changed, 84 insertions(+), 52 deletions(-) diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs index 6c0c7feef..717819182 100644 --- a/packages/desktop/src/controller.rs +++ b/packages/desktop/src/controller.rs @@ -86,6 +86,8 @@ impl DesktopController { (serde_json::to_string(&err).unwrap() + "\n").as_bytes(), ) .unwrap(); + } else { + panic!("{}", err); } } } diff --git a/packages/rsx_interpreter/src/captuered_context.rs b/packages/rsx_interpreter/src/captuered_context.rs index 8473771e9..665d8ed34 100644 --- a/packages/rsx_interpreter/src/captuered_context.rs +++ b/packages/rsx_interpreter/src/captuered_context.rs @@ -113,9 +113,9 @@ impl ToTokens for CapturedContextBuilder { let expr = segment.to_token_stream(); let as_string = expr.to_string(); let format_expr = if format_args.is_empty() { - "{".to_string() + format_args + "}" + "{".to_string() + &format_args + "}" } else { - "{".to_string() + ":" + format_args + "}" + "{".to_string() + ":" + &format_args + "}" }; Some(quote! { FormattedArg{ diff --git a/packages/rsx_interpreter/src/error.rs b/packages/rsx_interpreter/src/error.rs index 368d81d6f..82e5d4fb5 100644 --- a/packages/rsx_interpreter/src/error.rs +++ b/packages/rsx_interpreter/src/error.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use serde::{Deserialize, Serialize}; use crate::CodeLocation; @@ -37,3 +39,18 @@ impl ParseError { ParseError { message, location } } } + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::ParseError(error) => write!( + f, + "parse error:\n--> at {}:{}:{}\n\t{:?}\n", + error.location.file_path, error.location.line, error.location.column, error.message + ), + Error::RecompileRequiredError(reason) => { + write!(f, "recompile required: {:?}\n", reason) + } + } + } +} diff --git a/packages/rsx_interpreter/src/interperter.rs b/packages/rsx_interpreter/src/interperter.rs index f35488c05..07dc70bac 100644 --- a/packages/rsx_interpreter/src/interperter.rs +++ b/packages/rsx_interpreter/src/interperter.rs @@ -39,7 +39,7 @@ fn resolve_ifmt(ifmt: &IfmtInput, captured: &IfmtArgs) -> Result } } } - Segment::Literal(lit) => result.push_str(lit), + Segment::Literal(lit) => result.push_str(&lit), } } Ok(result) @@ -117,12 +117,12 @@ fn build_node<'a>( ElementAttr::AttrExpression { .. } | ElementAttr::CustomAttrExpression { .. } => { - let (name, value) = match &attr.attr { + let (name, value, span) = match &attr.attr { ElementAttr::AttrExpression { name, value } => { - (name.to_string(), value) + (name.to_string(), value, name.span()) } ElementAttr::CustomAttrExpression { name, value } => { - (name.value(), value) + (name.value(), value, name.span()) } _ => unreachable!(), }; @@ -140,6 +140,11 @@ fn build_node<'a>( is_volatile: false, namespace, }); + } else { + return Err(Error::ParseError(ParseError::new( + syn::Error::new(span, format!("unknown attribute: {}", name)), + ctx.location.clone(), + ))); } } else { return Err(Error::RecompileRequiredError( diff --git a/packages/rsx_interpreter/src/lib.rs b/packages/rsx_interpreter/src/lib.rs index ff50dcdb2..b5e596865 100644 --- a/packages/rsx_interpreter/src/lib.rs +++ b/packages/rsx_interpreter/src/lib.rs @@ -150,6 +150,8 @@ impl RsxContext { fn report_error(&self, error: Error) { if let Some(handler) = &self.data.write().unwrap().error_handler { handler.handle_error(error) + } else { + panic!("no error handler set for this platform...\n{}", error); } } diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 115e70fe5..e7a4d098d 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -62,6 +62,7 @@ use dioxus_core::prelude::Component; use dioxus_core::SchedulerMsg; use dioxus_core::VirtualDom; use futures_util::FutureExt; +use web_sys::console; mod cache; mod cfg; @@ -172,50 +173,6 @@ pub fn launch_with_props( pub async fn run_with_props(root: Component, root_props: T, cfg: WebConfig) { let mut dom = VirtualDom::new_with_props(root, root_props); - for s in crate::cache::BUILTIN_INTERNED_STRINGS { - wasm_bindgen::intern(s); - } - for s in &cfg.cached_strings { - wasm_bindgen::intern(s); - } - - let tasks = dom.get_scheduler_channel(); - - let sender_callback: Rc = - Rc::new(move |event| tasks.unbounded_send(event).unwrap()); - - let should_hydrate = cfg.hydrate; - - let mut websys_dom = dom::WebsysDom::new(cfg, sender_callback); - - log::trace!("rebuilding app"); - - if should_hydrate { - // todo: we need to split rebuild and initialize into two phases - // it's a waste to produce edits just to get the vdom loaded - let _ = dom.rebuild(); - - if let Err(err) = websys_dom.rehydrate(&dom) { - log::error!( - "Rehydration failed {:?}. Rebuild DOM into element from scratch", - &err - ); - - websys_dom.root.set_text_content(None); - - // errrrr we should split rebuild into two phases - // one that initializes things and one that produces edits - let edits = dom.rebuild(); - - websys_dom.apply_edits(edits.edits); - } - } else { - let edits = dom.rebuild(); - websys_dom.apply_edits(edits.edits); - } - - let mut work_loop = ric_raf::RafLoop::new(); - #[cfg(feature = "hot-reload")] { use dioxus_rsx_interpreter::error::Error; @@ -274,12 +231,61 @@ pub async fn run_with_props(root: Component, root_props: T // forward stream to the websocket dom.base_scope().spawn_forever(async move { while let Some(err) = error_channel_receiver.next().await { - ws.send_with_str(serde_json::to_string(&err).unwrap().as_str()) - .unwrap(); + if ws.ready_state() == WebSocket::OPEN { + ws.send_with_str(serde_json::to_string(&err).unwrap().as_str()) + .unwrap(); + } else { + console::warn_1(&"WebSocket is not open, cannot send error. Run with dioxus serve --hot-reload to enable hot reloading.".into()); + panic!("{}", err); + } } }); } + for s in crate::cache::BUILTIN_INTERNED_STRINGS { + wasm_bindgen::intern(s); + } + for s in &cfg.cached_strings { + wasm_bindgen::intern(s); + } + + let tasks = dom.get_scheduler_channel(); + + let sender_callback: Rc = + Rc::new(move |event| tasks.unbounded_send(event).unwrap()); + + let should_hydrate = cfg.hydrate; + + let mut websys_dom = dom::WebsysDom::new(cfg, sender_callback); + + log::trace!("rebuilding app"); + + if should_hydrate { + // todo: we need to split rebuild and initialize into two phases + // it's a waste to produce edits just to get the vdom loaded + let _ = dom.rebuild(); + + if let Err(err) = websys_dom.rehydrate(&dom) { + log::error!( + "Rehydration failed {:?}. Rebuild DOM into element from scratch", + &err + ); + + websys_dom.root.set_text_content(None); + + // errrrr we should split rebuild into two phases + // one that initializes things and one that produces edits + let edits = dom.rebuild(); + + websys_dom.apply_edits(edits.edits); + } + } else { + let edits = dom.rebuild(); + websys_dom.apply_edits(edits.edits); + } + + let mut work_loop = ric_raf::RafLoop::new(); + loop { log::trace!("waiting for work"); // if virtualdom has nothing, wait for it to have something before requesting idle time