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
}