From 6b07a4290e3c534e80b83fb486284110cc1d004d Mon Sep 17 00:00:00 2001 From: marc2332 Date: Fri, 26 Jan 2024 15:23:19 +0100 Subject: [PATCH 1/8] chore: Fix docs typo --- book/src/guides/style.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/guides/style.md b/book/src/guides/style.md index a9184d8e3..2053e54de 100644 --- a/book/src/guides/style.md +++ b/book/src/guides/style.md @@ -126,7 +126,7 @@ Compatible elements: [`rect`](/guides/elements.html#rect) The attributes that have colors as values can use the following syntax: #### Static colors -- `rect` +- `red` - `blue` - `green` - `yellow` From 6842e8c1b16c31bdda84efe8ebbd93440a31ac47 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Sat, 27 Jan 2024 18:04:03 +0100 Subject: [PATCH 2/8] docs: Fix typo --- book/src/guides/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/guides/testing.md b/book/src/guides/testing.md index aa2d05d9e..0cf151773 100644 --- a/book/src/guides/testing.md +++ b/book/src/guides/testing.md @@ -139,7 +139,7 @@ async fn test() { let mut utils = launch_test_with_config( our_component, - TestingConfig::default().with_size((500.0, 800.0).into()), + *TestingConfig::default().with_size((500.0, 800.0).into()), ); let root = utils.root(); From 93c0909bf467e591101e655d780d59e64d25e82e Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Sun, 28 Jan 2024 10:59:25 +0100 Subject: [PATCH 3/8] chore: Update winit and accesskit (#458) * chore: Update winit and accesskit * refinements * improvements and fixes --- Cargo.toml | 12 +- crates/components/src/accordion.rs | 2 +- crates/components/src/button.rs | 2 +- crates/components/src/dropdown.rs | 4 +- crates/components/src/input.rs | 1 - crates/components/src/slider.rs | 2 +- crates/components/src/switch.rs | 2 +- .../accessibility/accessibility_provider.rs | 125 ++-- .../src/accessibility/accessibility_state.rs | 26 +- crates/core/src/types.rs | 6 +- crates/devtools/src/lib.rs | 3 +- crates/dom/src/dom.rs | 4 +- crates/elements/src/events/keyboard.rs | 628 ++++++++---------- crates/freya/src/launch.rs | 3 +- crates/hooks/src/use_accessibility.rs | 33 +- crates/hooks/src/use_focus.rs | 37 +- crates/renderer/src/accessibility.rs | 31 +- crates/renderer/src/app.rs | 6 +- crates/renderer/src/event_loop.rs | 107 +-- crates/renderer/src/lib.rs | 4 +- crates/renderer/src/window.rs | 8 +- crates/state/src/custom_attributes.rs | 4 +- crates/state/src/values/accessibility.rs | 15 +- crates/testing/src/launch.rs | 5 +- crates/testing/src/test_handler.rs | 7 +- 25 files changed, 487 insertions(+), 590 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8b1777bc7..c5b9c6fa7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,13 +39,13 @@ dioxus-router = { version = "0.4", default-features = false } skia-safe = { version = "0.67.0", features = ["gl", "textlayout", "svg"] } gl = "0.14.0" -glutin = "0.30.10" -glutin-winit = "0.3.0" -raw-window-handle = "0.5.1" -winit = "0.28.7" +glutin = "0.31.2" +glutin-winit = "0.4.2" +raw-window-handle = "0.5.2" +winit = "0.29.9" tokio = { version = "1.33.0", features = ["sync", "rt-multi-thread", "time", "macros"] } -accesskit = { version = "0.11.0", features = ["serde"]} -accesskit_winit = "0.14.1" +accesskit = { version = "0.12.2", features = ["serde"]} +accesskit_winit = "0.18.0" zbus = "3.14.1" euclid = "0.22.9" diff --git a/crates/components/src/accordion.rs b/crates/components/src/accordion.rs index 17e3bb781..2dca9d9f6 100644 --- a/crates/components/src/accordion.rs +++ b/crates/components/src/accordion.rs @@ -94,7 +94,7 @@ pub fn Accordion<'a>(cx: Scope<'a, AccordionProps<'a>>) -> Element<'a> { let onmouseenter = { to_owned![status, platform]; move |_| { - platform.set_cursor(CursorIcon::Hand); + platform.set_cursor(CursorIcon::Pointer); status.set(AccordionStatus::Hovering); } }; diff --git a/crates/components/src/button.rs b/crates/components/src/button.rs index bf9420943..e557640cf 100644 --- a/crates/components/src/button.rs +++ b/crates/components/src/button.rs @@ -92,7 +92,7 @@ pub fn Button<'a>(cx: Scope<'a, ButtonProps<'a>>) -> Element { let onmouseenter = { to_owned![status, platform]; move |_| { - platform.set_cursor(CursorIcon::Hand); + platform.set_cursor(CursorIcon::Pointer); status.set(ButtonStatus::Hovering); } }; diff --git a/crates/components/src/dropdown.rs b/crates/components/src/dropdown.rs index eed7956df..c0d85a252 100644 --- a/crates/components/src/dropdown.rs +++ b/crates/components/src/dropdown.rs @@ -84,7 +84,7 @@ where let onmouseenter = { to_owned![platform]; move |_| { - platform.set_cursor(CursorIcon::Hand); + platform.set_cursor(CursorIcon::Pointer); status.set(DropdownItemStatus::Hovering); } }; @@ -231,7 +231,7 @@ where let onmouseenter = { to_owned![status, platform]; move |_| { - platform.set_cursor(CursorIcon::Hand); + platform.set_cursor(CursorIcon::Pointer); status.set(DropdownStatus::Hovering); } }; diff --git a/crates/components/src/input.rs b/crates/components/src/input.rs index dcf825985..20ac7f65f 100644 --- a/crates/components/src/input.rs +++ b/crates/components/src/input.rs @@ -64,7 +64,6 @@ pub struct InputProps<'a> { /// ```rust,no_run /// # use freya::prelude::*; /// fn app(cx: Scope) -> Element { -/// use_init_focus(cx); /// let value = use_state(cx, String::new); /// /// render!( diff --git a/crates/components/src/slider.rs b/crates/components/src/slider.rs index d5743d7a4..5de205a3f 100644 --- a/crates/components/src/slider.rs +++ b/crates/components/src/slider.rs @@ -104,7 +104,7 @@ pub fn Slider<'a>(cx: Scope<'a, SliderProps>) -> Element<'a> { let onmouseenter = move |_: MouseEvent| { *status.write_silent() = SliderStatus::Hovering; - platform.set_cursor(CursorIcon::Hand); + platform.set_cursor(CursorIcon::Pointer); }; let onmouseover = move |e: MouseEvent| { diff --git a/crates/components/src/switch.rs b/crates/components/src/switch.rs index d839a0a40..18e23f894 100644 --- a/crates/components/src/switch.rs +++ b/crates/components/src/switch.rs @@ -83,7 +83,7 @@ pub fn Switch<'a>(cx: Scope<'a, SwitchProps<'a>>) -> Element<'a> { let onmouseenter = move |_: MouseEvent| { *status.write_silent() = SwitchStatus::Hovering; - platform.set_cursor(CursorIcon::Hand); + platform.set_cursor(CursorIcon::Pointer); }; let onclick = |_: MouseEvent| { diff --git a/crates/core/src/accessibility/accessibility_provider.rs b/crates/core/src/accessibility/accessibility_provider.rs index 3349ee164..e30659ec0 100644 --- a/crates/core/src/accessibility/accessibility_provider.rs +++ b/crates/core/src/accessibility/accessibility_provider.rs @@ -8,13 +8,14 @@ use dioxus_native_core::{ NodeId, }; use freya_dom::prelude::{DioxusDOM, DioxusNode}; -use freya_node_state::AccessibilityState; +use freya_node_state::AccessibilityNodeState; use std::slice::Iter; -use tokio::sync::watch; use torin::{prelude::NodeAreas, torin::Torin}; use crate::layout::*; +use super::accessibility_state::ACCESSIBILITY_ROOT_ID; + /// Direction for the next Accessibility Node to be focused. #[derive(PartialEq)] pub enum AccessibilityFocusDirection { @@ -29,7 +30,7 @@ pub trait AccessibilityProvider { dioxus_node: &DioxusNode, node_areas: &NodeAreas, accessibility_id: AccessibilityId, - node_accessibility: &AccessibilityState, + node_accessibility: &AccessibilityNodeState, ) { let mut builder = NodeBuilder::new(Role::Unknown); @@ -88,20 +89,17 @@ pub trait AccessibilityProvider { fn nodes(&self) -> Iter<(AccessibilityId, Node)>; /// Get the currently focused Node's ID. - fn focus_id(&self) -> Option; + fn focus_id(&self) -> AccessibilityId; /// Update the focused Node ID. - fn set_focus(&mut self, new_focus_id: Option); + fn set_focus(&mut self, new_focus_id: AccessibilityId); /// Update the focused Node ID and generate a TreeUpdate if necessary. - fn set_focus_with_update( - &mut self, - new_focus_id: Option, - ) -> Option { + fn set_focus_with_update(&mut self, new_focus_id: AccessibilityId) -> Option { self.set_focus(new_focus_id); // Only focus the element if it exists - let node_focused_exists = self.nodes().any(|node| Some(node.0) == new_focus_id); + let node_focused_exists = self.nodes().any(|node| node.0 == new_focus_id); if node_focused_exists { Some(TreeUpdate { nodes: Vec::new(), @@ -133,13 +131,16 @@ pub trait AccessibilityProvider { nodes.extend(self.nodes().cloned()); nodes.reverse(); - let focus = self.nodes().find_map(|node| { - if Some(node.0) == self.focus_id() { - Some(node.0) - } else { - None - } - }); + let focus = self + .nodes() + .find_map(|node| { + if node.0 == self.focus_id() { + Some(node.0) + } else { + None + } + }) + .unwrap_or(ACCESSIBILITY_ROOT_ID); TreeUpdate { nodes, @@ -149,58 +150,42 @@ pub trait AccessibilityProvider { } /// Focus the next/previous Node starting from the currently focused Node. - fn set_focus_on_next_node( - &mut self, - direction: AccessibilityFocusDirection, - focus_sender: &watch::Sender>, - ) -> Option { - // Start from the focused node or from the first registered node - let focused_node_id = self.focus_id().or(self.nodes().nth(0).map(|node| node.0)); - if let Some(focused_node_id) = focused_node_id { - let current_node = self - .nodes() - .enumerate() - .find(|(_, node)| node.0 == focused_node_id) - .map(|(i, _)| i); - - if let Some(node_index) = current_node { - let target_node_index = if direction == AccessibilityFocusDirection::Forward { - // Find the next Node - if node_index == self.nodes().len() - 1 { - 0 - } else { - node_index + 1 - } - } else { - // Find the previous Node - if node_index == 0 { - self.nodes().len() - 1 - } else { - node_index - 1 - } - }; - - let target_node = self - .nodes() - .enumerate() - .find(|(i, _)| *i == target_node_index) - .map(|(_, node)| node.0); - - self.set_focus(target_node); + fn set_focus_on_next_node(&mut self, direction: AccessibilityFocusDirection) -> TreeUpdate { + let node_index = self + .nodes() + .enumerate() + .find(|(_, node)| node.0 == self.focus_id()) + .map(|(i, _)| i) + .unwrap(); + + let target_node_index = if direction == AccessibilityFocusDirection::Forward { + // Find the next Node + if node_index == self.nodes().len() - 1 { + 0 } else { - // Select the first Node - self.set_focus(self.nodes().next().map(|(id, _)| *id)) + node_index + 1 } + } else { + // Find the previous Node + if node_index == 0 { + self.nodes().len() - 1 + } else { + node_index - 1 + } + }; - focus_sender.send(self.focus_id()).ok(); + let target_node = self + .nodes() + .nth(target_node_index) + .map(|(id, _)| *id) + .unwrap_or(ACCESSIBILITY_ROOT_ID); - Some(TreeUpdate { - nodes: Vec::new(), - tree: None, - focus: self.focus_id(), - }) - } else { - None + self.set_focus(target_node); + + TreeUpdate { + nodes: Vec::new(), + tree: None, + focus: self.focus_id(), } } } @@ -227,13 +212,13 @@ impl NodeAccessibility for DioxusNode<'_> { } } - /// Collect all the AccessibilityIDs from a Node's children + /// Collect all the AccessibilityIDs from a Node's AccessibilityNodeState fn get_accessibility_children(&self) -> Vec { self.children() .iter() .filter_map(|child| { - let node_accessibility = &*child.get::().unwrap(); - node_accessibility.focus_id + let node_accessibility = &*child.get::().unwrap(); + node_accessibility.accessibility_id }) .collect::>() } @@ -250,8 +235,8 @@ pub fn process_accessibility( let node_areas = layout.get(*node_id).unwrap(); let dioxus_node = rdom.get(*node_id); if let Some(dioxus_node) = dioxus_node { - let node_accessibility = &*dioxus_node.get::().unwrap(); - if let Some(accessibility_id) = node_accessibility.focus_id { + let node_accessibility = &*dioxus_node.get::().unwrap(); + if let Some(accessibility_id) = node_accessibility.accessibility_id { access_provider.add_node( &dioxus_node, node_areas, diff --git a/crates/core/src/accessibility/accessibility_state.rs b/crates/core/src/accessibility/accessibility_state.rs index c8c6b3a88..0a4fd0d15 100644 --- a/crates/core/src/accessibility/accessibility_state.rs +++ b/crates/core/src/accessibility/accessibility_state.rs @@ -1,16 +1,12 @@ use crate::accessibility::*; use accesskit::{Node, NodeClassSet, NodeId as AccessibilityId}; -use std::{ - num::NonZeroU128, - sync::{Arc, Mutex}, -}; +use std::sync::{Arc, Mutex}; pub type SharedAccessibilityState = Arc>; -pub const ROOT_ID: AccessibilityId = AccessibilityId(unsafe { NonZeroU128::new_unchecked(1) }); +pub const ACCESSIBILITY_ROOT_ID: AccessibilityId = AccessibilityId(0); /// Manages the Accessibility integration. -#[derive(Default)] pub struct AccessibilityState { /// Accessibility Nodes pub nodes: Vec<(AccessibilityId, Node)>, @@ -19,12 +15,16 @@ pub struct AccessibilityState { pub node_classes: NodeClassSet, /// Current focused Accessibility Node. - pub focus: Option, + pub focused_id: AccessibilityId, } impl AccessibilityState { - pub fn new() -> Self { - Self::default() + pub fn new(focused_id: AccessibilityId) -> Self { + Self { + focused_id, + node_classes: NodeClassSet::default(), + nodes: Vec::default(), + } } /// Wrap it in a `Arc>`. @@ -47,12 +47,12 @@ impl AccessibilityProvider for AccessibilityState { self.nodes.iter() } - fn focus_id(&self) -> Option { - self.focus + fn focus_id(&self) -> AccessibilityId { + self.focused_id } - fn set_focus(&mut self, new_focus_id: Option) { - self.focus = new_focus_id; + fn set_focus(&mut self, new_focus_id: AccessibilityId) { + self.focused_id = new_focus_id; } fn push_node(&mut self, id: AccessibilityId, node: Node) { diff --git a/crates/core/src/types.rs b/crates/core/src/types.rs index b3369e605..17070ad05 100644 --- a/crates/core/src/types.rs +++ b/crates/core/src/types.rs @@ -1,14 +1,14 @@ use crate::events::{DomEvent, FreyaEvent}; use dioxus_native_core::NodeId; -use accesskit::NodeId as AccessibilityId; +pub use accesskit::NodeId as AccessibilityId; use rustc_hash::FxHashMap; use smallvec::SmallVec; use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; use tokio::sync::watch; -pub type FocusSender = watch::Sender>; -pub type FocusReceiver = watch::Receiver>; +pub type FocusSender = watch::Sender; +pub type FocusReceiver = watch::Receiver; pub type EventEmitter = UnboundedSender; pub type EventReceiver = UnboundedReceiver; pub type EventsQueue = SmallVec<[FreyaEvent; 2]>; diff --git a/crates/devtools/src/lib.rs b/crates/devtools/src/lib.rs index 3a56d272f..0296b6ae2 100644 --- a/crates/devtools/src/lib.rs +++ b/crates/devtools/src/lib.rs @@ -9,7 +9,7 @@ use freya_components::*; use freya_core::node::{get_node_state, NodeState}; use freya_dom::prelude::SafeDOM; use freya_elements::elements as dioxus_elements; -use freya_hooks::{use_init_accessibility, use_init_focus, use_init_theme, use_theme, DARK_THEME}; +use freya_hooks::{use_init_accessibility, use_init_theme, use_theme, DARK_THEME}; use freya_renderer::HoveredNode; use std::sync::Arc; @@ -52,7 +52,6 @@ struct AppWithDevtoolsProps { #[allow(non_snake_case)] fn AppWithDevtools(cx: Scope) -> Element { - use_init_focus(cx); use_init_accessibility(cx); #[allow(non_snake_case)] diff --git a/crates/dom/src/dom.rs b/crates/dom/src/dom.rs index 4d0a1391c..4fe598b2c 100644 --- a/crates/dom/src/dom.rs +++ b/crates/dom/src/dom.rs @@ -8,7 +8,7 @@ use dioxus_native_core::{ }; use freya_node_state::{ - AccessibilityState, CursorSettings, CustomAttributeValues, FontStyleState, LayoutState, + AccessibilityNodeState, CursorSettings, CustomAttributeValues, FontStyleState, LayoutState, References, Style, Transform, }; use std::sync::MutexGuard; @@ -93,7 +93,7 @@ impl Default for FreyaDOM { LayoutState::to_type_erased(), Style::to_type_erased(), Transform::to_type_erased(), - AccessibilityState::to_type_erased(), + AccessibilityNodeState::to_type_erased(), ]); let dioxus_integration_state = DioxusState::create(&mut rdom); Self { diff --git a/crates/elements/src/events/keyboard.rs b/crates/elements/src/events/keyboard.rs index f5223e984..4f57ab7e3 100644 --- a/crates/elements/src/events/keyboard.rs +++ b/crates/elements/src/events/keyboard.rs @@ -1,359 +1,311 @@ pub use keyboard_types::{Code, Key, Modifiers}; -use winit::event::{ModifiersState, VirtualKeyCode}; +use winit::keyboard::{self, NamedKey}; -// Map winit modifiers to keyboard_types modifiers -pub fn get_modifiers(modifiers: ModifiersState) -> Modifiers { +// Return the equivalent of Winit `ModifiersState` in keyboard_types +pub fn map_winit_modifiers(modifiers: keyboard::ModifiersState) -> Modifiers { let mut new_modifiers = Modifiers::empty(); - if modifiers.alt() { + if modifiers.alt_key() { new_modifiers.toggle(Modifiers::ALT); } - if modifiers.ctrl() { + if modifiers.control_key() { new_modifiers.toggle(Modifiers::CONTROL); } - if modifiers.shift() { + if modifiers.shift_key() { new_modifiers.toggle(Modifiers::SHIFT); } - if modifiers.logo() { + if modifiers.super_key() { new_modifiers.toggle(Modifiers::META); } new_modifiers } -/// Only return keys that aren't text -pub fn get_non_text_keys(key: &VirtualKeyCode) -> Key { +/// Return the equivalent Winit's `Key` in keyboard_types +pub fn map_winit_key(key: &keyboard::Key) -> Key { match key { - VirtualKeyCode::Key1 => Key::Unidentified, - VirtualKeyCode::Key2 => Key::Unidentified, - VirtualKeyCode::Key3 => Key::Unidentified, - VirtualKeyCode::Key4 => Key::Unidentified, - VirtualKeyCode::Key5 => Key::Unidentified, - VirtualKeyCode::Key6 => Key::Unidentified, - VirtualKeyCode::Key7 => Key::Unidentified, - VirtualKeyCode::Key8 => Key::Unidentified, - VirtualKeyCode::Key9 => Key::Unidentified, - VirtualKeyCode::Key0 => Key::Unidentified, - VirtualKeyCode::A => Key::Unidentified, - VirtualKeyCode::B => Key::Unidentified, - VirtualKeyCode::C => Key::Unidentified, - VirtualKeyCode::D => Key::Unidentified, - VirtualKeyCode::E => Key::Unidentified, - VirtualKeyCode::F => Key::Unidentified, - VirtualKeyCode::G => Key::Unidentified, - VirtualKeyCode::H => Key::Unidentified, - VirtualKeyCode::I => Key::Unidentified, - VirtualKeyCode::J => Key::Unidentified, - VirtualKeyCode::K => Key::Unidentified, - VirtualKeyCode::L => Key::Unidentified, - VirtualKeyCode::M => Key::Unidentified, - VirtualKeyCode::N => Key::Unidentified, - VirtualKeyCode::O => Key::Unidentified, - VirtualKeyCode::P => Key::Unidentified, - VirtualKeyCode::Q => Key::Unidentified, - VirtualKeyCode::R => Key::Unidentified, - VirtualKeyCode::S => Key::Unidentified, - VirtualKeyCode::T => Key::Unidentified, - VirtualKeyCode::U => Key::Unidentified, - VirtualKeyCode::V => Key::Unidentified, - VirtualKeyCode::W => Key::Unidentified, - VirtualKeyCode::X => Key::Unidentified, - VirtualKeyCode::Y => Key::Unidentified, - VirtualKeyCode::Z => Key::Unidentified, - VirtualKeyCode::Escape => Key::Escape, - VirtualKeyCode::F1 => Key::F1, - VirtualKeyCode::F2 => Key::F2, - VirtualKeyCode::F3 => Key::F3, - VirtualKeyCode::F4 => Key::F4, - VirtualKeyCode::F5 => Key::F5, - VirtualKeyCode::F6 => Key::F6, - VirtualKeyCode::F7 => Key::F7, - VirtualKeyCode::F8 => Key::F8, - VirtualKeyCode::F9 => Key::F9, - VirtualKeyCode::F10 => Key::F10, - VirtualKeyCode::F11 => Key::F11, - VirtualKeyCode::F12 => Key::F12, - VirtualKeyCode::F13 => Key::F13, - VirtualKeyCode::F14 => Key::F14, - VirtualKeyCode::F15 => Key::F15, - VirtualKeyCode::F16 => Key::F16, - VirtualKeyCode::F17 => Key::F17, - VirtualKeyCode::F18 => Key::F18, - VirtualKeyCode::F19 => Key::F19, - VirtualKeyCode::F20 => Key::F20, - VirtualKeyCode::F21 => Key::F21, - VirtualKeyCode::F22 => Key::F22, - VirtualKeyCode::F23 => Key::F23, - VirtualKeyCode::F24 => Key::F24, - VirtualKeyCode::Snapshot => Key::Unidentified, - VirtualKeyCode::Scroll => Key::Unidentified, - VirtualKeyCode::Pause => Key::Pause, - VirtualKeyCode::Insert => Key::Insert, - VirtualKeyCode::Home => Key::Home, - VirtualKeyCode::Delete => Key::Delete, - VirtualKeyCode::End => Key::End, - VirtualKeyCode::PageDown => Key::PageDown, - VirtualKeyCode::PageUp => Key::PageUp, - VirtualKeyCode::Left => Key::ArrowLeft, - VirtualKeyCode::Up => Key::ArrowUp, - VirtualKeyCode::Right => Key::ArrowRight, - VirtualKeyCode::Down => Key::ArrowDown, - VirtualKeyCode::Back => Key::Backspace, - VirtualKeyCode::Return => Key::Enter, - VirtualKeyCode::Space => Key::Unidentified, - VirtualKeyCode::Compose => Key::Compose, - VirtualKeyCode::Caret => Key::Unidentified, - VirtualKeyCode::Numlock => Key::NumLock, - VirtualKeyCode::Numpad0 => Key::Unidentified, - VirtualKeyCode::Numpad1 => Key::Unidentified, - VirtualKeyCode::Numpad2 => Key::Unidentified, - VirtualKeyCode::Numpad3 => Key::Unidentified, - VirtualKeyCode::Numpad4 => Key::Unidentified, - VirtualKeyCode::Numpad5 => Key::Unidentified, - VirtualKeyCode::Numpad6 => Key::Unidentified, - VirtualKeyCode::Numpad7 => Key::Unidentified, - VirtualKeyCode::Numpad8 => Key::Unidentified, - VirtualKeyCode::Numpad9 => Key::Unidentified, - VirtualKeyCode::NumpadAdd => Key::Unidentified, - VirtualKeyCode::NumpadDivide => Key::Unidentified, - VirtualKeyCode::NumpadDecimal => Key::Unidentified, - VirtualKeyCode::NumpadComma => Key::Unidentified, - VirtualKeyCode::NumpadEnter => Key::Unidentified, - VirtualKeyCode::NumpadEquals => Key::Unidentified, - VirtualKeyCode::NumpadMultiply => Key::Unidentified, - VirtualKeyCode::NumpadSubtract => Key::Unidentified, - VirtualKeyCode::AbntC1 => Key::Unidentified, - VirtualKeyCode::AbntC2 => Key::Unidentified, - VirtualKeyCode::Apostrophe => Key::Unidentified, - VirtualKeyCode::Apps => Key::Unidentified, - VirtualKeyCode::Asterisk => Key::Unidentified, - VirtualKeyCode::At => Key::Unidentified, - VirtualKeyCode::Ax => Key::Unidentified, - VirtualKeyCode::Backslash => Key::Unidentified, - VirtualKeyCode::Calculator => Key::Unidentified, - VirtualKeyCode::Capital => Key::Unidentified, - VirtualKeyCode::Colon => Key::Unidentified, - VirtualKeyCode::Comma => Key::Unidentified, - VirtualKeyCode::Convert => Key::Unidentified, - VirtualKeyCode::Equals => Key::Unidentified, - VirtualKeyCode::Grave => Key::Unidentified, - VirtualKeyCode::Kana => Key::Unidentified, - VirtualKeyCode::Kanji => Key::Unidentified, - VirtualKeyCode::LAlt => Key::Alt, - VirtualKeyCode::LBracket => Key::Unidentified, - VirtualKeyCode::LControl => Key::Control, - VirtualKeyCode::LShift => Key::Shift, - VirtualKeyCode::LWin => Key::Meta, - VirtualKeyCode::Mail => Key::Unidentified, - VirtualKeyCode::MediaSelect => Key::Unidentified, - VirtualKeyCode::MediaStop => Key::Unidentified, - VirtualKeyCode::Minus => Key::Unidentified, - VirtualKeyCode::Mute => Key::Unidentified, - VirtualKeyCode::MyComputer => Key::Unidentified, - VirtualKeyCode::NavigateForward => Key::Unidentified, - VirtualKeyCode::NavigateBackward => Key::Unidentified, - VirtualKeyCode::NextTrack => Key::Unidentified, - VirtualKeyCode::NoConvert => Key::Unidentified, - VirtualKeyCode::OEM102 => Key::Unidentified, - VirtualKeyCode::Period => Key::Unidentified, - VirtualKeyCode::PlayPause => Key::Unidentified, - VirtualKeyCode::Plus => Key::Unidentified, - VirtualKeyCode::Power => Key::Unidentified, - VirtualKeyCode::PrevTrack => Key::Unidentified, - VirtualKeyCode::RAlt => Key::AltGraph, - VirtualKeyCode::RBracket => Key::Unidentified, - VirtualKeyCode::RControl => Key::Control, - VirtualKeyCode::RShift => Key::Shift, - VirtualKeyCode::RWin => Key::Meta, - VirtualKeyCode::Semicolon => Key::Unidentified, - VirtualKeyCode::Slash => Key::Unidentified, - VirtualKeyCode::Sleep => Key::Unidentified, - VirtualKeyCode::Stop => Key::Unidentified, - VirtualKeyCode::Sysrq => Key::Unidentified, - VirtualKeyCode::Tab => Key::Unidentified, - VirtualKeyCode::Underline => Key::Unidentified, - VirtualKeyCode::Unlabeled => Key::Unidentified, - VirtualKeyCode::VolumeDown => Key::AudioVolumeDown, - VirtualKeyCode::VolumeUp => Key::AudioVolumeUp, - VirtualKeyCode::Wake => Key::Unidentified, - VirtualKeyCode::WebBack => Key::Unidentified, - VirtualKeyCode::WebFavorites => Key::Unidentified, - VirtualKeyCode::WebForward => Key::Unidentified, - VirtualKeyCode::WebHome => Key::Unidentified, - VirtualKeyCode::WebRefresh => Key::Unidentified, - VirtualKeyCode::WebSearch => Key::Unidentified, - VirtualKeyCode::WebStop => Key::Unidentified, - VirtualKeyCode::Yen => Key::Unidentified, - VirtualKeyCode::Copy => Key::Copy, - VirtualKeyCode::Paste => Key::Paste, - VirtualKeyCode::Cut => Key::Cut, + keyboard::Key::Character(c) => Key::Character(c.to_string()), + keyboard::Key::Named(named_key) => { + match named_key { + NamedKey::Delete => Key::Delete, + NamedKey::Backspace => Key::Backspace, + // Take every variant of NamedKey and add the matching variant of Key + NamedKey::ArrowDown => Key::ArrowDown, + NamedKey::ArrowLeft => Key::ArrowLeft, + NamedKey::ArrowRight => Key::ArrowRight, + NamedKey::ArrowUp => Key::ArrowUp, + NamedKey::End => Key::End, + NamedKey::Home => Key::Home, + NamedKey::PageDown => Key::PageDown, + NamedKey::PageUp => Key::PageUp, + NamedKey::Tab => Key::Tab, + NamedKey::Enter => Key::Enter, + NamedKey::Escape => Key::Escape, + NamedKey::F1 => Key::F1, + NamedKey::F2 => Key::F2, + NamedKey::F3 => Key::F3, + NamedKey::F4 => Key::F4, + NamedKey::F5 => Key::F5, + NamedKey::F6 => Key::F6, + NamedKey::F7 => Key::F7, + NamedKey::F8 => Key::F8, + NamedKey::F9 => Key::F9, + NamedKey::F10 => Key::F10, + NamedKey::F11 => Key::F11, + NamedKey::F12 => Key::F12, + NamedKey::F13 => Key::F13, + NamedKey::F14 => Key::F14, + NamedKey::F15 => Key::F15, + NamedKey::F16 => Key::F16, + NamedKey::F17 => Key::F17, + NamedKey::F18 => Key::F18, + NamedKey::F19 => Key::F19, + NamedKey::F20 => Key::F20, + NamedKey::F21 => Key::F21, + NamedKey::F22 => Key::F22, + NamedKey::F23 => Key::F23, + NamedKey::F24 => Key::F24, + NamedKey::Pause => Key::Pause, + NamedKey::Insert => Key::Insert, + NamedKey::ContextMenu => Key::ContextMenu, + NamedKey::BrowserBack => Key::BrowserBack, + NamedKey::BrowserFavorites => Key::BrowserFavorites, + NamedKey::BrowserForward => Key::BrowserForward, + NamedKey::BrowserHome => Key::BrowserHome, + NamedKey::BrowserRefresh => Key::BrowserRefresh, + NamedKey::BrowserSearch => Key::BrowserSearch, + NamedKey::BrowserStop => Key::BrowserStop, + NamedKey::MediaTrackNext => Key::MediaTrackNext, + NamedKey::MediaPlayPause => Key::MediaPlayPause, + NamedKey::MediaTrackPrevious => Key::MediaTrackPrevious, + NamedKey::MediaStop => Key::MediaStop, + NamedKey::AudioVolumeDown => Key::AudioVolumeDown, + NamedKey::AudioVolumeMute => Key::AudioVolumeMute, + NamedKey::AudioVolumeUp => Key::AudioVolumeUp, + NamedKey::LaunchApplication2 => Key::LaunchApplication2, + NamedKey::LaunchMail => Key::LaunchMail, + NamedKey::Convert => Key::Convert, + NamedKey::Alt => Key::Alt, + NamedKey::AltGraph => Key::AltGraph, + NamedKey::CapsLock => Key::CapsLock, + NamedKey::Control => Key::Control, + NamedKey::Fn => Key::Fn, + NamedKey::FnLock => Key::FnLock, + NamedKey::NumLock => Key::NumLock, + NamedKey::ScrollLock => Key::ScrollLock, + NamedKey::Shift => Key::Shift, + NamedKey::Symbol => Key::Symbol, + NamedKey::SymbolLock => Key::SymbolLock, + NamedKey::Meta => Key::Meta, + NamedKey::Hyper => Key::Hyper, + NamedKey::Super => Key::Super, + NamedKey::Clear => Key::Clear, + NamedKey::Copy => Key::Copy, + NamedKey::CrSel => Key::CrSel, + NamedKey::Cut => Key::Cut, + NamedKey::EraseEof => Key::EraseEof, + NamedKey::ExSel => Key::ExSel, + NamedKey::Paste => Key::Paste, + NamedKey::Redo => Key::Redo, + NamedKey::Undo => Key::Undo, + NamedKey::Accept => Key::Accept, + NamedKey::Again => Key::Again, + NamedKey::Attn => Key::Attn, + NamedKey::Cancel => Key::Cancel, + NamedKey::Execute => Key::Execute, + NamedKey::Find => Key::Find, + NamedKey::Help => Key::Help, + NamedKey::Play => Key::Play, + NamedKey::Props => Key::Props, + NamedKey::Select => Key::Select, + NamedKey::ZoomIn => Key::ZoomIn, + NamedKey::ZoomOut => Key::ZoomOut, + NamedKey::BrightnessDown => Key::BrightnessDown, + NamedKey::BrightnessUp => Key::BrightnessUp, + NamedKey::Eject => Key::Eject, + NamedKey::LogOff => Key::LogOff, + NamedKey::Power => Key::Power, + NamedKey::PowerOff => Key::PowerOff, + NamedKey::PrintScreen => Key::PrintScreen, + NamedKey::Hibernate => Key::Hibernate, + NamedKey::Standby => Key::Standby, + NamedKey::WakeUp => Key::WakeUp, + NamedKey::AllCandidates => Key::AllCandidates, + NamedKey::Alphanumeric => Key::Alphanumeric, + NamedKey::CodeInput => Key::CodeInput, + NamedKey::Compose => Key::Compose, + NamedKey::FinalMode => Key::FinalMode, + NamedKey::GroupFirst => Key::GroupFirst, + NamedKey::GroupLast => Key::GroupLast, + NamedKey::GroupNext => Key::GroupNext, + NamedKey::GroupPrevious => Key::GroupPrevious, + NamedKey::ModeChange => Key::ModeChange, + NamedKey::NextCandidate => Key::NextCandidate, + NamedKey::NonConvert => Key::NonConvert, + NamedKey::PreviousCandidate => Key::PreviousCandidate, + NamedKey::Process => Key::Process, + NamedKey::SingleCandidate => Key::SingleCandidate, + NamedKey::HangulMode => Key::HangulMode, + NamedKey::HanjaMode => Key::HanjaMode, + NamedKey::JunjaMode => Key::JunjaMode, + NamedKey::Eisu => Key::Eisu, + NamedKey::Hankaku => Key::Hankaku, + NamedKey::Hiragana => Key::Hiragana, + NamedKey::HiraganaKatakana => Key::HiraganaKatakana, + NamedKey::KanaMode => Key::KanaMode, + NamedKey::KanjiMode => Key::KanjiMode, + NamedKey::Katakana => Key::Katakana, + NamedKey::Romaji => Key::Romaji, + NamedKey::Zenkaku => Key::Zenkaku, + NamedKey::ZenkakuHankaku => Key::ZenkakuHankaku, + NamedKey::Soft1 => Key::Soft1, + NamedKey::Soft2 => Key::Soft2, + NamedKey::Soft3 => Key::Soft3, + NamedKey::Soft4 => Key::Soft4, + NamedKey::ChannelDown => Key::ChannelDown, + NamedKey::ChannelUp => Key::ChannelUp, + NamedKey::Close => Key::Close, + NamedKey::MailForward => Key::MailForward, + NamedKey::MailReply => Key::MailReply, + NamedKey::MailSend => Key::MailSend, + NamedKey::MediaClose => Key::MediaClose, + NamedKey::MediaFastForward => Key::MediaFastForward, + NamedKey::MediaPause => Key::MediaPause, + NamedKey::MediaPlay => Key::MediaPlay, + NamedKey::MediaRecord => Key::MediaRecord, + _ => Key::Unidentified, + } + } + _ => Key::Unidentified, } } -/// Return the equivalent code of Winit and `keyboard_types` -pub fn from_winit_to_code(key: &VirtualKeyCode) -> Code { - match key { - VirtualKeyCode::Key1 => Code::Digit1, - VirtualKeyCode::Key2 => Code::Digit2, - VirtualKeyCode::Key3 => Code::Digit3, - VirtualKeyCode::Key4 => Code::Digit4, - VirtualKeyCode::Key5 => Code::Digit5, - VirtualKeyCode::Key6 => Code::Digit6, - VirtualKeyCode::Key7 => Code::Digit7, - VirtualKeyCode::Key8 => Code::Digit8, - VirtualKeyCode::Key9 => Code::Digit9, - VirtualKeyCode::Key0 => Code::Digit0, - VirtualKeyCode::A => Code::KeyA, - VirtualKeyCode::B => Code::KeyB, - VirtualKeyCode::C => Code::KeyC, - VirtualKeyCode::D => Code::KeyD, - VirtualKeyCode::E => Code::KeyE, - VirtualKeyCode::F => Code::KeyF, - VirtualKeyCode::G => Code::KeyG, - VirtualKeyCode::H => Code::KeyH, - VirtualKeyCode::I => Code::KeyI, - VirtualKeyCode::J => Code::KeyJ, - VirtualKeyCode::K => Code::KeyK, - VirtualKeyCode::L => Code::KeyL, - VirtualKeyCode::M => Code::KeyM, - VirtualKeyCode::N => Code::KeyN, - VirtualKeyCode::O => Code::KeyO, - VirtualKeyCode::P => Code::KeyP, - VirtualKeyCode::Q => Code::KeyQ, - VirtualKeyCode::R => Code::KeyR, - VirtualKeyCode::S => Code::KeyS, - VirtualKeyCode::T => Code::KeyT, - VirtualKeyCode::U => Code::KeyU, - VirtualKeyCode::V => Code::KeyV, - VirtualKeyCode::W => Code::KeyW, - VirtualKeyCode::X => Code::KeyX, - VirtualKeyCode::Y => Code::KeyY, - VirtualKeyCode::Z => Code::KeyZ, - VirtualKeyCode::Escape => Code::Escape, - VirtualKeyCode::F1 => Code::F1, - VirtualKeyCode::F2 => Code::F2, - VirtualKeyCode::F3 => Code::F3, - VirtualKeyCode::F4 => Code::F4, - VirtualKeyCode::F5 => Code::F5, - VirtualKeyCode::F6 => Code::F6, - VirtualKeyCode::F7 => Code::F7, - VirtualKeyCode::F8 => Code::F8, - VirtualKeyCode::F9 => Code::F9, - VirtualKeyCode::F10 => Code::F10, - VirtualKeyCode::F11 => Code::F11, - VirtualKeyCode::F12 => Code::F12, - VirtualKeyCode::F13 => Code::F13, - VirtualKeyCode::F14 => Code::F14, - VirtualKeyCode::F15 => Code::F15, - VirtualKeyCode::F16 => Code::F16, - VirtualKeyCode::F17 => Code::F17, - VirtualKeyCode::F18 => Code::F18, - VirtualKeyCode::F19 => Code::F19, - VirtualKeyCode::F20 => Code::F20, - VirtualKeyCode::F21 => Code::F21, - VirtualKeyCode::F22 => Code::F22, - VirtualKeyCode::F23 => Code::F23, - VirtualKeyCode::F24 => Code::F24, - VirtualKeyCode::Snapshot => Code::Unidentified, - VirtualKeyCode::Scroll => Code::Unidentified, - VirtualKeyCode::Pause => Code::Pause, - VirtualKeyCode::Insert => Code::Insert, - VirtualKeyCode::Home => Code::Home, - VirtualKeyCode::Delete => Code::Delete, - VirtualKeyCode::End => Code::End, - VirtualKeyCode::PageDown => Code::PageDown, - VirtualKeyCode::PageUp => Code::PageUp, - VirtualKeyCode::Left => Code::ArrowLeft, - VirtualKeyCode::Up => Code::ArrowUp, - VirtualKeyCode::Right => Code::ArrowRight, - VirtualKeyCode::Down => Code::ArrowDown, - VirtualKeyCode::Back => Code::Backspace, - VirtualKeyCode::Return => Code::Enter, - VirtualKeyCode::Space => Code::Space, - VirtualKeyCode::Compose => Code::Unidentified, - VirtualKeyCode::Caret => Code::Unidentified, - VirtualKeyCode::Numlock => Code::NumLock, - VirtualKeyCode::Numpad0 => Code::Numpad0, - VirtualKeyCode::Numpad1 => Code::Numpad1, - VirtualKeyCode::Numpad2 => Code::Numpad2, - VirtualKeyCode::Numpad3 => Code::Numpad3, - VirtualKeyCode::Numpad4 => Code::Numpad4, - VirtualKeyCode::Numpad5 => Code::Numpad5, - VirtualKeyCode::Numpad6 => Code::Numpad6, - VirtualKeyCode::Numpad7 => Code::Numpad7, - VirtualKeyCode::Numpad8 => Code::Numpad8, - VirtualKeyCode::Numpad9 => Code::Numpad9, - VirtualKeyCode::NumpadAdd => Code::NumpadAdd, - VirtualKeyCode::NumpadDivide => Code::NumpadDivide, - VirtualKeyCode::NumpadDecimal => Code::NumpadDecimal, - VirtualKeyCode::NumpadComma => Code::NumpadComma, - VirtualKeyCode::NumpadEnter => Code::NumpadEnter, - VirtualKeyCode::NumpadEquals => Code::NumpadEqual, - VirtualKeyCode::NumpadMultiply => Code::NumpadMultiply, - VirtualKeyCode::NumpadSubtract => Code::NumpadSubtract, - VirtualKeyCode::AbntC1 => Code::Unidentified, - VirtualKeyCode::AbntC2 => Code::Unidentified, - VirtualKeyCode::Apostrophe => Code::Unidentified, - VirtualKeyCode::Apps => Code::Unidentified, - VirtualKeyCode::Asterisk => Code::Unidentified, - VirtualKeyCode::At => Code::Unidentified, - VirtualKeyCode::Ax => Code::Unidentified, - VirtualKeyCode::Backslash => Code::Backslash, - VirtualKeyCode::Calculator => Code::Unidentified, - VirtualKeyCode::Capital => Code::Unidentified, - VirtualKeyCode::Colon => Code::Unidentified, - VirtualKeyCode::Comma => Code::Comma, - VirtualKeyCode::Convert => Code::Convert, - VirtualKeyCode::Equals => Code::Equal, - VirtualKeyCode::Grave => Code::Unidentified, - VirtualKeyCode::Kana => Code::Unidentified, - VirtualKeyCode::Kanji => Code::Unidentified, - VirtualKeyCode::LAlt => Code::Unidentified, - VirtualKeyCode::LBracket => Code::BracketLeft, - VirtualKeyCode::LControl => Code::ControlLeft, - VirtualKeyCode::LShift => Code::ShiftLeft, - VirtualKeyCode::LWin => Code::MetaLeft, - VirtualKeyCode::Mail => Code::Unidentified, - VirtualKeyCode::MediaSelect => Code::Unidentified, - VirtualKeyCode::MediaStop => Code::Unidentified, - VirtualKeyCode::Minus => Code::Unidentified, - VirtualKeyCode::Mute => Code::Unidentified, - VirtualKeyCode::MyComputer => Code::Unidentified, - VirtualKeyCode::NavigateForward => Code::Unidentified, - VirtualKeyCode::NavigateBackward => Code::Unidentified, - VirtualKeyCode::NextTrack => Code::Unidentified, - VirtualKeyCode::NoConvert => Code::Unidentified, - VirtualKeyCode::OEM102 => Code::Unidentified, - VirtualKeyCode::Period => Code::Unidentified, - VirtualKeyCode::PlayPause => Code::Unidentified, - VirtualKeyCode::Plus => Code::Unidentified, - VirtualKeyCode::Power => Code::Unidentified, - VirtualKeyCode::PrevTrack => Code::Unidentified, - VirtualKeyCode::RAlt => Code::AltRight, - VirtualKeyCode::RBracket => Code::BracketRight, - VirtualKeyCode::RControl => Code::ControlRight, - VirtualKeyCode::RShift => Code::ShiftRight, - VirtualKeyCode::RWin => Code::MetaRight, - VirtualKeyCode::Semicolon => Code::Semicolon, - VirtualKeyCode::Slash => Code::Unidentified, - VirtualKeyCode::Sleep => Code::Unidentified, - VirtualKeyCode::Stop => Code::Unidentified, - VirtualKeyCode::Sysrq => Code::Unidentified, - VirtualKeyCode::Tab => Code::Tab, - VirtualKeyCode::Underline => Code::Unidentified, - VirtualKeyCode::Unlabeled => Code::Unidentified, - VirtualKeyCode::VolumeDown => Code::AudioVolumeDown, - VirtualKeyCode::VolumeUp => Code::AudioVolumeUp, - VirtualKeyCode::Wake => Code::Unidentified, - VirtualKeyCode::WebBack => Code::Unidentified, - VirtualKeyCode::WebFavorites => Code::Unidentified, - VirtualKeyCode::WebForward => Code::Unidentified, - VirtualKeyCode::WebHome => Code::Unidentified, - VirtualKeyCode::WebRefresh => Code::Unidentified, - VirtualKeyCode::WebSearch => Code::Unidentified, - VirtualKeyCode::WebStop => Code::Unidentified, - VirtualKeyCode::Yen => Code::IntlYen, - VirtualKeyCode::Copy => Code::Copy, - VirtualKeyCode::Paste => Code::Paste, - VirtualKeyCode::Cut => Code::Cut, +/// Return the equivalent of Winit's `PhysicalKey` in keyboard_types +pub fn map_winit_physical_key(key: &keyboard::PhysicalKey) -> Code { + if let keyboard::PhysicalKey::Code(key) = key { + match key { + keyboard::KeyCode::Digit1 => Code::Digit1, + keyboard::KeyCode::Digit2 => Code::Digit2, + keyboard::KeyCode::Digit3 => Code::Digit3, + keyboard::KeyCode::Digit4 => Code::Digit4, + keyboard::KeyCode::Digit5 => Code::Digit5, + keyboard::KeyCode::Digit6 => Code::Digit6, + keyboard::KeyCode::Digit7 => Code::Digit7, + keyboard::KeyCode::Digit8 => Code::Digit8, + keyboard::KeyCode::Digit9 => Code::Digit9, + keyboard::KeyCode::Digit0 => Code::Digit0, + keyboard::KeyCode::KeyA => Code::KeyA, + keyboard::KeyCode::KeyB => Code::KeyB, + keyboard::KeyCode::KeyC => Code::KeyC, + keyboard::KeyCode::KeyD => Code::KeyD, + keyboard::KeyCode::KeyE => Code::KeyE, + keyboard::KeyCode::KeyF => Code::KeyF, + keyboard::KeyCode::KeyG => Code::KeyG, + keyboard::KeyCode::KeyH => Code::KeyH, + keyboard::KeyCode::KeyI => Code::KeyI, + keyboard::KeyCode::KeyJ => Code::KeyJ, + keyboard::KeyCode::KeyK => Code::KeyK, + keyboard::KeyCode::KeyL => Code::KeyL, + keyboard::KeyCode::KeyM => Code::KeyM, + keyboard::KeyCode::KeyN => Code::KeyN, + keyboard::KeyCode::KeyO => Code::KeyO, + keyboard::KeyCode::KeyP => Code::KeyP, + keyboard::KeyCode::KeyQ => Code::KeyQ, + keyboard::KeyCode::KeyR => Code::KeyR, + keyboard::KeyCode::KeyS => Code::KeyS, + keyboard::KeyCode::KeyT => Code::KeyT, + keyboard::KeyCode::KeyU => Code::KeyU, + keyboard::KeyCode::KeyV => Code::KeyV, + keyboard::KeyCode::KeyW => Code::KeyW, + keyboard::KeyCode::KeyX => Code::KeyX, + keyboard::KeyCode::KeyY => Code::KeyY, + keyboard::KeyCode::KeyZ => Code::KeyZ, + keyboard::KeyCode::Escape => Code::Escape, + keyboard::KeyCode::F1 => Code::F1, + keyboard::KeyCode::F2 => Code::F2, + keyboard::KeyCode::F3 => Code::F3, + keyboard::KeyCode::F4 => Code::F4, + keyboard::KeyCode::F5 => Code::F5, + keyboard::KeyCode::F6 => Code::F6, + keyboard::KeyCode::F7 => Code::F7, + keyboard::KeyCode::F8 => Code::F8, + keyboard::KeyCode::F9 => Code::F9, + keyboard::KeyCode::F10 => Code::F10, + keyboard::KeyCode::F11 => Code::F11, + keyboard::KeyCode::F12 => Code::F12, + keyboard::KeyCode::F13 => Code::F13, + keyboard::KeyCode::F14 => Code::F14, + keyboard::KeyCode::F15 => Code::F15, + keyboard::KeyCode::F16 => Code::F16, + keyboard::KeyCode::F17 => Code::F17, + keyboard::KeyCode::F18 => Code::F18, + keyboard::KeyCode::F19 => Code::F19, + keyboard::KeyCode::F20 => Code::F20, + keyboard::KeyCode::F21 => Code::F21, + keyboard::KeyCode::F22 => Code::F22, + keyboard::KeyCode::F23 => Code::F23, + keyboard::KeyCode::F24 => Code::F24, + keyboard::KeyCode::Pause => Code::Pause, + keyboard::KeyCode::Insert => Code::Insert, + keyboard::KeyCode::Home => Code::Home, + keyboard::KeyCode::Delete => Code::Delete, + keyboard::KeyCode::End => Code::End, + keyboard::KeyCode::PageDown => Code::PageDown, + keyboard::KeyCode::PageUp => Code::PageUp, + keyboard::KeyCode::ArrowLeft => Code::ArrowLeft, + keyboard::KeyCode::ArrowUp => Code::ArrowUp, + keyboard::KeyCode::ArrowRight => Code::ArrowRight, + keyboard::KeyCode::ArrowDown => Code::ArrowDown, + keyboard::KeyCode::Backspace => Code::Backspace, + keyboard::KeyCode::Enter => Code::Enter, + keyboard::KeyCode::Space => Code::Space, + keyboard::KeyCode::NumLock => Code::NumLock, + keyboard::KeyCode::Numpad0 => Code::Numpad0, + keyboard::KeyCode::Numpad1 => Code::Numpad1, + keyboard::KeyCode::Numpad2 => Code::Numpad2, + keyboard::KeyCode::Numpad3 => Code::Numpad3, + keyboard::KeyCode::Numpad4 => Code::Numpad4, + keyboard::KeyCode::Numpad5 => Code::Numpad5, + keyboard::KeyCode::Numpad6 => Code::Numpad6, + keyboard::KeyCode::Numpad7 => Code::Numpad7, + keyboard::KeyCode::Numpad8 => Code::Numpad8, + keyboard::KeyCode::Numpad9 => Code::Numpad9, + keyboard::KeyCode::NumpadAdd => Code::NumpadAdd, + keyboard::KeyCode::NumpadDivide => Code::NumpadDivide, + keyboard::KeyCode::NumpadDecimal => Code::NumpadDecimal, + keyboard::KeyCode::NumpadComma => Code::NumpadComma, + keyboard::KeyCode::NumpadEnter => Code::NumpadEnter, + keyboard::KeyCode::NumpadEqual => Code::NumpadEqual, + keyboard::KeyCode::NumpadMultiply => Code::NumpadMultiply, + keyboard::KeyCode::NumpadSubtract => Code::NumpadSubtract, + keyboard::KeyCode::Backslash => Code::Backslash, + keyboard::KeyCode::Comma => Code::Comma, + keyboard::KeyCode::Convert => Code::Convert, + keyboard::KeyCode::Equal => Code::Equal, + keyboard::KeyCode::BracketLeft => Code::BracketLeft, + keyboard::KeyCode::BracketRight => Code::BracketRight, + keyboard::KeyCode::ShiftLeft => Code::ShiftLeft, + keyboard::KeyCode::Meta => Code::MetaLeft, + keyboard::KeyCode::MediaSelect => Code::Unidentified, + keyboard::KeyCode::MediaStop => Code::Unidentified, + keyboard::KeyCode::Minus => Code::Unidentified, + keyboard::KeyCode::Period => Code::Unidentified, + keyboard::KeyCode::Power => Code::Unidentified, + keyboard::KeyCode::AltRight => Code::AltRight, + keyboard::KeyCode::ControlLeft => Code::ControlLeft, + keyboard::KeyCode::ControlRight => Code::ControlRight, + keyboard::KeyCode::ShiftRight => Code::ShiftRight, + keyboard::KeyCode::Semicolon => Code::Semicolon, + keyboard::KeyCode::Slash => Code::Unidentified, + keyboard::KeyCode::Sleep => Code::Unidentified, + keyboard::KeyCode::Tab => Code::Tab, + keyboard::KeyCode::AudioVolumeUp => Code::AudioVolumeUp, + keyboard::KeyCode::IntlYen => Code::IntlYen, + keyboard::KeyCode::Copy => Code::Copy, + keyboard::KeyCode::Paste => Code::Paste, + keyboard::KeyCode::Cut => Code::Cut, + _ => Code::Unidentified, + } + } else { + Code::Unidentified } } diff --git a/crates/freya/src/launch.rs b/crates/freya/src/launch.rs index 3693ea9bf..9c4186627 100644 --- a/crates/freya/src/launch.rs +++ b/crates/freya/src/launch.rs @@ -233,7 +233,7 @@ fn with_accessibility(app: Component) -> VirtualDom { use dioxus_core::fc_to_builder; use dioxus_core::{Element, Scope}; use dioxus_core_macro::render; - use freya_hooks::{use_init_accessibility, use_init_focus}; + use freya_hooks::use_init_accessibility; struct RootProps { app: Component, @@ -241,7 +241,6 @@ fn with_accessibility(app: Component) -> VirtualDom { #[allow(non_snake_case)] fn Root(cx: Scope) -> Element { - use_init_focus(cx); use_init_accessibility(cx); #[allow(non_snake_case)] diff --git a/crates/hooks/src/use_accessibility.rs b/crates/hooks/src/use_accessibility.rs index a835d9c71..a0ceb530c 100644 --- a/crates/hooks/src/use_accessibility.rs +++ b/crates/hooks/src/use_accessibility.rs @@ -1,24 +1,32 @@ +use std::rc::Rc; + use dioxus_core::ScopeState; -use dioxus_hooks::{to_owned, use_memo, use_shared_state}; use freya_common::EventMessage; use freya_core::{navigation_mode::NavigatorState, types::FocusReceiver}; -use crate::{use_platform, FocusId}; +use dioxus_hooks::{ + to_owned, use_context_provider, use_memo, use_shared_state, use_shared_state_provider, RefCell, +}; +use freya_core::{accessibility::ACCESSIBILITY_ROOT_ID, types::AccessibilityId}; + +use crate::use_platform; + +pub type AccessibilityIdCounter = Rc>; /// Sync both the Focus shared state and the platform accessibility focus pub fn use_init_accessibility(cx: &ScopeState) { let platform = use_platform(cx); - let focused_id = use_shared_state::>(cx).unwrap(); + use_context_provider(cx, || Rc::new(RefCell::new(0u64))); + use_shared_state_provider::(cx, || ACCESSIBILITY_ROOT_ID); + let focused_id = use_shared_state::(cx).unwrap(); let current_focused_id = *focused_id.read(); - // Tell the renderer the new focused node + // Notify the platform that a new Node has been focused manually let _ = use_memo(cx, &(current_focused_id,), move |(focused_id,)| { - if let Some(focused_id) = focused_id { - platform - .send(EventMessage::FocusAccessibilityNode(focused_id)) - .unwrap(); - } + platform + .send(EventMessage::FocusAccessibilityNode(focused_id)) + .unwrap(); }); cx.use_hook(|| { @@ -53,6 +61,7 @@ pub fn use_init_accessibility(cx: &ScopeState) { #[cfg(test)] mod test { use freya::prelude::*; + use freya_core::accessibility::ACCESSIBILITY_ROOT_ID; use freya_testing::{ events::pointer::MouseButton, launch_test_with_config, FreyaEvent, TestingConfig, }; @@ -88,7 +97,7 @@ mod test { // Initial state utils.wait_for_update().await; - assert!(utils.focus_id().is_none()); + assert_eq!(utils.focus_id(), ACCESSIBILITY_ROOT_ID); // Click on the first rect utils.push_event(FreyaEvent::Mouse { @@ -101,7 +110,7 @@ mod test { utils.wait_for_update().await; utils.wait_for_update().await; let first_focus_id = utils.focus_id(); - assert!(first_focus_id.is_some()); + assert_ne!(first_focus_id, ACCESSIBILITY_ROOT_ID); // Click on the second rect utils.push_event(FreyaEvent::Mouse { @@ -115,6 +124,6 @@ mod test { utils.wait_for_update().await; let second_focus_id = utils.focus_id(); assert_ne!(first_focus_id, second_focus_id); - assert!(second_focus_id.is_some()); + assert_ne!(second_focus_id, ACCESSIBILITY_ROOT_ID); } } diff --git a/crates/hooks/src/use_focus.rs b/crates/hooks/src/use_focus.rs index ca8a1dbb4..df9c4347a 100644 --- a/crates/hooks/src/use_focus.rs +++ b/crates/hooks/src/use_focus.rs @@ -1,27 +1,25 @@ -use std::num::NonZeroU128; - use accesskit::NodeId as AccessibilityId; use dioxus_core::{AttributeValue, Scope, ScopeState}; -use dioxus_hooks::{use_shared_state, use_shared_state_provider, UseSharedState}; +use dioxus_hooks::{use_context, use_shared_state, UseSharedState}; +use freya_core::accessibility::ACCESSIBILITY_ROOT_ID; use freya_core::navigation_mode::{NavigationMode, NavigatorState}; use freya_elements::events::{keyboard::Code, KeyboardEvent}; use freya_node_state::CustomAttributeValues; -use uuid::Uuid; -pub type FocusId = AccessibilityId; +use crate::AccessibilityIdCounter; /// Manage the focus operations of given Node #[derive(Clone)] pub struct UseFocus { id: AccessibilityId, - focused_id: UseSharedState>, + focused_id: UseSharedState, navigation_state: NavigatorState, } impl UseFocus { /// Focus this node pub fn focus(&self) { - *self.focused_id.write() = Some(self.id) + *self.focused_id.write() = self.id } /// Get the node focus ID @@ -31,12 +29,12 @@ impl UseFocus { /// Create a node focus ID attribute pub fn attribute<'b, T>(&self, cx: Scope<'b, T>) -> AttributeValue<'b> { - cx.any_value(CustomAttributeValues::FocusId(self.id)) + cx.any_value(CustomAttributeValues::AccessibilityId(self.id)) } /// Check if this node is currently focused pub fn is_focused(&self) -> bool { - Some(self.id) == *self.focused_id.read() + self.id == *self.focused_id.read() } /// Check if this node is currently selected @@ -46,7 +44,7 @@ impl UseFocus { /// Unfocus the currently focused node. pub fn unfocus(&self) { - *self.focused_id.write() = None; + *self.focused_id.write() = ACCESSIBILITY_ROOT_ID; } /// Validate keydown event @@ -57,27 +55,26 @@ impl UseFocus { /// Create a focus manager for a node. pub fn use_focus(cx: &ScopeState) -> &UseFocus { - let focused_id = use_shared_state::>(cx); + let accessibility_id_counter = use_context::(cx).unwrap(); + let focused_id = use_shared_state::(cx).unwrap(); + + cx.use_hook(|| { + let mut counter = accessibility_id_counter.borrow_mut(); + *counter += 1; + let id = AccessibilityId(*counter); - cx.use_hook(move || { - let focused_id = focused_id.unwrap().clone(); - let id = AccessibilityId(NonZeroU128::new(Uuid::new_v4().as_u128()).unwrap()); let navigation_state = cx .consume_context::() .expect("This is not expected, and likely a bug. Please, report it."); + UseFocus { id, - focused_id, + focused_id: focused_id.clone(), navigation_state, } }) } -/// Create a focus provider. -pub fn use_init_focus(cx: &ScopeState) { - use_shared_state_provider::>(cx, || None); -} - #[cfg(test)] mod test { use crate::use_focus; diff --git a/crates/renderer/src/accessibility.rs b/crates/renderer/src/accessibility.rs index 45821af1c..b07409243 100644 --- a/crates/renderer/src/accessibility.rs +++ b/crates/renderer/src/accessibility.rs @@ -4,7 +4,7 @@ use freya_common::EventMessage; use freya_core::{ prelude::{ AccessibilityFocusDirection, AccessibilityProvider, AccessibilityState, - SharedAccessibilityState, ROOT_ID, + SharedAccessibilityState, ACCESSIBILITY_ROOT_ID, }, types::FocusSender, }; @@ -19,14 +19,14 @@ pub struct NativeAccessibility { impl NativeAccessibility { pub fn new(window: &Window, proxy: EventLoopProxy) -> Self { let title = window.title(); - let accessibility_state = AccessibilityState::new().wrap(); + let accessibility_state = AccessibilityState::new(ACCESSIBILITY_ROOT_ID).wrap(); let accessibility_adapter = { let accessibility_state = accessibility_state.clone(); Adapter::new( window, move || { let mut accessibility_state = accessibility_state.lock().unwrap(); - accessibility_state.process(ROOT_ID, title.as_str()) + accessibility_state.process(ACCESSIBILITY_ROOT_ID, title.as_str()) }, proxy, ) @@ -47,15 +47,15 @@ impl NativeAccessibility { .accessibility_state .lock() .unwrap() - .set_focus_with_update(Some(id)); + .set_focus_with_update(id); if let Some(tree) = tree { - self.accessibility_adapter.update(tree); + self.accessibility_adapter.update_if_active(|| tree); } } - /// Validate a winit event for accessibility - pub fn on_accessibility_window_event(&mut self, window: &Window, event: &WindowEvent) -> bool { - self.accessibility_adapter.on_event(window, event) + /// Process an Accessibility event + pub fn process_accessibility_event(&mut self, window: &Window, event: &WindowEvent) { + self.accessibility_adapter.process_event(window, event) } /// Remove the accessibility nodes @@ -69,8 +69,8 @@ impl NativeAccessibility { .accessibility_state .lock() .unwrap() - .process(ROOT_ID, title); - self.accessibility_adapter.update(tree); + .process(ACCESSIBILITY_ROOT_ID, title); + self.accessibility_adapter.update_if_active(|| tree); } /// Focus the next accessibility node @@ -83,9 +83,12 @@ impl NativeAccessibility { .accessibility_state .lock() .unwrap() - .set_focus_on_next_node(direction, focus_sender); - if let Some(tree) = tree { - self.accessibility_adapter.update(tree); - } + .set_focus_on_next_node(direction); + + focus_sender + .send(tree.focus) + .expect("Failed to focus the Node."); + + self.accessibility_adapter.update_if_active(|| tree); } } diff --git a/crates/renderer/src/app.rs b/crates/renderer/src/app.rs index b34567c9f..7bdee026e 100644 --- a/crates/renderer/src/app.rs +++ b/crates/renderer/src/app.rs @@ -101,7 +101,7 @@ impl App { font_collection.set_dynamic_font_manager(mgr); let (event_emitter, event_receiver) = mpsc::unbounded_channel::(); - let (focus_sender, focus_receiver) = watch::channel(None); + let (focus_sender, focus_receiver) = watch::channel(ACCESSIBILITY_ROOT_ID); plugins.send(PluginEvent::WindowCreated(window_env.window_mut())); @@ -352,9 +352,9 @@ impl App { &mut self.accessibility } - pub fn on_window_event(&mut self, event: &WindowEvent) -> bool { + pub fn process_accessibility_event(&mut self, event: &WindowEvent) { self.accessibility - .on_accessibility_window_event(&self.window_env.window, event) + .process_accessibility_event(&self.window_env.window, event) } pub fn focus_next_node(&mut self, direction: AccessibilityFocusDirection) { diff --git a/crates/renderer/src/event_loop.rs b/crates/renderer/src/event_loop.rs index ae96902a3..96a1a8a9d 100644 --- a/crates/renderer/src/event_loop.rs +++ b/crates/renderer/src/event_loop.rs @@ -3,14 +3,14 @@ use accesskit_winit::ActionRequestEvent; use freya_common::EventMessage; use freya_core::prelude::*; use freya_elements::events::keyboard::{ - from_winit_to_code, get_modifiers, get_non_text_keys, Code, Key, + map_winit_key, map_winit_modifiers, map_winit_physical_key, }; use torin::geometry::CursorPoint; use winit::event::{ - ElementState, Event, KeyboardInput, ModifiersState, MouseScrollDelta, StartCause, Touch, - TouchPhase, VirtualKeyCode, WindowEvent, + ElementState, Event, KeyEvent, MouseScrollDelta, StartCause, Touch, TouchPhase, WindowEvent, }; -use winit::event_loop::{ControlFlow, EventLoop, EventLoopProxy}; +use winit::event_loop::{EventLoop, EventLoopProxy}; +use winit::keyboard::{KeyCode, ModifiersState, PhysicalKey}; use crate::app::App; use crate::HoveredNode; @@ -27,17 +27,14 @@ pub fn run_event_loop( hovered_node: HoveredNode, ) { let mut cursor_pos = CursorPoint::default(); - let mut last_keydown = Key::Unidentified; - let mut last_code = Code::Unidentified; let mut modifiers_state = ModifiersState::empty(); let window_env = app.window_env(); window_env.run_on_setup(); - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - match event { + event_loop + .run(move |event, event_loop| match event { Event::NewEvents(StartCause::Init) => { _ = proxy.send_event(EventMessage::PollVDOM); } @@ -76,14 +73,15 @@ pub fn run_event_loop( app.poll_vdom(); } } - Event::RedrawRequested(_) => { - app.process_layout(); - app.render(&hovered_node); - app.tick(); - } - Event::WindowEvent { event, .. } if app.on_window_event(&event) => { + Event::WindowEvent { event, .. } => { + app.process_accessibility_event(&event); match event { - WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::CloseRequested => event_loop.exit(), + WindowEvent::RedrawRequested => { + app.process_layout(); + app.render(&hovered_node); + app.tick(); + } WindowEvent::MouseInput { state, button, .. } => { app.set_navigation_mode(NavigationMode::NotKeyboard); @@ -118,32 +116,24 @@ pub fn run_event_loop( } } WindowEvent::ModifiersChanged(modifiers) => { - modifiers_state = modifiers; - } - WindowEvent::ReceivedCharacter(a) => { - // Emit the received character if the last pressed key wasn't text - if last_keydown == Key::Unidentified || !modifiers_state.is_empty() { - app.send_event(FreyaEvent::Keyboard { - name: "keydown".to_string(), - key: Key::Character(a.to_string()), - code: last_code, - modifiers: get_modifiers(modifiers_state), - }); - } + modifiers_state = modifiers.state(); } WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(virtual_keycode), + event: + KeyEvent { + physical_key, + logical_key, state, .. }, .. } => { - if state == ElementState::Pressed && virtual_keycode == VirtualKeyCode::Tab + if state == ElementState::Pressed + && physical_key == PhysicalKey::Code(KeyCode::Tab) { app.set_navigation_mode(NavigationMode::Keyboard); - let direction = if modifiers_state.shift() { + + let direction = if modifiers_state.shift_key() { AccessibilityFocusDirection::Backward } else { AccessibilityFocusDirection::Forward @@ -158,45 +148,12 @@ pub fn run_event_loop( ElementState::Pressed => "keydown", ElementState::Released => "keyup", }; - - // Only emit keys that aren't text (e.g ArrowUp isn't text) - // Text characters will be emitted by `WindowEvent::ReceivedCharacter` - let key = get_non_text_keys(&virtual_keycode); - if key != Key::Unidentified { - // Winit doesn't enable the alt modifier when pressing the AltGraph key, this is a workaround - if key == Key::AltGraph { - if state == ElementState::Pressed { - modifiers_state.insert(ModifiersState::ALT) - } else { - modifiers_state.remove(ModifiersState::ALT) - } - } - - if state == ElementState::Pressed { - // Cache this key so `WindowEvent::ReceivedCharacter` knows - // it shouldn't emit anything until this same key emits keyup - last_keydown = key.clone(); - } else { - // Uncache any key - last_keydown = Key::Unidentified; - } - app.send_event(FreyaEvent::Keyboard { - name: event_name.to_string(), - key, - code: from_winit_to_code(&virtual_keycode), - modifiers: get_modifiers(modifiers_state), - }); - } else { - last_keydown = Key::Unidentified; - } - - if state == ElementState::Pressed { - // Cache the key code on keydown event - last_code = from_winit_to_code(&virtual_keycode); - } else { - // Uncache any key code - last_code = Code::Unidentified; - } + app.send_event(FreyaEvent::Keyboard { + name: event_name.to_string(), + key: map_winit_key(&logical_key), + code: map_winit_physical_key(&physical_key), + modifiers: map_winit_modifiers(modifiers_state), + }) } WindowEvent::CursorMoved { position, .. } => { cursor_pos = CursorPoint::from((position.x, position.y)); @@ -237,10 +194,10 @@ pub fn run_event_loop( _ => {} } } - Event::LoopDestroyed => { + Event::LoopExiting => { app.window_env().run_on_exit(); } _ => (), - } - }); + }) + .expect("Failed to run Eventloop."); } diff --git a/crates/renderer/src/lib.rs b/crates/renderer/src/lib.rs index da278d55c..47fb2a486 100644 --- a/crates/renderer/src/lib.rs +++ b/crates/renderer/src/lib.rs @@ -41,7 +41,9 @@ impl DesktopRenderer { let _guard = rt.enter(); - let event_loop = EventLoopBuilder::::with_user_event().build(); + let event_loop = EventLoopBuilder::::with_user_event() + .build() + .expect("Failed to create event loop."); let proxy = event_loop.create_proxy(); // Hotreload support for Dioxus diff --git a/crates/renderer/src/window.rs b/crates/renderer/src/window.rs index 3383dab73..d7d33a183 100644 --- a/crates/renderer/src/window.rs +++ b/crates/renderer/src/window.rs @@ -3,19 +3,17 @@ use freya_common::EventMessage; use freya_core::prelude::*; use freya_dom::prelude::FreyaDOM; use freya_engine::prelude::*; -use glutin::prelude::{PossiblyCurrentContextGlSurfaceAccessor, PossiblyCurrentGlContext}; +use glutin::prelude::PossiblyCurrentGlContext; use std::ffi::CString; use std::num::NonZeroU32; use torin::geometry::{Area, Size2D}; use gl::{types::*, *}; use glutin::context::GlProfile; +use glutin::context::NotCurrentGlContext; use glutin::{ config::{ConfigTemplateBuilder, GlConfig}, - context::{ - ContextApi, ContextAttributesBuilder, NotCurrentGlContextSurfaceAccessor, - PossiblyCurrentContext, - }, + context::{ContextApi, ContextAttributesBuilder, PossiblyCurrentContext}, display::{GetGlDisplay, GlDisplay}, prelude::GlSurface, surface::{Surface as GlutinSurface, SurfaceAttributesBuilder, WindowSurface}, diff --git a/crates/state/src/custom_attributes.rs b/crates/state/src/custom_attributes.rs index 73f73d3d6..052df7b09 100644 --- a/crates/state/src/custom_attributes.rs +++ b/crates/state/src/custom_attributes.rs @@ -111,7 +111,7 @@ pub enum CustomAttributeValues { CursorReference(CursorReference), Bytes(Vec), ImageReference(ImageReference), - FocusId(AccessibilityId), + AccessibilityId(AccessibilityId), TextHighlights(Vec<(usize, usize)>), Canvas(CanvasReference), } @@ -123,7 +123,7 @@ impl Debug for CustomAttributeValues { Self::CursorReference(_) => f.debug_tuple("CursorReference").finish(), Self::Bytes(_) => f.debug_tuple("Bytes").finish(), Self::ImageReference(_) => f.debug_tuple("ImageReference").finish(), - Self::FocusId(_) => f.debug_tuple("FocusId").finish(), + Self::AccessibilityId(_) => f.debug_tuple("AccessibilityId").finish(), Self::TextHighlights(_) => f.debug_tuple("TextHighlights").finish(), Self::Canvas(_) => f.debug_tuple("Canvas").finish(), } diff --git a/crates/state/src/values/accessibility.rs b/crates/state/src/values/accessibility.rs index e18f3d112..5e83a4948 100644 --- a/crates/state/src/values/accessibility.rs +++ b/crates/state/src/values/accessibility.rs @@ -11,8 +11,8 @@ use dioxus_native_core_macro::partial_derive_state; use crate::CustomAttributeValues; #[derive(Clone, Debug, PartialEq, Eq, Default, Component)] -pub struct AccessibilityState { - pub focus_id: Option, +pub struct AccessibilityNodeState { + pub accessibility_id: Option, pub role: Option, pub alt: Option, pub name: Option, @@ -20,7 +20,7 @@ pub struct AccessibilityState { } #[partial_derive_state] -impl State for AccessibilityState { +impl State for AccessibilityNodeState { type ParentDependencies = (); type ChildDependencies = (); @@ -44,17 +44,18 @@ impl State for AccessibilityState { _children: Vec<::ElementBorrowed<'a>>, _context: &SendAnyMap, ) -> bool { - let mut accessibility = AccessibilityState::default(); + let mut accessibility = AccessibilityNodeState::default(); if let Some(attributes) = node_view.attributes() { for attr in attributes { #[allow(clippy::single_match)] match attr.attribute.name.as_str() { "focus_id" => { - if let OwnedAttributeValue::Custom(CustomAttributeValues::FocusId(id)) = - attr.value + if let OwnedAttributeValue::Custom( + CustomAttributeValues::AccessibilityId(id), + ) = attr.value { - accessibility.focus_id = Some(*id); + accessibility.accessibility_id = Some(*id); } } "role" => { diff --git a/crates/testing/src/launch.rs b/crates/testing/src/launch.rs index bdd8575c5..ef2b35f45 100644 --- a/crates/testing/src/launch.rs +++ b/crates/testing/src/launch.rs @@ -6,7 +6,7 @@ use freya_common::EventMessage; use freya_core::prelude::*; use freya_dom::prelude::{FreyaDOM, SafeDOM}; use freya_engine::prelude::*; -use freya_hooks::{use_init_accessibility, use_init_focus}; +use freya_hooks::use_init_accessibility; use std::sync::{Arc, Mutex}; use tokio::sync::broadcast; use tokio::sync::mpsc::unbounded_channel; @@ -44,7 +44,7 @@ pub fn launch_test_with_config(root: Component<()>, config: TestingConfig) -> Te config, platform_event_emitter, platform_event_receiver, - accessibility_state: SharedAccessibilityState::default(), + accessibility_state: Arc::new(Mutex::new(AccessibilityState::new(ACCESSIBILITY_ROOT_ID))), ticker_sender: broadcast::channel(5).0, navigation_state: NavigatorState::new(NavigationMode::NotKeyboard), }; @@ -61,7 +61,6 @@ fn with_accessibility(app: Component) -> VirtualDom { #[allow(non_snake_case)] fn Root(cx: Scope) -> Element { - use_init_focus(cx); use_init_accessibility(cx); #[allow(non_snake_case)] diff --git a/crates/testing/src/test_handler.rs b/crates/testing/src/test_handler.rs index 3c6d4b2d2..05b1d7229 100644 --- a/crates/testing/src/test_handler.rs +++ b/crates/testing/src/test_handler.rs @@ -100,10 +100,7 @@ impl TestingHandler { } } EventMessage::FocusAccessibilityNode(node_id) => { - self.accessibility_state - .lock() - .unwrap() - .set_focus(Some(node_id)); + self.accessibility_state.lock().unwrap().set_focus(node_id); } _ => {} } @@ -182,7 +179,7 @@ impl TestingHandler { self.utils.get_node_by_id(root_id) } - pub fn focus_id(&self) -> Option { + pub fn focus_id(&self) -> AccessibilityId { self.accessibility_state.lock().unwrap().focus_id() } } From a03f1b312007aef998a7037c3730b0e452c45b2b Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Sun, 28 Jan 2024 10:59:32 +0100 Subject: [PATCH 4/8] chore: Remove unnecessary mask flags on some node states (#481) --- crates/state/src/layout.rs | 5 +---- crates/state/src/transform.rs | 6 ++---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/crates/state/src/layout.rs b/crates/state/src/layout.rs index e459acc0e..b9e39b1dc 100644 --- a/crates/state/src/layout.rs +++ b/crates/state/src/layout.rs @@ -64,8 +64,7 @@ impl State for LayoutState { "position_bottom", "position_left", ])) - .with_tag() - .with_text(); + .with_tag(); fn update<'a>( &mut self, @@ -85,8 +84,6 @@ impl State for LayoutState { DirectionMode::Horizontal } else if let Some("text") = node_view.tag() { DirectionMode::Horizontal - } else if node_view.text().is_some() { - DirectionMode::Horizontal } else { DirectionMode::Vertical }, diff --git a/crates/state/src/transform.rs b/crates/state/src/transform.rs index 7941a68be..6f4f5f476 100644 --- a/crates/state/src/transform.rs +++ b/crates/state/src/transform.rs @@ -21,10 +21,8 @@ impl State for Transform { type NodeDependencies = (); - const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new() - .with_attrs(AttributeMaskBuilder::Some(&["rotate"])) - .with_tag() - .with_text(); + const NODE_MASK: NodeMaskBuilder<'static> = + NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["rotate"])); fn update<'a>( &mut self, From 4ebcc4181d201d63969df049d864c321faf07105 Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Sun, 28 Jan 2024 11:01:46 +0100 Subject: [PATCH 5/8] chore: Move Accessibility node state (#475) --- crates/state/src/{values => }/accessibility.rs | 0 crates/state/src/lib.rs | 2 ++ crates/state/src/values/mod.rs | 2 -- 3 files changed, 2 insertions(+), 2 deletions(-) rename crates/state/src/{values => }/accessibility.rs (100%) diff --git a/crates/state/src/values/accessibility.rs b/crates/state/src/accessibility.rs similarity index 100% rename from crates/state/src/values/accessibility.rs rename to crates/state/src/accessibility.rs diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index 42e476699..a8b918a24 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -1,3 +1,4 @@ +mod accessibility; mod cursor; mod custom_attributes; mod font_style; @@ -8,6 +9,7 @@ mod style; mod transform; mod values; +pub use accessibility::*; pub use cursor::*; pub use custom_attributes::*; pub use font_style::*; diff --git a/crates/state/src/values/mod.rs b/crates/state/src/values/mod.rs index 6d851aaaf..f8c6ab347 100644 --- a/crates/state/src/values/mod.rs +++ b/crates/state/src/values/mod.rs @@ -1,4 +1,3 @@ -mod accessibility; mod alignment; mod border; mod color; @@ -15,7 +14,6 @@ mod shadow; mod size; mod text_shadow; -pub use accessibility::*; pub use alignment::*; pub use border::*; pub use color::*; From aa2fa257424575139d142e58a7e641384efacc59 Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Sun, 28 Jan 2024 11:41:21 +0100 Subject: [PATCH 6/8] chore: Simplify accessibility integration (#484) * chore: Move Accessibility node state * chore: Simplify Accessibility integration by removing unnecessary abstractions * clean up --- .../accessibility/accessibility_manager.rs | 203 ++++++++++++++ .../accessibility/accessibility_provider.rs | 250 ------------------ .../src/accessibility/accessibility_state.rs | 61 ----- crates/core/src/accessibility/mod.rs | 83 +++++- crates/renderer/src/accessibility.rs | 32 +-- crates/renderer/src/app.rs | 10 +- crates/testing/src/launch.rs | 2 +- crates/testing/src/test_handler.rs | 6 +- 8 files changed, 307 insertions(+), 340 deletions(-) create mode 100644 crates/core/src/accessibility/accessibility_manager.rs delete mode 100644 crates/core/src/accessibility/accessibility_provider.rs delete mode 100644 crates/core/src/accessibility/accessibility_state.rs diff --git a/crates/core/src/accessibility/accessibility_manager.rs b/crates/core/src/accessibility/accessibility_manager.rs new file mode 100644 index 000000000..bbe14ec96 --- /dev/null +++ b/crates/core/src/accessibility/accessibility_manager.rs @@ -0,0 +1,203 @@ +use crate::accessibility::*; +use accesskit::{ + Action, DefaultActionVerb, Node, NodeBuilder, NodeClassSet, NodeId as AccessibilityId, Rect, + Role, Tree, TreeUpdate, +}; +use freya_dom::prelude::DioxusNode; +use freya_node_state::AccessibilityNodeState; +use std::sync::{Arc, Mutex}; +use torin::prelude::NodeAreas; + +pub type SharedAccessibilityManager = Arc>; + +pub const ACCESSIBILITY_ROOT_ID: AccessibilityId = AccessibilityId(0); + +/// Manages the Accessibility integration. +pub struct AccessibilityManager { + /// Accessibility Nodes + pub nodes: Vec<(AccessibilityId, Node)>, + /// Accessibility tree + pub node_classes: NodeClassSet, + /// Current focused Accessibility Node. + pub focused_id: AccessibilityId, +} + +impl AccessibilityManager { + pub fn new(focused_id: AccessibilityId) -> Self { + Self { + focused_id, + node_classes: NodeClassSet::default(), + nodes: Vec::default(), + } + } + + /// Wrap it in a `Arc>`. + pub fn wrap(self) -> SharedAccessibilityManager { + Arc::new(Mutex::new(self)) + } + + /// Clear the Accessibility Nodes. + pub fn clear(&mut self) { + self.nodes.clear(); + } + + pub fn push_node(&mut self, id: AccessibilityId, node: Node) { + self.nodes.push((id, node)) + } + + /// Add a Node to the Accessibility Tree. + pub fn add_node( + &mut self, + dioxus_node: &DioxusNode, + node_areas: &NodeAreas, + accessibility_id: AccessibilityId, + node_accessibility: &AccessibilityNodeState, + ) { + let mut builder = NodeBuilder::new(Role::Unknown); + + // Set children + let children = dioxus_node.get_accessibility_children(); + if !children.is_empty() { + builder.set_children(children); + } + + // Set text value + if let Some(alt) = &node_accessibility.alt { + builder.set_value(alt.to_owned()); + } else if let Some(value) = dioxus_node.get_inner_texts() { + builder.set_value(value); + } + + // Set name + if let Some(name) = &node_accessibility.name { + builder.set_name(name.to_owned()); + } + + // Set role + if let Some(role) = node_accessibility.role { + builder.set_role(role); + } + + // Set the area + let area = node_areas.area.to_f64(); + builder.set_bounds(Rect { + x0: area.min_x(), + x1: area.max_x(), + y0: area.min_y(), + y1: area.max_y(), + }); + + // Set focusable action + if node_accessibility.focusable { + builder.add_action(Action::Focus); + } else { + builder.add_action(Action::Default); + builder.set_default_action_verb(DefaultActionVerb::Focus); + } + + // Insert the node into the Tree + let node = builder.build(&mut self.node_classes); + self.push_node(accessibility_id, node); + } + + /// Update the focused Node ID and generate a TreeUpdate if necessary. + pub fn set_focus_with_update(&mut self, new_focus_id: AccessibilityId) -> Option { + self.focused_id = new_focus_id; + + // Only focus the element if it exists + let node_focused_exists = self.nodes.iter().any(|node| node.0 == new_focus_id); + if node_focused_exists { + Some(TreeUpdate { + nodes: Vec::new(), + tree: None, + focus: self.focused_id, + }) + } else { + None + } + } + + /// Create the root Accessibility Node. + pub fn build_root(&mut self, root_name: &str) -> Node { + let mut builder = NodeBuilder::new(Role::Window); + builder.set_name(root_name.to_string()); + builder.set_children( + self.nodes + .iter() + .map(|(id, _)| *id) + .collect::>(), + ); + + builder.build(&mut self.node_classes) + } + + /// Process the Nodes accessibility Tree + pub fn process(&mut self, root_id: AccessibilityId, root_name: &str) -> TreeUpdate { + let root = self.build_root(root_name); + let mut nodes = vec![(root_id, root)]; + nodes.extend(self.nodes.clone()); + nodes.reverse(); + + let focus = self + .nodes + .iter() + .find_map(|node| { + if node.0 == self.focused_id { + Some(node.0) + } else { + None + } + }) + .unwrap_or(ACCESSIBILITY_ROOT_ID); + + TreeUpdate { + nodes, + tree: Some(Tree::new(root_id)), + focus, + } + } + + /// Focus the next/previous Node starting from the currently focused Node. + pub fn set_focus_on_next_node(&mut self, direction: AccessibilityFocusDirection) -> TreeUpdate { + let node_index = self + .nodes + .iter() + .enumerate() + .find(|(_, node)| node.0 == self.focused_id) + .map(|(i, _)| i); + + let target_node = if direction == AccessibilityFocusDirection::Forward { + // Find the next Node + if let Some(node_index) = node_index { + if node_index == self.nodes.len() - 1 { + self.nodes.first() + } else { + self.nodes.get(node_index + 1) + } + } else { + self.nodes.first() + } + } else { + // Find the previous Node + if let Some(node_index) = node_index { + if node_index == 0 { + self.nodes.get(node_index - 1) + } else { + self.nodes.last() + } + } else { + self.nodes.last() + } + }; + + self.focused_id = target_node + .map(|(id, _)| *id) + .unwrap_or(ACCESSIBILITY_ROOT_ID); + + TreeUpdate { + nodes: Vec::new(), + tree: None, + focus: self.focused_id, + } + } +} diff --git a/crates/core/src/accessibility/accessibility_provider.rs b/crates/core/src/accessibility/accessibility_provider.rs deleted file mode 100644 index e30659ec0..000000000 --- a/crates/core/src/accessibility/accessibility_provider.rs +++ /dev/null @@ -1,250 +0,0 @@ -use accesskit::{ - Action, DefaultActionVerb, Node, NodeBuilder, NodeClassSet, NodeId as AccessibilityId, Rect, - Role, Tree, TreeUpdate, -}; -use dioxus_native_core::{ - prelude::{NodeType, TextNode}, - real_dom::NodeImmutable, - NodeId, -}; -use freya_dom::prelude::{DioxusDOM, DioxusNode}; -use freya_node_state::AccessibilityNodeState; -use std::slice::Iter; -use torin::{prelude::NodeAreas, torin::Torin}; - -use crate::layout::*; - -use super::accessibility_state::ACCESSIBILITY_ROOT_ID; - -/// Direction for the next Accessibility Node to be focused. -#[derive(PartialEq)] -pub enum AccessibilityFocusDirection { - Forward, - Backward, -} - -pub trait AccessibilityProvider { - /// Add a Node to the Accessibility Tree. - fn add_node( - &mut self, - dioxus_node: &DioxusNode, - node_areas: &NodeAreas, - accessibility_id: AccessibilityId, - node_accessibility: &AccessibilityNodeState, - ) { - let mut builder = NodeBuilder::new(Role::Unknown); - - // Set children - let children = dioxus_node.get_accessibility_children(); - if !children.is_empty() { - builder.set_children(children); - } - - // Set text value - if let Some(alt) = &node_accessibility.alt { - builder.set_value(alt.to_owned()); - } else if let Some(value) = dioxus_node.get_inner_texts() { - builder.set_value(value); - } - - // Set name - if let Some(name) = &node_accessibility.name { - builder.set_name(name.to_owned()); - } - - // Set role - if let Some(role) = node_accessibility.role { - builder.set_role(role); - } - - // Set the area - let area = node_areas.area.to_f64(); - builder.set_bounds(Rect { - x0: area.min_x(), - x1: area.max_x(), - y0: area.min_y(), - y1: area.max_y(), - }); - - // Set focusable action - if node_accessibility.focusable { - builder.add_action(Action::Focus); - } else { - builder.add_action(Action::Default); - builder.set_default_action_verb(DefaultActionVerb::Focus); - } - - // Insert the node into the Tree - let node = builder.build(self.node_classes()); - self.push_node(accessibility_id, node); - } - - /// Push a Node into the Accesibility Tree. - fn push_node(&mut self, id: AccessibilityId, node: Node); - - /// Mutable reference to the NodeClassSet. - fn node_classes(&mut self) -> &mut NodeClassSet; - - /// Iterator over the Accessibility Tree of Nodes. - fn nodes(&self) -> Iter<(AccessibilityId, Node)>; - - /// Get the currently focused Node's ID. - fn focus_id(&self) -> AccessibilityId; - - /// Update the focused Node ID. - fn set_focus(&mut self, new_focus_id: AccessibilityId); - - /// Update the focused Node ID and generate a TreeUpdate if necessary. - fn set_focus_with_update(&mut self, new_focus_id: AccessibilityId) -> Option { - self.set_focus(new_focus_id); - - // Only focus the element if it exists - let node_focused_exists = self.nodes().any(|node| node.0 == new_focus_id); - if node_focused_exists { - Some(TreeUpdate { - nodes: Vec::new(), - tree: None, - focus: self.focus_id(), - }) - } else { - None - } - } - - /// Create the root Accessibility Node. - fn build_root(&mut self, root_name: &str) -> Node { - let mut builder = NodeBuilder::new(Role::Window); - builder.set_name(root_name.to_string()); - builder.set_children( - self.nodes() - .map(|(id, _)| *id) - .collect::>(), - ); - - builder.build(self.node_classes()) - } - - /// Process the Nodes accessibility Tree - fn process(&mut self, root_id: AccessibilityId, root_name: &str) -> TreeUpdate { - let root = self.build_root(root_name); - let mut nodes = vec![(root_id, root)]; - nodes.extend(self.nodes().cloned()); - nodes.reverse(); - - let focus = self - .nodes() - .find_map(|node| { - if node.0 == self.focus_id() { - Some(node.0) - } else { - None - } - }) - .unwrap_or(ACCESSIBILITY_ROOT_ID); - - TreeUpdate { - nodes, - tree: Some(Tree::new(root_id)), - focus, - } - } - - /// Focus the next/previous Node starting from the currently focused Node. - fn set_focus_on_next_node(&mut self, direction: AccessibilityFocusDirection) -> TreeUpdate { - let node_index = self - .nodes() - .enumerate() - .find(|(_, node)| node.0 == self.focus_id()) - .map(|(i, _)| i) - .unwrap(); - - let target_node_index = if direction == AccessibilityFocusDirection::Forward { - // Find the next Node - if node_index == self.nodes().len() - 1 { - 0 - } else { - node_index + 1 - } - } else { - // Find the previous Node - if node_index == 0 { - self.nodes().len() - 1 - } else { - node_index - 1 - } - }; - - let target_node = self - .nodes() - .nth(target_node_index) - .map(|(id, _)| *id) - .unwrap_or(ACCESSIBILITY_ROOT_ID); - - self.set_focus(target_node); - - TreeUpdate { - nodes: Vec::new(), - tree: None, - focus: self.focus_id(), - } - } -} - -/// Shortcut functions to retrieve Acessibility info from a Dioxus Node -trait NodeAccessibility { - /// Return the first TextNode from this Node - fn get_inner_texts(&self) -> Option; - - /// Collect all the AccessibilityIDs from a Node's children - fn get_accessibility_children(&self) -> Vec; -} - -impl NodeAccessibility for DioxusNode<'_> { - /// Return the first TextNode from this Node - fn get_inner_texts(&self) -> Option { - let children = self.children(); - let first_child = children.first()?; - let node_type = first_child.node_type(); - if let NodeType::Text(TextNode { text, .. }) = &*node_type { - Some(text.to_owned()) - } else { - None - } - } - - /// Collect all the AccessibilityIDs from a Node's AccessibilityNodeState - fn get_accessibility_children(&self) -> Vec { - self.children() - .iter() - .filter_map(|child| { - let node_accessibility = &*child.get::().unwrap(); - node_accessibility.accessibility_id - }) - .collect::>() - } -} - -pub fn process_accessibility( - layers: &Layers, - layout: &Torin, - rdom: &DioxusDOM, - access_provider: &mut impl AccessibilityProvider, -) { - for layer in layers.layers.values() { - for node_id in layer { - let node_areas = layout.get(*node_id).unwrap(); - let dioxus_node = rdom.get(*node_id); - if let Some(dioxus_node) = dioxus_node { - let node_accessibility = &*dioxus_node.get::().unwrap(); - if let Some(accessibility_id) = node_accessibility.accessibility_id { - access_provider.add_node( - &dioxus_node, - node_areas, - accessibility_id, - node_accessibility, - ); - } - } - } - } -} diff --git a/crates/core/src/accessibility/accessibility_state.rs b/crates/core/src/accessibility/accessibility_state.rs deleted file mode 100644 index 0a4fd0d15..000000000 --- a/crates/core/src/accessibility/accessibility_state.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::accessibility::*; -use accesskit::{Node, NodeClassSet, NodeId as AccessibilityId}; -use std::sync::{Arc, Mutex}; - -pub type SharedAccessibilityState = Arc>; - -pub const ACCESSIBILITY_ROOT_ID: AccessibilityId = AccessibilityId(0); - -/// Manages the Accessibility integration. -pub struct AccessibilityState { - /// Accessibility Nodes - pub nodes: Vec<(AccessibilityId, Node)>, - - /// Accessibility tree - pub node_classes: NodeClassSet, - - /// Current focused Accessibility Node. - pub focused_id: AccessibilityId, -} - -impl AccessibilityState { - pub fn new(focused_id: AccessibilityId) -> Self { - Self { - focused_id, - node_classes: NodeClassSet::default(), - nodes: Vec::default(), - } - } - - /// Wrap it in a `Arc>`. - pub fn wrap(self) -> SharedAccessibilityState { - Arc::new(Mutex::new(self)) - } - - /// Clear the Accessibility Nodes. - pub fn clear(&mut self) { - self.nodes.clear(); - } -} - -impl AccessibilityProvider for AccessibilityState { - fn node_classes(&mut self) -> &mut NodeClassSet { - &mut self.node_classes - } - - fn nodes(&self) -> std::slice::Iter<(AccessibilityId, Node)> { - self.nodes.iter() - } - - fn focus_id(&self) -> AccessibilityId { - self.focused_id - } - - fn set_focus(&mut self, new_focus_id: AccessibilityId) { - self.focused_id = new_focus_id; - } - - fn push_node(&mut self, id: AccessibilityId, node: Node) { - self.nodes.push((id, node)) - } -} diff --git a/crates/core/src/accessibility/mod.rs b/crates/core/src/accessibility/mod.rs index 0ef7059dd..b202b16ed 100644 --- a/crates/core/src/accessibility/mod.rs +++ b/crates/core/src/accessibility/mod.rs @@ -1,5 +1,80 @@ -pub mod accessibility_provider; -pub mod accessibility_state; +pub mod accessibility_manager; +pub use accessibility_manager::*; -pub use accessibility_provider::*; -pub use accessibility_state::*; +use accesskit::NodeId as AccessibilityId; +use dioxus_native_core::{ + node::{NodeType, TextNode}, + real_dom::NodeImmutable, + NodeId, +}; +use freya_dom::dom::{DioxusDOM, DioxusNode}; +use freya_node_state::AccessibilityNodeState; +use torin::torin::Torin; + +use crate::layout::Layers; + +/// Direction for the next Accessibility Node to be focused. +#[derive(PartialEq)] +pub enum AccessibilityFocusDirection { + Forward, + Backward, +} + +/// Shortcut functions to retrieve Acessibility info from a Dioxus Node +trait NodeAccessibility { + /// Return the first TextNode from this Node + fn get_inner_texts(&self) -> Option; + + /// Collect all the AccessibilityIDs from a Node's children + fn get_accessibility_children(&self) -> Vec; +} + +impl NodeAccessibility for DioxusNode<'_> { + /// Return the first TextNode from this Node + fn get_inner_texts(&self) -> Option { + let children = self.children(); + let first_child = children.first()?; + let node_type = first_child.node_type(); + if let NodeType::Text(TextNode { text, .. }) = &*node_type { + Some(text.to_owned()) + } else { + None + } + } + + /// Collect all the AccessibilityIDs from a Node's children + fn get_accessibility_children(&self) -> Vec { + self.children() + .iter() + .filter_map(|child| { + let node_accessibility = &*child.get::().unwrap(); + node_accessibility.accessibility_id + }) + .collect::>() + } +} + +pub fn process_accessibility( + layers: &Layers, + layout: &Torin, + rdom: &DioxusDOM, + accessibility_manager: &mut AccessibilityManager, +) { + for layer in layers.layers.values() { + for node_id in layer { + let node_areas = layout.get(*node_id).unwrap(); + let dioxus_node = rdom.get(*node_id); + if let Some(dioxus_node) = dioxus_node { + let node_accessibility = &*dioxus_node.get::().unwrap(); + if let Some(accessibility_id) = node_accessibility.accessibility_id { + accessibility_manager.add_node( + &dioxus_node, + node_areas, + accessibility_id, + node_accessibility, + ); + } + } + } + } +} diff --git a/crates/renderer/src/accessibility.rs b/crates/renderer/src/accessibility.rs index b07409243..9419d6a6a 100644 --- a/crates/renderer/src/accessibility.rs +++ b/crates/renderer/src/accessibility.rs @@ -3,48 +3,48 @@ use accesskit_winit::Adapter; use freya_common::EventMessage; use freya_core::{ prelude::{ - AccessibilityFocusDirection, AccessibilityProvider, AccessibilityState, - SharedAccessibilityState, ACCESSIBILITY_ROOT_ID, + AccessibilityFocusDirection, AccessibilityManager, SharedAccessibilityManager, + ACCESSIBILITY_ROOT_ID, }, types::FocusSender, }; use winit::{event::WindowEvent, event_loop::EventLoopProxy, window::Window}; /// Manages the accessibility integration with Accesskit. -pub struct NativeAccessibility { - accessibility_state: SharedAccessibilityState, +pub struct AccessKitManager { + accessibility_manager: SharedAccessibilityManager, accessibility_adapter: Adapter, } -impl NativeAccessibility { +impl AccessKitManager { pub fn new(window: &Window, proxy: EventLoopProxy) -> Self { let title = window.title(); - let accessibility_state = AccessibilityState::new(ACCESSIBILITY_ROOT_ID).wrap(); + let accessibility_manager = AccessibilityManager::new(ACCESSIBILITY_ROOT_ID).wrap(); let accessibility_adapter = { - let accessibility_state = accessibility_state.clone(); + let accessibility_manager = accessibility_manager.clone(); Adapter::new( window, move || { - let mut accessibility_state = accessibility_state.lock().unwrap(); - accessibility_state.process(ACCESSIBILITY_ROOT_ID, title.as_str()) + let mut accessibility_manager = accessibility_manager.lock().unwrap(); + accessibility_manager.process(ACCESSIBILITY_ROOT_ID, title.as_str()) }, proxy, ) }; Self { - accessibility_state, + accessibility_manager, accessibility_adapter, } } - pub fn accessibility_state(&self) -> &SharedAccessibilityState { - &self.accessibility_state + pub fn accessibility_manager(&self) -> &SharedAccessibilityManager { + &self.accessibility_manager } /// Focus a new accessibility node pub fn set_accessibility_focus(&mut self, id: AccessibilityId) { let tree = self - .accessibility_state + .accessibility_manager .lock() .unwrap() .set_focus_with_update(id); @@ -60,13 +60,13 @@ impl NativeAccessibility { /// Remove the accessibility nodes pub fn clear_accessibility(&mut self) { - self.accessibility_state.lock().unwrap().clear(); + self.accessibility_manager.lock().unwrap().clear(); } /// Process the accessibility nodes pub fn render_accessibility(&mut self, title: &str) { let tree = self - .accessibility_state + .accessibility_manager .lock() .unwrap() .process(ACCESSIBILITY_ROOT_ID, title); @@ -80,7 +80,7 @@ impl NativeAccessibility { focus_sender: &FocusSender, ) { let tree = self - .accessibility_state + .accessibility_manager .lock() .unwrap() .set_focus_on_next_node(direction); diff --git a/crates/renderer/src/app.rs b/crates/renderer/src/app.rs index 7bdee026e..525d26e1a 100644 --- a/crates/renderer/src/app.rs +++ b/crates/renderer/src/app.rs @@ -20,7 +20,7 @@ use uuid::Uuid; use winit::event::WindowEvent; use winit::{dpi::PhysicalSize, event_loop::EventLoopProxy}; -use crate::accessibility::NativeAccessibility; +use crate::accessibility::AccessKitManager; use crate::{FontsConfig, HoveredNode, WindowEnv}; fn winit_waker(proxy: &EventLoopProxy) -> std::task::Waker { @@ -61,7 +61,7 @@ pub struct App { focus_sender: FocusSender, focus_receiver: FocusReceiver, - accessibility: NativeAccessibility, + accessibility: AccessKitManager, font_collection: FontCollection, @@ -82,7 +82,7 @@ impl App { fonts_config: FontsConfig, mut plugins: PluginsManager, ) -> Self { - let accessibility = NativeAccessibility::new(&window_env.window, proxy.clone()); + let accessibility = AccessKitManager::new(&window_env.window, proxy.clone()); window_env.window_mut().set_visible(true); @@ -283,7 +283,7 @@ impl App { layers, &layout, rdom, - &mut *self.accessibility.accessibility_state().lock().unwrap(), + &mut self.accessibility.accessibility_manager().lock().unwrap(), ); } @@ -348,7 +348,7 @@ impl App { &mut self.window_env } - pub fn accessibility(&mut self) -> &mut NativeAccessibility { + pub fn accessibility(&mut self) -> &mut AccessKitManager { &mut self.accessibility } diff --git a/crates/testing/src/launch.rs b/crates/testing/src/launch.rs index ef2b35f45..10b507e27 100644 --- a/crates/testing/src/launch.rs +++ b/crates/testing/src/launch.rs @@ -44,7 +44,7 @@ pub fn launch_test_with_config(root: Component<()>, config: TestingConfig) -> Te config, platform_event_emitter, platform_event_receiver, - accessibility_state: Arc::new(Mutex::new(AccessibilityState::new(ACCESSIBILITY_ROOT_ID))), + accessibility_manager: AccessibilityManager::new(ACCESSIBILITY_ROOT_ID).wrap(), ticker_sender: broadcast::channel(5).0, navigation_state: NavigatorState::new(NavigationMode::NotKeyboard), }; diff --git a/crates/testing/src/test_handler.rs b/crates/testing/src/test_handler.rs index 05b1d7229..b2cba2910 100644 --- a/crates/testing/src/test_handler.rs +++ b/crates/testing/src/test_handler.rs @@ -30,7 +30,7 @@ pub struct TestingHandler { pub(crate) elements_state: ElementsState, pub(crate) font_collection: FontCollection, pub(crate) viewports: Viewports, - pub(crate) accessibility_state: SharedAccessibilityState, + pub(crate) accessibility_manager: SharedAccessibilityManager, pub(crate) config: TestingConfig, @@ -100,7 +100,7 @@ impl TestingHandler { } } EventMessage::FocusAccessibilityNode(node_id) => { - self.accessibility_state.lock().unwrap().set_focus(node_id); + self.accessibility_manager.lock().unwrap().focused_id = node_id; } _ => {} } @@ -180,6 +180,6 @@ impl TestingHandler { } pub fn focus_id(&self) -> AccessibilityId { - self.accessibility_state.lock().unwrap().focus_id() + self.accessibility_manager.lock().unwrap().focused_id } } From 28f50085f9c813f7e9add5647801d22813e8c637 Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Sun, 28 Jan 2024 12:09:02 +0100 Subject: [PATCH 7/8] feat: Partial IME Support (#459) * feat: IME Support * chore: Update winit and accesskit * refinements * improvements * temp fix * improvements and fixes * improvements * fixes * fix * fix * clean up --- crates/components/src/input.rs | 2 ++ crates/hooks/src/text_editor.rs | 12 ++++++-- crates/renderer/src/accessibility.rs | 42 ++++++++++++++++++++++++++-- crates/renderer/src/app.rs | 19 +++++++++---- crates/renderer/src/event_loop.rs | 25 ++++++++++++----- crates/renderer/src/window.rs | 6 ++++ 6 files changed, 88 insertions(+), 18 deletions(-) diff --git a/crates/components/src/input.rs b/crates/components/src/input.rs index 20ac7f65f..e391e4d4d 100644 --- a/crates/components/src/input.rs +++ b/crates/components/src/input.rs @@ -194,6 +194,8 @@ pub fn Input<'a>(cx: Scope<'a, InputProps<'a>>) -> Element { corner_radius: "10", margin: "{margin}", cursor_reference: cursor_attr, + focus_id: focus_manager.attribute(cx), + role: "textInput", main_align: "center", paragraph { margin: "8 12", diff --git a/crates/hooks/src/text_editor.rs b/crates/hooks/src/text_editor.rs index 90dbc791d..587d74de5 100644 --- a/crates/hooks/src/text_editor.rs +++ b/crates/hooks/src/text_editor.rs @@ -387,8 +387,9 @@ pub trait TextEditor: Sized + Clone + Display { } _ => { if let Ok(ch) = character.parse::() { - if !ch.is_ascii_control() { - // Adds a new character + // https://github.com/marc2332/freya/issues/461 + if !ch.is_ascii_control() && ch.len_utf8() <= 2 { + // Inserts a character let char_idx = self.line_to_char(self.cursor_row()) + self.cursor_col(); self.insert(character, char_idx); @@ -396,6 +397,13 @@ pub trait TextEditor: Sized + Clone + Display { event.insert(TextEvent::TEXT_CHANGED); } + } else if character.is_ascii() { + // Inserts a text + let char_idx = self.line_to_char(self.cursor_row()) + self.cursor_col(); + self.insert(character, char_idx); + self.set_cursor_pos(char_idx + character.len()); + + event.insert(TextEvent::TEXT_CHANGED); } } } diff --git a/crates/renderer/src/accessibility.rs b/crates/renderer/src/accessibility.rs index 9419d6a6a..56670d39b 100644 --- a/crates/renderer/src/accessibility.rs +++ b/crates/renderer/src/accessibility.rs @@ -8,7 +8,12 @@ use freya_core::{ }, types::FocusSender, }; -use winit::{event::WindowEvent, event_loop::EventLoopProxy, window::Window}; +use winit::{ + dpi::{LogicalPosition, LogicalSize}, + event::WindowEvent, + event_loop::EventLoopProxy, + window::Window, +}; /// Manages the accessibility integration with Accesskit. pub struct AccessKitManager { @@ -42,17 +47,43 @@ impl AccessKitManager { } /// Focus a new accessibility node - pub fn set_accessibility_focus(&mut self, id: AccessibilityId) { + pub fn set_accessibility_focus(&self, id: AccessibilityId, window: &Window) { let tree = self .accessibility_manager .lock() .unwrap() .set_focus_with_update(id); if let Some(tree) = tree { + // Update the IME Cursor area + self.update_ime_position(tree.focus, window); + + // Update the adapter self.accessibility_adapter.update_if_active(|| tree); } } + fn update_ime_position(&self, accessibility_id: AccessibilityId, window: &Window) { + let accessibility_manager = self.accessibility_manager.lock().unwrap(); + let node = accessibility_manager + .nodes + .iter() + .find_map(|(id, n)| { + if *id == accessibility_id { + Some(n) + } else { + None + } + }) + .unwrap(); + let node_bounds = node.bounds(); + if let Some(node_bounds) = node_bounds { + window.set_ime_cursor_area( + LogicalPosition::new(node_bounds.min_x(), node_bounds.min_y()), + LogicalSize::new(node_bounds.width(), node_bounds.height()), + ) + } + } + /// Process an Accessibility event pub fn process_accessibility_event(&mut self, window: &Window, event: &WindowEvent) { self.accessibility_adapter.process_event(window, event) @@ -75,9 +106,10 @@ impl AccessKitManager { /// Focus the next accessibility node pub fn focus_next_node( - &mut self, + &self, direction: AccessibilityFocusDirection, focus_sender: &FocusSender, + window: &Window, ) { let tree = self .accessibility_manager @@ -89,6 +121,10 @@ impl AccessKitManager { .send(tree.focus) .expect("Failed to focus the Node."); + // Update the IME Cursor area + self.update_ime_position(tree.focus, window); + + // Update the Adapter self.accessibility_adapter.update_if_active(|| tree); } } diff --git a/crates/renderer/src/app.rs b/crates/renderer/src/app.rs index 525d26e1a..d5aea02e2 100644 --- a/crates/renderer/src/app.rs +++ b/crates/renderer/src/app.rs @@ -344,12 +344,16 @@ impl App { ); } - pub fn window_env(&mut self) -> &mut WindowEnv { + pub fn window_env(&self) -> &WindowEnv { + &self.window_env + } + + pub fn window_env_mut(&mut self) -> &mut WindowEnv { &mut self.window_env } - pub fn accessibility(&mut self) -> &mut AccessKitManager { - &mut self.accessibility + pub fn accessibility(&self) -> &AccessKitManager { + &self.accessibility } pub fn process_accessibility_event(&mut self, event: &WindowEvent) { @@ -357,9 +361,12 @@ impl App { .process_accessibility_event(&self.window_env.window, event) } - pub fn focus_next_node(&mut self, direction: AccessibilityFocusDirection) { - self.accessibility - .focus_next_node(direction, &self.focus_sender) + pub fn focus_next_node(&self, direction: AccessibilityFocusDirection) { + self.accessibility.focus_next_node( + direction, + &self.focus_sender, + self.window_env().window(), + ) } pub fn tick(&self) { diff --git a/crates/renderer/src/event_loop.rs b/crates/renderer/src/event_loop.rs index 96a1a8a9d..47f93c906 100644 --- a/crates/renderer/src/event_loop.rs +++ b/crates/renderer/src/event_loop.rs @@ -3,11 +3,12 @@ use accesskit_winit::ActionRequestEvent; use freya_common::EventMessage; use freya_core::prelude::*; use freya_elements::events::keyboard::{ - map_winit_key, map_winit_modifiers, map_winit_physical_key, + map_winit_key, map_winit_modifiers, map_winit_physical_key, Code, Key, }; use torin::geometry::CursorPoint; use winit::event::{ - ElementState, Event, KeyEvent, MouseScrollDelta, StartCause, Touch, TouchPhase, WindowEvent, + ElementState, Event, Ime, KeyEvent, MouseScrollDelta, StartCause, Touch, TouchPhase, + WindowEvent, }; use winit::event_loop::{EventLoop, EventLoopProxy}; use winit::keyboard::{KeyCode, ModifiersState, PhysicalKey}; @@ -29,7 +30,7 @@ pub fn run_event_loop( let mut cursor_pos = CursorPoint::default(); let mut modifiers_state = ModifiersState::empty(); - let window_env = app.window_env(); + let window_env = app.window_env_mut(); window_env.run_on_setup(); @@ -39,10 +40,11 @@ pub fn run_event_loop( _ = proxy.send_event(EventMessage::PollVDOM); } Event::UserEvent(EventMessage::FocusAccessibilityNode(id)) => { - app.accessibility().set_accessibility_focus(id); + app.accessibility() + .set_accessibility_focus(id, app.window_env().window()); } Event::UserEvent(EventMessage::RequestRerender) => { - app.window_env().window_mut().request_redraw(); + app.window_env_mut().window_mut().request_redraw(); } Event::UserEvent(EventMessage::RequestRedraw) => app.render(&hovered_node), Event::UserEvent(EventMessage::RequestRelayout) => { @@ -56,7 +58,8 @@ pub fn run_event_loop( .. })) => { if Action::Focus == request.action { - app.accessibility().set_accessibility_focus(request.target); + app.accessibility() + .set_accessibility_focus(request.target, app.window_env().window()); } } Event::UserEvent(EventMessage::SetCursorIcon(icon)) => { @@ -77,6 +80,14 @@ pub fn run_event_loop( app.process_accessibility_event(&event); match event { WindowEvent::CloseRequested => event_loop.exit(), + WindowEvent::Ime(Ime::Commit(text)) => { + app.send_event(FreyaEvent::Keyboard { + name: "keydown".to_string(), + key: Key::Character(text), + code: Code::Unidentified, + modifiers: map_winit_modifiers(modifiers_state), + }); + } WindowEvent::RedrawRequested => { app.process_layout(); app.render(&hovered_node); @@ -195,7 +206,7 @@ pub fn run_event_loop( } } Event::LoopExiting => { - app.window_env().run_on_exit(); + app.window_env_mut().run_on_exit(); } _ => (), }) diff --git a/crates/renderer/src/window.rs b/crates/renderer/src/window.rs index d7d33a183..ea083288c 100644 --- a/crates/renderer/src/window.rs +++ b/crates/renderer/src/window.rs @@ -102,6 +102,7 @@ impl WindowEnv { .unwrap(); let mut window = window.expect("Could not create window with OpenGL context"); + window.set_ime_allowed(true); let raw_window_handle = window.raw_window_handle(); let context_attributes = ContextAttributesBuilder::new() @@ -210,6 +211,11 @@ impl WindowEnv { &mut self.window } + /// Get a reference to the Window. + pub fn window(&self) -> &Window { + &self.window + } + /// Measure the layout pub fn process_layout( &mut self, From 9545d109542f7c702f8612741bb4efffc07a74c6 Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Sun, 28 Jan 2024 12:09:11 +0100 Subject: [PATCH 8/8] feat: Support individual vertical gaps but shared horizontal gaps (#476) * feat: Add support for individual vertical gaps but same horizontal * docs * typo * improvement --- book/src/guides/layout.md | 4 ++-- .../elements/src/_docs/attributes/margin.md | 3 ++- .../elements/src/_docs/attributes/padding.md | 3 ++- crates/state/src/values/gaps.rs | 19 ++++++++++++++++++ crates/state/tests/parse_gaps.rs | 6 ++++++ examples/paddings.rs | 20 +++++++++++++++---- 6 files changed, 47 insertions(+), 8 deletions(-) diff --git a/book/src/guides/layout.md b/book/src/guides/layout.md index 7ebcd4d57..ea49c7752 100644 --- a/book/src/guides/layout.md +++ b/book/src/guides/layout.md @@ -170,7 +170,7 @@ fn app(cx: Scope) -> Element { ### padding -Specify the inner paddings of an element. You can do so by three different ways, just like in CSS. +Specify the inner paddings of an element. You can do so by three different ways. ```rust, no_run fn app(cx: Scope) -> Element { @@ -209,7 +209,7 @@ fn app(cx: Scope) -> Element { ### margin -Specify the margin of an element. You can do so by three different ways, just like in CSS. +Specify the margin of an element. You can do so by three different ways. ```rust, no_run fn app(cx: Scope) -> Element { diff --git a/crates/elements/src/_docs/attributes/margin.md b/crates/elements/src/_docs/attributes/margin.md index b8c049b37..05dfa3e37 100644 --- a/crates/elements/src/_docs/attributes/margin.md +++ b/crates/elements/src/_docs/attributes/margin.md @@ -1,5 +1,5 @@ Specify the margin of an element. -You can do so by three different ways, just like in CSS. +You can do so by four different ways, just like in CSS. ### Example @@ -10,6 +10,7 @@ fn app(cx: Scope) -> Element { rect { margin: "25", // 25 in all sides margin: "100 50", // 100 in top and bottom, and 50 in left and right + margin: "2 15 25", // 2 in top, 15 in left and right, and 25 in bottom margin: "5 7 3 9" // 5 in top, 7 in right, 3 in bottom and 9 in left } ) diff --git a/crates/elements/src/_docs/attributes/padding.md b/crates/elements/src/_docs/attributes/padding.md index 1d4243df1..f04c2fd94 100644 --- a/crates/elements/src/_docs/attributes/padding.md +++ b/crates/elements/src/_docs/attributes/padding.md @@ -1,4 +1,4 @@ -Specify the inner paddings of an element. You can do so by three different ways, just like in CSS. +Specify the inner paddings of an element. You can do so by four different ways, just like in CSS. ### Example @@ -9,6 +9,7 @@ fn app(cx: Scope) -> Element { rect { padding: "25", // 25 in all sides padding: "100 50", // 100 in top and bottom, and 50 in left and right + padding: "2 15 25", // 2 in top, 15 in left and right, and 25 in bottom padding: "5 7 3 9" // 5 in top, 7 in right, 3 in bottom and 9 in left } ) diff --git a/crates/state/src/values/gaps.rs b/crates/state/src/values/gaps.rs index b86d260fe..ddad6455a 100644 --- a/crates/state/src/values/gaps.rs +++ b/crates/state/src/values/gaps.rs @@ -43,6 +43,25 @@ impl Parse for Gaps { .map_err(|_| ParseGapError)?, ) } + // Individual vertical but same horizontal + 3 => { + let top = values + .next() + .ok_or(ParseGapError)? + .parse::() + .map_err(|_| ParseGapError)?; + let left_and_right = values + .next() + .ok_or(ParseGapError)? + .parse::() + .map_err(|_| ParseGapError)?; + let bottom = values + .next() + .ok_or(ParseGapError)? + .parse::() + .map_err(|_| ParseGapError)?; + paddings = Gaps::new(top, left_and_right, bottom, left_and_right); + } // Each directions 4 => { paddings = Gaps::new( diff --git a/crates/state/tests/parse_gaps.rs b/crates/state/tests/parse_gaps.rs index 53b16931f..9150a9244 100644 --- a/crates/state/tests/parse_gaps.rs +++ b/crates/state/tests/parse_gaps.rs @@ -18,3 +18,9 @@ fn parse_sides_gaps() { let gaps = Gaps::parse("1 2 3 4"); assert_eq!(gaps, Ok(Gaps::new(1.0, 2.0, 3.0, 4.0))); } + +#[test] +fn parse_horizontal_axis_and_vertical_sides() { + let gaps = Gaps::parse("5 50 30"); + assert_eq!(gaps, Ok(Gaps::new(5.0, 50.0, 30.0, 50.0))); +} diff --git a/examples/paddings.rs b/examples/paddings.rs index 7612a0741..d5de303e5 100644 --- a/examples/paddings.rs +++ b/examples/paddings.rs @@ -13,7 +13,7 @@ fn app(cx: Scope) -> Element { render!( rect { overflow: "clip", - height: "33%", + height: "25%", width: "100%", padding: "15", background: "black", @@ -25,7 +25,7 @@ fn app(cx: Scope) -> Element { } rect { overflow: "clip", - height: "33%", + height: "25%", width: "100%", padding: "10 30 50 70", background: "gray", @@ -37,10 +37,22 @@ fn app(cx: Scope) -> Element { } rect { overflow: "clip", - height: "33%", + height: "25%", width: "100%", padding: "25 125", - background: "white", + background: "black", + rect { + height: "100%", + width: "100%", + background: "yellow" + } + } + rect { + overflow: "clip", + height: "25%", + width: "100%", + padding: "30 50 10", + background: "gray", rect { height: "100%", width: "100%",