From 8d8e78bc9145676e334856df1fb4cb059bb6649b Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Sat, 18 Jan 2025 11:20:16 +0100 Subject: [PATCH 01/12] feat: Allow `FnOnce` closures in window callbacks (#1047) * feat: Allow `FnOnce` closures in window hooks * chore: Also update `on_exit` * chore: Simplify impl --- crates/renderer/src/config.rs | 16 ++++++---------- crates/renderer/src/renderer.rs | 4 ++-- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/crates/renderer/src/config.rs b/crates/renderer/src/config.rs index 226cf6e4d..021509202 100644 --- a/crates/renderer/src/config.rs +++ b/crates/renderer/src/config.rs @@ -1,7 +1,4 @@ -use std::{ - io::Cursor, - sync::Arc, -}; +use std::io::Cursor; use freya_core::{ plugins::{ @@ -23,6 +20,7 @@ use winit::{ }, }; +pub type WindowCallback = Box; pub type EventLoopBuilderHook = Box)>; pub type WindowBuilderHook = Box WindowAttributes>; pub type EmbeddedFonts<'a> = Vec<(&'a str, &'a [u8])>; @@ -119,8 +117,6 @@ impl LaunchConfig<'_, ()> { } } -pub type WindowCallback = Arc>; - impl<'a, T: Clone> LaunchConfig<'a, T> { /// Specify a Window size. pub fn with_size(mut self, width: f64, height: f64) -> Self { @@ -201,14 +197,14 @@ impl<'a, T: Clone> LaunchConfig<'a, T> { } /// Register a callback that will be executed when the window is created. - pub fn on_setup(mut self, callback: fn(&mut Window)) -> Self { - self.window_config.on_setup = Some(Arc::new(Box::new(callback))); + pub fn on_setup(mut self, callback: impl FnOnce(&mut Window) + 'static) -> Self { + self.window_config.on_setup = Some(Box::new(callback)); self } /// Register a callback that will be executed when the window is closed. - pub fn on_exit(mut self, callback: fn(&mut Window)) -> Self { - self.window_config.on_exit = Some(Arc::new(Box::new(callback))); + pub fn on_exit(mut self, callback: impl FnOnce(&mut Window) + 'static) -> Self { + self.window_config.on_exit = Some(Box::new(callback)); self } diff --git a/crates/renderer/src/renderer.rs b/crates/renderer/src/renderer.rs index 7da56834a..5ebe2d6aa 100644 --- a/crates/renderer/src/renderer.rs +++ b/crates/renderer/src/renderer.rs @@ -139,7 +139,7 @@ impl<'a, State: Clone + 'static> DesktopRenderer<'a, State> { /// Run the `on_setup` callback that was passed to the launch function pub fn run_on_setup(&mut self) { let state = self.state.created_state(); - if let Some(on_setup) = &state.window_config.on_setup { + if let Some(on_setup) = state.window_config.on_setup.take() { (on_setup)(&mut state.window) } } @@ -147,7 +147,7 @@ impl<'a, State: Clone + 'static> DesktopRenderer<'a, State> { /// Run the `on_exit` callback that was passed to the launch function pub fn run_on_exit(&mut self) { let state = self.state.created_state(); - if let Some(on_exit) = &state.window_config.on_exit { + if let Some(on_exit) = state.window_config.on_exit.take() { (on_exit)(&mut state.window) } } From 65ed375c9704ffe4a62ff22298834a4f4180dc4e Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Sat, 18 Jan 2025 11:20:47 +0100 Subject: [PATCH 02/12] feat: Move the size options of NetworkImage from its theme to its props (#1045) * feat: Move the size options of `NetworkImage` from its theme to its props * clean up --- crates/components/src/network_image.rs | 34 ++++++++++++++------------ crates/hooks/src/theming/base.rs | 4 --- crates/hooks/src/theming/mod.rs | 10 -------- examples/accessibility.rs | 6 ++--- 4 files changed, 21 insertions(+), 33 deletions(-) diff --git a/crates/components/src/network_image.rs b/crates/components/src/network_image.rs index 426e613bf..548df379d 100644 --- a/crates/components/src/network_image.rs +++ b/crates/components/src/network_image.rs @@ -2,13 +2,10 @@ use bytes::Bytes; use dioxus::prelude::*; use freya_elements::elements as dioxus_elements; use freya_hooks::{ - use_applied_theme, use_asset_cacher, use_focus, AssetAge, AssetConfiguration, - NetworkImageTheme, - NetworkImageThemeWith, }; use freya_node_state::dynamic_bytes; use reqwest::Url; @@ -18,18 +15,18 @@ use crate::Loader; /// Properties for the [`NetworkImage`] component. #[derive(Props, Clone, PartialEq)] pub struct NetworkImageProps { - /// Theme override. - pub theme: Option, - + /// Width of the image container. Default to `fill`. + #[props(default = "fill".into())] + pub width: String, + /// Height of the image container. Default to `fill`. + #[props(default = "fill".into())] + pub height: String, /// URL of the image. pub url: ReadOnlySignal, - /// Fallback element. pub fallback: Option, - /// Loading element. pub loading: Option, - /// Information about the image. pub alt: Option, } @@ -63,7 +60,16 @@ pub enum ImageState { /// ) /// } #[allow(non_snake_case)] -pub fn NetworkImage(props: NetworkImageProps) -> Element { +pub fn NetworkImage( + NetworkImageProps { + width, + height, + url, + fallback, + loading, + alt, + }: NetworkImageProps, +) -> Element { let mut asset_cacher = use_asset_cacher(); let focus = use_focus(); let mut status = use_signal(|| ImageState::Loading); @@ -71,11 +77,9 @@ pub fn NetworkImage(props: NetworkImageProps) -> Element { let mut assets_tasks = use_signal::>(Vec::new); let a11y_id = focus.attribute(); - let NetworkImageTheme { width, height } = use_applied_theme!(&props.theme, network_image); - let alt = props.alt.as_deref(); use_effect(move || { - let url = props.url.read().clone(); + let url = url.read().clone(); // Cancel previous asset fetching requests for asset_task in assets_tasks.write().drain(..) { asset_task.cancel(); @@ -132,7 +136,7 @@ pub fn NetworkImage(props: NetworkImageProps) -> Element { }) } ImageState::Loading => { - if let Some(loading_element) = props.loading { + if let Some(loading_element) = loading { rsx!({ loading_element }) } else { rsx!( @@ -147,7 +151,7 @@ pub fn NetworkImage(props: NetworkImageProps) -> Element { } } _ => { - if let Some(fallback_element) = props.fallback { + if let Some(fallback_element) = fallback { rsx!({ fallback_element }) } else { rsx!( diff --git a/crates/hooks/src/theming/base.rs b/crates/hooks/src/theming/base.rs index b3663e7c4..5f0a5b3dd 100644 --- a/crates/hooks/src/theming/base.rs +++ b/crates/hooks/src/theming/base.rs @@ -176,10 +176,6 @@ pub(crate) const BASE_THEME: Theme = Theme { width: cow_borrowed!("100%"), height: cow_borrowed!("100%"), }, - network_image: NetworkImageTheme { - width: cow_borrowed!("100%"), - height: cow_borrowed!("100%"), - }, icon: IconTheme { width: cow_borrowed!("10"), height: cow_borrowed!("10"), diff --git a/crates/hooks/src/theming/mod.rs b/crates/hooks/src/theming/mod.rs index e2e463150..0e4ce632e 100644 --- a/crates/hooks/src/theming/mod.rs +++ b/crates/hooks/src/theming/mod.rs @@ -385,15 +385,6 @@ define_theme! { } } -define_theme! { - %[component] - pub NetworkImage { - %[cows] - width: str, - height: str, - } -} - define_theme! { %[component] pub Icon { @@ -610,7 +601,6 @@ pub struct Theme { pub input: InputTheme, pub canvas: CanvasTheme, pub graph: GraphTheme, - pub network_image: NetworkImageTheme, pub icon: IconTheme, pub sidebar: SidebarTheme, pub sidebar_item: SidebarItemTheme, diff --git a/examples/accessibility.rs b/examples/accessibility.rs index 27aa9d0cc..4d01d54da 100644 --- a/examples/accessibility.rs +++ b/examples/accessibility.rs @@ -71,10 +71,8 @@ fn app() -> Element { } NetworkImage { url, - theme: theme_with!(NetworkImageTheme { - width: "150".into(), - height: "100".into(), - }), + width: "150", + height: "100", alt: "This is an image" } } From b508749d2b53adcaa44b7ed9bbc7539548f8d93e Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Sat, 18 Jan 2025 11:21:01 +0100 Subject: [PATCH 03/12] fix: Poll one more time the overflowed content test (#1048) * fix: Poll one more time the overflowed content test --- crates/components/src/overflowed_content.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/components/src/overflowed_content.rs b/crates/components/src/overflowed_content.rs index 855013066..fc8f1539f 100644 --- a/crates/components/src/overflowed_content.rs +++ b/crates/components/src/overflowed_content.rs @@ -128,6 +128,7 @@ mod test { utils.wait_for_update().await; utils.wait_for_update().await; utils.wait_for_update().await; + utils.wait_for_update().await; assert_ne!(label.layout().unwrap().area.min_x(), 50.); } } From 2851a04f5a0b4412a72f2a8d499f5644e55ead94 Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Sat, 18 Jan 2025 11:22:40 +0100 Subject: [PATCH 04/12] docs: Update Windows's setup instructions (#1054) * docs: Update Windows's setup instructions * fmt --- book/src/setup.md | 4 ++-- crates/freya/src/_docs/development_setup.rs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/book/src/setup.md b/book/src/setup.md index f42e8a079..c21918d13 100644 --- a/book/src/setup.md +++ b/book/src/setup.md @@ -3,8 +3,8 @@ Make sure you have [Rust](https://www.rust-lang.org/) and your OS dependencies installed. ### Windows - -You will need C++ build tools which you can get through Visual Studio 2022, learn more [here](https://learn.microsoft.com/en-us/windows/dev-environment/rust/setup#install-visual-studio-recommended-or-the-microsoft-c-build-tools). +Install Visual Studio 2022 with the `Desktop Development with C++` workflow. +You can learn learn more [here](https://learn.microsoft.com/en-us/windows/dev-environment/rust/setup#install-visual-studio-recommended-or-the-microsoft-c-build-tools). ### Linux diff --git a/crates/freya/src/_docs/development_setup.rs b/crates/freya/src/_docs/development_setup.rs index 7f1e6cf86..cda4e1aa0 100644 --- a/crates/freya/src/_docs/development_setup.rs +++ b/crates/freya/src/_docs/development_setup.rs @@ -4,7 +4,8 @@ //! //! ### Windows //! -//! You will need C++ build tools which you can get through Visual Studio 2022, learn more [here](https://learn.microsoft.com/en-us/windows/dev-environment/rust/setup#install-visual-studio-recommended-or-the-microsoft-c-build-tools). +//! Install Visual Studio 2022 with the `Desktop Development with C++` workflow. +//! You can learn learn more [here](https://learn.microsoft.com/en-us/windows/dev-environment/rust/setup#install-visual-studio-recommended-or-the-microsoft-c-build-tools). //! //! ### Linux //! From 187772a9cf00db09166bf3f19f210eebae79f6ee Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Sat, 18 Jan 2025 11:23:32 +0100 Subject: [PATCH 05/12] feat: Improve dropdown styling (#1039) --- crates/components/src/dropdown.rs | 40 +++++++++++++++++++++++-------- crates/hooks/src/theming/base.rs | 5 +++- crates/hooks/src/theming/mod.rs | 3 +++ 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/crates/components/src/dropdown.rs b/crates/components/src/dropdown.rs index 0ef73230f..18177ccd1 100644 --- a/crates/components/src/dropdown.rs +++ b/crates/components/src/dropdown.rs @@ -14,6 +14,7 @@ use freya_hooks::{ use_applied_theme, use_focus, use_platform, + DropdownItemTheme, DropdownItemThemeWith, DropdownTheme, DropdownThemeWith, @@ -70,13 +71,25 @@ where let is_focused = focus.is_focused(); let is_selected = *selected.read() == value; + let DropdownItemTheme { + font_theme, + background, + hover_background, + select_background, + border_fill, + select_border_fill, + } = &theme; + let background = match *status.read() { - _ if is_selected => theme.select_background, - _ if is_focused => theme.hover_background, - DropdownItemStatus::Hovering => theme.hover_background, - DropdownItemStatus::Idle => theme.background, + _ if is_selected => select_background, + DropdownItemStatus::Hovering => hover_background, + DropdownItemStatus::Idle => background, + }; + let border = if focus.is_selected() { + format!("2 inner {select_border_fill}") + } else { + format!("1 inner {border_fill}") }; - let color = theme.font_theme.color; use_drop(move || { if *status.peek() == DropdownItemStatus::Hovering { @@ -114,10 +127,11 @@ where rsx!( rect { width: "fill-min", - color: "{color}", + color: "{font_theme.color}", a11y_id, - a11y_role:"button", + a11y_role: "button", background: "{background}", + border, padding: "6 22 6 16", corner_radius: "6", main_align: "center", @@ -249,13 +263,19 @@ where background_button, hover_background, border_fill, + focus_border_fill, arrow_fill, } = &theme; - let button_background = match *status.read() { + let background = match *status.read() { DropdownStatus::Hovering => hover_background, DropdownStatus::Idle => background_button, }; + let border = if focus.is_selected() { + format!("2 inner {focus_border_fill}") + } else { + format!("1 inner {border_fill}") + }; let selected = selected.read().to_string(); @@ -270,11 +290,11 @@ where onglobalkeydown, margin: "{margin}", a11y_id, - background: "{button_background}", + background: "{background}", color: "{font_theme.color}", corner_radius: "8", padding: "8 16", - border: "1 inner {border_fill}", + border, shadow: "0 4 5 0 rgb(0, 0, 0, 0.1)", direction: "horizontal", main_align: "center", diff --git a/crates/hooks/src/theming/base.rs b/crates/hooks/src/theming/base.rs index 5f0a5b3dd..84150cf50 100644 --- a/crates/hooks/src/theming/base.rs +++ b/crates/hooks/src/theming/base.rs @@ -126,12 +126,15 @@ pub(crate) const BASE_THEME: Theme = Theme { color: cow_borrowed!("key(color)"), }, border_fill: cow_borrowed!("key(surface)"), + focus_border_fill: cow_borrowed!("key(focused_border)"), arrow_fill: cow_borrowed!("key(solid)"), }, dropdown_item: DropdownItemTheme { background: cow_borrowed!("key(background)"), - select_background: cow_borrowed!("key(neutral_surface)"), + select_background: cow_borrowed!("key(secondary_surface)"), hover_background: cow_borrowed!("key(focused_surface)"), + border_fill: cow_borrowed!("none"), + select_border_fill: cow_borrowed!("key(focused_border)"), font_theme: FontTheme { color: cow_borrowed!("key(color)"), }, diff --git a/crates/hooks/src/theming/mod.rs b/crates/hooks/src/theming/mod.rs index 0e4ce632e..3330bd906 100644 --- a/crates/hooks/src/theming/mod.rs +++ b/crates/hooks/src/theming/mod.rs @@ -192,6 +192,7 @@ define_theme! { background_button: str, hover_background: str, border_fill: str, + focus_border_fill: str, arrow_fill: str, %[subthemes] font_theme: FontTheme, @@ -205,6 +206,8 @@ define_theme! { background: str, select_background: str, hover_background: str, + border_fill: str, + select_border_fill: str, %[subthemes] font_theme: FontTheme, } From 9a8a25fe76d1e280404663215468687d1282ba4f Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Sat, 18 Jan 2025 11:23:46 +0100 Subject: [PATCH 06/12] refactor: Use Rc> instead of Arc> when sharing the DOM (#1052) * refactor: Use Rc> instead of Arc> when sharing the DOM * more code --- crates/core/Cargo.toml | 2 +- crates/core/src/dom/doms.rs | 54 ++++++++++++++++-------------- crates/devtools/Cargo.toml | 2 +- crates/testing/Cargo.toml | 2 +- crates/testing/src/test_handler.rs | 2 +- 5 files changed, 32 insertions(+), 30 deletions(-) diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index ae4bcbdc5..6bd8dcdb2 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -15,7 +15,7 @@ categories = ["gui", "asynchronous"] features = ["freya-engine/mocked-engine"] [features] -shared = [] +rc-dom = [] skia-engine = ["freya-engine/skia-engine"] fade-cached-incremental-areas = [] diff --git a/crates/core/src/dom/doms.rs b/crates/core/src/dom/doms.rs index c1e597046..81998c80c 100644 --- a/crates/core/src/dom/doms.rs +++ b/crates/core/src/dom/doms.rs @@ -1,7 +1,15 @@ -use std::sync::{ - Arc, - Mutex, - MutexGuard, +use std::{ + cell::{ + Ref, + RefCell, + RefMut, + }, + rc::Rc, + sync::{ + Arc, + Mutex, + MutexGuard, + }, }; use dioxus_core::VirtualDom; @@ -52,14 +60,14 @@ pub type DioxusNode<'a> = NodeRef<'a, CustomAttributeValues>; /// Tiny wrapper over [FreyaDOM] to make it thread-safe if desired. /// This is primarily used by the Devtools and Testing renderer. pub struct SafeDOM { - #[cfg(not(feature = "shared"))] + #[cfg(not(feature = "rc-dom"))] pub fdom: FreyaDOM, - #[cfg(feature = "shared")] - pub fdom: Arc>, + #[cfg(feature = "rc-dom")] + pub fdom: Rc>, } -#[cfg(feature = "shared")] +#[cfg(feature = "rc-dom")] impl Clone for SafeDOM { fn clone(&self) -> Self { Self { @@ -69,52 +77,46 @@ impl Clone for SafeDOM { } impl SafeDOM { - #[cfg(not(feature = "shared"))] + #[cfg(not(feature = "rc-dom"))] pub fn new(fdom: FreyaDOM) -> Self { Self { fdom } } - #[cfg(feature = "shared")] + #[cfg(feature = "rc-dom")] pub fn new(fdom: FreyaDOM) -> Self { Self { - fdom: Arc::new(Mutex::new(fdom)), + fdom: Rc::new(RefCell::new(fdom)), } } /// Get a reference to the DOM. - #[cfg(not(feature = "shared"))] + #[cfg(not(feature = "rc-dom"))] pub fn get(&self) -> &FreyaDOM { &self.fdom } /// Get a reference to the DOM. - #[cfg(not(feature = "shared"))] + #[cfg(not(feature = "rc-dom"))] pub fn try_get(&self) -> Option<&FreyaDOM> { Some(&self.fdom) } /// Get a mutable reference to the DOM. - #[cfg(not(feature = "shared"))] + #[cfg(not(feature = "rc-dom"))] pub fn get_mut(&mut self) -> &mut FreyaDOM { &mut self.fdom } /// Get a reference to the DOM. - #[cfg(feature = "shared")] - pub fn get(&self) -> MutexGuard { - return self.fdom.lock().unwrap(); - } - - /// Get a reference to the DOM. - #[cfg(feature = "shared")] - pub fn try_get(&self) -> Option> { - return self.fdom.try_lock().ok(); + #[cfg(feature = "rc-dom")] + pub fn get(&self) -> Ref { + return self.fdom.borrow(); } /// Get a mutable reference to the dom. - #[cfg(feature = "shared")] - pub fn get_mut(&self) -> MutexGuard { - return self.fdom.lock().unwrap(); + #[cfg(feature = "rc-dom")] + pub fn get_mut(&self) -> RefMut { + return self.fdom.borrow_mut(); } } diff --git a/crates/devtools/Cargo.toml b/crates/devtools/Cargo.toml index 283dfe450..d388c4915 100644 --- a/crates/devtools/Cargo.toml +++ b/crates/devtools/Cargo.toml @@ -22,7 +22,7 @@ freya-node-state = { workspace = true } freya-renderer = { workspace = true } freya-elements = { workspace = true } freya-hooks = { workspace = true } -freya-core = { workspace = true, features = ["shared"] } +freya-core = { workspace = true } freya-components = { workspace = true } freya-engine = { workspace = true } torin = { workspace = true } diff --git a/crates/testing/Cargo.toml b/crates/testing/Cargo.toml index 13ed18133..e2bbc97d1 100644 --- a/crates/testing/Cargo.toml +++ b/crates/testing/Cargo.toml @@ -21,7 +21,7 @@ skia-engine = ["freya-engine/skia-engine"] freya-elements = { workspace = true } freya-common = { workspace = true } freya-node-state = { workspace = true } -freya-core = { workspace = true, features = ["shared"] } +freya-core = { workspace = true, features = ["rc-dom"] } freya-hooks = { workspace = true } freya-components = { workspace = true } freya-engine = { workspace = true } diff --git a/crates/testing/src/test_handler.rs b/crates/testing/src/test_handler.rs index db2e6e339..925d0aacd 100644 --- a/crates/testing/src/test_handler.rs +++ b/crates/testing/src/test_handler.rs @@ -78,7 +78,7 @@ impl TestingHandler { pub(crate) fn init_dom(&mut self) { self.provide_vdom_contexts(); let sdom = self.utils.sdom(); - let mut fdom = sdom.get(); + let mut fdom = sdom.get_mut(); fdom.init_dom(&mut self.vdom, SCALE_FACTOR as f32); } From cfd70efd34a0ee583ca1c5c877c1af7e62c5af9b Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Sat, 18 Jan 2025 11:23:57 +0100 Subject: [PATCH 07/12] feat: Show role alongside tag in devtools inspector (#1053) --- crates/core/src/node.rs | 8 ++++++++ crates/devtools/Cargo.toml | 2 ++ crates/devtools/src/node.rs | 23 ++++++++++++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/crates/core/src/node.rs b/crates/core/src/node.rs index c01a2822d..57e172572 100644 --- a/crates/core/src/node.rs +++ b/crates/core/src/node.rs @@ -1,6 +1,7 @@ use freya_engine::prelude::*; use freya_native_core::real_dom::NodeImmutable; use freya_node_state::{ + AccessibilityNodeState, Border, CornerRadius, CursorState, @@ -34,6 +35,7 @@ pub struct NodeState { pub size: LayoutState, pub style: StyleState, pub transform: TransformState, + pub accessibility: AccessibilityNodeState, } pub fn get_node_state(node: &DioxusNode) -> NodeState { @@ -67,6 +69,11 @@ pub fn get_node_state(node: &DioxusNode) -> NodeState { .as_deref() .cloned() .unwrap_or_default(); + let accessibility = node + .get::() + .as_deref() + .cloned() + .unwrap_or_default(); NodeState { cursor, @@ -75,6 +82,7 @@ pub fn get_node_state(node: &DioxusNode) -> NodeState { size, style, transform, + accessibility, } } diff --git a/crates/devtools/Cargo.toml b/crates/devtools/Cargo.toml index d388c4915..c2614a983 100644 --- a/crates/devtools/Cargo.toml +++ b/crates/devtools/Cargo.toml @@ -27,6 +27,8 @@ freya-components = { workspace = true } freya-engine = { workspace = true } torin = { workspace = true } dioxus-radio = "0.2" +serde_json = "1.0.107" +accesskit = { workspace = true } dioxus = { workspace = true } freya-native-core = { workspace = true } diff --git a/crates/devtools/src/node.rs b/crates/devtools/src/node.rs index b8d4f461e..c27389529 100644 --- a/crates/devtools/src/node.rs +++ b/crates/devtools/src/node.rs @@ -1,3 +1,4 @@ +use accesskit::Role; use dioxus::prelude::*; use freya_components::{ ArrowIcon, @@ -49,6 +50,26 @@ pub fn NodeElement( let margin_left = (node.height * 10) as f32 - 20.; let id = node_id.index(); + let role = node + .state + .accessibility + .builder + .clone() + .and_then(|builder| { + let built_node = builder.build(); + let role = built_node.role(); + if role != Role::GenericContainer { + serde_json::to_value(&role) + .ok() + .and_then(|v| v.as_str().map(String::from)) + } else { + None + } + }); + let name = role + .map(|role| format!("{}, tag: {}", role, node.tag)) + .unwrap_or_else(|| node.tag.to_string()); + rsx!( rect { corner_radius: "7", @@ -84,7 +105,7 @@ pub fn NodeElement( label { font_size: "14", color: "white", - "{node.tag} ({id})" + "{name}, id: {id}" } } ) From 3ac3c361ee9b62d636912474af40a052bc625296 Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Sat, 18 Jan 2025 11:25:31 +0100 Subject: [PATCH 08/12] fix: Manually drop previous animated values when the run callback reruns (#1046) --- crates/hooks/src/use_animation.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/hooks/src/use_animation.rs b/crates/hooks/src/use_animation.rs index 9f5718367..eb789d2f3 100644 --- a/crates/hooks/src/use_animation.rs +++ b/crates/hooks/src/use_animation.rs @@ -753,11 +753,16 @@ pub fn use_animation( let has_run_yet = use_signal(|| false); let task = use_signal(|| None); let last_direction = use_signal(|| AnimDirection::Reverse); + let mut prev_value = use_signal::>>(|| None); let context = use_memo(move || { + if let Some(prev_value) = prev_value.take() { + prev_value.manually_drop(); + } let mut conf = AnimConfiguration::default(); let value = run(&mut conf); let value = Signal::new(value); + prev_value.set(Some(value)); Context { value, conf } }); @@ -791,11 +796,16 @@ where let has_run_yet = use_signal(|| false); let task = use_signal(|| None); let last_direction = use_signal(|| AnimDirection::Reverse); + let mut prev_value = use_signal::>>(|| None); let context = use_memo(use_reactive(deps, move |deps| { + if let Some(prev_value) = prev_value.take() { + prev_value.manually_drop(); + } let mut conf = AnimConfiguration::default(); let value = run(&mut conf, deps); let value = Signal::new(value); + prev_value.set(Some(value)); Context { value, conf } })); From bd8967547bebcbc09f1092b1709569abe5174eaa Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Sat, 18 Jan 2025 16:59:59 +0100 Subject: [PATCH 09/12] feat: `OnDepsChange::Rerun` for `use_animation` (#1041) * feat: `OnDepsChange::Rerun` for `use_animation` * fmt --- crates/hooks/src/use_animation.rs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/crates/hooks/src/use_animation.rs b/crates/hooks/src/use_animation.rs index eb789d2f3..0423d98b3 100644 --- a/crates/hooks/src/use_animation.rs +++ b/crates/hooks/src/use_animation.rs @@ -497,6 +497,7 @@ pub enum OnDepsChange { #[default] Reset, Finish, + Rerun, } /// Animate your elements. Use [`use_animation`] to use this. @@ -781,6 +782,20 @@ pub fn use_animation( } }); + use_memo(move || { + let context = context.read(); + if *has_run_yet.peek() { + match context.conf.on_deps_change { + OnDepsChange::Finish => animation.finish(), + OnDepsChange::Rerun => { + let last_direction = *animation.last_direction.peek(); + animation.run(last_direction); + } + _ => {} + } + } + }); + animation } @@ -820,8 +835,14 @@ where use_memo(move || { let context = context.read(); - if *has_run_yet.peek() && context.conf.on_deps_change == OnDepsChange::Finish { - animation.finish() + if *has_run_yet.peek() { + match context.conf.on_deps_change { + OnDepsChange::Finish => animation.finish(), + OnDepsChange::Rerun => { + animation.run(*animation.last_direction.peek()); + } + _ => {} + } } }); From 81f95502df50682bf59aa93b5f2a9cf5739e6fed Mon Sep 17 00:00:00 2001 From: marc2332 Date: Sat, 18 Jan 2025 17:13:11 +0100 Subject: [PATCH 10/12] ci: Run clippy in the CI with devtools feature --- .github/workflows/rust.yml | 2 +- crates/devtools/src/node.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d13052763..7323ab6f4 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -37,7 +37,7 @@ jobs: - name: Check examples run: cargo check --examples - name: Lint - run: cargo clippy --workspace --examples --features "docs, use_camera" -- -D warnings + run: cargo clippy --workspace --examples --features "docs, use_camera, devtools" -- -D warnings - name: Format run: cargo +nightly fmt --all --check -- --error-on-unformatted --unstable-features - name: Run Linux tests diff --git a/crates/devtools/src/node.rs b/crates/devtools/src/node.rs index c27389529..4e3bc6591 100644 --- a/crates/devtools/src/node.rs +++ b/crates/devtools/src/node.rs @@ -59,7 +59,7 @@ pub fn NodeElement( let built_node = builder.build(); let role = built_node.role(); if role != Role::GenericContainer { - serde_json::to_value(&role) + serde_json::to_value(role) .ok() .and_then(|v| v.as_str().map(String::from)) } else { From c753f9a6e3530f9e6dbb4b761b3a6d964e1f36e4 Mon Sep 17 00:00:00 2001 From: marc2332 Date: Sat, 18 Jan 2025 21:38:37 +0100 Subject: [PATCH 11/12] chore: Move rc-dom imports behind rc-dom crate feature --- crates/core/src/dom/doms.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/core/src/dom/doms.rs b/crates/core/src/dom/doms.rs index 81998c80c..3b7b2c468 100644 --- a/crates/core/src/dom/doms.rs +++ b/crates/core/src/dom/doms.rs @@ -1,3 +1,9 @@ +use std::sync::{ + Arc, + Mutex, + MutexGuard, +}; +#[cfg(feature = "rc-dom")] use std::{ cell::{ Ref, @@ -5,11 +11,6 @@ use std::{ RefMut, }, rc::Rc, - sync::{ - Arc, - Mutex, - MutexGuard, - }, }; use dioxus_core::VirtualDom; From 621f10540ab7df1d79615704eed720e86f46ee0b Mon Sep 17 00:00:00 2001 From: Marc Espin Date: Sun, 19 Jan 2025 16:26:15 +0100 Subject: [PATCH 12/12] 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}" + } + } + ) +}