From 621f10540ab7df1d79615704eed720e86f46ee0b Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Sun, 19 Jan 2025 16:26:15 +0100 Subject: [PATCH] feat: Delegated focus (#1058) * feat: Delegated focus * fix lint * simplify --- crates/freya/src/lib.rs | 5 ++- crates/hooks/src/use_focus.rs | 17 +++++-- crates/hooks/src/use_platform.rs | 9 +++- examples/delegated_focus.rs | 76 ++++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 examples/delegated_focus.rs diff --git a/crates/freya/src/lib.rs b/crates/freya/src/lib.rs index be512fa1f..de88d158e 100644 --- a/crates/freya/src/lib.rs +++ b/crates/freya/src/lib.rs @@ -110,7 +110,10 @@ pub mod prelude { pub use dioxus_hooks::*; pub use dioxus_signals::*; pub use freya_components::*; - pub use freya_core::prelude::PreferredTheme; + pub use freya_core::prelude::{ + AccessibilityId, + PreferredTheme, + }; pub use freya_elements::{ elements as dioxus_elements, events::*, diff --git a/crates/hooks/src/use_focus.rs b/crates/hooks/src/use_focus.rs index 59f9f59a7..afdb4e261 100644 --- a/crates/hooks/src/use_focus.rs +++ b/crates/hooks/src/use_focus.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use dioxus_core::{ + prelude::consume_context, use_hook, AttributeValue, }; @@ -45,6 +46,12 @@ pub struct UseFocus { } impl UseFocus { + pub fn new_id() -> AccessibilityId { + let accessibility_generator = consume_context::>(); + + AccessibilityId(accessibility_generator.new_id()) + } + /// Focus this node pub fn focus(&mut self) { if !*self.is_focused.peek() { @@ -95,14 +102,18 @@ impl UseFocus { /// Create a focus manager for a node. pub fn use_focus() -> UseFocus { - let accessibility_generator = use_context::>(); + let id = use_hook(UseFocus::new_id); + + use_focus_from_id(id) +} + +/// Create a focus manager for a node with the provided [AccessibilityId]. +pub fn use_focus_from_id(id: AccessibilityId) -> UseFocus { let focused_id = use_context::>(); let navigation_mode = use_context::>(); let navigation_mark = use_context::>(); let platform = use_platform(); - let id = use_hook(|| AccessibilityId(accessibility_generator.new_id())); - let is_focused = use_memo(move || id == *focused_id.read()); let is_selected = diff --git a/crates/hooks/src/use_platform.rs b/crates/hooks/src/use_platform.rs index ba97f23a7..d80e81745 100644 --- a/crates/hooks/src/use_platform.rs +++ b/crates/hooks/src/use_platform.rs @@ -9,7 +9,10 @@ use dioxus_signals::{ Readable, Signal, }; -use freya_core::prelude::EventMessage; +use freya_core::{ + prelude::EventMessage, + types::AccessibilityId, +}; use tokio::sync::{ broadcast, mpsc::UnboundedSender, @@ -130,6 +133,10 @@ impl UsePlatform { self.send(EventMessage::RequestRerender).ok(); } + pub fn focus(&self, id: AccessibilityId) { + self.send(EventMessage::FocusAccessibilityNode(id)).ok(); + } + pub fn new_ticker(&self) -> Ticker { Ticker { inner: self.ticker.peek().resubscribe(), diff --git a/examples/delegated_focus.rs b/examples/delegated_focus.rs new file mode 100644 index 000000000..c9e5b8e1e --- /dev/null +++ b/examples/delegated_focus.rs @@ -0,0 +1,76 @@ +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] + +use freya::prelude::*; + +fn main() { + launch_with_props(app, "Controlled Focus", (400.0, 350.0)); +} + +fn app() -> Element { + let nodes = use_hook(|| { + [ + UseFocus::new_id(), + UseFocus::new_id(), + UseFocus::new_id(), + UseFocus::new_id(), + ] + }); + let mut current = use_signal(|| 0); + + let onwheel = move |_| { + current += 1; + if current() == 4 { + current.set(0); + } + }; + + use_effect(move || { + let platform = UsePlatform::new(); + platform.focus(nodes[current()]); + }); + + rsx!( + rect { + height: "100%", + width: "100%", + background: "black", + color: "white", + direction: "horizontal", + main_align: "center", + cross_align: "center", + onwheel, + for (i, id) in nodes.iter().enumerate() { + Card { + key: "{i}", + id: *id, + index: i + } + } + } + ) +} + +#[component] +fn Card(index: usize, id: AccessibilityId) -> Element { + let focus = use_focus_from_id(id); + let background = if focus.is_focused() { + "rgb(0, 119, 182)" + } else { + "black" + }; + + rsx!( + rect { + height: "100", + width: "100", + a11y_id: focus.attribute(), + background, + label { + "Card {index}" + } + } + ) +}