From d611f8563aec0e7b987b0613f4a81b1cb772a82a Mon Sep 17 00:00:00 2001 From: Matt Hunzinger Date: Tue, 18 Jun 2024 15:37:20 -0400 Subject: [PATCH 01/21] Connect the hot-reloading crate --- packages/dioxus-blitz/Cargo.toml | 6 ++- packages/dioxus-blitz/src/lib.rs | 57 ++++++++++++++++++++++++++--- packages/dioxus-blitz/src/waker.rs | 21 +++++++++-- packages/dioxus-blitz/src/window.rs | 6 +-- 4 files changed, 77 insertions(+), 13 deletions(-) diff --git a/packages/dioxus-blitz/Cargo.toml b/packages/dioxus-blitz/Cargo.toml index a85095c4..213bfa16 100644 --- a/packages/dioxus-blitz/Cargo.toml +++ b/packages/dioxus-blitz/Cargo.toml @@ -3,7 +3,9 @@ name = "dioxus-blitz" version = "0.0.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +hot-reload = [] +default = ["hot-reload"] [dependencies] tao = { version = "0.26.1", features = ["serde"] } @@ -11,6 +13,8 @@ muda = { version = "0.11.5", features = ["serde"] } tokio = { workspace = true, features = ["full"] } dioxus = { workspace = true } dioxus-ssr = { workspace = true } +dioxus-cli-config = { git = "https://github.com/dioxuslabs/dioxus", rev = "a3aa6ae771a2d0a4d8cb6055c41efc0193b817ef" } +dioxus-hot-reload = { git = "https://github.com/dioxuslabs/dioxus", rev = "a3aa6ae771a2d0a4d8cb6055c41efc0193b817ef" } futures-util = "0.3.30" vello = { workspace = true } wgpu = { workspace = true } diff --git a/packages/dioxus-blitz/src/lib.rs b/packages/dioxus-blitz/src/lib.rs index 46e30b88..c31ec937 100644 --- a/packages/dioxus-blitz/src/lib.rs +++ b/packages/dioxus-blitz/src/lib.rs @@ -2,7 +2,7 @@ mod documents; mod waker; mod window; -use crate::waker::{EventData, UserWindowEvent}; +use crate::waker::{EventData, UserEvent}; use crate::{documents::HtmlDocument, window::View}; use blitz::RenderState; @@ -93,7 +93,7 @@ fn launch_with_window(window: View<'static, Doc>) { let _guard = rt.enter(); // Build an event loop for the application - let event_loop = EventLoopBuilder::::with_user_event().build(); + let event_loop = EventLoopBuilder::::with_user_event().build(); let proxy = event_loop.create_proxy(); // Multiwindow ftw @@ -132,6 +132,26 @@ fn launch_with_window(window: View<'static, Doc>) { initial = false; } + // Setup hot-reloading if enabled. + #[cfg(all( + feature = "hot-reload", + debug_assertions, + not(target_os = "android"), + not(target_os = "ios") + ))] + { + let Ok(cfg) = dioxus_cli_config::CURRENT_CONFIG.as_ref() else { + return; + }; + + dioxus_hot_reload::connect_at(cfg.target_dir.join("dioxusin"), { + let proxy = proxy.clone(); + move |template| { + let _ = proxy.send_event(UserEvent::HotReloadEvent(template)); + } + }); + } + match event { // Exit the app when close is request // Not always necessary @@ -143,13 +163,40 @@ fn launch_with_window(window: View<'static, Doc>) { // Nothing else to do, try redrawing? Event::MainEventsCleared => {} - Event::UserEvent(UserWindowEvent(EventData::Poll, id)) => { - if let Some(view) = windows.get_mut(&id) { + Event::UserEvent(UserEvent::Window { + data: EventData::Poll, + window_id, + }) => { + if let Some(view) = windows.get_mut(&window_id) { if view.poll() { view.request_redraw(); } }; } + + #[cfg(all( + feature = "hot-reload", + debug_assertions, + not(target_os = "android"), + not(target_os = "ios") + ))] + Event::UserEvent(UserEvent::HotReloadEvent(msg)) => { + + + match msg { + dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => { + dbg!("Update template {:?}", template); + } + dioxus_hot_reload::HotReloadMsg::Shutdown => { + * control_flow = ControlFlow::Exit; + } + dioxus_hot_reload::HotReloadMsg::UpdateAsset(asset) => { + dbg!("Update asset {:?}", asset); + } + } + + } + // Event::UserEvent(_redraw) => { // for (_, view) in windows.iter() { // view.request_redraw(); @@ -157,7 +204,7 @@ fn launch_with_window(window: View<'static, Doc>) { // } Event::NewEvents(_) => { for id in windows.keys() { - _ = proxy.send_event(UserWindowEvent(EventData::Poll, *id)); + _ = proxy.send_event(UserEvent::Window { data: EventData::Poll, window_id: *id }); } } diff --git a/packages/dioxus-blitz/src/waker.rs b/packages/dioxus-blitz/src/waker.rs index 2b391ad4..f7835a61 100644 --- a/packages/dioxus-blitz/src/waker.rs +++ b/packages/dioxus-blitz/src/waker.rs @@ -3,7 +3,20 @@ use std::sync::Arc; use tao::{event_loop::EventLoopProxy, window::WindowId}; #[derive(Debug, Clone)] -pub struct UserWindowEvent(pub EventData, pub WindowId); +pub enum UserEvent { + Window { + window_id: WindowId, + data: EventData, + }, + /// Handle a hotreload event, basically telling us to update our templates + #[cfg(all( + feature = "hot-reload", + debug_assertions, + not(target_os = "android"), + not(target_os = "ios") + ))] + HotReloadEvent(dioxus_hot_reload::HotReloadMsg), +} #[derive(Debug, Clone)] pub enum EventData { @@ -17,9 +30,9 @@ pub enum EventData { /// This lets the VirtualDom "come up for air" and process events while the main thread is blocked by the WebView. /// /// All other IO lives in the Tokio runtime, -pub fn tao_waker(proxy: &EventLoopProxy, id: WindowId) -> std::task::Waker { +pub fn tao_waker(proxy: &EventLoopProxy, id: WindowId) -> std::task::Waker { struct DomHandle { - proxy: EventLoopProxy, + proxy: EventLoopProxy, id: WindowId, } @@ -32,7 +45,7 @@ pub fn tao_waker(proxy: &EventLoopProxy, id: WindowId) -> std:: fn wake_by_ref(arc_self: &Arc) { _ = arc_self .proxy - .send_event(UserWindowEvent(EventData::Poll, arc_self.id)); + .send_event(UserEvent::Window { data: EventData::Poll, window_id: arc_self.id }) } } diff --git a/packages/dioxus-blitz/src/window.rs b/packages/dioxus-blitz/src/window.rs index 5fb5c626..fae38a47 100644 --- a/packages/dioxus-blitz/src/window.rs +++ b/packages/dioxus-blitz/src/window.rs @@ -1,4 +1,4 @@ -use crate::waker::UserWindowEvent; +use crate::waker::UserEvent; use blitz::{RenderState, Renderer, Viewport}; use blitz_dom::DocumentLike; @@ -277,8 +277,8 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { pub fn resume( &mut self, - event_loop: &EventLoopWindowTarget, - proxy: &EventLoopProxy, + event_loop: &EventLoopWindowTarget, + proxy: &EventLoopProxy, rt: &tokio::runtime::Runtime, ) { let window_builder = || { From e710cdc1e7a96bfb8f065cc4ee9fe2bfc7c5ff41 Mon Sep 17 00:00:00 2001 From: Matt Hunzinger Date: Tue, 18 Jun 2024 16:01:04 -0400 Subject: [PATCH 02/21] Apply template updates from the hot-reload CLI --- .../src/documents/dioxus_document.rs | 3 +- packages/dioxus-blitz/src/lib.rs | 81 +++++++++++-------- packages/dom/Cargo.toml | 2 - packages/dom/src/document.rs | 10 ++- 4 files changed, 57 insertions(+), 39 deletions(-) diff --git a/packages/dioxus-blitz/src/documents/dioxus_document.rs b/packages/dioxus-blitz/src/documents/dioxus_document.rs index 82655f28..6bb23736 100644 --- a/packages/dioxus-blitz/src/documents/dioxus_document.rs +++ b/packages/dioxus-blitz/src/documents/dioxus_document.rs @@ -31,7 +31,7 @@ fn qual_name(local_name: &str, namespace: Option<&str>) -> QualName { } pub struct DioxusDocument { - vdom: VirtualDom, + pub(crate) vdom: VirtualDom, vdom_state: DioxusState, inner: Document, } @@ -122,6 +122,7 @@ impl DocumentLike for DioxusDocument { false } + } impl DioxusDocument { diff --git a/packages/dioxus-blitz/src/lib.rs b/packages/dioxus-blitz/src/lib.rs index c31ec937..dd5728d3 100644 --- a/packages/dioxus-blitz/src/lib.rs +++ b/packages/dioxus-blitz/src/lib.rs @@ -106,6 +106,26 @@ fn launch_with_window(window: View<'static, Doc>) { #[cfg(not(any(target_os = "android", target_os = "ios")))] let mut initial = true; + // Setup hot-reloading if enabled. + #[cfg(all( + feature = "hot-reload", + debug_assertions, + not(target_os = "android"), + not(target_os = "ios") + ))] + { + let Ok(cfg) = dioxus_cli_config::CURRENT_CONFIG.as_ref() else { + return; + }; + + dioxus_hot_reload::connect_at(cfg.target_dir.join("dioxusin"), { + let proxy = proxy.clone(); + move |template| { + let _ = proxy.send_event(UserEvent::HotReloadEvent(template)); + } + }); + } + event_loop.run(move |event, event_loop, control_flow| { *control_flow = ControlFlow::Wait; @@ -132,26 +152,6 @@ fn launch_with_window(window: View<'static, Doc>) { initial = false; } - // Setup hot-reloading if enabled. - #[cfg(all( - feature = "hot-reload", - debug_assertions, - not(target_os = "android"), - not(target_os = "ios") - ))] - { - let Ok(cfg) = dioxus_cli_config::CURRENT_CONFIG.as_ref() else { - return; - }; - - dioxus_hot_reload::connect_at(cfg.target_dir.join("dioxusin"), { - let proxy = proxy.clone(); - move |template| { - let _ = proxy.send_event(UserEvent::HotReloadEvent(template)); - } - }); - } - match event { // Exit the app when close is request // Not always necessary @@ -180,22 +180,32 @@ fn launch_with_window(window: View<'static, Doc>) { not(target_os = "android"), not(target_os = "ios") ))] - Event::UserEvent(UserEvent::HotReloadEvent(msg)) => { - - - match msg { - dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => { - dbg!("Update template {:?}", template); + Event::UserEvent(UserEvent::HotReloadEvent(msg)) => match msg { + dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => { + dbg!("Update template {:?}", template); + + for window in windows.values_mut() { + if let Some(dx_doc) = window + .renderer + .dom + .as_any_mut() + .downcast_mut::() + { + dx_doc.vdom.replace_template(template); } - dioxus_hot_reload::HotReloadMsg::Shutdown => { - * control_flow = ControlFlow::Exit; - } - dioxus_hot_reload::HotReloadMsg::UpdateAsset(asset) => { - dbg!("Update asset {:?}", asset); + + if window.poll() { + window.request_redraw(); } } - - } + } + dioxus_hot_reload::HotReloadMsg::Shutdown => { + *control_flow = ControlFlow::Exit; + } + dioxus_hot_reload::HotReloadMsg::UpdateAsset(asset) => { + dbg!("Update asset {:?}", asset); + } + }, // Event::UserEvent(_redraw) => { // for (_, view) in windows.iter() { @@ -204,7 +214,10 @@ fn launch_with_window(window: View<'static, Doc>) { // } Event::NewEvents(_) => { for id in windows.keys() { - _ = proxy.send_event(UserEvent::Window { data: EventData::Poll, window_id: *id }); + _ = proxy.send_event(UserEvent::Window { + data: EventData::Poll, + window_id: *id, + }); } } diff --git a/packages/dom/Cargo.toml b/packages/dom/Cargo.toml index 42d13143..56bdf07a 100644 --- a/packages/dom/Cargo.toml +++ b/packages/dom/Cargo.toml @@ -3,8 +3,6 @@ name = "blitz-dom" version = "0.0.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] style = { workspace = true, features = ["servo"] } selectors = { workspace = true } diff --git a/packages/dom/src/document.rs b/packages/dom/src/document.rs index cfda135f..05b3bd19 100644 --- a/packages/dom/src/document.rs +++ b/packages/dom/src/document.rs @@ -4,6 +4,7 @@ use crate::{Node, NodeData, TextNodeData}; // use quadtree_rs::Quadtree; use selectors::{matching::QuirksMode, Element}; use slab::Slab; +use std::any::Any; use std::collections::HashMap; use style::invalidation::element::restyle_hints::RestyleHint; use style::selector_parser::ServoElementSnapshot; @@ -20,7 +21,7 @@ use style_traits::dom::ElementState; use taffy::AvailableSpace; use url::Url; -pub trait DocumentLike: AsRef + AsMut + Into { +pub trait DocumentLike: AsRef + AsMut + Into + 'static { fn poll(&mut self, _cx: std::task::Context) -> bool { // Default implementation does nothing false @@ -30,9 +31,14 @@ pub trait DocumentLike: AsRef + AsMut + Into { // Default implementation does nothing false } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } } -impl DocumentLike for Document {} +impl DocumentLike for Document { +} pub struct Document { /// A bump-backed tree From 860e80d97257c5a7d5a6bfba14f246ace7928e86 Mon Sep 17 00:00:00 2001 From: Matt Hunzinger Date: Tue, 18 Jun 2024 19:02:00 -0400 Subject: [PATCH 03/21] Run cargo fmt --- packages/dioxus-blitz/src/documents/dioxus_document.rs | 1 - packages/dioxus-blitz/src/waker.rs | 7 ++++--- packages/dom/src/document.rs | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/dioxus-blitz/src/documents/dioxus_document.rs b/packages/dioxus-blitz/src/documents/dioxus_document.rs index 6bb23736..e612540d 100644 --- a/packages/dioxus-blitz/src/documents/dioxus_document.rs +++ b/packages/dioxus-blitz/src/documents/dioxus_document.rs @@ -122,7 +122,6 @@ impl DocumentLike for DioxusDocument { false } - } impl DioxusDocument { diff --git a/packages/dioxus-blitz/src/waker.rs b/packages/dioxus-blitz/src/waker.rs index f7835a61..b3f830a0 100644 --- a/packages/dioxus-blitz/src/waker.rs +++ b/packages/dioxus-blitz/src/waker.rs @@ -43,9 +43,10 @@ pub fn tao_waker(proxy: &EventLoopProxy, id: WindowId) -> std::task:: impl ArcWake for DomHandle { fn wake_by_ref(arc_self: &Arc) { - _ = arc_self - .proxy - .send_event(UserEvent::Window { data: EventData::Poll, window_id: arc_self.id }) + _ = arc_self.proxy.send_event(UserEvent::Window { + data: EventData::Poll, + window_id: arc_self.id, + }) } } diff --git a/packages/dom/src/document.rs b/packages/dom/src/document.rs index 05b3bd19..3185899d 100644 --- a/packages/dom/src/document.rs +++ b/packages/dom/src/document.rs @@ -37,8 +37,7 @@ pub trait DocumentLike: AsRef + AsMut + Into + 'st } } -impl DocumentLike for Document { -} +impl DocumentLike for Document {} pub struct Document { /// A bump-backed tree From 46a48b468d76448b1a086c85caf746d90d0b6035 Mon Sep 17 00:00:00 2001 From: Matt Hunzinger Date: Tue, 18 Jun 2024 19:17:55 -0400 Subject: [PATCH 04/21] Make hot-reload a non-default feature --- packages/dioxus-blitz/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dioxus-blitz/Cargo.toml b/packages/dioxus-blitz/Cargo.toml index 213bfa16..ea5ab550 100644 --- a/packages/dioxus-blitz/Cargo.toml +++ b/packages/dioxus-blitz/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [features] hot-reload = [] -default = ["hot-reload"] +default = [] [dependencies] tao = { version = "0.26.1", features = ["serde"] } From b09b2f9b5f15873b0b24fffde08c8203d0f79aba Mon Sep 17 00:00:00 2001 From: Matt Hunzinger Date: Thu, 20 Jun 2024 18:21:28 -0400 Subject: [PATCH 05/21] Fix panic on root node and clean up --- packages/dioxus-blitz/src/lib.rs | 14 ++++++++------ packages/dioxus-blitz/src/window.rs | 7 +------ packages/dom/src/document.rs | 4 +++- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/dioxus-blitz/src/lib.rs b/packages/dioxus-blitz/src/lib.rs index dd9f9ae9..1b320639 100644 --- a/packages/dioxus-blitz/src/lib.rs +++ b/packages/dioxus-blitz/src/lib.rs @@ -12,7 +12,7 @@ use documents::DioxusDocument; use muda::{MenuEvent, MenuId}; use std::collections::HashMap; use url::Url; -use winit::event_loop::{EventLoop, EventLoopBuilder}; +use winit::event_loop::EventLoop; use winit::window::WindowId; use winit::{ event::{Event, WindowEvent}, @@ -126,6 +126,9 @@ fn launch_with_window(window: View<'static, Doc>) { }); } + // the move to winit wants us to use a struct with a run method instead of the callback approach + // we want to just keep the callback approach for now + #[allow(deprecated)] event_loop .run(move |event, event_loop| { event_loop.set_control_flow(ControlFlow::Wait); @@ -190,8 +193,6 @@ fn launch_with_window(window: View<'static, Doc>) { ))] Event::UserEvent(UserEvent::HotReloadEvent(msg)) => match msg { dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => { - dbg!("Update template {:?}", template); - for window in windows.values_mut() { if let Some(dx_doc) = window .renderer @@ -200,15 +201,16 @@ fn launch_with_window(window: View<'static, Doc>) { .downcast_mut::() { dx_doc.vdom.replace_template(template); - } - if window.poll() { - window.request_redraw(); + if window.poll() { + window.request_redraw(); + } } } } dioxus_hot_reload::HotReloadMsg::Shutdown => event_loop.exit(), dioxus_hot_reload::HotReloadMsg::UpdateAsset(asset) => { + // TODO dioxus-desktop seems to handle this by forcing a reload of all stylesheets. dbg!("Update asset {:?}", asset); } }, diff --git a/packages/dioxus-blitz/src/window.rs b/packages/dioxus-blitz/src/window.rs index b0599100..4a7ebc7a 100644 --- a/packages/dioxus-blitz/src/window.rs +++ b/packages/dioxus-blitz/src/window.rs @@ -1,9 +1,7 @@ use crate::waker::UserEvent; use blitz::{RenderState, Renderer, Viewport}; use blitz_dom::DocumentLike; -use wgpu::rwh::HasWindowHandle; use winit::keyboard::PhysicalKey; -use winit::window::WindowAttributes; use std::sync::Arc; use std::task::Waker; @@ -19,8 +17,6 @@ use winit::event_loop::{ActiveEventLoop, EventLoopProxy}; target_os = "openbsd" ))] use winit::platform::unix::WindowExtUnix; -#[cfg(target_os = "windows")] -use winit::platform::windows::WindowExtWindows; use winit::{event::WindowEvent, keyboard::KeyCode, keyboard::ModifiersState, window::Window}; #[cfg(not(target_os = "macos"))] @@ -224,7 +220,7 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { }; if let RenderState::Active(state) = &self.renderer.render_state { - state.window.set_cursor_icon(tao_cursor); + state.window.set_cursor(tao_cursor); self.request_redraw(); } } @@ -248,7 +244,6 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { winit::event::MouseScrollDelta::PixelDelta(offsets) => { self.renderer.scroll_by(offsets.y) } - _ => {} }; self.request_redraw(); } diff --git a/packages/dom/src/document.rs b/packages/dom/src/document.rs index 395c5f91..ab1d5e42 100644 --- a/packages/dom/src/document.rs +++ b/packages/dom/src/document.rs @@ -222,7 +222,9 @@ impl Document { let node = &self.nodes[node_id]; let node_child_idx = node.child_idx; - let parent_id = node.parent.unwrap(); + + // Get this node's parent, or the root node if it has none. + let parent_id = node.parent.unwrap_or_default(); let parent = &mut self.nodes[parent_id]; let mut children = std::mem::take(&mut parent.children); From b2702035fa5657a7e3ce5d7fcb9d5a096dedcb57 Mon Sep 17 00:00:00 2001 From: Matt Hunzinger Date: Thu, 20 Jun 2024 19:37:56 -0400 Subject: [PATCH 06/21] Import accesskit --- packages/blitz/Cargo.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/blitz/Cargo.toml b/packages/blitz/Cargo.toml index 1d01a462..e2631030 100644 --- a/packages/blitz/Cargo.toml +++ b/packages/blitz/Cargo.toml @@ -3,7 +3,9 @@ name = "blitz" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +accesskit = ["dep:accesskit"] +default = ["accesskit"] [dependencies] slab = "0.4.9" @@ -16,6 +18,7 @@ tokio = { workspace = true, features = ["full"] } vello = { workspace = true } wgpu = { workspace = true } +accesskit = { version = "0.15.0", optional = true } app_units = "0.7.3" atomic_refcell = { version = "0.1.13", features = ["serde"] } fxhash = "0.2.1" From a6c643c3417baaed9a8a6117fde5ac6bddee3f77 Mon Sep 17 00:00:00 2001 From: Matt Hunzinger Date: Thu, 20 Jun 2024 20:39:52 -0400 Subject: [PATCH 07/21] Pipe accesskit events to View --- packages/blitz/Cargo.toml | 5 -- packages/dioxus-blitz/Cargo.toml | 5 +- packages/dioxus-blitz/src/lib.rs | 82 ++++++++++++++++------------- packages/dioxus-blitz/src/waker.rs | 18 ++++++- packages/dioxus-blitz/src/window.rs | 30 +++++++++-- 5 files changed, 92 insertions(+), 48 deletions(-) diff --git a/packages/blitz/Cargo.toml b/packages/blitz/Cargo.toml index e2631030..2235901b 100644 --- a/packages/blitz/Cargo.toml +++ b/packages/blitz/Cargo.toml @@ -3,10 +3,6 @@ name = "blitz" version = "0.1.0" edition = "2021" -[features] -accesskit = ["dep:accesskit"] -default = ["accesskit"] - [dependencies] slab = "0.4.9" style = { workspace = true, features = ["servo"] } @@ -18,7 +14,6 @@ tokio = { workspace = true, features = ["full"] } vello = { workspace = true } wgpu = { workspace = true } -accesskit = { version = "0.15.0", optional = true } app_units = "0.7.3" atomic_refcell = { version = "0.1.13", features = ["serde"] } fxhash = "0.2.1" diff --git a/packages/dioxus-blitz/Cargo.toml b/packages/dioxus-blitz/Cargo.toml index 6e7abc50..94ada8f7 100644 --- a/packages/dioxus-blitz/Cargo.toml +++ b/packages/dioxus-blitz/Cargo.toml @@ -4,10 +4,13 @@ version = "0.0.0" edition = "2021" [features] +accesskit = ["dep:accesskit", "dep:accesskit_winit"] hot-reload = [] -default = [] +default = ["accesskit"] [dependencies] +accesskit = { version = "0.15.0", optional = true } +accesskit_winit = { version = "0.21.1", optional = true } winit = { version = "0.30.2", features = ["rwh_06"] } muda = { version = "0.11.5", features = ["serde"] } tokio = { workspace = true, features = ["full"] } diff --git a/packages/dioxus-blitz/src/lib.rs b/packages/dioxus-blitz/src/lib.rs index 2e7290bd..2b90cec7 100644 --- a/packages/dioxus-blitz/src/lib.rs +++ b/packages/dioxus-blitz/src/lib.rs @@ -106,6 +106,7 @@ fn launch_with_window(window: View<'static, Doc>) { #[cfg(not(any(target_os = "android", target_os = "ios")))] let mut initial = true; + // Setup hot-reloading if enabled. #[cfg(all( feature = "hot-reload", @@ -126,6 +127,8 @@ fn launch_with_window(window: View<'static, Doc>) { }); } + + // the move to winit wants us to use a struct with a run method instead of the callback approach // we want to just keep the callback approach for now #[allow(deprecated)] @@ -177,45 +180,52 @@ fn launch_with_window(window: View<'static, Doc>) { }; } - Event::UserEvent(UserEvent::Window { - data: EventData::Poll, - window_id: id, - }) => { - if let Some(view) = windows.get_mut(&id) { - if view.poll() { - view.request_redraw(); + Event::UserEvent(user_event) => match user_event { + UserEvent::Window { + data: EventData::Poll, + window_id: id, + } => { + if let Some(view) = windows.get_mut(&id) { + if view.poll() { + view.request_redraw(); + } + }; + } + #[cfg(feature = "accesskit")] + UserEvent::Accessibility(accessibility_event) => { + if let Some(window) = windows.get_mut(&accessibility_event.window_id) { + window.handle_accessibility_event(&accessibility_event); } - }; - } - - #[cfg(all( - feature = "hot-reload", - debug_assertions, - not(target_os = "android"), - not(target_os = "ios") - ))] - Event::UserEvent(UserEvent::HotReloadEvent(msg)) => match msg { - dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => { - for window in windows.values_mut() { - if let Some(dx_doc) = window - .renderer - .dom - .as_any_mut() - .downcast_mut::() - { - dx_doc.vdom.replace_template(template); - - if window.poll() { - window.request_redraw(); + } + #[cfg(all( + feature = "hot-reload", + debug_assertions, + not(target_os = "android"), + not(target_os = "ios") + ))] + UserEvent::HotReloadEvent(msg) => match msg { + dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => { + for window in windows.values_mut() { + if let Some(dx_doc) = window + .renderer + .dom + .as_any_mut() + .downcast_mut::() + { + dx_doc.vdom.replace_template(template); + + if window.poll() { + window.request_redraw(); + } } } } - } - dioxus_hot_reload::HotReloadMsg::Shutdown => event_loop.exit(), - dioxus_hot_reload::HotReloadMsg::UpdateAsset(asset) => { - // TODO dioxus-desktop seems to handle this by forcing a reload of all stylesheets. - dbg!("Update asset {:?}", asset); - } + dioxus_hot_reload::HotReloadMsg::Shutdown => event_loop.exit(), + dioxus_hot_reload::HotReloadMsg::UpdateAsset(asset) => { + // TODO dioxus-desktop seems to handle this by forcing a reload of all stylesheets. + dbg!("Update asset {:?}", asset); + } + }, }, // Event::UserEvent(_redraw) => { @@ -245,7 +255,7 @@ fn launch_with_window(window: View<'static, Doc>) { } => { if let Some(window) = windows.get_mut(&window_id) { window.handle_window_event(event); - }; + } } _ => (), diff --git a/packages/dioxus-blitz/src/waker.rs b/packages/dioxus-blitz/src/waker.rs index 2c20983e..1fd5faf2 100644 --- a/packages/dioxus-blitz/src/waker.rs +++ b/packages/dioxus-blitz/src/waker.rs @@ -2,13 +2,21 @@ use futures_util::task::ArcWake; use std::sync::Arc; use winit::{event_loop::EventLoopProxy, window::WindowId}; +#[cfg(feature = "accesskit")] +use accesskit_winit::Event as AccessibilityEvent; + #[derive(Debug, Clone)] pub enum UserEvent { Window { window_id: WindowId, data: EventData, }, - /// Handle a hotreload event, basically telling us to update our templates + + /// An accessibility event from `accesskit`. + #[cfg(feature = "accesskit")] + Accessibility(Arc), + + /// A hotreload event, basically telling us to update our templates. #[cfg(all( feature = "hot-reload", debug_assertions, @@ -18,6 +26,14 @@ pub enum UserEvent { HotReloadEvent(dioxus_hot_reload::HotReloadMsg), } +#[cfg(feature = "accesskit")] +impl From for UserEvent { + fn from(value: AccessibilityEvent) -> Self { + Self::Accessibility(Arc::new(value)) + } +} + + #[derive(Debug, Clone)] pub enum EventData { Poll, diff --git a/packages/dioxus-blitz/src/window.rs b/packages/dioxus-blitz/src/window.rs index ae4c3de2..9e07cc08 100644 --- a/packages/dioxus-blitz/src/window.rs +++ b/packages/dioxus-blitz/src/window.rs @@ -15,6 +15,15 @@ use winit::event::{ElementState, MouseButton}; use winit::event_loop::{ActiveEventLoop, EventLoopProxy}; use winit::{event::WindowEvent, keyboard::KeyCode, keyboard::ModifiersState, window::Window}; +struct State { + /// Accessibility adapter for `accesskit`. + #[cfg(feature = "accesskit")] + adapter: accesskit_winit::Adapter, + + /// Main menu bar of this view's window. + _menu: Menu, +} + pub(crate) struct View<'s, Doc: DocumentLike> { pub(crate) renderer: Renderer<'s, Window, Doc>, pub(crate) scene: Scene, @@ -23,8 +32,8 @@ pub(crate) struct View<'s, Doc: DocumentLike> { /// need to store them in order to have access to them when processing keypress events keyboard_modifiers: ModifiersState, - /// Main menu bar of this view's window. - menu: Option, + /// State of this view, created on [`View::resume`]. + state: Option, } impl<'a, Doc: DocumentLike> View<'a, Doc> { @@ -34,7 +43,7 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { scene: Scene::new(), waker: None, keyboard_modifiers: Default::default(), - menu: None, + state: None, } } } @@ -268,6 +277,11 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { } } + #[cfg(feature = "accesskit")] + pub fn handle_accessibility_event(&mut self, event: &accesskit_winit::Event) { + todo!() + } + pub fn resume( &mut self, event_loop: &ActiveEventLoop, @@ -282,10 +296,16 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { })) .unwrap(); - self.menu = Some(init_menu( + // Initialize the menu and accessibility adapter in this view's state. + let menu = init_menu( #[cfg(target_os = "windows")] &window, - )); + ); + self.state = Some(State { + #[cfg(feature = "accesskit")] + adapter: accesskit_winit::Adapter::with_event_loop_proxy(&window, proxy.clone()), + _menu: menu, + }); let size: winit::dpi::PhysicalSize = window.inner_size(); let mut viewport = Viewport::new((size.width, size.height)); From f4d03f7ba93e7b714f52d188df9b68f9c2ad75df Mon Sep 17 00:00:00 2001 From: Matt Hunzinger Date: Thu, 20 Jun 2024 21:06:56 -0400 Subject: [PATCH 08/21] Build example accesskit tree --- examples/accessibility.rs | 13 ++++++++++ packages/dioxus-blitz/src/lib.rs | 5 +--- packages/dioxus-blitz/src/waker.rs | 3 +-- packages/dioxus-blitz/src/window.rs | 38 +++++++++++++++++++++++++++-- 4 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 examples/accessibility.rs diff --git a/examples/accessibility.rs b/examples/accessibility.rs new file mode 100644 index 00000000..44b894f2 --- /dev/null +++ b/examples/accessibility.rs @@ -0,0 +1,13 @@ +use dioxus::prelude::*; + +fn main() { + dioxus_blitz::launch(app); +} + +fn app() -> Element { + rsx! { + body { + "Dioxus 4 all" + } + } +} diff --git a/packages/dioxus-blitz/src/lib.rs b/packages/dioxus-blitz/src/lib.rs index 2b90cec7..e2ccd0f0 100644 --- a/packages/dioxus-blitz/src/lib.rs +++ b/packages/dioxus-blitz/src/lib.rs @@ -106,7 +106,6 @@ fn launch_with_window(window: View<'static, Doc>) { #[cfg(not(any(target_os = "android", target_os = "ios")))] let mut initial = true; - // Setup hot-reloading if enabled. #[cfg(all( feature = "hot-reload", @@ -127,8 +126,6 @@ fn launch_with_window(window: View<'static, Doc>) { }); } - - // the move to winit wants us to use a struct with a run method instead of the callback approach // we want to just keep the callback approach for now #[allow(deprecated)] @@ -194,7 +191,7 @@ fn launch_with_window(window: View<'static, Doc>) { #[cfg(feature = "accesskit")] UserEvent::Accessibility(accessibility_event) => { if let Some(window) = windows.get_mut(&accessibility_event.window_id) { - window.handle_accessibility_event(&accessibility_event); + window.handle_accessibility_event(&accessibility_event.window_event); } } #[cfg(all( diff --git a/packages/dioxus-blitz/src/waker.rs b/packages/dioxus-blitz/src/waker.rs index 1fd5faf2..9398dd67 100644 --- a/packages/dioxus-blitz/src/waker.rs +++ b/packages/dioxus-blitz/src/waker.rs @@ -15,7 +15,7 @@ pub enum UserEvent { /// An accessibility event from `accesskit`. #[cfg(feature = "accesskit")] Accessibility(Arc), - + /// A hotreload event, basically telling us to update our templates. #[cfg(all( feature = "hot-reload", @@ -33,7 +33,6 @@ impl From for UserEvent { } } - #[derive(Debug, Clone)] pub enum EventData { Poll, diff --git a/packages/dioxus-blitz/src/window.rs b/packages/dioxus-blitz/src/window.rs index 9e07cc08..e285c44d 100644 --- a/packages/dioxus-blitz/src/window.rs +++ b/packages/dioxus-blitz/src/window.rs @@ -278,8 +278,42 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { } #[cfg(feature = "accesskit")] - pub fn handle_accessibility_event(&mut self, event: &accesskit_winit::Event) { - todo!() + pub fn handle_accessibility_event(&mut self, event: &accesskit_winit::WindowEvent) { + let Some(ref mut state) = self.state else { + return; + }; + + match event { + accesskit_winit::WindowEvent::InitialTreeRequested => { + let doc = self.renderer.dom.as_ref(); + let root = doc.root_node(); + + let mut window = accesskit::NodeBuilder::new(accesskit::Role::Window); + + let mut text = accesskit::NodeBuilder::new(accesskit::Role::StaticText); + + text.set_name(dbg!(doc.get_node(root.children[0]).unwrap().text_content())); + window.push_child(accesskit::NodeId(1)); + + let tree = accesskit::Tree::new(accesskit::NodeId(0)); + let tree_update = accesskit::TreeUpdate { + nodes: vec![ + (accesskit::NodeId(0), window.build()), + (accesskit::NodeId(1), text.build()), + ], + tree: Some(tree), + focus: accesskit::NodeId(1), + }; + + state.adapter.update_if_active(|| tree_update) + } + accesskit_winit::WindowEvent::AccessibilityDeactivated => { + // TODO + } + accesskit_winit::WindowEvent::ActionRequested(_action) => { + // TODO + } + } } pub fn resume( From dde3fcb4a4aae9ab5fd8e1dff765dc8b749326c5 Mon Sep 17 00:00:00 2001 From: Matt Hunzinger Date: Thu, 20 Jun 2024 21:33:29 -0400 Subject: [PATCH 09/21] Add Document::visit method and build initial accesskit tree from View --- packages/dioxus-blitz/src/window.rs | 30 ++++++++++++++++++++--------- packages/dom/src/document.rs | 19 +++++++++++++++++- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/packages/dioxus-blitz/src/window.rs b/packages/dioxus-blitz/src/window.rs index e285c44d..4a7cbb55 100644 --- a/packages/dioxus-blitz/src/window.rs +++ b/packages/dioxus-blitz/src/window.rs @@ -286,21 +286,33 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { match event { accesskit_winit::WindowEvent::InitialTreeRequested => { let doc = self.renderer.dom.as_ref(); - let root = doc.root_node(); - let mut window = accesskit::NodeBuilder::new(accesskit::Role::Window); + let mut nodes = Vec::new(); + let mut next_id = 1; + + doc.visit(|node| { + let mut node_builder = accesskit::NodeBuilder::default(); + if let Some(element_data) = node.element_data() { + node_builder.set_role(accesskit::Role::Group); + node_builder.set_name(element_data.name.local.to_string()) + } else if node.is_text_node() { + node_builder.set_role(accesskit::Role::StaticText); + node_builder.set_name(node.text_content()); + } - let mut text = accesskit::NodeBuilder::new(accesskit::Role::StaticText); + nodes.push((accesskit::NodeId(next_id), node_builder.build())); + next_id += 1; + }); - text.set_name(dbg!(doc.get_node(root.children[0]).unwrap().text_content())); - window.push_child(accesskit::NodeId(1)); + let mut window = accesskit::NodeBuilder::new(accesskit::Role::Window); + for (id, _) in &nodes { + window.push_child(*id); + } + nodes.push((accesskit::NodeId(0), window.build())); let tree = accesskit::Tree::new(accesskit::NodeId(0)); let tree_update = accesskit::TreeUpdate { - nodes: vec![ - (accesskit::NodeId(0), window.build()), - (accesskit::NodeId(1), text.build()), - ], + nodes, tree: Some(tree), focus: accesskit::NodeId(1), }; diff --git a/packages/dom/src/document.rs b/packages/dom/src/document.rs index ab1d5e42..29aa3958 100644 --- a/packages/dom/src/document.rs +++ b/packages/dom/src/document.rs @@ -5,7 +5,7 @@ use crate::{Node, NodeData, TextNodeData}; use selectors::{matching::QuirksMode, Element}; use slab::Slab; use std::any::Any; -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; use style::invalidation::element::restyle_hints::RestyleHint; use style::selector_parser::ServoElementSnapshot; use style::servo::media_queries::FontMetricsProvider; @@ -529,6 +529,23 @@ impl Document { pub fn set_document(&mut self, _content: String) {} pub fn add_element(&mut self) {} + + pub fn visit(&self, mut visit: F) + where + F: FnMut(&Node), + { + let mut stack = VecDeque::new(); + stack.push_front(0); + + while let Some(node_key) = stack.pop_back() { + let node = &self.nodes[node_key]; + visit(node); + + for &child_key in &node.children { + stack.push_front(child_key); + } + } + } } impl AsRef for Document { From dcbfb91fff58a083a86780d075503ff1f3bff66f Mon Sep 17 00:00:00 2001 From: Matt Hunzinger Date: Thu, 20 Jun 2024 22:27:19 -0400 Subject: [PATCH 10/21] Create mapping from accesskit id to node id --- packages/dioxus-blitz/src/window.rs | 26 +++++++++++++++++++++----- packages/dom/src/document.rs | 4 ++-- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/packages/dioxus-blitz/src/window.rs b/packages/dioxus-blitz/src/window.rs index 4a7cbb55..e90573c6 100644 --- a/packages/dioxus-blitz/src/window.rs +++ b/packages/dioxus-blitz/src/window.rs @@ -20,6 +20,9 @@ struct State { #[cfg(feature = "accesskit")] adapter: accesskit_winit::Adapter, + #[cfg(feature = "accesskit")] + node_ids: std::collections::HashMap, + /// Main menu bar of this view's window. _menu: Menu, } @@ -290,18 +293,29 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { let mut nodes = Vec::new(); let mut next_id = 1; - doc.visit(|node| { + doc.visit(|node_id, node| { let mut node_builder = accesskit::NodeBuilder::default(); if let Some(element_data) = node.element_data() { - node_builder.set_role(accesskit::Role::Group); - node_builder.set_name(element_data.name.local.to_string()) + let name = element_data.name.local.to_string(); + let role = match &*name { + "button" => accesskit::Role::Button, + "div" | "section" => accesskit::Role::Group, + "p" => accesskit::Role::Paragraph, + _ => accesskit::Role::Unknown, + }; + + node_builder.set_role(role); + node_builder.set_name(name); } else if node.is_text_node() { node_builder.set_role(accesskit::Role::StaticText); node_builder.set_name(node.text_content()); } - nodes.push((accesskit::NodeId(next_id), node_builder.build())); + let id = accesskit::NodeId(next_id); next_id += 1; + + state.node_ids.insert(id, node_id); + nodes.push((id, node_builder.build())); }); let mut window = accesskit::NodeBuilder::new(accesskit::Role::Window); @@ -322,7 +336,7 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { accesskit_winit::WindowEvent::AccessibilityDeactivated => { // TODO } - accesskit_winit::WindowEvent::ActionRequested(_action) => { + accesskit_winit::WindowEvent::ActionRequested(_req) => { // TODO } } @@ -350,6 +364,8 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { self.state = Some(State { #[cfg(feature = "accesskit")] adapter: accesskit_winit::Adapter::with_event_loop_proxy(&window, proxy.clone()), + #[cfg(feature = "accesskit")] + node_ids: Default::default(), _menu: menu, }); diff --git a/packages/dom/src/document.rs b/packages/dom/src/document.rs index 29aa3958..7fe878a4 100644 --- a/packages/dom/src/document.rs +++ b/packages/dom/src/document.rs @@ -532,14 +532,14 @@ impl Document { pub fn visit(&self, mut visit: F) where - F: FnMut(&Node), + F: FnMut(usize, &Node), { let mut stack = VecDeque::new(); stack.push_front(0); while let Some(node_key) = stack.pop_back() { let node = &self.nodes[node_key]; - visit(node); + visit(node_key, node); for &child_key in &node.children { stack.push_front(child_key); From 5b4e544911f3613186b03daa095a16f0573af8ea Mon Sep 17 00:00:00 2001 From: Matt Hunzinger Date: Fri, 21 Jun 2024 14:23:08 -0400 Subject: [PATCH 11/21] Send basic tree updates on dom change --- packages/dioxus-blitz/src/window.rs | 65 ++++++++++++++++++++++++++++- packages/dom/src/document.rs | 11 ++++- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/packages/dioxus-blitz/src/window.rs b/packages/dioxus-blitz/src/window.rs index e90573c6..b78fc578 100644 --- a/packages/dioxus-blitz/src/window.rs +++ b/packages/dioxus-blitz/src/window.rs @@ -1,12 +1,13 @@ use crate::waker::UserEvent; use blitz::{RenderState, Renderer, Viewport}; -use blitz_dom::DocumentLike; +use blitz_dom::{Document, DocumentLike, Node}; use winit::keyboard::PhysicalKey; #[allow(unused)] use wgpu::rwh::HasWindowHandle; use muda::{AboutMetadata, Menu, MenuId, MenuItem, PredefinedMenuItem, Submenu}; +use std::mem; use std::sync::Arc; use std::task::Waker; use vello::Scene; @@ -23,10 +24,40 @@ struct State { #[cfg(feature = "accesskit")] node_ids: std::collections::HashMap, + #[cfg(feature = "accesskit")] + next_id: u64, + /// Main menu bar of this view's window. _menu: Menu, } +impl State { + fn build_node(&mut self, node_id: usize, node: &Node) -> (accesskit::NodeId, accesskit::Node) { + let mut node_builder = accesskit::NodeBuilder::default(); + if let Some(element_data) = node.element_data() { + let name = element_data.name.local.to_string(); + let role = match &*name { + "button" => accesskit::Role::Button, + "div" | "section" => accesskit::Role::Group, + "p" => accesskit::Role::Paragraph, + _ => accesskit::Role::Unknown, + }; + + node_builder.set_role(role); + node_builder.set_name(name); + } else if node.is_text_node() { + node_builder.set_role(accesskit::Role::StaticText); + node_builder.set_name(node.text_content()); + } + + let id = accesskit::NodeId(self.next_id); + self.next_id += 1; + + self.node_ids.insert(id, node_id); + (id, node_builder.build()) + } +} + pub(crate) struct View<'s, Doc: DocumentLike> { pub(crate) renderer: Renderer<'s, Window, Doc>, pub(crate) scene: Scene, @@ -57,7 +88,35 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { None => false, Some(waker) => { let cx = std::task::Context::from_waker(waker); - self.renderer.poll(cx) + if self.renderer.poll(cx) { + let Some(ref mut state) = self.state else { + return true; + }; + + let changed = mem::take(&mut self.renderer.dom.as_mut().changed); + if !changed.is_empty() { + let doc = self.renderer.dom.as_ref(); + + let nodes = changed + .iter() + .map(|id| { + let node = doc.get_node(*id).unwrap(); + state.build_node(*id, node) + }) + .collect(); + + let tree_update = accesskit::TreeUpdate { + nodes, + tree: None, + focus: accesskit::NodeId(1), + }; + state.adapter.update_if_active(|| tree_update); + } + + true + } else { + false + } } } } @@ -366,6 +425,8 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { adapter: accesskit_winit::Adapter::with_event_loop_proxy(&window, proxy.clone()), #[cfg(feature = "accesskit")] node_ids: Default::default(), + #[cfg(feature = "accesskit")] + next_id: 1, _menu: menu, }); diff --git a/packages/dom/src/document.rs b/packages/dom/src/document.rs index 7fe878a4..5182c8ea 100644 --- a/packages/dom/src/document.rs +++ b/packages/dom/src/document.rs @@ -5,7 +5,7 @@ use crate::{Node, NodeData, TextNodeData}; use selectors::{matching::QuirksMode, Element}; use slab::Slab; use std::any::Any; -use std::collections::{HashMap, VecDeque}; +use std::collections::{HashMap, HashSet, VecDeque}; use style::invalidation::element::restyle_hints::RestyleHint; use style::selector_parser::ServoElementSnapshot; use style::servo::media_queries::FontMetricsProvider; @@ -92,6 +92,8 @@ pub struct Document { pub(crate) layout_ctx: parley::LayoutContext, pub(crate) hover_node_id: Option, + + pub changed: HashSet, } impl Document { @@ -122,6 +124,7 @@ impl Document { layout_ctx: parley::LayoutContext::new(), hover_node_id: None, + changed: HashSet::new(), }; // Initialise document with root Document node @@ -184,6 +187,9 @@ impl Document { // id as usize, // ); + // Mark the new node as changed. + self.changed.insert(id); + id } @@ -227,6 +233,9 @@ impl Document { let parent_id = node.parent.unwrap_or_default(); let parent = &mut self.nodes[parent_id]; + // Mark the node's parent as changed. + self.changed.insert(parent_id); + let mut children = std::mem::take(&mut parent.children); children.splice( node_child_idx..node_child_idx, From f2be9f35b19a4ff6aadb2c273b5b2d2c69d6af8f Mon Sep 17 00:00:00 2001 From: Matt Hunzinger Date: Fri, 21 Jun 2024 14:39:35 -0400 Subject: [PATCH 12/21] Clean up and map ids to node ids --- packages/dioxus-blitz/src/window.rs | 55 ++++++++++++++--------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/packages/dioxus-blitz/src/window.rs b/packages/dioxus-blitz/src/window.rs index b78fc578..a806f661 100644 --- a/packages/dioxus-blitz/src/window.rs +++ b/packages/dioxus-blitz/src/window.rs @@ -24,6 +24,9 @@ struct State { #[cfg(feature = "accesskit")] node_ids: std::collections::HashMap, + #[cfg(feature = "accesskit")] + id_to_node_ids: std::collections::HashMap, + #[cfg(feature = "accesskit")] next_id: u64, @@ -32,7 +35,11 @@ struct State { } impl State { - fn build_node(&mut self, node_id: usize, node: &Node) -> (accesskit::NodeId, accesskit::Node) { + fn build_node( + &mut self, + node_id: usize, + node: &Node, + ) -> (accesskit::NodeId, accesskit::NodeBuilder) { let mut node_builder = accesskit::NodeBuilder::default(); if let Some(element_data) = node.element_data() { let name = element_data.name.local.to_string(); @@ -54,7 +61,9 @@ impl State { self.next_id += 1; self.node_ids.insert(id, node_id); - (id, node_builder.build()) + self.id_to_node_ids.insert(node_id, id); + + (id, node_builder) } } @@ -97,18 +106,26 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { if !changed.is_empty() { let doc = self.renderer.dom.as_ref(); - let nodes = changed + let updates: Vec<_> = changed .iter() .map(|id| { let node = doc.get_node(*id).unwrap(); - state.build_node(*id, node) + (id, state.build_node(*id, node)) }) .collect(); + let mut nodes = Vec::new(); + for (id, (node_id, mut node)) in updates { + for child_id in &doc.get_node(*id).unwrap().children { + node.push_child(state.id_to_node_ids[child_id]) + } + nodes.push((node_id, node.build())) + } + let tree_update = accesskit::TreeUpdate { nodes, tree: None, - focus: accesskit::NodeId(1), + focus: accesskit::NodeId(0), }; state.adapter.update_if_active(|| tree_update); } @@ -350,30 +367,8 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { let doc = self.renderer.dom.as_ref(); let mut nodes = Vec::new(); - let mut next_id = 1; - doc.visit(|node_id, node| { - let mut node_builder = accesskit::NodeBuilder::default(); - if let Some(element_data) = node.element_data() { - let name = element_data.name.local.to_string(); - let role = match &*name { - "button" => accesskit::Role::Button, - "div" | "section" => accesskit::Role::Group, - "p" => accesskit::Role::Paragraph, - _ => accesskit::Role::Unknown, - }; - - node_builder.set_role(role); - node_builder.set_name(name); - } else if node.is_text_node() { - node_builder.set_role(accesskit::Role::StaticText); - node_builder.set_name(node.text_content()); - } - - let id = accesskit::NodeId(next_id); - next_id += 1; - - state.node_ids.insert(id, node_id); + let (id, node_builder) = state.build_node(node_id, node); nodes.push((id, node_builder.build())); }); @@ -387,7 +382,7 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { let tree_update = accesskit::TreeUpdate { nodes, tree: Some(tree), - focus: accesskit::NodeId(1), + focus: accesskit::NodeId(0), }; state.adapter.update_if_active(|| tree_update) @@ -426,6 +421,8 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { #[cfg(feature = "accesskit")] node_ids: Default::default(), #[cfg(feature = "accesskit")] + id_to_node_ids: Default::default(), + #[cfg(feature = "accesskit")] next_id: 1, _menu: menu, }); From a4193834b09506f00343adcef94657af854ce0a6 Mon Sep 17 00:00:00 2001 From: Matt Hunzinger Date: Fri, 21 Jun 2024 15:28:06 -0400 Subject: [PATCH 13/21] Rebuild tree until observability is figured out --- packages/dioxus-blitz/src/window.rs | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/packages/dioxus-blitz/src/window.rs b/packages/dioxus-blitz/src/window.rs index a806f661..49374133 100644 --- a/packages/dioxus-blitz/src/window.rs +++ b/packages/dioxus-blitz/src/window.rs @@ -106,28 +106,26 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { if !changed.is_empty() { let doc = self.renderer.dom.as_ref(); - let updates: Vec<_> = changed - .iter() - .map(|id| { - let node = doc.get_node(*id).unwrap(); - (id, state.build_node(*id, node)) - }) - .collect(); - let mut nodes = Vec::new(); - for (id, (node_id, mut node)) in updates { - for child_id in &doc.get_node(*id).unwrap().children { - node.push_child(state.id_to_node_ids[child_id]) - } - nodes.push((node_id, node.build())) + doc.visit(|node_id, node| { + let (id, node_builder) = state.build_node(node_id, node); + nodes.push((id, node_builder.build())); + }); + + let mut window = accesskit::NodeBuilder::new(accesskit::Role::Window); + for (id, _) in &nodes { + window.push_child(*id); } + nodes.push((accesskit::NodeId(0), window.build())); + let tree = accesskit::Tree::new(accesskit::NodeId(0)); let tree_update = accesskit::TreeUpdate { nodes, - tree: None, + tree: Some(tree), focus: accesskit::NodeId(0), }; - state.adapter.update_if_active(|| tree_update); + + state.adapter.update_if_active(|| tree_update) } true From d407e0bc3c0fe9879b0f022d9f91d794188ef797 Mon Sep 17 00:00:00 2001 From: Matt Hunzinger Date: Fri, 21 Jun 2024 16:05:25 -0400 Subject: [PATCH 14/21] Clean up --- examples/accessibility.rs | 9 ++- packages/dioxus-blitz/src/window.rs | 91 ++++++++++++----------------- 2 files changed, 44 insertions(+), 56 deletions(-) diff --git a/examples/accessibility.rs b/examples/accessibility.rs index 44b894f2..3ffa387f 100644 --- a/examples/accessibility.rs +++ b/examples/accessibility.rs @@ -7,7 +7,14 @@ fn main() { fn app() -> Element { rsx! { body { - "Dioxus 4 all" + App {} } } } + +#[component] +fn App() -> Element { + rsx! { + div { "Dioxus for all" } + } +} diff --git a/packages/dioxus-blitz/src/window.rs b/packages/dioxus-blitz/src/window.rs index 49374133..96febaa4 100644 --- a/packages/dioxus-blitz/src/window.rs +++ b/packages/dioxus-blitz/src/window.rs @@ -1,6 +1,6 @@ use crate::waker::UserEvent; use blitz::{RenderState, Renderer, Viewport}; -use blitz_dom::{Document, DocumentLike, Node}; +use blitz_dom::{DocumentLike, Node}; use winit::keyboard::PhysicalKey; #[allow(unused)] @@ -98,34 +98,10 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { Some(waker) => { let cx = std::task::Context::from_waker(waker); if self.renderer.poll(cx) { - let Some(ref mut state) = self.state else { - return true; - }; - + // TODO send fine grained accessibility tree updates. let changed = mem::take(&mut self.renderer.dom.as_mut().changed); if !changed.is_empty() { - let doc = self.renderer.dom.as_ref(); - - let mut nodes = Vec::new(); - doc.visit(|node_id, node| { - let (id, node_builder) = state.build_node(node_id, node); - nodes.push((id, node_builder.build())); - }); - - let mut window = accesskit::NodeBuilder::new(accesskit::Role::Window); - for (id, _) in &nodes { - window.push_child(*id); - } - nodes.push((accesskit::NodeId(0), window.build())); - - let tree = accesskit::Tree::new(accesskit::NodeId(0)); - let tree_update = accesskit::TreeUpdate { - nodes, - tree: Some(tree), - focus: accesskit::NodeId(0), - }; - - state.adapter.update_if_active(|| tree_update) + self.build_accessibility_tree() } true @@ -356,35 +332,8 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { #[cfg(feature = "accesskit")] pub fn handle_accessibility_event(&mut self, event: &accesskit_winit::WindowEvent) { - let Some(ref mut state) = self.state else { - return; - }; - match event { - accesskit_winit::WindowEvent::InitialTreeRequested => { - let doc = self.renderer.dom.as_ref(); - - let mut nodes = Vec::new(); - doc.visit(|node_id, node| { - let (id, node_builder) = state.build_node(node_id, node); - nodes.push((id, node_builder.build())); - }); - - let mut window = accesskit::NodeBuilder::new(accesskit::Role::Window); - for (id, _) in &nodes { - window.push_child(*id); - } - nodes.push((accesskit::NodeId(0), window.build())); - - let tree = accesskit::Tree::new(accesskit::NodeId(0)); - let tree_update = accesskit::TreeUpdate { - nodes, - tree: Some(tree), - focus: accesskit::NodeId(0), - }; - - state.adapter.update_if_active(|| tree_update) - } + accesskit_winit::WindowEvent::InitialTreeRequested => self.build_accessibility_tree(), accesskit_winit::WindowEvent::AccessibilityDeactivated => { // TODO } @@ -446,6 +395,38 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { self.waker = None; self.renderer.suspend(); } + + fn build_accessibility_tree(&mut self) { + let Some(ref mut state) = self.state else { + return; + }; + + let changed = mem::take(&mut self.renderer.dom.as_mut().changed); + if !changed.is_empty() { + let doc = self.renderer.dom.as_ref(); + + let mut nodes = Vec::new(); + doc.visit(|node_id, node| { + let (id, node_builder) = state.build_node(node_id, node); + nodes.push((id, node_builder.build())); + }); + + let mut window = accesskit::NodeBuilder::new(accesskit::Role::Window); + for (id, _) in &nodes { + window.push_child(*id); + } + nodes.push((accesskit::NodeId(0), window.build())); + + let tree = accesskit::Tree::new(accesskit::NodeId(0)); + let tree_update = accesskit::TreeUpdate { + nodes, + tree: Some(tree), + focus: accesskit::NodeId(0), + }; + + state.adapter.update_if_active(|| tree_update) + } + } } /// Initialize the default menu bar. From 05b04a2eb0b8b88c205120a0e02a07988fc161df Mon Sep 17 00:00:00 2001 From: Matt Hunzinger Date: Fri, 21 Jun 2024 16:22:44 -0400 Subject: [PATCH 15/21] Fix tree hierarchy --- packages/dioxus-blitz/src/window.rs | 60 +++++++++++++++++------------ 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/packages/dioxus-blitz/src/window.rs b/packages/dioxus-blitz/src/window.rs index 96febaa4..6325c7c1 100644 --- a/packages/dioxus-blitz/src/window.rs +++ b/packages/dioxus-blitz/src/window.rs @@ -98,10 +98,13 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { Some(waker) => { let cx = std::task::Context::from_waker(waker); if self.renderer.poll(cx) { - // TODO send fine grained accessibility tree updates. - let changed = mem::take(&mut self.renderer.dom.as_mut().changed); - if !changed.is_empty() { - self.build_accessibility_tree() + #[cfg(feature = "accesskit")] + { + // TODO send fine grained accessibility tree updates. + let changed = mem::take(&mut self.renderer.dom.as_mut().changed); + if !changed.is_empty() { + self.build_accessibility_tree() + } } true @@ -396,36 +399,45 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { self.renderer.suspend(); } + #[cfg(feature = "accesskit")] fn build_accessibility_tree(&mut self) { let Some(ref mut state) = self.state else { return; }; - let changed = mem::take(&mut self.renderer.dom.as_mut().changed); - if !changed.is_empty() { - let doc = self.renderer.dom.as_ref(); + let doc = self.renderer.dom.as_ref(); - let mut nodes = Vec::new(); - doc.visit(|node_id, node| { - let (id, node_builder) = state.build_node(node_id, node); - nodes.push((id, node_builder.build())); - }); + let mut nodes = std::collections::HashMap::new(); + let mut window = accesskit::NodeBuilder::new(accesskit::Role::Window); - let mut window = accesskit::NodeBuilder::new(accesskit::Role::Window); - for (id, _) in &nodes { - window.push_child(*id); + doc.visit(|node_id, node| { + let (id, node_builder) = state.build_node(node_id, node); + + if let Some(parent_id) = node.parent { + let (_, parent_node): &mut (_, accesskit::NodeBuilder) = + nodes.get_mut(&parent_id).unwrap(); + parent_node.push_child(id) + } else { + window.push_child(id) } - nodes.push((accesskit::NodeId(0), window.build())); - let tree = accesskit::Tree::new(accesskit::NodeId(0)); - let tree_update = accesskit::TreeUpdate { - nodes, - tree: Some(tree), - focus: accesskit::NodeId(0), - }; + nodes.insert(node_id, (id, node_builder)); + }); - state.adapter.update_if_active(|| tree_update) - } + let mut nodes: Vec<_> = nodes + .into_iter() + .map(|(_, (id, node))| (id, node.build())) + .collect(); + nodes.push((accesskit::NodeId(0), window.build())); + + let tree = accesskit::Tree::new(accesskit::NodeId(0)); + let tree_update = accesskit::TreeUpdate { + nodes, + tree: Some(tree), + focus: accesskit::NodeId(0), + }; + + state.adapter.update_if_active(|| tree_update) } } From 3d3e67d136b007b75c40034cf6101e3999b76ff8 Mon Sep 17 00:00:00 2001 From: Matt Hunzinger Date: Wed, 26 Jun 2024 15:46:12 -0400 Subject: [PATCH 16/21] Create AccessibilityState struct and refactor --- packages/dioxus-blitz/src/window.rs | 51 +++++++++++++---------------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/packages/dioxus-blitz/src/window.rs b/packages/dioxus-blitz/src/window.rs index 6325c7c1..5cd04bd5 100644 --- a/packages/dioxus-blitz/src/window.rs +++ b/packages/dioxus-blitz/src/window.rs @@ -16,30 +16,27 @@ use winit::event::{ElementState, MouseButton}; use winit::event_loop::{ActiveEventLoop, EventLoopProxy}; use winit::{event::WindowEvent, keyboard::KeyCode, keyboard::ModifiersState, window::Window}; -struct State { - /// Accessibility adapter for `accesskit`. - #[cfg(feature = "accesskit")] +#[cfg(feature = "accesskit")] +struct AccessibilityState { + /// Adapter to connect to the [`EventLoop`](`winit::event_loop::EventLoop`). adapter: accesskit_winit::Adapter, - #[cfg(feature = "accesskit")] - node_ids: std::collections::HashMap, - - #[cfg(feature = "accesskit")] - id_to_node_ids: std::collections::HashMap, + /// Next ID to assign an an [`accesskit::Node`]. + next_id: u64, +} +struct State { #[cfg(feature = "accesskit")] - next_id: u64, + /// Accessibility adapter for `accesskit`. + accessibility: AccessibilityState, /// Main menu bar of this view's window. _menu: Menu, } impl State { - fn build_node( - &mut self, - node_id: usize, - node: &Node, - ) -> (accesskit::NodeId, accesskit::NodeBuilder) { + #[cfg(feature = "accesskit")] + fn build_node(&mut self, node: &Node) -> (accesskit::NodeId, accesskit::NodeBuilder) { let mut node_builder = accesskit::NodeBuilder::default(); if let Some(element_data) = node.element_data() { let name = element_data.name.local.to_string(); @@ -57,11 +54,8 @@ impl State { node_builder.set_name(node.text_content()); } - let id = accesskit::NodeId(self.next_id); - self.next_id += 1; - - self.node_ids.insert(id, node_id); - self.id_to_node_ids.insert(node_id, id); + let id = accesskit::NodeId(self.accessibility.next_id); + self.accessibility.next_id += 1; (id, node_builder) } @@ -367,13 +361,14 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { ); self.state = Some(State { #[cfg(feature = "accesskit")] - adapter: accesskit_winit::Adapter::with_event_loop_proxy(&window, proxy.clone()), - #[cfg(feature = "accesskit")] - node_ids: Default::default(), - #[cfg(feature = "accesskit")] - id_to_node_ids: Default::default(), - #[cfg(feature = "accesskit")] - next_id: 1, + accessibility: AccessibilityState { + adapter: accesskit_winit::Adapter::with_event_loop_proxy( + &window, + proxy.clone(), + ), + next_id: 1, + }, + _menu: menu, }); @@ -411,7 +406,7 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { let mut window = accesskit::NodeBuilder::new(accesskit::Role::Window); doc.visit(|node_id, node| { - let (id, node_builder) = state.build_node(node_id, node); + let (id, node_builder) = state.build_node(node); if let Some(parent_id) = node.parent { let (_, parent_node): &mut (_, accesskit::NodeBuilder) = @@ -437,7 +432,7 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { focus: accesskit::NodeId(0), }; - state.adapter.update_if_active(|| tree_update) + state.accessibility.adapter.update_if_active(|| tree_update) } } From 6a009f50330b096ed4e00517c26e8f7835ffafee Mon Sep 17 00:00:00 2001 From: Matt Hunzinger Date: Wed, 26 Jun 2024 15:53:43 -0400 Subject: [PATCH 17/21] Handle more accesskit roles --- packages/dioxus-blitz/src/window.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/dioxus-blitz/src/window.rs b/packages/dioxus-blitz/src/window.rs index 5cd04bd5..36def911 100644 --- a/packages/dioxus-blitz/src/window.rs +++ b/packages/dioxus-blitz/src/window.rs @@ -37,14 +37,29 @@ struct State { impl State { #[cfg(feature = "accesskit")] fn build_node(&mut self, node: &Node) -> (accesskit::NodeId, accesskit::NodeBuilder) { + use accesskit::Role; + use blitz_dom::local_name; + let mut node_builder = accesskit::NodeBuilder::default(); if let Some(element_data) = node.element_data() { let name = element_data.name.local.to_string(); + + // TODO match more roles let role = match &*name { - "button" => accesskit::Role::Button, - "div" | "section" => accesskit::Role::Group, - "p" => accesskit::Role::Paragraph, - _ => accesskit::Role::Unknown, + "button" => Role::Button, + "div" => Role::GenericContainer, + "header" => Role::Header, + "h1" | "h2" | "h3" | "h4" | "h5" | "h6" => Role::Heading, + "p" => Role::Paragraph, + "section" => Role::Section, + "input" => { + let ty = element_data.attr(local_name!("type")).unwrap_or("text"); + match ty { + "number" => Role::NumberInput, + _ => Role::TextInput, + } + } + _ => Role::Unknown, }; node_builder.set_role(role); From 7f7e91ba54abd47138b86a77a7abc85ca3f49042 Mon Sep 17 00:00:00 2001 From: Matt Hunzinger Date: Wed, 26 Jun 2024 16:02:47 -0400 Subject: [PATCH 18/21] Move accessibility to new module --- packages/dioxus-blitz/src/accessibility.rs | 92 +++++++++++++++ packages/dioxus-blitz/src/lib.rs | 3 + packages/dioxus-blitz/src/window.rs | 124 +++------------------ 3 files changed, 112 insertions(+), 107 deletions(-) create mode 100644 packages/dioxus-blitz/src/accessibility.rs diff --git a/packages/dioxus-blitz/src/accessibility.rs b/packages/dioxus-blitz/src/accessibility.rs new file mode 100644 index 00000000..57033756 --- /dev/null +++ b/packages/dioxus-blitz/src/accessibility.rs @@ -0,0 +1,92 @@ +use accesskit::Role; +use blitz_dom::{local_name, Document, Node}; +use winit::{event_loop::EventLoopProxy, window::Window}; +use crate::waker::UserEvent; + +/// State of the accessibility node tree and platform adapter. +pub struct AccessibilityState { + /// Adapter to connect to the [`EventLoop`](`winit::event_loop::EventLoop`). + adapter: accesskit_winit::Adapter, + + /// Next ID to assign an an [`accesskit::Node`]. + next_id: u64, +} + +impl AccessibilityState { + pub fn new(window: &Window, proxy: EventLoopProxy) -> Self { + Self { + adapter: accesskit_winit::Adapter::with_event_loop_proxy(window, proxy.clone()), + next_id: 1, + } + } + pub fn build_tree(&mut self, doc: &Document) { + let mut nodes = std::collections::HashMap::new(); + let mut window = accesskit::NodeBuilder::new(accesskit::Role::Window); + + doc.visit(|node_id, node| { + let (id, node_builder) = self.build_node(node); + + if let Some(parent_id) = node.parent { + let (_, parent_node): &mut (_, accesskit::NodeBuilder) = + nodes.get_mut(&parent_id).unwrap(); + parent_node.push_child(id) + } else { + window.push_child(id) + } + + nodes.insert(node_id, (id, node_builder)); + }); + + let mut nodes: Vec<_> = nodes + .into_iter() + .map(|(_, (id, node))| (id, node.build())) + .collect(); + nodes.push((accesskit::NodeId(0), window.build())); + + let tree = accesskit::Tree::new(accesskit::NodeId(0)); + let tree_update = accesskit::TreeUpdate { + nodes, + tree: Some(tree), + focus: accesskit::NodeId(0), + }; + + self.adapter.update_if_active(|| tree_update) + } + + #[cfg(feature = "accesskit")] + fn build_node(&mut self, node: &Node) -> (accesskit::NodeId, accesskit::NodeBuilder) { + let mut node_builder = accesskit::NodeBuilder::default(); + if let Some(element_data) = node.element_data() { + let name = element_data.name.local.to_string(); + + // TODO match more roles + let role = match &*name { + "button" => Role::Button, + "div" => Role::GenericContainer, + "header" => Role::Header, + "h1" | "h2" | "h3" | "h4" | "h5" | "h6" => Role::Heading, + "p" => Role::Paragraph, + "section" => Role::Section, + "input" => { + let ty = element_data.attr(local_name!("type")).unwrap_or("text"); + match ty { + "number" => Role::NumberInput, + _ => Role::TextInput, + } + } + _ => Role::Unknown, + }; + + node_builder.set_role(role); + node_builder.set_name(name); + } else if node.is_text_node() { + node_builder.set_role(accesskit::Role::StaticText); + node_builder.set_name(node.text_content()); + } + + let id = accesskit::NodeId(self.next_id); + self.next_id += 1; + + (id, node_builder) + } +} diff --git a/packages/dioxus-blitz/src/lib.rs b/packages/dioxus-blitz/src/lib.rs index e2ccd0f0..b62d2ce2 100644 --- a/packages/dioxus-blitz/src/lib.rs +++ b/packages/dioxus-blitz/src/lib.rs @@ -2,6 +2,9 @@ mod documents; mod waker; mod window; +#[cfg(feature = "accesskit")] +mod accessibility; + use crate::waker::{EventData, UserEvent}; use crate::{documents::HtmlDocument, window::View}; diff --git a/packages/dioxus-blitz/src/window.rs b/packages/dioxus-blitz/src/window.rs index 36def911..42e31099 100644 --- a/packages/dioxus-blitz/src/window.rs +++ b/packages/dioxus-blitz/src/window.rs @@ -1,6 +1,6 @@ use crate::waker::UserEvent; use blitz::{RenderState, Renderer, Viewport}; -use blitz_dom::{DocumentLike, Node}; +use blitz_dom::DocumentLike; use winit::keyboard::PhysicalKey; #[allow(unused)] @@ -16,66 +16,15 @@ use winit::event::{ElementState, MouseButton}; use winit::event_loop::{ActiveEventLoop, EventLoopProxy}; use winit::{event::WindowEvent, keyboard::KeyCode, keyboard::ModifiersState, window::Window}; -#[cfg(feature = "accesskit")] -struct AccessibilityState { - /// Adapter to connect to the [`EventLoop`](`winit::event_loop::EventLoop`). - adapter: accesskit_winit::Adapter, - - /// Next ID to assign an an [`accesskit::Node`]. - next_id: u64, -} - struct State { #[cfg(feature = "accesskit")] /// Accessibility adapter for `accesskit`. - accessibility: AccessibilityState, + accessibility: crate::accessibility::AccessibilityState, /// Main menu bar of this view's window. _menu: Menu, } -impl State { - #[cfg(feature = "accesskit")] - fn build_node(&mut self, node: &Node) -> (accesskit::NodeId, accesskit::NodeBuilder) { - use accesskit::Role; - use blitz_dom::local_name; - - let mut node_builder = accesskit::NodeBuilder::default(); - if let Some(element_data) = node.element_data() { - let name = element_data.name.local.to_string(); - - // TODO match more roles - let role = match &*name { - "button" => Role::Button, - "div" => Role::GenericContainer, - "header" => Role::Header, - "h1" | "h2" | "h3" | "h4" | "h5" | "h6" => Role::Heading, - "p" => Role::Paragraph, - "section" => Role::Section, - "input" => { - let ty = element_data.attr(local_name!("type")).unwrap_or("text"); - match ty { - "number" => Role::NumberInput, - _ => Role::TextInput, - } - } - _ => Role::Unknown, - }; - - node_builder.set_role(role); - node_builder.set_name(name); - } else if node.is_text_node() { - node_builder.set_role(accesskit::Role::StaticText); - node_builder.set_name(node.text_content()); - } - - let id = accesskit::NodeId(self.accessibility.next_id); - self.accessibility.next_id += 1; - - (id, node_builder) - } -} - pub(crate) struct View<'s, Doc: DocumentLike> { pub(crate) renderer: Renderer<'s, Window, Doc>, pub(crate) scene: Scene, @@ -109,10 +58,12 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { if self.renderer.poll(cx) { #[cfg(feature = "accesskit")] { - // TODO send fine grained accessibility tree updates. - let changed = mem::take(&mut self.renderer.dom.as_mut().changed); - if !changed.is_empty() { - self.build_accessibility_tree() + if let Some(ref mut state) = self.state { + // TODO send fine grained accessibility tree updates. + let changed = mem::take(&mut self.renderer.dom.as_mut().changed); + if !changed.is_empty() { + state.accessibility.build_tree(self.renderer.dom.as_ref()); + } } } @@ -345,7 +296,11 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { #[cfg(feature = "accesskit")] pub fn handle_accessibility_event(&mut self, event: &accesskit_winit::WindowEvent) { match event { - accesskit_winit::WindowEvent::InitialTreeRequested => self.build_accessibility_tree(), + accesskit_winit::WindowEvent::InitialTreeRequested => { + if let Some(ref mut state) = self.state { + state.accessibility.build_tree(self.renderer.dom.as_ref()); + } + } accesskit_winit::WindowEvent::AccessibilityDeactivated => { // TODO } @@ -376,14 +331,10 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { ); self.state = Some(State { #[cfg(feature = "accesskit")] - accessibility: AccessibilityState { - adapter: accesskit_winit::Adapter::with_event_loop_proxy( - &window, - proxy.clone(), - ), - next_id: 1, - }, - + accessibility: crate::accessibility::AccessibilityState::new( + &window, + proxy.clone(), + ), _menu: menu, }); @@ -408,47 +359,6 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { self.waker = None; self.renderer.suspend(); } - - #[cfg(feature = "accesskit")] - fn build_accessibility_tree(&mut self) { - let Some(ref mut state) = self.state else { - return; - }; - - let doc = self.renderer.dom.as_ref(); - - let mut nodes = std::collections::HashMap::new(); - let mut window = accesskit::NodeBuilder::new(accesskit::Role::Window); - - doc.visit(|node_id, node| { - let (id, node_builder) = state.build_node(node); - - if let Some(parent_id) = node.parent { - let (_, parent_node): &mut (_, accesskit::NodeBuilder) = - nodes.get_mut(&parent_id).unwrap(); - parent_node.push_child(id) - } else { - window.push_child(id) - } - - nodes.insert(node_id, (id, node_builder)); - }); - - let mut nodes: Vec<_> = nodes - .into_iter() - .map(|(_, (id, node))| (id, node.build())) - .collect(); - nodes.push((accesskit::NodeId(0), window.build())); - - let tree = accesskit::Tree::new(accesskit::NodeId(0)); - let tree_update = accesskit::TreeUpdate { - nodes, - tree: Some(tree), - focus: accesskit::NodeId(0), - }; - - state.accessibility.adapter.update_if_active(|| tree_update) - } } /// Initialize the default menu bar. From 0ce74dd7c262c892e512f146e32c55a4af2005a6 Mon Sep 17 00:00:00 2001 From: Matt Hunzinger Date: Wed, 26 Jun 2024 16:03:39 -0400 Subject: [PATCH 19/21] Rename accesskit feature flag to accessibility --- packages/dioxus-blitz/Cargo.toml | 4 ++-- packages/dioxus-blitz/src/accessibility.rs | 4 ++-- packages/dioxus-blitz/src/lib.rs | 4 ++-- packages/dioxus-blitz/src/waker.rs | 6 +++--- packages/dioxus-blitz/src/window.rs | 8 ++++---- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/dioxus-blitz/Cargo.toml b/packages/dioxus-blitz/Cargo.toml index 94ada8f7..1dbf7a47 100644 --- a/packages/dioxus-blitz/Cargo.toml +++ b/packages/dioxus-blitz/Cargo.toml @@ -4,9 +4,9 @@ version = "0.0.0" edition = "2021" [features] -accesskit = ["dep:accesskit", "dep:accesskit_winit"] +accessibility = ["dep:accesskit", "dep:accesskit_winit"] hot-reload = [] -default = ["accesskit"] +default = ["accessibility"] [dependencies] accesskit = { version = "0.15.0", optional = true } diff --git a/packages/dioxus-blitz/src/accessibility.rs b/packages/dioxus-blitz/src/accessibility.rs index 57033756..756ffb07 100644 --- a/packages/dioxus-blitz/src/accessibility.rs +++ b/packages/dioxus-blitz/src/accessibility.rs @@ -1,7 +1,7 @@ +use crate::waker::UserEvent; use accesskit::Role; use blitz_dom::{local_name, Document, Node}; use winit::{event_loop::EventLoopProxy, window::Window}; -use crate::waker::UserEvent; /// State of the accessibility node tree and platform adapter. pub struct AccessibilityState { @@ -53,7 +53,7 @@ impl AccessibilityState { self.adapter.update_if_active(|| tree_update) } - #[cfg(feature = "accesskit")] + #[cfg(feature = "accessibility")] fn build_node(&mut self, node: &Node) -> (accesskit::NodeId, accesskit::NodeBuilder) { let mut node_builder = accesskit::NodeBuilder::default(); if let Some(element_data) = node.element_data() { diff --git a/packages/dioxus-blitz/src/lib.rs b/packages/dioxus-blitz/src/lib.rs index b62d2ce2..67b3350e 100644 --- a/packages/dioxus-blitz/src/lib.rs +++ b/packages/dioxus-blitz/src/lib.rs @@ -2,7 +2,7 @@ mod documents; mod waker; mod window; -#[cfg(feature = "accesskit")] +#[cfg(feature = "accessibility")] mod accessibility; use crate::waker::{EventData, UserEvent}; @@ -191,7 +191,7 @@ fn launch_with_window(window: View<'static, Doc>) { } }; } - #[cfg(feature = "accesskit")] + #[cfg(feature = "accessibility")] UserEvent::Accessibility(accessibility_event) => { if let Some(window) = windows.get_mut(&accessibility_event.window_id) { window.handle_accessibility_event(&accessibility_event.window_event); diff --git a/packages/dioxus-blitz/src/waker.rs b/packages/dioxus-blitz/src/waker.rs index 9398dd67..962fe039 100644 --- a/packages/dioxus-blitz/src/waker.rs +++ b/packages/dioxus-blitz/src/waker.rs @@ -2,7 +2,7 @@ use futures_util::task::ArcWake; use std::sync::Arc; use winit::{event_loop::EventLoopProxy, window::WindowId}; -#[cfg(feature = "accesskit")] +#[cfg(feature = "accessibility")] use accesskit_winit::Event as AccessibilityEvent; #[derive(Debug, Clone)] @@ -13,7 +13,7 @@ pub enum UserEvent { }, /// An accessibility event from `accesskit`. - #[cfg(feature = "accesskit")] + #[cfg(feature = "accessibility")] Accessibility(Arc), /// A hotreload event, basically telling us to update our templates. @@ -26,7 +26,7 @@ pub enum UserEvent { HotReloadEvent(dioxus_hot_reload::HotReloadMsg), } -#[cfg(feature = "accesskit")] +#[cfg(feature = "accessibility")] impl From for UserEvent { fn from(value: AccessibilityEvent) -> Self { Self::Accessibility(Arc::new(value)) diff --git a/packages/dioxus-blitz/src/window.rs b/packages/dioxus-blitz/src/window.rs index 42e31099..a102bd3b 100644 --- a/packages/dioxus-blitz/src/window.rs +++ b/packages/dioxus-blitz/src/window.rs @@ -17,7 +17,7 @@ use winit::event_loop::{ActiveEventLoop, EventLoopProxy}; use winit::{event::WindowEvent, keyboard::KeyCode, keyboard::ModifiersState, window::Window}; struct State { - #[cfg(feature = "accesskit")] + #[cfg(feature = "accessibility")] /// Accessibility adapter for `accesskit`. accessibility: crate::accessibility::AccessibilityState, @@ -56,7 +56,7 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { Some(waker) => { let cx = std::task::Context::from_waker(waker); if self.renderer.poll(cx) { - #[cfg(feature = "accesskit")] + #[cfg(feature = "accessibility")] { if let Some(ref mut state) = self.state { // TODO send fine grained accessibility tree updates. @@ -293,7 +293,7 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { } } - #[cfg(feature = "accesskit")] + #[cfg(feature = "accessibility")] pub fn handle_accessibility_event(&mut self, event: &accesskit_winit::WindowEvent) { match event { accesskit_winit::WindowEvent::InitialTreeRequested => { @@ -330,7 +330,7 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> { &window, ); self.state = Some(State { - #[cfg(feature = "accesskit")] + #[cfg(feature = "accessibility")] accessibility: crate::accessibility::AccessibilityState::new( &window, proxy.clone(), From 9ae0fb7999b827edccd6a49ed3ed069f1bdc3514 Mon Sep 17 00:00:00 2001 From: Matt Hunzinger Date: Mon, 8 Jul 2024 15:30:27 -0400 Subject: [PATCH 20/21] Set element node name instead of html tag --- packages/dioxus-blitz/src/accessibility.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dioxus-blitz/src/accessibility.rs b/packages/dioxus-blitz/src/accessibility.rs index 756ffb07..a2c860d3 100644 --- a/packages/dioxus-blitz/src/accessibility.rs +++ b/packages/dioxus-blitz/src/accessibility.rs @@ -78,7 +78,7 @@ impl AccessibilityState { }; node_builder.set_role(role); - node_builder.set_name(name); + node_builder.set_html_tag(name); } else if node.is_text_node() { node_builder.set_role(accesskit::Role::StaticText); node_builder.set_name(node.text_content()); From a882185736facb914e9d104ebc372aa39f5984a3 Mon Sep 17 00:00:00 2001 From: Matt Hunzinger Date: Mon, 8 Jul 2024 15:45:53 -0400 Subject: [PATCH 21/21] Add labelled_by relationship for text nodes and elements --- packages/dioxus-blitz/src/accessibility.rs | 42 +++++++++++----------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/dioxus-blitz/src/accessibility.rs b/packages/dioxus-blitz/src/accessibility.rs index a2c860d3..16e4c2be 100644 --- a/packages/dioxus-blitz/src/accessibility.rs +++ b/packages/dioxus-blitz/src/accessibility.rs @@ -1,5 +1,5 @@ use crate::waker::UserEvent; -use accesskit::Role; +use accesskit::{NodeBuilder, NodeId, Role, Tree, TreeUpdate}; use blitz_dom::{local_name, Document, Node}; use winit::{event_loop::EventLoopProxy, window::Window}; @@ -21,18 +21,15 @@ impl AccessibilityState { } pub fn build_tree(&mut self, doc: &Document) { let mut nodes = std::collections::HashMap::new(); - let mut window = accesskit::NodeBuilder::new(accesskit::Role::Window); + let mut window = NodeBuilder::new(Role::Window); doc.visit(|node_id, node| { - let (id, node_builder) = self.build_node(node); - - if let Some(parent_id) = node.parent { - let (_, parent_node): &mut (_, accesskit::NodeBuilder) = - nodes.get_mut(&parent_id).unwrap(); - parent_node.push_child(id) - } else { - window.push_child(id) - } + let parent = node + .parent + .and_then(|parent_id| nodes.get_mut(&parent_id)) + .map(|(_, parent)| parent) + .unwrap_or(&mut window); + let (id, node_builder) = self.build_node(node, parent); nodes.insert(node_id, (id, node_builder)); }); @@ -41,21 +38,24 @@ impl AccessibilityState { .into_iter() .map(|(_, (id, node))| (id, node.build())) .collect(); - nodes.push((accesskit::NodeId(0), window.build())); + nodes.push((NodeId(0), window.build())); - let tree = accesskit::Tree::new(accesskit::NodeId(0)); - let tree_update = accesskit::TreeUpdate { + let tree = Tree::new(NodeId(0)); + let tree_update = TreeUpdate { nodes, tree: Some(tree), - focus: accesskit::NodeId(0), + focus: NodeId(0), }; self.adapter.update_if_active(|| tree_update) } - #[cfg(feature = "accessibility")] - fn build_node(&mut self, node: &Node) -> (accesskit::NodeId, accesskit::NodeBuilder) { - let mut node_builder = accesskit::NodeBuilder::default(); + fn build_node(&mut self, node: &Node, parent: &mut NodeBuilder) -> (NodeId, NodeBuilder) { + let mut node_builder = NodeBuilder::default(); + + let id = NodeId(self.next_id); + self.next_id += 1; + if let Some(element_data) = node.element_data() { let name = element_data.name.local.to_string(); @@ -80,12 +80,12 @@ impl AccessibilityState { node_builder.set_role(role); node_builder.set_html_tag(name); } else if node.is_text_node() { - node_builder.set_role(accesskit::Role::StaticText); + node_builder.set_role(Role::StaticText); node_builder.set_name(node.text_content()); + parent.push_labelled_by(id) } - let id = accesskit::NodeId(self.next_id); - self.next_id += 1; + parent.push_child(id); (id, node_builder) }