From d7e4fcda801aea7d455d1f281afc520375c97879 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Thu, 12 May 2022 10:54:38 +0300 Subject: [PATCH 01/20] 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/20] 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/20] 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/20] 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/20] 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 | 358 +++++++++++++++++++------------- 4 files changed, 347 insertions(+), 180 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 { - '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, - }, - ' ' => 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, - // 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, - _ => 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:?}") - } - }; - // 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(), - })) + 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' => 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!(), + }, + ' ' => 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 + // '*' => 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, + }, + TermKeyCode::Null => return None, + TermKeyCode::Esc => Code::Escape, + }; + + 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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())); } } }