From f02cc108e1abb6f91fc26be7f66ecb83441e43e1 Mon Sep 17 00:00:00 2001 From: delan azabani Date: Wed, 12 Nov 2025 17:28:27 +0800 Subject: [PATCH 01/26] Initial multi-tree adapter Co-authored-by: Luke Warlow --- accesskit_multi_tree/Cargo.toml | 8 ++++++++ accesskit_multi_tree/src/lib.rs | 16 ++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 accesskit_multi_tree/Cargo.toml create mode 100644 accesskit_multi_tree/src/lib.rs diff --git a/accesskit_multi_tree/Cargo.toml b/accesskit_multi_tree/Cargo.toml new file mode 100644 index 00000000..5d53b88d --- /dev/null +++ b/accesskit_multi_tree/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "accesskit_multi_tree" +version = "0.1.0" +edition = "2024" + +[dependencies] +accesskit = "0.21.1" +accesskit_winit = "0.29.2" diff --git a/accesskit_multi_tree/src/lib.rs b/accesskit_multi_tree/src/lib.rs new file mode 100644 index 00000000..d53f392d --- /dev/null +++ b/accesskit_multi_tree/src/lib.rs @@ -0,0 +1,16 @@ +use accesskit::TreeUpdate; + +pub struct Adapter { + // TODO: servoshell on Android and OpenHarmony do not use winit + inner: accesskit_winit::Adapter, +} + +impl Adapter { + pub fn new(inner: accesskit_winit::Adapter) -> Self { + Self { inner } + } + + pub fn update_if_active(&mut self, updater: impl FnOnce() -> TreeUpdate) { + self.inner.update_if_active(updater); + } +} From 2d8c613190dcbc0a4bac6d23650a7c1eaa94ab3f Mon Sep 17 00:00:00 2001 From: delan azabani Date: Wed, 12 Nov 2025 19:42:20 +0800 Subject: [PATCH 02/26] Implement some of the multi-tree Adapter Co-authored-by: Luke Warlow --- accesskit_multi_tree/src/lib.rs | 93 +++++++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 4 deletions(-) diff --git a/accesskit_multi_tree/src/lib.rs b/accesskit_multi_tree/src/lib.rs index d53f392d..4669a1ca 100644 --- a/accesskit_multi_tree/src/lib.rs +++ b/accesskit_multi_tree/src/lib.rs @@ -1,16 +1,101 @@ -use accesskit::TreeUpdate; +use std::{collections::HashMap, sync::atomic::AtomicUsize}; + +use accesskit::{NodeId, TreeUpdate}; + +pub static NEXT_TREE_ID: AtomicUsize = AtomicUsize::new(0); pub struct Adapter { // TODO: servoshell on Android and OpenHarmony do not use winit inner: accesskit_winit::Adapter, + + next_tree_id: TreeId, + root_tree_id: TreeId, + subtrees: HashMap, + id_map: HashMap>, + next_node_id: NodeId, +} + +pub struct SubtreeInfo { + parent_tree_id: TreeId, + parent_node_id: NodeId, } +#[repr(transparent)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TreeId(u64); + impl Adapter { pub fn new(inner: accesskit_winit::Adapter) -> Self { - Self { inner } + Self { + inner, + next_tree_id: TreeId(1), + root_tree_id: TreeId(0), + subtrees: HashMap::default(), + id_map: HashMap::default(), + next_node_id: NodeId(0), + } + } + + pub fn register_new_subtree(&mut self, parent_tree_id: TreeId, parent_node_id: NodeId) { + let subtree_id = self.next_tree_id(); + assert!(self.subtrees.insert(subtree_id, SubtreeInfo { parent_tree_id, parent_node_id }).is_none()); + } + + pub fn unregister_subtree(&mut self, subtree_id: TreeId) { + // Assert not root tree id + // Remove from subtrees + // Remove from id map + // No need to send a TreeUpdate, the provider of the parent subtree will do it + } + + pub fn update_if_active(&mut self, tree_id: TreeId, updater: impl FnOnce() -> TreeUpdate) { + // Q: what happens if the graft node (`parent_node_id`) gets removed by the parent tree? + // If we keep the registration, then we need to detect when the graft node gets readded + // (if ever), and resend the subtree in full, which requires caching the subtree or telling + // the provider to resend it in full (via initial tree request?). + // If we remove the registration, then we need to detect when the graft node got removed, + // which could happen at any ancestor, which requires caching the parent tree. + // For now we just leave this undefined: if you remove the graft node, you must unregister. + // - Maybe we could use an accesskit_consumer::ChangeHandler to detect these cases? + // - Easy way: we set up our own accesskit_consumer::Tree and ChangeHandler + // - Hard way: we plumb through the lower-level Adapters and expose the one in + // atspi common / android / windows? macOS has one too, but maybe used differently? + let mut subtree_update = updater(); + if let Some(tree) = subtree_update.tree.as_mut() { + tree.root = self.map_id(tree_id, tree.root); + } + #[expect(unused_variables)] + for (node_id, node) in subtree_update.nodes.iter_mut() { + *node_id = self.map_id(tree_id, *node_id); + // TODO: map ids of all node references: + // children, controls, labelled_by, details, described_by, flow_to, ... + // TODO: what do we do about .level()? + } + self.inner.update_if_active(|| subtree_update); + } + + fn map_id(&mut self, tree_id: TreeId, node_id: NodeId) -> NodeId { + let map = self.id_map.get_mut(&tree_id).expect("Tree not registered"); + if let Some(result) = map.get(&node_id) { + return *result; + } + let result = self.next_node_id(); + let map = self.id_map.get_mut(&tree_id).expect("Tree not registered"); + assert!(map.insert(node_id, result).is_none()); + result + } + + fn next_tree_id(&mut self) -> TreeId { + let subtree_id = self.next_tree_id; + // TODO handle wrapping? Seems unnecessary for sequential usize = u64 + self.next_tree_id = TreeId(subtree_id.0.checked_add(1).expect("TreeId overflow")); + subtree_id } - pub fn update_if_active(&mut self, updater: impl FnOnce() -> TreeUpdate) { - self.inner.update_if_active(updater); + fn next_node_id(&mut self) -> NodeId { + let node_id = self.next_node_id; + // TODO handle wrapping? Seems unnecessary for sequential usize = u64 + self.next_node_id = NodeId(node_id.0.checked_add(1).expect("NodeId overflow")); + node_id } } From 14f1493ce636328d2f7b7332d52929fec24cbd1d Mon Sep 17 00:00:00 2001 From: delan azabani Date: Thu, 13 Nov 2025 17:57:52 +0800 Subject: [PATCH 03/26] Rewrite node ids in update_if_active() Co-authored-by: Luke Warlow --- accesskit_multi_tree/src/lib.rs | 46 +++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/accesskit_multi_tree/src/lib.rs b/accesskit_multi_tree/src/lib.rs index 4669a1ca..905f1687 100644 --- a/accesskit_multi_tree/src/lib.rs +++ b/accesskit_multi_tree/src/lib.rs @@ -2,14 +2,13 @@ use std::{collections::HashMap, sync::atomic::AtomicUsize}; use accesskit::{NodeId, TreeUpdate}; -pub static NEXT_TREE_ID: AtomicUsize = AtomicUsize::new(0); +const ROOT_TREE_ID: TreeId = TreeId(0); pub struct Adapter { // TODO: servoshell on Android and OpenHarmony do not use winit inner: accesskit_winit::Adapter, next_tree_id: TreeId, - root_tree_id: TreeId, subtrees: HashMap, id_map: HashMap>, next_node_id: NodeId, @@ -26,19 +25,25 @@ pub struct TreeId(u64); impl Adapter { pub fn new(inner: accesskit_winit::Adapter) -> Self { - Self { + let mut result = Self { inner, next_tree_id: TreeId(1), - root_tree_id: TreeId(0), subtrees: HashMap::default(), id_map: HashMap::default(), next_node_id: NodeId(0), - } + }; + assert!(result.id_map.insert(result.root_tree_id(), HashMap::default()).is_none()); + result + } + + pub fn root_tree_id(&self) -> TreeId { + ROOT_TREE_ID } pub fn register_new_subtree(&mut self, parent_tree_id: TreeId, parent_node_id: NodeId) { let subtree_id = self.next_tree_id(); assert!(self.subtrees.insert(subtree_id, SubtreeInfo { parent_tree_id, parent_node_id }).is_none()); + assert!(self.id_map.insert(subtree_id, HashMap::default()).is_none()); } pub fn unregister_subtree(&mut self, subtree_id: TreeId) { @@ -61,14 +66,31 @@ impl Adapter { // - Hard way: we plumb through the lower-level Adapters and expose the one in // atspi common / android / windows? macOS has one too, but maybe used differently? let mut subtree_update = updater(); + subtree_update.focus = self.map_id(tree_id, subtree_update.focus); if let Some(tree) = subtree_update.tree.as_mut() { tree.root = self.map_id(tree_id, tree.root); } - #[expect(unused_variables)] for (node_id, node) in subtree_update.nodes.iter_mut() { *node_id = self.map_id(tree_id, *node_id); - // TODO: map ids of all node references: - // children, controls, labelled_by, details, described_by, flow_to, ... + // Map ids of all node references. + // These correspond to the `node_id_vec_property_methods` and `node_id_property_methods` + // lists in `accesskit/src/lib.rs`. + // TODO: could we make the vec rewrites avoid allocation? + self.map_node_id_vec_property(tree_id, node.children().to_owned(), |new| node.set_children(new)); + self.map_node_id_vec_property(tree_id, node.controls().to_owned(), |new| node.set_controls(new)); + self.map_node_id_vec_property(tree_id, node.details().to_owned(), |new| node.set_details(new)); + self.map_node_id_vec_property(tree_id, node.described_by().to_owned(), |new| node.set_described_by(new)); + self.map_node_id_vec_property(tree_id, node.flow_to().to_owned(), |new| node.set_flow_to(new)); + self.map_node_id_vec_property(tree_id, node.labelled_by().to_owned(), |new| node.set_labelled_by(new)); + self.map_node_id_vec_property(tree_id, node.owns().to_owned(), |new| node.set_owns(new)); + self.map_node_id_vec_property(tree_id, node.radio_group().to_owned(), |new| node.set_radio_group(new)); + node.active_descendant().map(|node_id| node.set_active_descendant(self.map_id(tree_id, node_id))); + node.error_message().map(|node_id| node.set_error_message(self.map_id(tree_id, node_id))); + node.in_page_link_target().map(|node_id| node.set_in_page_link_target(self.map_id(tree_id, node_id))); + node.member_of().map(|node_id| node.set_member_of(self.map_id(tree_id, node_id))); + node.next_on_line().map(|node_id| node.set_next_on_line(self.map_id(tree_id, node_id))); + node.previous_on_line().map(|node_id| node.set_previous_on_line(self.map_id(tree_id, node_id))); + node.popup_for().map(|node_id| node.set_popup_for(self.map_id(tree_id, node_id))); // TODO: what do we do about .level()? } self.inner.update_if_active(|| subtree_update); @@ -85,6 +107,14 @@ impl Adapter { result } + fn map_node_id_vec_property(&mut self, tree_id: TreeId, node_ids: Vec, setter: impl FnOnce(Vec)) { + let mut children = node_ids; + for node_id in children.iter_mut() { + *node_id = self.map_id(tree_id, *node_id); + } + setter(children); + } + fn next_tree_id(&mut self) -> TreeId { let subtree_id = self.next_tree_id; // TODO handle wrapping? Seems unnecessary for sequential usize = u64 From 17cfd201077dfb990790edcaefae6b9babba96f6 Mon Sep 17 00:00:00 2001 From: delan azabani Date: Thu, 13 Nov 2025 18:17:38 +0800 Subject: [PATCH 04/26] =?UTF-8?q?Rename=20our=20concept=20of=20=E2=80=9Ctr?= =?UTF-8?q?ee=E2=80=9D=20to=20=E2=80=9Csubtree=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit we were apparently misusing the term in some places to mean a subtree that did not include the root (now called a child subtree). Co-authored-by: Luke Warlow --- accesskit_multi_tree/src/lib.rs | 96 +++++++++++++++++---------------- 1 file changed, 51 insertions(+), 45 deletions(-) diff --git a/accesskit_multi_tree/src/lib.rs b/accesskit_multi_tree/src/lib.rs index 905f1687..c2cb05b9 100644 --- a/accesskit_multi_tree/src/lib.rs +++ b/accesskit_multi_tree/src/lib.rs @@ -2,123 +2,129 @@ use std::{collections::HashMap, sync::atomic::AtomicUsize}; use accesskit::{NodeId, TreeUpdate}; -const ROOT_TREE_ID: TreeId = TreeId(0); +const ROOT_SUBTREE_ID: SubtreeId = SubtreeId(0); +/// Adapter that combines a root subtree with any number of other subtrees. +/// +/// The root subtree is always defined, and has id [`Self::root_subtree_id`]. To define additional +/// subtrees, call [`Self::register_new_subtree`]. pub struct Adapter { // TODO: servoshell on Android and OpenHarmony do not use winit inner: accesskit_winit::Adapter, - next_tree_id: TreeId, - subtrees: HashMap, - id_map: HashMap>, + next_subtree_id: SubtreeId, + child_subtrees: HashMap, + id_map: HashMap>, next_node_id: NodeId, } pub struct SubtreeInfo { - parent_tree_id: TreeId, + parent_subtree_id: SubtreeId, parent_node_id: NodeId, } #[repr(transparent)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct TreeId(u64); +pub struct SubtreeId(u64); impl Adapter { pub fn new(inner: accesskit_winit::Adapter) -> Self { let mut result = Self { inner, - next_tree_id: TreeId(1), - subtrees: HashMap::default(), + next_subtree_id: SubtreeId(1), + child_subtrees: HashMap::default(), id_map: HashMap::default(), next_node_id: NodeId(0), }; - assert!(result.id_map.insert(result.root_tree_id(), HashMap::default()).is_none()); + assert!(result.id_map.insert(result.root_subtree_id(), HashMap::default()).is_none()); result } - pub fn root_tree_id(&self) -> TreeId { - ROOT_TREE_ID + pub fn root_subtree_id(&self) -> SubtreeId { + ROOT_SUBTREE_ID } - pub fn register_new_subtree(&mut self, parent_tree_id: TreeId, parent_node_id: NodeId) { - let subtree_id = self.next_tree_id(); - assert!(self.subtrees.insert(subtree_id, SubtreeInfo { parent_tree_id, parent_node_id }).is_none()); + pub fn register_child_subtree(&mut self, parent_subtree_id: SubtreeId, parent_node_id: NodeId) { + let subtree_id = self.next_subtree_id(); + assert!(self.child_subtrees.insert(subtree_id, SubtreeInfo { parent_subtree_id, parent_node_id }).is_none()); assert!(self.id_map.insert(subtree_id, HashMap::default()).is_none()); + // TODO: assert that the parent subtree is already registered (or is the root) } - pub fn unregister_subtree(&mut self, subtree_id: TreeId) { - // Assert not root tree id + pub fn unregister_subtree(&mut self, subtree_id: SubtreeId) { + // Assert not root subtree id // Remove from subtrees // Remove from id map // No need to send a TreeUpdate, the provider of the parent subtree will do it } - pub fn update_if_active(&mut self, tree_id: TreeId, updater: impl FnOnce() -> TreeUpdate) { - // Q: what happens if the graft node (`parent_node_id`) gets removed by the parent tree? + pub fn update_if_active(&mut self, subtree_id: SubtreeId, updater: impl FnOnce() -> TreeUpdate) { + // Q: what happens if the graft node (`parent_node_id`) gets removed by the parent subtree? // If we keep the registration, then we need to detect when the graft node gets readded // (if ever), and resend the subtree in full, which requires caching the subtree or telling // the provider to resend it in full (via initial tree request?). // If we remove the registration, then we need to detect when the graft node got removed, - // which could happen at any ancestor, which requires caching the parent tree. + // which could happen at any ancestor, which requires caching the parent subtree. // For now we just leave this undefined: if you remove the graft node, you must unregister. // - Maybe we could use an accesskit_consumer::ChangeHandler to detect these cases? // - Easy way: we set up our own accesskit_consumer::Tree and ChangeHandler // - Hard way: we plumb through the lower-level Adapters and expose the one in // atspi common / android / windows? macOS has one too, but maybe used differently? let mut subtree_update = updater(); - subtree_update.focus = self.map_id(tree_id, subtree_update.focus); + subtree_update.focus = self.map_id(subtree_id, subtree_update.focus); + // TODO: rewrite the root correctly if let Some(tree) = subtree_update.tree.as_mut() { - tree.root = self.map_id(tree_id, tree.root); + tree.root = self.map_id(subtree_id, tree.root); } for (node_id, node) in subtree_update.nodes.iter_mut() { - *node_id = self.map_id(tree_id, *node_id); + *node_id = self.map_id(subtree_id, *node_id); // Map ids of all node references. // These correspond to the `node_id_vec_property_methods` and `node_id_property_methods` // lists in `accesskit/src/lib.rs`. // TODO: could we make the vec rewrites avoid allocation? - self.map_node_id_vec_property(tree_id, node.children().to_owned(), |new| node.set_children(new)); - self.map_node_id_vec_property(tree_id, node.controls().to_owned(), |new| node.set_controls(new)); - self.map_node_id_vec_property(tree_id, node.details().to_owned(), |new| node.set_details(new)); - self.map_node_id_vec_property(tree_id, node.described_by().to_owned(), |new| node.set_described_by(new)); - self.map_node_id_vec_property(tree_id, node.flow_to().to_owned(), |new| node.set_flow_to(new)); - self.map_node_id_vec_property(tree_id, node.labelled_by().to_owned(), |new| node.set_labelled_by(new)); - self.map_node_id_vec_property(tree_id, node.owns().to_owned(), |new| node.set_owns(new)); - self.map_node_id_vec_property(tree_id, node.radio_group().to_owned(), |new| node.set_radio_group(new)); - node.active_descendant().map(|node_id| node.set_active_descendant(self.map_id(tree_id, node_id))); - node.error_message().map(|node_id| node.set_error_message(self.map_id(tree_id, node_id))); - node.in_page_link_target().map(|node_id| node.set_in_page_link_target(self.map_id(tree_id, node_id))); - node.member_of().map(|node_id| node.set_member_of(self.map_id(tree_id, node_id))); - node.next_on_line().map(|node_id| node.set_next_on_line(self.map_id(tree_id, node_id))); - node.previous_on_line().map(|node_id| node.set_previous_on_line(self.map_id(tree_id, node_id))); - node.popup_for().map(|node_id| node.set_popup_for(self.map_id(tree_id, node_id))); + self.map_node_id_vec_property(subtree_id, node.children().to_owned(), |new| node.set_children(new)); + self.map_node_id_vec_property(subtree_id, node.controls().to_owned(), |new| node.set_controls(new)); + self.map_node_id_vec_property(subtree_id, node.details().to_owned(), |new| node.set_details(new)); + self.map_node_id_vec_property(subtree_id, node.described_by().to_owned(), |new| node.set_described_by(new)); + self.map_node_id_vec_property(subtree_id, node.flow_to().to_owned(), |new| node.set_flow_to(new)); + self.map_node_id_vec_property(subtree_id, node.labelled_by().to_owned(), |new| node.set_labelled_by(new)); + self.map_node_id_vec_property(subtree_id, node.owns().to_owned(), |new| node.set_owns(new)); + self.map_node_id_vec_property(subtree_id, node.radio_group().to_owned(), |new| node.set_radio_group(new)); + node.active_descendant().map(|node_id| node.set_active_descendant(self.map_id(subtree_id, node_id))); + node.error_message().map(|node_id| node.set_error_message(self.map_id(subtree_id, node_id))); + node.in_page_link_target().map(|node_id| node.set_in_page_link_target(self.map_id(subtree_id, node_id))); + node.member_of().map(|node_id| node.set_member_of(self.map_id(subtree_id, node_id))); + node.next_on_line().map(|node_id| node.set_next_on_line(self.map_id(subtree_id, node_id))); + node.previous_on_line().map(|node_id| node.set_previous_on_line(self.map_id(subtree_id, node_id))); + node.popup_for().map(|node_id| node.set_popup_for(self.map_id(subtree_id, node_id))); // TODO: what do we do about .level()? } self.inner.update_if_active(|| subtree_update); } - fn map_id(&mut self, tree_id: TreeId, node_id: NodeId) -> NodeId { - let map = self.id_map.get_mut(&tree_id).expect("Tree not registered"); + fn map_id(&mut self, subtree_id: SubtreeId, node_id: NodeId) -> NodeId { + let map = self.id_map.get_mut(&subtree_id).expect("Subtree not registered"); if let Some(result) = map.get(&node_id) { return *result; } let result = self.next_node_id(); - let map = self.id_map.get_mut(&tree_id).expect("Tree not registered"); + let map = self.id_map.get_mut(&subtree_id).expect("Subtree not registered"); assert!(map.insert(node_id, result).is_none()); result } - fn map_node_id_vec_property(&mut self, tree_id: TreeId, node_ids: Vec, setter: impl FnOnce(Vec)) { + fn map_node_id_vec_property(&mut self, subtree_id: SubtreeId, node_ids: Vec, setter: impl FnOnce(Vec)) { let mut children = node_ids; for node_id in children.iter_mut() { - *node_id = self.map_id(tree_id, *node_id); + *node_id = self.map_id(subtree_id, *node_id); } setter(children); } - fn next_tree_id(&mut self) -> TreeId { - let subtree_id = self.next_tree_id; + fn next_subtree_id(&mut self) -> SubtreeId { + let subtree_id = self.next_subtree_id; // TODO handle wrapping? Seems unnecessary for sequential usize = u64 - self.next_tree_id = TreeId(subtree_id.0.checked_add(1).expect("TreeId overflow")); + self.next_subtree_id = SubtreeId(subtree_id.0.checked_add(1).expect("SubtreeId overflow")); subtree_id } From 004f8e784d011d03a0b3f1a19f28ef9db877051f Mon Sep 17 00:00:00 2001 From: delan azabani Date: Thu, 13 Nov 2025 18:22:47 +0800 Subject: [PATCH 05/26] Assert that parent subtree is registered when registering children Co-authored-by: Luke Warlow --- accesskit_multi_tree/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/accesskit_multi_tree/src/lib.rs b/accesskit_multi_tree/src/lib.rs index c2cb05b9..e2cb8e74 100644 --- a/accesskit_multi_tree/src/lib.rs +++ b/accesskit_multi_tree/src/lib.rs @@ -46,9 +46,9 @@ impl Adapter { pub fn register_child_subtree(&mut self, parent_subtree_id: SubtreeId, parent_node_id: NodeId) { let subtree_id = self.next_subtree_id(); + assert!(self.subtree_is_registered(parent_subtree_id)); assert!(self.child_subtrees.insert(subtree_id, SubtreeInfo { parent_subtree_id, parent_node_id }).is_none()); assert!(self.id_map.insert(subtree_id, HashMap::default()).is_none()); - // TODO: assert that the parent subtree is already registered (or is the root) } pub fn unregister_subtree(&mut self, subtree_id: SubtreeId) { @@ -134,4 +134,8 @@ impl Adapter { self.next_node_id = NodeId(node_id.0.checked_add(1).expect("NodeId overflow")); node_id } + + fn subtree_is_registered(&self, subtree_id: SubtreeId) -> bool { + subtree_id == self.root_subtree_id() || self.child_subtrees.contains_key(&subtree_id) + } } From 5d0991c782588e1a3cd5f29ea6b9ff762fe9f985 Mon Sep 17 00:00:00 2001 From: delan azabani Date: Thu, 13 Nov 2025 20:25:00 +0800 Subject: [PATCH 06/26] Add initial unit test Co-authored-by: Luke Warlow --- accesskit_multi_tree/src/lib.rs | 134 ++++++++++++++++++++++++++++++-- 1 file changed, 127 insertions(+), 7 deletions(-) diff --git a/accesskit_multi_tree/src/lib.rs b/accesskit_multi_tree/src/lib.rs index e2cb8e74..3833c628 100644 --- a/accesskit_multi_tree/src/lib.rs +++ b/accesskit_multi_tree/src/lib.rs @@ -2,6 +2,11 @@ use std::{collections::HashMap, sync::atomic::AtomicUsize}; use accesskit::{NodeId, TreeUpdate}; +#[cfg(not(test))] +type InnerAdapter = accesskit_winit::Adapter; +#[cfg(test)] +type InnerAdapter = self::test::InnerAdapter; + const ROOT_SUBTREE_ID: SubtreeId = SubtreeId(0); /// Adapter that combines a root subtree with any number of other subtrees. @@ -9,8 +14,9 @@ const ROOT_SUBTREE_ID: SubtreeId = SubtreeId(0); /// The root subtree is always defined, and has id [`Self::root_subtree_id`]. To define additional /// subtrees, call [`Self::register_new_subtree`]. pub struct Adapter { - // TODO: servoshell on Android and OpenHarmony do not use winit - inner: accesskit_winit::Adapter, + // TODO: servoshell on Android and OpenHarmony do not use winit. + // Allow switching to non-winit inner Adapter, maybe using conditional compilation? + inner: InnerAdapter, next_subtree_id: SubtreeId, child_subtrees: HashMap, @@ -28,7 +34,11 @@ pub struct SubtreeInfo { pub struct SubtreeId(u64); impl Adapter { - pub fn new(inner: accesskit_winit::Adapter) -> Self { + // TODO: ensure that the ActivationHandler in `inner` is well-behaved (pushes updates to this adapter). + // We suspect we can only guarantee this if *we* set the handler, which means *we* create the InnerAdapter. + // Same goes for ActionHandler, we can intercept the caller’s handler and rewrite the target id. + // TODO: if using a winit inner Adapter, expose the two winit-specific constructors. + pub fn new(inner: InnerAdapter) -> Self { let mut result = Self { inner, next_subtree_id: SubtreeId(1), @@ -44,11 +54,12 @@ impl Adapter { ROOT_SUBTREE_ID } - pub fn register_child_subtree(&mut self, parent_subtree_id: SubtreeId, parent_node_id: NodeId) { + pub fn register_child_subtree(&mut self, parent_subtree_id: SubtreeId, parent_node_id: NodeId) -> SubtreeId { let subtree_id = self.next_subtree_id(); assert!(self.subtree_is_registered(parent_subtree_id)); assert!(self.child_subtrees.insert(subtree_id, SubtreeInfo { parent_subtree_id, parent_node_id }).is_none()); assert!(self.id_map.insert(subtree_id, HashMap::default()).is_none()); + subtree_id } pub fn unregister_subtree(&mut self, subtree_id: SubtreeId) { @@ -71,6 +82,11 @@ impl Adapter { // - Hard way: we plumb through the lower-level Adapters and expose the one in // atspi common / android / windows? macOS has one too, but maybe used differently? let mut subtree_update = updater(); + // TODO: rewrite the focus correctly. + // We think the model is something like: every subtree has its local idea of the focused + // node, but that node may not end up being the focused node globally. The globally focused + // node is the focused node of the root subtree, unless that node is a graft node, in which + // case it’s the focused node of the child subtree being grafted there (recursively). subtree_update.focus = self.map_id(subtree_id, subtree_update.focus); // TODO: rewrite the root correctly if let Some(tree) = subtree_update.tree.as_mut() { @@ -114,11 +130,20 @@ impl Adapter { } fn map_node_id_vec_property(&mut self, subtree_id: SubtreeId, node_ids: Vec, setter: impl FnOnce(Vec)) { - let mut children = node_ids; - for node_id in children.iter_mut() { + // If node id vec properties return an empty slice from their getters, don’t bother + // calling the setters. This may be slightly more efficient, and also works around a + // potentially busted derived impl PartialEq for Properties where PropertyId::Unset in + // indices is considered unequal to PropertyValue::NodeIdVec(vec![]). It should be + // equal, because all properties currently default to empty by definition: + // + if node_ids.is_empty() { + return; + } + let mut node_ids = node_ids; + for node_id in node_ids.iter_mut() { *node_id = self.map_id(subtree_id, *node_id); } - setter(children); + setter(node_ids); } fn next_subtree_id(&mut self) -> SubtreeId { @@ -138,4 +163,99 @@ impl Adapter { fn subtree_is_registered(&self, subtree_id: SubtreeId) -> bool { subtree_id == self.root_subtree_id() || self.child_subtrees.contains_key(&subtree_id) } + + #[cfg(test)] + fn take_tree_updates(&mut self) -> Vec { + self.inner.take_tree_updates() + } +} + +#[cfg(test)] +mod test { + use accesskit::{Node, NodeId, Role, Tree, TreeUpdate}; + + use crate::Adapter; + + #[derive(Default)] + pub struct InnerAdapter { + tree_updates: Vec, + } + + impl InnerAdapter { + pub fn update_if_active(&mut self, updater: impl FnOnce() -> TreeUpdate) { + self.tree_updates.push(updater()); + } + pub fn take_tree_updates(&mut self) -> Vec { + std::mem::take(&mut self.tree_updates) + } + } + + fn node(children: impl Into>) -> Node { + let mut result = Node::new(Role::Unknown); + result.set_children(children.into()); + result + } + + #[test] + fn test_update() { + let mut adapter = Adapter::new(InnerAdapter::default()); + adapter.update_if_active(adapter.root_subtree_id(), || TreeUpdate { + nodes: vec![ + (NodeId(13), node([NodeId(15), NodeId(14)])), + (NodeId(15), node([])), + (NodeId(14), node([])), + ], + tree: Some(Tree { + root: NodeId(13), + toolkit_name: None, + toolkit_version: None, + }), + focus: NodeId(13), + }); + let child_subtree_id = adapter.register_child_subtree(adapter.root_subtree_id(), NodeId(15)); + adapter.update_if_active(child_subtree_id, || TreeUpdate { + nodes: vec![ + (NodeId(25), node([NodeId(27), NodeId(26)])), + (NodeId(27), node([])), + (NodeId(26), node([])), + ], + tree: Some(Tree { + root: NodeId(25), + toolkit_name: None, + toolkit_version: None, + }), + focus: NodeId(25), + }); + let actual_updates = adapter.take_tree_updates(); + assert_eq!(actual_updates, vec![ + TreeUpdate { + nodes: vec![ + (NodeId(0), node([NodeId(1), NodeId(2)])), + (NodeId(1), node([])), + (NodeId(2), node([])), + ], + tree: Some(Tree { + root: NodeId(0), + toolkit_name: None, + toolkit_version: None, + }), + focus: NodeId(0), + }, + TreeUpdate { + nodes: vec![ + (NodeId(3), node([NodeId(4), NodeId(5)])), + (NodeId(4), node([])), + (NodeId(5), node([])), + ], + tree: Some(Tree { + // FIXME: this is incorrect + root: NodeId(3), + toolkit_name: None, + toolkit_version: None, + }), + // FIXME: this is incorrect + focus: NodeId(3), + }, + ]); + } } From 3e31a96bef711df737157200c1cdd4d5cc5a6696 Mon Sep 17 00:00:00 2001 From: Delan Azabani Date: Thu, 13 Nov 2025 20:47:15 +0800 Subject: [PATCH 07/26] Fix up Cargo.toml for workspace Co-authored-by: Luke Warlow --- Cargo.lock | 8 ++++++++ Cargo.toml | 1 + accesskit_multi_tree/Cargo.toml | 4 ++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f24a6c0d..92c451b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,6 +70,14 @@ dependencies = [ "objc2-foundation", ] +[[package]] +name = "accesskit_multi_tree" +version = "0.1.0" +dependencies = [ + "accesskit", + "accesskit_winit", +] + [[package]] name = "accesskit_unix" version = "0.17.1" diff --git a/Cargo.toml b/Cargo.toml index 79b96678..717c6131 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] resolver = "2" members = [ + "accesskit_multi_tree", "common", "consumer", "platforms/android", diff --git a/accesskit_multi_tree/Cargo.toml b/accesskit_multi_tree/Cargo.toml index 5d53b88d..8f8533aa 100644 --- a/accesskit_multi_tree/Cargo.toml +++ b/accesskit_multi_tree/Cargo.toml @@ -4,5 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] -accesskit = "0.21.1" -accesskit_winit = "0.29.2" +accesskit = { version = "0.21.1", path = "../common" } +accesskit_winit = { version = "0.29.1", path = "../platforms/winit" } From 7bf2a0eb1261c801851863512e68953554a16463 Mon Sep 17 00:00:00 2001 From: delan azabani Date: Wed, 3 Dec 2025 15:01:18 +0800 Subject: [PATCH 08/26] Rename and move multitree package --- Cargo.lock | 2 +- Cargo.toml | 2 +- accesskit_multi_tree/Cargo.toml | 8 -------- platforms/multitree/Cargo.toml | 16 ++++++++++++++++ .../multitree}/src/lib.rs | 0 5 files changed, 18 insertions(+), 10 deletions(-) delete mode 100644 accesskit_multi_tree/Cargo.toml create mode 100644 platforms/multitree/Cargo.toml rename {accesskit_multi_tree => platforms/multitree}/src/lib.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 92c451b4..9e1ed5b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,7 +71,7 @@ dependencies = [ ] [[package]] -name = "accesskit_multi_tree" +name = "accesskit_multitree" version = "0.1.0" dependencies = [ "accesskit", diff --git a/Cargo.toml b/Cargo.toml index 717c6131..d54ee2a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ [workspace] resolver = "2" members = [ - "accesskit_multi_tree", "common", "consumer", "platforms/android", "platforms/atspi-common", "platforms/macos", + "platforms/multitree", "platforms/unix", "platforms/windows", "platforms/winit", diff --git a/accesskit_multi_tree/Cargo.toml b/accesskit_multi_tree/Cargo.toml deleted file mode 100644 index 8f8533aa..00000000 --- a/accesskit_multi_tree/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "accesskit_multi_tree" -version = "0.1.0" -edition = "2024" - -[dependencies] -accesskit = { version = "0.21.1", path = "../common" } -accesskit_winit = { version = "0.29.1", path = "../platforms/winit" } diff --git a/platforms/multitree/Cargo.toml b/platforms/multitree/Cargo.toml new file mode 100644 index 00000000..2e6c6681 --- /dev/null +++ b/platforms/multitree/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "accesskit_multitree" +version = "0.1.0" +authors.workspace = true +license = "Apache-2.0" +description = "AccessKit UI accessibility infrastructure: multitree adapter" +categories.workspace = true +keywords = ["gui", "ui", "accessibility", "winit"] +repository.workspace = true +readme = "README.md" +edition.workspace = true +rust-version.workspace = true + +[dependencies] +accesskit = { version = "0.21.1", path = "../../common" } +accesskit_winit = { version = "0.29.1", path = "../winit" } diff --git a/accesskit_multi_tree/src/lib.rs b/platforms/multitree/src/lib.rs similarity index 100% rename from accesskit_multi_tree/src/lib.rs rename to platforms/multitree/src/lib.rs From e89abf40c18737b61c6e1dc89f433f025eea3e20 Mon Sep 17 00:00:00 2001 From: delan azabani Date: Wed, 3 Dec 2025 15:51:11 +0800 Subject: [PATCH 09/26] Clean up some compile warnings --- platforms/multitree/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platforms/multitree/src/lib.rs b/platforms/multitree/src/lib.rs index 3833c628..27cea287 100644 --- a/platforms/multitree/src/lib.rs +++ b/platforms/multitree/src/lib.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, sync::atomic::AtomicUsize}; +use std::collections::HashMap; use accesskit::{NodeId, TreeUpdate}; @@ -62,6 +62,7 @@ impl Adapter { subtree_id } + #[expect(unused)] pub fn unregister_subtree(&mut self, subtree_id: SubtreeId) { // Assert not root subtree id // Remove from subtrees From 78da6cd428b183b8cdd07e6e3b99376c4cea3683 Mon Sep 17 00:00:00 2001 From: Luke Warlow Date: Mon, 24 Nov 2025 16:09:50 +0000 Subject: [PATCH 10/26] Switch to creating the inner adapter ourselves Tests are currently commented out due to wanting access to the winit types. --- platforms/multitree/Cargo.toml | 14 ++ platforms/multitree/src/lib.rs | 294 +++++++++++++++++++++------------ 2 files changed, 205 insertions(+), 103 deletions(-) diff --git a/platforms/multitree/Cargo.toml b/platforms/multitree/Cargo.toml index 2e6c6681..7b25ed1b 100644 --- a/platforms/multitree/Cargo.toml +++ b/platforms/multitree/Cargo.toml @@ -14,3 +14,17 @@ rust-version.workspace = true [dependencies] accesskit = { version = "0.21.1", path = "../../common" } accesskit_winit = { version = "0.29.1", path = "../winit" } + +[dependencies.winit] +version = "0.30.5" +default-features = false + +[dev-dependencies.winit] +version = "0.30.5" +features = [ + "x11", + "wayland", + "wayland-dlopen", + "wayland-csd-adwaita", +] +default-features = false diff --git a/platforms/multitree/src/lib.rs b/platforms/multitree/src/lib.rs index 27cea287..9ba0a530 100644 --- a/platforms/multitree/src/lib.rs +++ b/platforms/multitree/src/lib.rs @@ -1,14 +1,53 @@ +use accesskit::{ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler, NodeId, TreeUpdate}; +use accesskit_winit::{Event, WindowEvent}; use std::collections::HashMap; +use winit::event_loop::{ActiveEventLoop, EventLoopProxy}; +use winit::window::{Window, WindowId}; -use accesskit::{NodeId, TreeUpdate}; - -#[cfg(not(test))] +// #[cfg(not(test))] type InnerAdapter = accesskit_winit::Adapter; -#[cfg(test)] -type InnerAdapter = self::test::InnerAdapter; +// #[cfg(test)] +// type InnerAdapter = self::test::InnerAdapter; const ROOT_SUBTREE_ID: SubtreeId = SubtreeId(0); +//winit stuff that should just be exposed by accesskit imo +struct WinitActivationHandler + Send + 'static> { + window_id: WindowId, + proxy: EventLoopProxy, +} + +impl + Send + 'static> ActivationHandler for WinitActivationHandler { + fn request_initial_tree(&mut self) -> Option { + let event = Event { + window_id: self.window_id, + window_event: WindowEvent::InitialTreeRequested, + }; + self.proxy.send_event(event.into()).ok(); + None + } +} + +struct WinitActionHandler + Send + 'static> { + window_id: WindowId, + proxy: EventLoopProxy, +} + +impl + Send + 'static> ActionHandler for WinitActionHandler { + fn do_action(&mut self, request: ActionRequest) { + let event = Event { + window_id: self.window_id, + window_event: WindowEvent::ActionRequested(request), + }; + self.proxy.send_event(event.into()).ok(); + } +} + +struct WinitDeactivationHandler + Send + 'static> { + window_id: WindowId, + proxy: EventLoopProxy, +} + /// Adapter that combines a root subtree with any number of other subtrees. /// /// The root subtree is always defined, and has id [`Self::root_subtree_id`]. To define additional @@ -33,19 +72,68 @@ pub struct SubtreeInfo { #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct SubtreeId(u64); +impl + Send + 'static> DeactivationHandler for WinitDeactivationHandler { + fn deactivate_accessibility(&mut self) { + let event = Event { + window_id: self.window_id, + window_event: WindowEvent::AccessibilityDeactivated, + }; + self.proxy.send_event(event.into()).ok(); + } +} + impl Adapter { // TODO: ensure that the ActivationHandler in `inner` is well-behaved (pushes updates to this adapter). // We suspect we can only guarantee this if *we* set the handler, which means *we* create the InnerAdapter. // Same goes for ActionHandler, we can intercept the caller’s handler and rewrite the target id. // TODO: if using a winit inner Adapter, expose the two winit-specific constructors. - pub fn new(inner: InnerAdapter) -> Self { - let mut result = Self { + pub fn with_event_loop_proxy + Send + 'static>( + event_loop: &ActiveEventLoop, + window: &Window, + proxy: EventLoopProxy, + ) -> Self { + let window_id = window.id(); + let activation_handler = WinitActivationHandler { + window_id, + proxy: proxy.clone(), + }; + let action_handler = WinitActionHandler { + window_id, + proxy: proxy.clone(), + }; + let deactivation_handler = WinitDeactivationHandler { window_id, proxy }; + Self::with_direct_handlers( + event_loop, + window, + activation_handler, + action_handler, + deactivation_handler, + ) + } + + pub fn with_direct_handlers( + event_loop: &ActiveEventLoop, + window: &Window, + activation_handler: impl 'static + ActivationHandler + Send, + action_handler: impl 'static + ActionHandler + Send, + deactivation_handler: impl 'static + DeactivationHandler + Send, + ) -> Self { + let inner = InnerAdapter::with_direct_handlers( + event_loop, + window, + activation_handler, + action_handler, + deactivation_handler, + ); + + let mut result = Adapter { inner, next_subtree_id: SubtreeId(1), - child_subtrees: HashMap::default(), - id_map: HashMap::default(), + child_subtrees: HashMap::new(), + id_map: HashMap::new(), next_node_id: NodeId(0), }; + assert!(result.id_map.insert(result.root_subtree_id(), HashMap::default()).is_none()); result } @@ -165,98 +253,98 @@ impl Adapter { subtree_id == self.root_subtree_id() || self.child_subtrees.contains_key(&subtree_id) } - #[cfg(test)] - fn take_tree_updates(&mut self) -> Vec { - self.inner.take_tree_updates() - } -} - -#[cfg(test)] -mod test { - use accesskit::{Node, NodeId, Role, Tree, TreeUpdate}; - - use crate::Adapter; - - #[derive(Default)] - pub struct InnerAdapter { - tree_updates: Vec, - } - - impl InnerAdapter { - pub fn update_if_active(&mut self, updater: impl FnOnce() -> TreeUpdate) { - self.tree_updates.push(updater()); - } - pub fn take_tree_updates(&mut self) -> Vec { - std::mem::take(&mut self.tree_updates) - } - } - - fn node(children: impl Into>) -> Node { - let mut result = Node::new(Role::Unknown); - result.set_children(children.into()); - result - } - - #[test] - fn test_update() { - let mut adapter = Adapter::new(InnerAdapter::default()); - adapter.update_if_active(adapter.root_subtree_id(), || TreeUpdate { - nodes: vec![ - (NodeId(13), node([NodeId(15), NodeId(14)])), - (NodeId(15), node([])), - (NodeId(14), node([])), - ], - tree: Some(Tree { - root: NodeId(13), - toolkit_name: None, - toolkit_version: None, - }), - focus: NodeId(13), - }); - let child_subtree_id = adapter.register_child_subtree(adapter.root_subtree_id(), NodeId(15)); - adapter.update_if_active(child_subtree_id, || TreeUpdate { - nodes: vec![ - (NodeId(25), node([NodeId(27), NodeId(26)])), - (NodeId(27), node([])), - (NodeId(26), node([])), - ], - tree: Some(Tree { - root: NodeId(25), - toolkit_name: None, - toolkit_version: None, - }), - focus: NodeId(25), - }); - let actual_updates = adapter.take_tree_updates(); - assert_eq!(actual_updates, vec![ - TreeUpdate { - nodes: vec![ - (NodeId(0), node([NodeId(1), NodeId(2)])), - (NodeId(1), node([])), - (NodeId(2), node([])), - ], - tree: Some(Tree { - root: NodeId(0), - toolkit_name: None, - toolkit_version: None, - }), - focus: NodeId(0), - }, - TreeUpdate { - nodes: vec![ - (NodeId(3), node([NodeId(4), NodeId(5)])), - (NodeId(4), node([])), - (NodeId(5), node([])), - ], - tree: Some(Tree { - // FIXME: this is incorrect - root: NodeId(3), - toolkit_name: None, - toolkit_version: None, - }), - // FIXME: this is incorrect - focus: NodeId(3), - }, - ]); - } + // #[cfg(test)] + // fn take_tree_updates(&mut self) -> Vec { + // self.inner.take_tree_updates() + // } } +// +// #[cfg(test)] +// mod test { +// use accesskit::{Node, NodeId, Role, Tree, TreeUpdate}; +// +// use crate::Adapter; +// +// #[derive(Default)] +// pub struct InnerAdapter { +// tree_updates: Vec, +// } +// +// impl InnerAdapter { +// pub fn update_if_active(&mut self, updater: impl FnOnce() -> TreeUpdate) { +// self.tree_updates.push(updater()); +// } +// pub fn take_tree_updates(&mut self) -> Vec { +// std::mem::take(&mut self.tree_updates) +// } +// } +// +// fn node(children: impl Into>) -> Node { +// let mut result = Node::new(Role::Unknown); +// result.set_children(children.into()); +// result +// } +// +// #[test] +// fn test_update() { +// let mut adapter = Adapter::new(InnerAdapter::default()); +// adapter.update_if_active(adapter.root_subtree_id(), || TreeUpdate { +// nodes: vec![ +// (NodeId(13), node([NodeId(15), NodeId(14)])), +// (NodeId(15), node([])), +// (NodeId(14), node([])), +// ], +// tree: Some(Tree { +// root: NodeId(13), +// toolkit_name: None, +// toolkit_version: None, +// }), +// focus: NodeId(13), +// }); +// let child_subtree_id = adapter.register_child_subtree(adapter.root_subtree_id(), NodeId(15)); +// adapter.update_if_active(child_subtree_id, || TreeUpdate { +// nodes: vec![ +// (NodeId(25), node([NodeId(27), NodeId(26)])), +// (NodeId(27), node([])), +// (NodeId(26), node([])), +// ], +// tree: Some(Tree { +// root: NodeId(25), +// toolkit_name: None, +// toolkit_version: None, +// }), +// focus: NodeId(25), +// }); +// let actual_updates = adapter.take_tree_updates(); +// assert_eq!(actual_updates, vec![ +// TreeUpdate { +// nodes: vec![ +// (NodeId(0), node([NodeId(1), NodeId(2)])), +// (NodeId(1), node([])), +// (NodeId(2), node([])), +// ], +// tree: Some(Tree { +// root: NodeId(0), +// toolkit_name: None, +// toolkit_version: None, +// }), +// focus: NodeId(0), +// }, +// TreeUpdate { +// nodes: vec![ +// (NodeId(3), node([NodeId(4), NodeId(5)])), +// (NodeId(4), node([])), +// (NodeId(5), node([])), +// ], +// tree: Some(Tree { +// // FIXME: this is incorrect +// root: NodeId(3), +// toolkit_name: None, +// toolkit_version: None, +// }), +// // FIXME: this is incorrect +// focus: NodeId(3), +// }, +// ]); +// } +// } From 9b551f816085d807cb7f362cba8d736400ff208e Mon Sep 17 00:00:00 2001 From: Luke Warlow Date: Mon, 24 Nov 2025 16:13:52 +0000 Subject: [PATCH 11/26] Implement wrapping handlers for activation and action handlers The activation wrapper is currently a stub. Action handler is reverse mapping from the global ids back to local ids. --- platforms/multitree/src/lib.rs | 87 +++++++++++++++++++++++++++++----- 1 file changed, 75 insertions(+), 12 deletions(-) diff --git a/platforms/multitree/src/lib.rs b/platforms/multitree/src/lib.rs index 9ba0a530..a83bd6e6 100644 --- a/platforms/multitree/src/lib.rs +++ b/platforms/multitree/src/lib.rs @@ -1,6 +1,7 @@ -use accesskit::{ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler, NodeId, TreeUpdate}; +use accesskit::{ActionData, ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler, NodeId, TreeUpdate}; use accesskit_winit::{Event, WindowEvent}; use std::collections::HashMap; +use std::ptr::NonNull; use winit::event_loop::{ActiveEventLoop, EventLoopProxy}; use winit::window::{Window, WindowId}; @@ -55,11 +56,12 @@ struct WinitDeactivationHandler + Send + 'static> { pub struct Adapter { // TODO: servoshell on Android and OpenHarmony do not use winit. // Allow switching to non-winit inner Adapter, maybe using conditional compilation? - inner: InnerAdapter, + inner: Option, next_subtree_id: SubtreeId, child_subtrees: HashMap, id_map: HashMap>, + reverse_id_map: HashMap, next_node_id: NodeId, } @@ -118,22 +120,74 @@ impl Adapter { action_handler: impl 'static + ActionHandler + Send, deactivation_handler: impl 'static + DeactivationHandler + Send, ) -> Self { - let inner = InnerAdapter::with_direct_handlers( - event_loop, - window, - activation_handler, - action_handler, - deactivation_handler, - ); - let mut result = Adapter { - inner, + inner: None, next_subtree_id: SubtreeId(1), child_subtrees: HashMap::new(), id_map: HashMap::new(), + reverse_id_map: HashMap::new(), next_node_id: NodeId(0), }; + let adapter_ptr = NonNull::from(&mut result); + + struct ActivationHandlerWrapper { + inner: H, + adapter: NonNull + } + unsafe impl Send for ActivationHandlerWrapper {} + impl ActivationHandler for ActivationHandlerWrapper { + fn request_initial_tree(&mut self) -> Option { + // TODO for now we just require users of this adapter to send updates via update_if_active. + None + } + } + + struct ActionHandlerWrapper { + inner: H, + adapter: NonNull + } + unsafe impl Send for ActionHandlerWrapper {} + impl ActionHandler for ActionHandlerWrapper { + fn do_action(&mut self, mut request: ActionRequest) { + let adapter = unsafe { self.adapter.as_mut() }; + // Map from the global node id to the local node id and forward to the provided handlers + request.target = adapter.reverse_map_id(request.target).1; + if let Some(data) = request.data.as_mut() { + match data { + ActionData::SetTextSelection(selection) => { + let new_anchor = adapter.reverse_map_id(selection.anchor.node).1; + selection.anchor.node = new_anchor; + let new_focus = adapter.reverse_map_id(selection.focus.node).1; + selection.focus.node = new_focus; + } + _ => {} + } + } + self.inner.do_action(request) + } + } + + let activation_wrapper = ActivationHandlerWrapper { + inner: activation_handler, + adapter: adapter_ptr + }; + + let action_wrapper = ActionHandlerWrapper { + inner: action_handler, + adapter: adapter_ptr, + }; + + let inner = InnerAdapter::with_direct_handlers( + event_loop, + window, + activation_wrapper, + action_wrapper, + deactivation_handler, + ); + + result.inner = Some(inner); + assert!(result.id_map.insert(result.root_subtree_id(), HashMap::default()).is_none()); result } @@ -158,6 +212,10 @@ impl Adapter { // No need to send a TreeUpdate, the provider of the parent subtree will do it } + fn inner(&mut self) -> &mut InnerAdapter { + self.inner.as_mut().expect("Adapter.inner used before initialisation") + } + pub fn update_if_active(&mut self, subtree_id: SubtreeId, updater: impl FnOnce() -> TreeUpdate) { // Q: what happens if the graft node (`parent_node_id`) gets removed by the parent subtree? // If we keep the registration, then we need to detect when the graft node gets readded @@ -204,7 +262,7 @@ impl Adapter { node.popup_for().map(|node_id| node.set_popup_for(self.map_id(subtree_id, node_id))); // TODO: what do we do about .level()? } - self.inner.update_if_active(|| subtree_update); + self.inner().update_if_active(|| subtree_update); } fn map_id(&mut self, subtree_id: SubtreeId, node_id: NodeId) -> NodeId { @@ -215,9 +273,14 @@ impl Adapter { let result = self.next_node_id(); let map = self.id_map.get_mut(&subtree_id).expect("Subtree not registered"); assert!(map.insert(node_id, result).is_none()); + assert!(self.reverse_id_map.insert(result, (subtree_id, node_id)).is_none()); result } + fn reverse_map_id(&self, global_node_id: NodeId) -> (SubtreeId, NodeId) { + *self.reverse_id_map.get(&global_node_id).expect("Node not registered") + } + fn map_node_id_vec_property(&mut self, subtree_id: SubtreeId, node_ids: Vec, setter: impl FnOnce(Vec)) { // If node id vec properties return an empty slice from their getters, don’t bother // calling the setters. This may be slightly more efficient, and also works around a From 753de8df97dc2c0d7f5843b67dca1570826db353 Mon Sep 17 00:00:00 2001 From: Luke Warlow Date: Wed, 26 Nov 2025 15:17:01 +0000 Subject: [PATCH 12/26] Add new multi_tree code into patched accesskit. Exploring a deeper integration of multitree to work around API differences per platform adapter --- Cargo.lock | 4 +- Cargo.toml | 1 + platforms/multitree/CHANGELOG.md | 868 +++++++++++++++++++++++++++++++ platforms/multitree/Cargo.toml | 17 +- platforms/multitree/src/lib.rs | 263 ++-------- platforms/winit/Cargo.toml | 3 +- platforms/winit/src/lib.rs | 21 +- 7 files changed, 919 insertions(+), 258 deletions(-) create mode 100644 platforms/multitree/CHANGELOG.md diff --git a/Cargo.lock b/Cargo.lock index 9e1ed5b1..fa24a6a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,7 +75,6 @@ name = "accesskit_multitree" version = "0.1.0" dependencies = [ "accesskit", - "accesskit_winit", ] [[package]] @@ -114,11 +113,12 @@ dependencies = [ [[package]] name = "accesskit_winit" -version = "0.29.1" +version = "0.29.2" dependencies = [ "accesskit", "accesskit_android", "accesskit_macos", + "accesskit_multitree", "accesskit_unix", "accesskit_windows", "raw-window-handle 0.5.2", diff --git a/Cargo.toml b/Cargo.toml index d54ee2a6..88afd01d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "platforms/unix", "platforms/windows", "platforms/winit", + "platforms/multitree", ] default-members = [ "common", diff --git a/platforms/multitree/CHANGELOG.md b/platforms/multitree/CHANGELOG.md new file mode 100644 index 00000000..1245751b --- /dev/null +++ b/platforms/multitree/CHANGELOG.md @@ -0,0 +1,868 @@ +# Changelog + +* The following workspace dependencies were updated + * dependencies + * accesskit_macos bumped from 0.1.4 to 0.1.5 + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.8.0 to 0.8.1 + * accesskit_windows bumped from 0.10.0 to 0.10.1 + * accesskit_macos bumped from 0.2.0 to 0.2.1 + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.10.1 to 0.10.2 + * accesskit_macos bumped from 0.2.1 to 0.3.0 + +* The following workspace dependencies were updated + * dependencies + * accesskit_macos bumped from 0.3.0 to 0.4.0 + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.10.3 to 0.10.4 + * accesskit_macos bumped from 0.4.1 to 0.4.2 + * accesskit_unix bumped from 0.1.0 to 0.1.1 + +* The following workspace dependencies were updated + * dependencies + * accesskit_unix bumped from 0.3.0 to 0.3.1 + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.10.0 to 0.10.1 + * accesskit_windows bumped from 0.13.0 to 0.13.1 + * accesskit_macos bumped from 0.6.0 to 0.6.1 + * accesskit_unix bumped from 0.3.1 to 0.3.2 + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.13.1 to 0.13.2 + +* The following workspace dependencies were updated + * dependencies + * accesskit_macos bumped from 0.6.1 to 0.6.2 + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.10.1 to 0.11.0 + * accesskit_windows bumped from 0.13.2 to 0.13.3 + * accesskit_macos bumped from 0.6.2 to 0.6.3 + * accesskit_unix bumped from 0.3.2 to 0.3.3 + +* The following workspace dependencies were updated + * dependencies + * accesskit_macos bumped from 0.7.0 to 0.7.1 + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.11.0 to 0.11.1 + * accesskit_windows bumped from 0.14.0 to 0.14.1 + * accesskit_macos bumped from 0.7.1 to 0.8.0 + * accesskit_unix bumped from 0.5.0 to 0.5.1 + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.11.1 to 0.11.2 + * accesskit_windows bumped from 0.14.1 to 0.14.2 + * accesskit_macos bumped from 0.8.0 to 0.9.0 + * accesskit_unix bumped from 0.5.1 to 0.5.2 + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.14.2 to 0.14.3 + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.16.0 to 0.16.1 + * accesskit_unix bumped from 0.7.1 to 0.7.2 + +* The following workspace dependencies were updated + * dependencies + * accesskit_unix bumped from 0.7.2 to 0.7.3 + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.16.1 to 0.16.2 + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.12.2 to 0.12.3 + * accesskit_windows bumped from 0.16.2 to 0.16.3 + * accesskit_macos bumped from 0.11.0 to 0.11.1 + * accesskit_unix bumped from 0.7.3 to 0.7.4 + +* The following workspace dependencies were updated + * dependencies + * accesskit_unix bumped from 0.7.4 to 0.7.5 + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.16.3 to 0.16.4 + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.18.0 to 0.18.1 + * accesskit_macos bumped from 0.13.0 to 0.13.1 + * accesskit_unix bumped from 0.9.0 to 0.9.1 + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.18.1 to 0.18.2 + * accesskit_macos bumped from 0.13.1 to 0.13.2 + * accesskit_unix bumped from 0.9.1 to 0.9.2 + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.18.2 to 0.19.0 + * accesskit_macos bumped from 0.13.2 to 0.14.0 + * accesskit_unix bumped from 0.9.2 to 0.10.0 + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.19.0 to 0.20.0 + * accesskit_macos bumped from 0.14.0 to 0.15.0 + * accesskit_unix bumped from 0.10.0 to 0.10.1 + +* The following workspace dependencies were updated + * dependencies + * accesskit_unix bumped from 0.11.0 to 0.11.1 + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.16.2 to 0.16.3 + * accesskit_windows bumped from 0.23.1 to 0.23.2 + * accesskit_macos bumped from 0.17.2 to 0.17.3 + * accesskit_unix bumped from 0.12.2 to 0.12.3 + +* The following workspace dependencies were updated + * dependencies + * accesskit_macos bumped from 0.17.3 to 0.17.4 + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.17.0 to 0.17.1 + * accesskit_windows bumped from 0.24.0 to 0.24.1 + * accesskit_macos bumped from 0.18.0 to 0.18.1 + * accesskit_unix bumped from 0.13.0 to 0.13.1 + +## [0.29.1](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.29.0...accesskit_winit-v0.29.1) (2025-10-02) + + +### Bug Fixes + +* Impl `Clone` and `PartialEq` on `WindowEvent` ([#618](https://github.com/AccessKit/accesskit/issues/618)) ([3a4771b](https://github.com/AccessKit/accesskit/commit/3a4771b87455cc005c18152935535818a3f9f825)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.21.0 to 0.21.1 + * accesskit_windows bumped from 0.29.0 to 0.29.1 + * accesskit_macos bumped from 0.22.0 to 0.22.1 + * accesskit_unix bumped from 0.17.0 to 0.17.1 + * accesskit_android bumped from 0.4.0 to 0.4.1 + +## [0.29.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.28.0...accesskit_winit-v0.29.0) (2025-07-16) + + +### Features + +* Let parents declare actions supported on their children ([#593](https://github.com/AccessKit/accesskit/issues/593)) ([70b534b](https://github.com/AccessKit/accesskit/commit/70b534bed168a84b84cc35199588aa8ab784fb43)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.20.0 to 0.21.0 + * accesskit_windows bumped from 0.28.0 to 0.29.0 + * accesskit_macos bumped from 0.21.0 to 0.22.0 + * accesskit_unix bumped from 0.16.0 to 0.17.0 + * accesskit_android bumped from 0.3.0 to 0.4.0 + +## [0.28.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.27.0...accesskit_winit-v0.28.0) (2025-06-26) + + +### ⚠ BREAKING CHANGES + +* Force a semver-breaking release ([#589](https://github.com/AccessKit/accesskit/issues/589)) + +### Bug Fixes + +* Force a semver-breaking release ([#589](https://github.com/AccessKit/accesskit/issues/589)) ([2887cdd](https://github.com/AccessKit/accesskit/commit/2887cddde817ba3851688068d8d10de5cef7c624)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.19.0 to 0.20.0 + * accesskit_windows bumped from 0.27.0 to 0.28.0 + * accesskit_macos bumped from 0.20.0 to 0.21.0 + * accesskit_unix bumped from 0.15.0 to 0.16.0 + * accesskit_android bumped from 0.2.0 to 0.3.0 + +## [0.27.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.26.0...accesskit_winit-v0.27.0) (2025-05-06) + + +### ⚠ BREAKING CHANGES + +* Simplify the core Android adapter API ([#558](https://github.com/AccessKit/accesskit/issues/558)) +* Drop redundant `HasPopup::True` ([#550](https://github.com/AccessKit/accesskit/issues/550)) + +### Code Refactoring + +* Drop redundant `HasPopup::True` ([#550](https://github.com/AccessKit/accesskit/issues/550)) ([56abf17](https://github.com/AccessKit/accesskit/commit/56abf17356e4c7f13f64aaeaca6a63c8f7ede553)) +* Simplify the core Android adapter API ([#558](https://github.com/AccessKit/accesskit/issues/558)) ([7ac5911](https://github.com/AccessKit/accesskit/commit/7ac5911b11f3d6b8b777b91e6476e7073f6b0e4a)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.18.0 to 0.19.0 + * accesskit_windows bumped from 0.26.0 to 0.27.0 + * accesskit_macos bumped from 0.19.0 to 0.20.0 + * accesskit_unix bumped from 0.14.0 to 0.15.0 + * accesskit_android bumped from 0.1.1 to 0.2.0 + +## [0.26.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.25.0...accesskit_winit-v0.26.0) (2025-03-17) + + +### ⚠ BREAKING CHANGES + +* Panic if the window is visible when the adapter is created, for adapters where this is a problem ([#529](https://github.com/AccessKit/accesskit/issues/529)) + +### Bug Fixes + +* Panic if the window is visible when the adapter is created, for adapters where this is a problem ([#529](https://github.com/AccessKit/accesskit/issues/529)) ([c43c37b](https://github.com/AccessKit/accesskit/commit/c43c37ba2502656fcae4fd726b9b7db0bb520f31)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.25.0 to 0.26.0 + * accesskit_android bumped from 0.1.0 to 0.1.1 + +## [0.25.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.24.0...accesskit_winit-v0.25.0) (2025-03-08) + + +### ⚠ BREAKING CHANGES + +* Make accesskit_android an optional dependency of accesskit_winit ([#524](https://github.com/AccessKit/accesskit/issues/524)) + +### Bug Fixes + +* Make accesskit_android an optional dependency of accesskit_winit ([#524](https://github.com/AccessKit/accesskit/issues/524)) ([bb17d44](https://github.com/AccessKit/accesskit/commit/bb17d449b601eaffad1c7201ec5bf8de241bb8f8)) + +## [0.24.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.23.1...accesskit_winit-v0.24.0) (2025-03-06) + + +### ⚠ BREAKING CHANGES + +* Add event loop parameter to winit adapter constructors ([#517](https://github.com/AccessKit/accesskit/issues/517)) +* Drop `Tree::app_name` ([#492](https://github.com/AccessKit/accesskit/issues/492)) + +### Features + +* Android adapter ([#500](https://github.com/AccessKit/accesskit/issues/500)) ([7e65ac7](https://github.com/AccessKit/accesskit/commit/7e65ac77d7e108ac5b9f3722f488a2fdf2e3b3e0)) + + +### Bug Fixes + +* Update winit to 0.30.9 ([#511](https://github.com/AccessKit/accesskit/issues/511)) ([0be21e6](https://github.com/AccessKit/accesskit/commit/0be21e6a2979af483b573b1c9b07c677286b871d)) + + +### Code Refactoring + +* Add event loop parameter to winit adapter constructors ([#517](https://github.com/AccessKit/accesskit/issues/517)) ([0d15f24](https://github.com/AccessKit/accesskit/commit/0d15f246a301a68af4424f7602c2f3be25da9327)) +* Drop `Tree::app_name` ([#492](https://github.com/AccessKit/accesskit/issues/492)) ([089794c](https://github.com/AccessKit/accesskit/commit/089794c8f74957e91a19ae3df508e2a892f39ebc)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.17.1 to 0.18.0 + * accesskit_windows bumped from 0.24.1 to 0.25.0 + * accesskit_macos bumped from 0.18.1 to 0.19.0 + * accesskit_unix bumped from 0.13.1 to 0.14.0 + +## [0.23.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.22.4...accesskit_winit-v0.23.0) (2024-10-31) + + +### ⚠ BREAKING CHANGES + +* Rename `name` to `label` and use `value` for label content ([#475](https://github.com/AccessKit/accesskit/issues/475)) +* Rename `NodeBuilder` to `Node` and the old `Node` to `FrozenNode` ([#476](https://github.com/AccessKit/accesskit/issues/476)) +* Drop `DefaultActionVerb` ([#472](https://github.com/AccessKit/accesskit/issues/472)) +* Make the core crate no-std ([#468](https://github.com/AccessKit/accesskit/issues/468)) + +### Features + +* Make the core crate no-std ([#468](https://github.com/AccessKit/accesskit/issues/468)) ([2fa0d3f](https://github.com/AccessKit/accesskit/commit/2fa0d3f5b2b7ac11ef1751c133706f29e548bd6d)) + + +### Code Refactoring + +* Drop `DefaultActionVerb` ([#472](https://github.com/AccessKit/accesskit/issues/472)) ([ef3b003](https://github.com/AccessKit/accesskit/commit/ef3b0038224459094f650368412650bc3b69526b)) +* Rename `name` to `label` and use `value` for label content ([#475](https://github.com/AccessKit/accesskit/issues/475)) ([e0053a5](https://github.com/AccessKit/accesskit/commit/e0053a5399929e8e0d4f07aa18de604ed8766ace)) +* Rename `NodeBuilder` to `Node` and the old `Node` to `FrozenNode` ([#476](https://github.com/AccessKit/accesskit/issues/476)) ([7d8910e](https://github.com/AccessKit/accesskit/commit/7d8910e35f7bc0543724cc124941a3bd0304bcc0)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.16.3 to 0.17.0 + * accesskit_windows bumped from 0.23.2 to 0.24.0 + * accesskit_macos bumped from 0.17.4 to 0.18.0 + * accesskit_unix bumped from 0.12.3 to 0.13.0 + +## [0.22.2](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.22.1...accesskit_winit-v0.22.2) (2024-10-07) + + +### Bug Fixes + +* Update minimum supported Rust version to 1.75 ([#457](https://github.com/AccessKit/accesskit/issues/457)) ([fc622fe](https://github.com/AccessKit/accesskit/commit/fc622fe7657c80a4eedad6f6cded11d2538b54d5)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.16.1 to 0.16.2 + * accesskit_windows bumped from 0.23.0 to 0.23.1 + * accesskit_macos bumped from 0.17.1 to 0.17.2 + * accesskit_unix bumped from 0.12.1 to 0.12.2 + +## [0.22.1](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.22.0...accesskit_winit-v0.22.1) (2024-09-24) + + +### Bug Fixes + +* Use the new HWND type on accesskit_winit ([#453](https://github.com/AccessKit/accesskit/issues/453)) ([68a2462](https://github.com/AccessKit/accesskit/commit/68a24629381f0b18f6ed1ee008fe72ce9330092e)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.16.0 to 0.16.1 + * accesskit_windows bumped from 0.22.0 to 0.23.0 + * accesskit_macos bumped from 0.17.0 to 0.17.1 + * accesskit_unix bumped from 0.12.0 to 0.12.1 + +## [0.22.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.21.1...accesskit_winit-v0.22.0) (2024-06-29) + + +### ⚠ BREAKING CHANGES + +* Rename the `StaticText` role to `Label` ([#434](https://github.com/AccessKit/accesskit/issues/434)) + +### Code Refactoring + +* Rename the `StaticText` role to `Label` ([#434](https://github.com/AccessKit/accesskit/issues/434)) ([7086bc0](https://github.com/AccessKit/accesskit/commit/7086bc0fad446d3ed4a0fd5eff641a1e75f6c599)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.15.0 to 0.16.0 + * accesskit_windows bumped from 0.21.0 to 0.22.0 + * accesskit_macos bumped from 0.16.0 to 0.17.0 + * accesskit_unix bumped from 0.11.1 to 0.12.0 + +## [0.21.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.20.4...accesskit_winit-v0.21.0) (2024-06-09) + + +### Features + +* Add `author_id` property ([#424](https://github.com/AccessKit/accesskit/issues/424)) ([0d1c56f](https://github.com/AccessKit/accesskit/commit/0d1c56f0bdde58715e1c69f6015df600cb7cb8c1)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.14.0 to 0.15.0 + * accesskit_windows bumped from 0.20.0 to 0.21.0 + * accesskit_macos bumped from 0.15.0 to 0.16.0 + * accesskit_unix bumped from 0.10.1 to 0.11.0 + +## [0.20.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.19.0...accesskit_winit-v0.20.0) (2024-04-30) + + +### ⚠ BREAKING CHANGES + +* Update winit to 0.30 ([#397](https://github.com/AccessKit/accesskit/issues/397)) +* Drop `NodeClassSet` ([#389](https://github.com/AccessKit/accesskit/issues/389)) + +### Bug Fixes + +* Increase minimum supported Rust version to `1.70` ([#396](https://github.com/AccessKit/accesskit/issues/396)) ([a8398b8](https://github.com/AccessKit/accesskit/commit/a8398b847aa003de91042ac45e33126fc2cae053)) +* Update winit to 0.30 ([#397](https://github.com/AccessKit/accesskit/issues/397)) ([de93be3](https://github.com/AccessKit/accesskit/commit/de93be387c03a438fbf598670207e578686e6bcf)) + + +### Code Refactoring + +* Drop `NodeClassSet` ([#389](https://github.com/AccessKit/accesskit/issues/389)) ([1b153ed](https://github.com/AccessKit/accesskit/commit/1b153ed51f8421cdba2dc98beca2e8f5f8c781bc)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.13.0 to 0.14.0 + * accesskit_windows bumped from 0.17.0 to 0.18.0 + * accesskit_macos bumped from 0.12.0 to 0.13.0 + * accesskit_unix bumped from 0.8.0 to 0.9.0 + +## [0.19.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.18.7...accesskit_winit-v0.19.0) (2024-04-14) + + +### ⚠ BREAKING CHANGES + +* New approach to lazy initialization ([#375](https://github.com/AccessKit/accesskit/issues/375)) + +### Code Refactoring + +* New approach to lazy initialization ([#375](https://github.com/AccessKit/accesskit/issues/375)) ([9baebdc](https://github.com/AccessKit/accesskit/commit/9baebdceed7300389b6768815d7ae48f1ce401e4)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.12.3 to 0.13.0 + * accesskit_windows bumped from 0.16.4 to 0.17.0 + * accesskit_macos bumped from 0.11.1 to 0.12.0 + * accesskit_unix bumped from 0.7.5 to 0.8.0 + +## [0.18.1](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.18.0...accesskit_winit-v0.18.1) (2024-01-11) + + +### Bug Fixes + +* Run our own async executor on Unix ([#337](https://github.com/AccessKit/accesskit/issues/337)) ([8f937ba](https://github.com/AccessKit/accesskit/commit/8f937baaa510dd96da196501822b82f75f05b595)) +* Show an error at compile-time if no raw-window-handle feature is enabled for the winit adapter ([#339](https://github.com/AccessKit/accesskit/issues/339)) ([a24f5fd](https://github.com/AccessKit/accesskit/commit/a24f5fd443a683a6194b54244052ff3e1cc05de6)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit_unix bumped from 0.7.0 to 0.7.1 + +## [0.18.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.17.0...accesskit_winit-v0.18.0) (2024-01-03) + + +### ⚠ BREAKING CHANGES + +* Lazily activate Unix adapters ([#324](https://github.com/AccessKit/accesskit/issues/324)) +* Remove `accesskit_winit::Adapter::update` ([#325](https://github.com/AccessKit/accesskit/issues/325)) + +### Bug Fixes + +* Lazily activate Unix adapters ([#324](https://github.com/AccessKit/accesskit/issues/324)) ([54ed036](https://github.com/AccessKit/accesskit/commit/54ed036c99d87428a8eb5bb03fd77e9e31562d4c)) +* Remove `accesskit_winit::Adapter::update` ([#325](https://github.com/AccessKit/accesskit/issues/325)) ([f121bff](https://github.com/AccessKit/accesskit/commit/f121bffe9e651fd2ac6deb882f57e1c9b613b7eb)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.12.1 to 0.12.2 + * accesskit_windows bumped from 0.15.1 to 0.16.0 + * accesskit_macos bumped from 0.10.1 to 0.11.0 + * accesskit_unix bumped from 0.6.2 to 0.7.0 + +## [0.17.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.16.1...accesskit_winit-v0.17.0) (2023-12-14) + + +### ⚠ BREAKING CHANGES + +* Force a semver break for the winit rwh feature additions ([#322](https://github.com/AccessKit/accesskit/issues/322)) + +### Bug Fixes + +* Add a `rwh_05` feature flag to `accesskit_winit` ([#319](https://github.com/AccessKit/accesskit/issues/319)) ([f4d279c](https://github.com/AccessKit/accesskit/commit/f4d279c5ece16df2925c0e31dc82eaf192c40cd0)) +* Force a semver break for the winit rwh feature additions ([#322](https://github.com/AccessKit/accesskit/issues/322)) ([61acdb0](https://github.com/AccessKit/accesskit/commit/61acdb0ea083263c88a00ad4db637b25863852c0)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit_unix bumped from 0.6.1 to 0.6.2 + +## [0.16.1](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.16.0...accesskit_winit-v0.16.1) (2023-11-05) + + +### Bug Fixes + +* Account for window decorations when `accesskit_winit::Adapter::process_event` receives a resizing event on Unix ([#312](https://github.com/AccessKit/accesskit/issues/312)) ([e2b264c](https://github.com/AccessKit/accesskit/commit/e2b264c2e5b0fb699576f2ece905509c38ffc9be)) + +## [0.16.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.15.0...accesskit_winit-v0.16.0) (2023-11-04) + + +### ⚠ BREAKING CHANGES + +* Rename `accesskit_winit::Adapter::on_event` to `process_event` ([#307](https://github.com/AccessKit/accesskit/issues/307)) +* Bump winit to 0.29 ([#256](https://github.com/AccessKit/accesskit/issues/256)) + +### deps + +* Bump winit to 0.29 ([#256](https://github.com/AccessKit/accesskit/issues/256)) ([4eb21ff](https://github.com/AccessKit/accesskit/commit/4eb21ff64256fcf0a16ab831554b06b80de9b36e)) + + +### Bug Fixes + +* Add missing semicolons when not returning anything ([#303](https://github.com/AccessKit/accesskit/issues/303)) ([38d4de1](https://github.com/AccessKit/accesskit/commit/38d4de1442247e701047d75122a9638a2ed99b1f)) +* Use raw-window-handle 0.6 ([#310](https://github.com/AccessKit/accesskit/issues/310)) ([3fa69ab](https://github.com/AccessKit/accesskit/commit/3fa69ab4d9216b51b651d3cf2a9c8217a77069f4)) + + +### Code Refactoring + +* Rename `accesskit_winit::Adapter::on_event` to `process_event` ([#307](https://github.com/AccessKit/accesskit/issues/307)) ([6fbebde](https://github.com/AccessKit/accesskit/commit/6fbebdeb9d1e96b1776ed1faf7ad21d9cc0a68df)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.12.0 to 0.12.1 + * accesskit_windows bumped from 0.15.0 to 0.15.1 + * accesskit_macos bumped from 0.10.0 to 0.10.1 + * accesskit_unix bumped from 0.6.0 to 0.6.1 + +## [0.15.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.14.4...accesskit_winit-v0.15.0) (2023-09-27) + + +### ⚠ BREAKING CHANGES + +* Allow providing app_name, toolkit_name and toolkit_version in Tree, remove parameters from unix adapter constructor ([#291](https://github.com/AccessKit/accesskit/issues/291)) +* Make `ActionHandler::do_action` take `&mut self` ([#296](https://github.com/AccessKit/accesskit/issues/296)) +* Decouple in-tree focus from host window/view focus ([#278](https://github.com/AccessKit/accesskit/issues/278)) +* Switch to simple unsigned 64-bit integer for node IDs ([#276](https://github.com/AccessKit/accesskit/issues/276)) + +### Features + +* Allow providing app_name, toolkit_name and toolkit_version in Tree, remove parameters from unix adapter constructor ([#291](https://github.com/AccessKit/accesskit/issues/291)) ([5313860](https://github.com/AccessKit/accesskit/commit/531386023257150f49b5e4be942f359855fb7cb6)) + + +### Bug Fixes + +* Fix doc build for accesskit_winit ([#281](https://github.com/AccessKit/accesskit/issues/281)) ([e3b38b8](https://github.com/AccessKit/accesskit/commit/e3b38b8164d0c5442a5a1904165e2b05847376c2)) + + +### Code Refactoring + +* Decouple in-tree focus from host window/view focus ([#278](https://github.com/AccessKit/accesskit/issues/278)) ([d360d20](https://github.com/AccessKit/accesskit/commit/d360d20cf951e7643b81a5303006c9f7daa5bd56)) +* Make `ActionHandler::do_action` take `&mut self` ([#296](https://github.com/AccessKit/accesskit/issues/296)) ([4fc7846](https://github.com/AccessKit/accesskit/commit/4fc7846d732d61fb45c023060ebab96801a0053e)) +* Switch to simple unsigned 64-bit integer for node IDs ([#276](https://github.com/AccessKit/accesskit/issues/276)) ([3eadd48](https://github.com/AccessKit/accesskit/commit/3eadd48ec47854faa94a94ebf910ec08f514642f)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.11.2 to 0.12.0 + * accesskit_windows bumped from 0.14.3 to 0.15.0 + * accesskit_macos bumped from 0.9.0 to 0.10.0 + * accesskit_unix bumped from 0.5.2 to 0.6.0 + +## [0.14.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.13.0...accesskit_winit-v0.14.0) (2023-05-21) + + +### Features + +* Add features for async runtimes on Unix ([#248](https://github.com/AccessKit/accesskit/issues/248)) ([b56b4ea](https://github.com/AccessKit/accesskit/commit/b56b4ea7c967ee5a1dae21a2fa0dcd385346031e)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit_unix bumped from 0.4.0 to 0.5.0 + +## [0.13.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.12.5...accesskit_winit-v0.13.0) (2023-03-30) + + +### ⚠ BREAKING CHANGES + +* Force a semver-breaking version bump in downstream crates ([#234](https://github.com/AccessKit/accesskit/issues/234)) + +### Bug Fixes + +* Force a semver-breaking version bump in downstream crates ([#234](https://github.com/AccessKit/accesskit/issues/234)) ([773389b](https://github.com/AccessKit/accesskit/commit/773389bff857fa18edf15de426e029251fc34591)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.13.3 to 0.14.0 + * accesskit_macos bumped from 0.6.3 to 0.7.0 + * accesskit_unix bumped from 0.3.3 to 0.4.0 + +## [0.12.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.11.0...accesskit_winit-v0.12.0) (2023-02-18) + + +### Features + +* Feature-gate the Unix adapter in accesskit_winit ([#214](https://github.com/AccessKit/accesskit/issues/214)) ([be95807](https://github.com/AccessKit/accesskit/commit/be95807dda64f2a49b4d20cc9084b14a7aa2844e)) + +## [0.11.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.10.0...accesskit_winit-v0.11.0) (2023-02-12) + + +### ⚠ BREAKING CHANGES + +* Move thread synchronization into platform adapters; drop parking_lot ([#212](https://github.com/AccessKit/accesskit/issues/212)) + +### Code Refactoring + +* Move thread synchronization into platform adapters; drop parking_lot ([#212](https://github.com/AccessKit/accesskit/issues/212)) ([5df52e5](https://github.com/AccessKit/accesskit/commit/5df52e5545faddf6a51905409013c2f5be23981e)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.9.0 to 0.10.0 + * accesskit_windows bumped from 0.12.0 to 0.13.0 + * accesskit_macos bumped from 0.5.0 to 0.6.0 + * accesskit_unix bumped from 0.2.0 to 0.3.0 + +## [0.10.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.9.1...accesskit_winit-v0.10.0) (2023-02-05) + + +### ⚠ BREAKING CHANGES + +* Make `Node` opaque and optimize it for size ([#205](https://github.com/AccessKit/accesskit/issues/205)) + +### Code Refactoring + +* Make `Node` opaque and optimize it for size ([#205](https://github.com/AccessKit/accesskit/issues/205)) ([4811152](https://github.com/AccessKit/accesskit/commit/48111521439b76c1a8687418a4b20f9b705eac6d)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.8.1 to 0.9.0 + * accesskit_windows bumped from 0.11.0 to 0.12.0 + * accesskit_macos bumped from 0.4.2 to 0.5.0 + * accesskit_unix bumped from 0.1.1 to 0.2.0 + +## [0.9.1](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.9.0...accesskit_winit-v0.9.1) (2023-02-05) + + +### Bug Fixes + +* Don't force winit's X11 and Wayland features to be enabled ([#209](https://github.com/AccessKit/accesskit/issues/209)) ([a3ed357](https://github.com/AccessKit/accesskit/commit/a3ed35754ad8f69a8ed54adacc30b6d57c19329a)) + +## [0.9.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.8.1...accesskit_winit-v0.9.0) (2023-02-02) + + +### ⚠ BREAKING CHANGES + +* Update winit to 0.28 ([#207](https://github.com/AccessKit/accesskit/issues/207)) + +### Miscellaneous Chores + +* Update winit to 0.28 ([#207](https://github.com/AccessKit/accesskit/issues/207)) ([3ff0cf5](https://github.com/AccessKit/accesskit/commit/3ff0cf59f982af504499142a3804f7aeeb4defe0)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.10.4 to 0.11.0 + +## [0.8.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.7.3...accesskit_winit-v0.8.0) (2023-01-05) + + +### Features + +* Basic Unix platform adapter ([#198](https://github.com/AccessKit/accesskit/issues/198)) ([1cea32e](https://github.com/AccessKit/accesskit/commit/1cea32e44ee743b778ac941ceff9087ae745cb37)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.10.2 to 0.10.3 + * accesskit_macos bumped from 0.4.0 to 0.4.1 + +## [0.7.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.6.6...accesskit_winit-v0.7.0) (2022-11-29) + + +### ⚠ BREAKING CHANGES + +* Move lazy initialization from the core platform adapter to the caller ([#179](https://github.com/AccessKit/accesskit/issues/179)) + +### Code Refactoring + +* Move lazy initialization from the core platform adapter to the caller ([#179](https://github.com/AccessKit/accesskit/issues/179)) ([f35c941](https://github.com/AccessKit/accesskit/commit/f35c941f395f3162db376a69cfaaaf770d376267)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.9.3 to 0.10.0 + * accesskit_macos bumped from 0.1.5 to 0.2.0 + +### [0.6.4](https://www.github.com/AccessKit/accesskit/compare/accesskit_winit-v0.6.3...accesskit_winit-v0.6.4) (2022-11-25) + + +### Bug Fixes + +* Reduce the winit version requirement to match egui ([#170](https://www.github.com/AccessKit/accesskit/issues/170)) ([1d27482](https://www.github.com/AccessKit/accesskit/commit/1d27482221140c1f3b3e3eaf93e7feaf8105611d)) + +## [0.6.0](https://www.github.com/AccessKit/accesskit/compare/accesskit_winit-v0.5.1...accesskit_winit-v0.6.0) (2022-11-23) + + +### Features + +* **platforms/macos:** Basic macOS platform adapter ([#158](https://www.github.com/AccessKit/accesskit/issues/158)) ([a06725e](https://www.github.com/AccessKit/accesskit/commit/a06725e952e6041dbd366944fa793b746c9f195e)) + + +### Bug Fixes + +* **platforms/macos:** Fix macOS crate version number ([#161](https://www.github.com/AccessKit/accesskit/issues/161)) ([e0a6a40](https://www.github.com/AccessKit/accesskit/commit/e0a6a401050cdcaea4efa870ed77ae94388f1ce0)) +* **platforms/windows:** Re-export the windows-rs HWND type ([#159](https://www.github.com/AccessKit/accesskit/issues/159)) ([389187a](https://www.github.com/AccessKit/accesskit/commit/389187ac5e96895ed1763d14d315d2f8f4256460)) + +### [0.5.1](https://www.github.com/AccessKit/accesskit/compare/accesskit_winit-v0.5.0...accesskit_winit-v0.5.1) (2022-11-17) + + +### Bug Fixes + +* **platforms/winit:** Eliminate some problematic indirect dependencies ([#154](https://www.github.com/AccessKit/accesskit/issues/154)) ([58048ae](https://www.github.com/AccessKit/accesskit/commit/58048aebedc293eda5c5819ea66db9b40b8926b0)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.7.0 to 0.8.0 + +## [0.5.0](https://www.github.com/AccessKit/accesskit/compare/accesskit_winit-v0.4.0...accesskit_winit-v0.5.0) (2022-11-14) + + +### Features + +* **platforms/winit:** Allow a custom action handler ([#149](https://www.github.com/AccessKit/accesskit/issues/149)) ([cdb1a16](https://www.github.com/AccessKit/accesskit/commit/cdb1a164de06f18cad497409a514f270a8336b4c)) + +## [0.4.0](https://www.github.com/AccessKit/accesskit/compare/accesskit_winit-v0.3.3...accesskit_winit-v0.4.0) (2022-11-12) + + +### ⚠ BREAKING CHANGES + +* **platforms/windows:** Update to windows-rs 0.42.0 (#148) + +### Bug Fixes + +* **consumer, platforms/windows, platforms/winit:** Update to parking_lot 0.12.1 ([#146](https://www.github.com/AccessKit/accesskit/issues/146)) ([6772855](https://www.github.com/AccessKit/accesskit/commit/6772855a7b540fd728faad15d8d208b05c1bbd8a)) +* **platforms/windows:** Update to windows-rs 0.42.0 ([#148](https://www.github.com/AccessKit/accesskit/issues/148)) ([70d1a89](https://www.github.com/AccessKit/accesskit/commit/70d1a89f51fd6c3a32b7192d9d7f3937db09d196)) + +### [0.3.3](https://www.github.com/AccessKit/accesskit/compare/accesskit_winit-v0.3.2...accesskit_winit-v0.3.3) (2022-11-11) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.6.1 to 0.7.0 + +### [0.3.2](https://www.github.com/AccessKit/accesskit/compare/accesskit_winit-v0.3.1...accesskit_winit-v0.3.2) (2022-10-11) + + +### Bug Fixes + +* **platforms/winit:** Derive `Debug` on `ActionRequestEvent` ([#141](https://www.github.com/AccessKit/accesskit/issues/141)) ([8b84c75](https://www.github.com/AccessKit/accesskit/commit/8b84c7547c6fdb52cd6d5c6d79f812dc614f08dd)) + +### [0.3.1](https://www.github.com/AccessKit/accesskit/compare/accesskit_winit-v0.3.0...accesskit_winit-v0.3.1) (2022-10-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.6.0 to 0.6.1 + +## [0.3.0](https://www.github.com/AccessKit/accesskit/compare/accesskit_winit-v0.2.1...accesskit_winit-v0.3.0) (2022-10-09) + + +### ⚠ BREAKING CHANGES + +* Wrap `TreeUpdate` nodes in `Arc` (#135) +* Store node ID in `TreeUpdate`, not `accesskit::Node` (#132) + +### Code Refactoring + +* Store node ID in `TreeUpdate`, not `accesskit::Node` ([#132](https://www.github.com/AccessKit/accesskit/issues/132)) ([0bb86dd](https://www.github.com/AccessKit/accesskit/commit/0bb86ddb298cb5a253a91f07be0bad8b84b2fda3)) +* Wrap `TreeUpdate` nodes in `Arc` ([#135](https://www.github.com/AccessKit/accesskit/issues/135)) ([907bc18](https://www.github.com/AccessKit/accesskit/commit/907bc1820b80d95833b6c5c3acaa2a8a4e93a6c2)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.5.1 to 0.6.0 + +### [0.2.1](https://www.github.com/AccessKit/accesskit/compare/accesskit_winit-v0.2.0...accesskit_winit-v0.2.1) (2022-10-03) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.5.0 to 0.5.1 + +## [0.2.0](https://www.github.com/AccessKit/accesskit/compare/accesskit_winit-v0.1.0...accesskit_winit-v0.2.0) (2022-09-23) + + +### ⚠ BREAKING CHANGES + +* Basic live regions (#128) +* **platforms/windows:** Bump windows-rs dependency (#126) +* **platforms/winit:** Bump winit dependency (#125) + +### Features + +* Basic live regions ([#128](https://www.github.com/AccessKit/accesskit/issues/128)) ([03d745b](https://www.github.com/AccessKit/accesskit/commit/03d745b891147175bde2693cc10b96a2f6e31f39)) + + +### Miscellaneous Chores + +* **platforms/windows:** Bump windows-rs dependency ([#126](https://www.github.com/AccessKit/accesskit/issues/126)) ([472a75e](https://www.github.com/AccessKit/accesskit/commit/472a75e4214b90396f3282f247df08100ed8362d)) +* **platforms/winit:** Bump winit dependency ([#125](https://www.github.com/AccessKit/accesskit/issues/125)) ([6026c1b](https://www.github.com/AccessKit/accesskit/commit/6026c1b2ecede3ca2f2076075ed158000154b34e)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.4.0 to 0.5.0 + +## 0.1.0 (2022-07-22) + + +### Features + +* **platforms/winit:** New winit adapter ([#121](https://www.github.com/AccessKit/accesskit/issues/121)) ([fdc274e](https://www.github.com/AccessKit/accesskit/commit/fdc274e7d3a901873d2ad0c7a4824a19111787ef)) + + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.3.0 to 0.4.0 diff --git a/platforms/multitree/Cargo.toml b/platforms/multitree/Cargo.toml index 7b25ed1b..840c2d7f 100644 --- a/platforms/multitree/Cargo.toml +++ b/platforms/multitree/Cargo.toml @@ -12,19 +12,4 @@ edition.workspace = true rust-version.workspace = true [dependencies] -accesskit = { version = "0.21.1", path = "../../common" } -accesskit_winit = { version = "0.29.1", path = "../winit" } - -[dependencies.winit] -version = "0.30.5" -default-features = false - -[dev-dependencies.winit] -version = "0.30.5" -features = [ - "x11", - "wayland", - "wayland-dlopen", - "wayland-csd-adwaita", -] -default-features = false +accesskit = { version = "0.21.1", path = "../../common" } diff --git a/platforms/multitree/src/lib.rs b/platforms/multitree/src/lib.rs index a83bd6e6..1ad8077e 100644 --- a/platforms/multitree/src/lib.rs +++ b/platforms/multitree/src/lib.rs @@ -1,63 +1,10 @@ -use accesskit::{ActionData, ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler, NodeId, TreeUpdate}; -use accesskit_winit::{Event, WindowEvent}; +use accesskit::{ActionData, ActionHandler, ActionRequest, ActivationHandler, NodeId, TreeUpdate}; use std::collections::HashMap; use std::ptr::NonNull; -use winit::event_loop::{ActiveEventLoop, EventLoopProxy}; -use winit::window::{Window, WindowId}; - -// #[cfg(not(test))] -type InnerAdapter = accesskit_winit::Adapter; -// #[cfg(test)] -// type InnerAdapter = self::test::InnerAdapter; const ROOT_SUBTREE_ID: SubtreeId = SubtreeId(0); -//winit stuff that should just be exposed by accesskit imo -struct WinitActivationHandler + Send + 'static> { - window_id: WindowId, - proxy: EventLoopProxy, -} - -impl + Send + 'static> ActivationHandler for WinitActivationHandler { - fn request_initial_tree(&mut self) -> Option { - let event = Event { - window_id: self.window_id, - window_event: WindowEvent::InitialTreeRequested, - }; - self.proxy.send_event(event.into()).ok(); - None - } -} - -struct WinitActionHandler + Send + 'static> { - window_id: WindowId, - proxy: EventLoopProxy, -} - -impl + Send + 'static> ActionHandler for WinitActionHandler { - fn do_action(&mut self, request: ActionRequest) { - let event = Event { - window_id: self.window_id, - window_event: WindowEvent::ActionRequested(request), - }; - self.proxy.send_event(event.into()).ok(); - } -} - -struct WinitDeactivationHandler + Send + 'static> { - window_id: WindowId, - proxy: EventLoopProxy, -} - -/// Adapter that combines a root subtree with any number of other subtrees. -/// -/// The root subtree is always defined, and has id [`Self::root_subtree_id`]. To define additional -/// subtrees, call [`Self::register_new_subtree`]. -pub struct Adapter { - // TODO: servoshell on Android and OpenHarmony do not use winit. - // Allow switching to non-winit inner Adapter, maybe using conditional compilation? - inner: Option, - +pub struct MultiTreeAdapterState { next_subtree_id: SubtreeId, child_subtrees: HashMap, id_map: HashMap>, @@ -74,54 +21,9 @@ pub struct SubtreeInfo { #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct SubtreeId(u64); -impl + Send + 'static> DeactivationHandler for WinitDeactivationHandler { - fn deactivate_accessibility(&mut self) { - let event = Event { - window_id: self.window_id, - window_event: WindowEvent::AccessibilityDeactivated, - }; - self.proxy.send_event(event.into()).ok(); - } -} - -impl Adapter { - // TODO: ensure that the ActivationHandler in `inner` is well-behaved (pushes updates to this adapter). - // We suspect we can only guarantee this if *we* set the handler, which means *we* create the InnerAdapter. - // Same goes for ActionHandler, we can intercept the caller’s handler and rewrite the target id. - // TODO: if using a winit inner Adapter, expose the two winit-specific constructors. - pub fn with_event_loop_proxy + Send + 'static>( - event_loop: &ActiveEventLoop, - window: &Window, - proxy: EventLoopProxy, - ) -> Self { - let window_id = window.id(); - let activation_handler = WinitActivationHandler { - window_id, - proxy: proxy.clone(), - }; - let action_handler = WinitActionHandler { - window_id, - proxy: proxy.clone(), - }; - let deactivation_handler = WinitDeactivationHandler { window_id, proxy }; - Self::with_direct_handlers( - event_loop, - window, - activation_handler, - action_handler, - deactivation_handler, - ) - } - - pub fn with_direct_handlers( - event_loop: &ActiveEventLoop, - window: &Window, - activation_handler: impl 'static + ActivationHandler + Send, - action_handler: impl 'static + ActionHandler + Send, - deactivation_handler: impl 'static + DeactivationHandler + Send, - ) -> Self { - let mut result = Adapter { - inner: None, +impl MultiTreeAdapterState { + pub fn new() -> Self { + let mut result = MultiTreeAdapterState { next_subtree_id: SubtreeId(1), child_subtrees: HashMap::new(), id_map: HashMap::new(), @@ -129,11 +31,16 @@ impl Adapter { next_node_id: NodeId(0), }; - let adapter_ptr = NonNull::from(&mut result); + assert!(result.id_map.insert(result.root_subtree_id(), HashMap::default()).is_none()); + result + } + pub fn wrap_activation_handler( + &self, + activation_handler: impl 'static + ActivationHandler + Send + ) -> impl 'static + ActivationHandler + Send { struct ActivationHandlerWrapper { inner: H, - adapter: NonNull } unsafe impl Send for ActivationHandlerWrapper {} impl ActivationHandler for ActivationHandlerWrapper { @@ -143,22 +50,30 @@ impl Adapter { } } + ActivationHandlerWrapper { inner: activation_handler } + } + + pub fn wrap_action_handler( + &mut self, + action_handler: impl 'static + ActionHandler + Send + ) -> impl 'static + ActionHandler + Send { struct ActionHandlerWrapper { inner: H, - adapter: NonNull + // is this actually safe? + adapter_state: NonNull } unsafe impl Send for ActionHandlerWrapper {} impl ActionHandler for ActionHandlerWrapper { fn do_action(&mut self, mut request: ActionRequest) { - let adapter = unsafe { self.adapter.as_mut() }; + let adapter_state = unsafe { self.adapter_state.as_ref() }; // Map from the global node id to the local node id and forward to the provided handlers - request.target = adapter.reverse_map_id(request.target).1; + request.target = adapter_state.reverse_map_id(request.target).1; if let Some(data) = request.data.as_mut() { match data { ActionData::SetTextSelection(selection) => { - let new_anchor = adapter.reverse_map_id(selection.anchor.node).1; + let new_anchor = adapter_state.reverse_map_id(selection.anchor.node).1; selection.anchor.node = new_anchor; - let new_focus = adapter.reverse_map_id(selection.focus.node).1; + let new_focus = adapter_state.reverse_map_id(selection.focus.node).1; selection.focus.node = new_focus; } _ => {} @@ -167,29 +82,9 @@ impl Adapter { self.inner.do_action(request) } } + let adapter_state_ptr = NonNull::from(self); - let activation_wrapper = ActivationHandlerWrapper { - inner: activation_handler, - adapter: adapter_ptr - }; - - let action_wrapper = ActionHandlerWrapper { - inner: action_handler, - adapter: adapter_ptr, - }; - - let inner = InnerAdapter::with_direct_handlers( - event_loop, - window, - activation_wrapper, - action_wrapper, - deactivation_handler, - ); - - result.inner = Some(inner); - - assert!(result.id_map.insert(result.root_subtree_id(), HashMap::default()).is_none()); - result + ActionHandlerWrapper { inner: action_handler, adapter_state: adapter_state_ptr } } pub fn root_subtree_id(&self) -> SubtreeId { @@ -212,11 +107,7 @@ impl Adapter { // No need to send a TreeUpdate, the provider of the parent subtree will do it } - fn inner(&mut self) -> &mut InnerAdapter { - self.inner.as_mut().expect("Adapter.inner used before initialisation") - } - - pub fn update_if_active(&mut self, subtree_id: SubtreeId, updater: impl FnOnce() -> TreeUpdate) { + pub fn rewrite_tree_update(&mut self, subtree_id: SubtreeId, updater: impl FnOnce() -> TreeUpdate) -> impl FnOnce() -> TreeUpdate { // Q: what happens if the graft node (`parent_node_id`) gets removed by the parent subtree? // If we keep the registration, then we need to detect when the graft node gets readded // (if ever), and resend the subtree in full, which requires caching the subtree or telling @@ -228,6 +119,7 @@ impl Adapter { // - Easy way: we set up our own accesskit_consumer::Tree and ChangeHandler // - Hard way: we plumb through the lower-level Adapters and expose the one in // atspi common / android / windows? macOS has one too, but maybe used differently? + // TODO possibly we don't want to call updater until our return FnOnce is invoked? let mut subtree_update = updater(); // TODO: rewrite the focus correctly. // We think the model is something like: every subtree has its local idea of the focused @@ -262,7 +154,7 @@ impl Adapter { node.popup_for().map(|node_id| node.set_popup_for(self.map_id(subtree_id, node_id))); // TODO: what do we do about .level()? } - self.inner().update_if_active(|| subtree_update); + || subtree_update } fn map_id(&mut self, subtree_id: SubtreeId, node_id: NodeId) -> NodeId { @@ -315,99 +207,4 @@ impl Adapter { fn subtree_is_registered(&self, subtree_id: SubtreeId) -> bool { subtree_id == self.root_subtree_id() || self.child_subtrees.contains_key(&subtree_id) } - - // #[cfg(test)] - // fn take_tree_updates(&mut self) -> Vec { - // self.inner.take_tree_updates() - // } } -// -// #[cfg(test)] -// mod test { -// use accesskit::{Node, NodeId, Role, Tree, TreeUpdate}; -// -// use crate::Adapter; -// -// #[derive(Default)] -// pub struct InnerAdapter { -// tree_updates: Vec, -// } -// -// impl InnerAdapter { -// pub fn update_if_active(&mut self, updater: impl FnOnce() -> TreeUpdate) { -// self.tree_updates.push(updater()); -// } -// pub fn take_tree_updates(&mut self) -> Vec { -// std::mem::take(&mut self.tree_updates) -// } -// } -// -// fn node(children: impl Into>) -> Node { -// let mut result = Node::new(Role::Unknown); -// result.set_children(children.into()); -// result -// } -// -// #[test] -// fn test_update() { -// let mut adapter = Adapter::new(InnerAdapter::default()); -// adapter.update_if_active(adapter.root_subtree_id(), || TreeUpdate { -// nodes: vec![ -// (NodeId(13), node([NodeId(15), NodeId(14)])), -// (NodeId(15), node([])), -// (NodeId(14), node([])), -// ], -// tree: Some(Tree { -// root: NodeId(13), -// toolkit_name: None, -// toolkit_version: None, -// }), -// focus: NodeId(13), -// }); -// let child_subtree_id = adapter.register_child_subtree(adapter.root_subtree_id(), NodeId(15)); -// adapter.update_if_active(child_subtree_id, || TreeUpdate { -// nodes: vec![ -// (NodeId(25), node([NodeId(27), NodeId(26)])), -// (NodeId(27), node([])), -// (NodeId(26), node([])), -// ], -// tree: Some(Tree { -// root: NodeId(25), -// toolkit_name: None, -// toolkit_version: None, -// }), -// focus: NodeId(25), -// }); -// let actual_updates = adapter.take_tree_updates(); -// assert_eq!(actual_updates, vec![ -// TreeUpdate { -// nodes: vec![ -// (NodeId(0), node([NodeId(1), NodeId(2)])), -// (NodeId(1), node([])), -// (NodeId(2), node([])), -// ], -// tree: Some(Tree { -// root: NodeId(0), -// toolkit_name: None, -// toolkit_version: None, -// }), -// focus: NodeId(0), -// }, -// TreeUpdate { -// nodes: vec![ -// (NodeId(3), node([NodeId(4), NodeId(5)])), -// (NodeId(4), node([])), -// (NodeId(5), node([])), -// ], -// tree: Some(Tree { -// // FIXME: this is incorrect -// root: NodeId(3), -// toolkit_name: None, -// toolkit_version: None, -// }), -// // FIXME: this is incorrect -// focus: NodeId(3), -// }, -// ]); -// } -// } diff --git a/platforms/winit/Cargo.toml b/platforms/winit/Cargo.toml index 61680b88..22efae4f 100644 --- a/platforms/winit/Cargo.toml +++ b/platforms/winit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "accesskit_winit" -version = "0.29.1" +version = "0.29.2" authors.workspace = true license = "Apache-2.0" description = "AccessKit UI accessibility infrastructure: winit adapter" @@ -20,6 +20,7 @@ tokio = ["accesskit_unix/tokio"] [dependencies] accesskit = { version = "0.21.1", path = "../../common" } +accesskit_multitree = { version = "0.1.0", path = "../multitree" } winit = { version = "0.30.5", default-features = false } rwh_05 = { package = "raw-window-handle", version = "0.5", features = ["std"], optional = true } rwh_06 = { package = "raw-window-handle", version = "0.6.2", features = ["std"], optional = true } diff --git a/platforms/winit/src/lib.rs b/platforms/winit/src/lib.rs index 976fa54b..a4ce6300 100644 --- a/platforms/winit/src/lib.rs +++ b/platforms/winit/src/lib.rs @@ -62,6 +62,7 @@ use rwh_05 as raw_window_handle; #[cfg(feature = "rwh_06")] #[allow(unused)] use rwh_06 as raw_window_handle; +use accesskit_multitree::{MultiTreeAdapterState, SubtreeId}; mod platform_impl; @@ -109,9 +110,9 @@ impl + Send + 'static> ActionHandler for WinitActionHandler { } } -struct WinitDeactivationHandler + Send + 'static> { - window_id: WindowId, - proxy: EventLoopProxy, +pub struct WinitDeactivationHandler + Send + 'static> { + pub window_id: WindowId, + pub proxy: EventLoopProxy, } impl + Send + 'static> DeactivationHandler for WinitDeactivationHandler { @@ -126,6 +127,7 @@ impl + Send + 'static> DeactivationHandler for WinitDeactivationH pub struct Adapter { inner: platform_impl::Adapter, + pub multi_tree_state: MultiTreeAdapterState } impl Adapter { @@ -198,14 +200,16 @@ impl Adapter { panic!("The AccessKit winit adapter must be created before the window is shown (made visible) for the first time."); } + let mut multi_tree_adapter_state = MultiTreeAdapterState::new(); + let inner = platform_impl::Adapter::new( event_loop, window, - activation_handler, - action_handler, + multi_tree_adapter_state.wrap_activation_handler(activation_handler), + multi_tree_adapter_state.wrap_action_handler(action_handler), deactivation_handler, ); - Self { inner } + Self { inner, multi_tree_state: multi_tree_adapter_state } } /// Creates a new AccessKit adapter for a winit window. This must be done @@ -259,7 +263,12 @@ impl Adapter { /// or if the caller created the adapter using [`EventLoopProxy`], then /// the [`TreeUpdate`] returned by the provided function must contain /// a full tree. + // TODO choose between the following two implementations based on whether the multitree feature is enabled pub fn update_if_active(&mut self, updater: impl FnOnce() -> TreeUpdate) { self.inner.update_if_active(updater); } + + pub fn update_subtree_if_active(&mut self, subtree_id: SubtreeId, updater: impl FnOnce() -> TreeUpdate) { + self.inner.update_if_active(self.multi_tree_state.rewrite_tree_update(subtree_id, updater)); + } } From 15499265a892d4e8c318b9783762000a38c6cadc Mon Sep 17 00:00:00 2001 From: Luke Warlow Date: Wed, 26 Nov 2025 15:32:20 +0000 Subject: [PATCH 13/26] Add new multitree feature to accesskit_winit This way we only run the extra code when its been opted into. --- platforms/winit/Cargo.toml | 1 + platforms/winit/src/lib.rs | 29 +++++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/platforms/winit/Cargo.toml b/platforms/winit/Cargo.toml index 22efae4f..675f23e0 100644 --- a/platforms/winit/Cargo.toml +++ b/platforms/winit/Cargo.toml @@ -17,6 +17,7 @@ rwh_05 = ["winit/rwh_05", "dep:rwh_05"] rwh_06 = ["winit/rwh_06", "dep:rwh_06"] async-io = ["accesskit_unix/async-io"] tokio = ["accesskit_unix/tokio"] +multitree = [] [dependencies] accesskit = { version = "0.21.1", path = "../../common" } diff --git a/platforms/winit/src/lib.rs b/platforms/winit/src/lib.rs index a4ce6300..01664abe 100644 --- a/platforms/winit/src/lib.rs +++ b/platforms/winit/src/lib.rs @@ -127,6 +127,7 @@ impl + Send + 'static> DeactivationHandler for WinitDeactivationH pub struct Adapter { inner: platform_impl::Adapter, + #[cfg(feature = "multitree")] pub multi_tree_state: MultiTreeAdapterState } @@ -200,16 +201,35 @@ impl Adapter { panic!("The AccessKit winit adapter must be created before the window is shown (made visible) for the first time."); } + #[cfg(feature = "multitree")] let mut multi_tree_adapter_state = MultiTreeAdapterState::new(); + let activation = { + #[cfg(feature = "multitree")] + { multi_tree_adapter_state.wrap_activation_handler(activation_handler) } + #[cfg(not(feature = "multitree"))] + { activation_handler } + }; + + let action = { + #[cfg(feature = "multitree")] + { multi_tree_adapter_state.wrap_action_handler(action_handler) } + #[cfg(not(feature = "multitree"))] + { action_handler } + }; + let inner = platform_impl::Adapter::new( event_loop, window, - multi_tree_adapter_state.wrap_activation_handler(activation_handler), - multi_tree_adapter_state.wrap_action_handler(action_handler), + activation, + action, deactivation_handler, ); - Self { inner, multi_tree_state: multi_tree_adapter_state } + Self { + inner, + #[cfg(feature = "multitree")] + multi_tree_state: multi_tree_adapter_state + } } /// Creates a new AccessKit adapter for a winit window. This must be done @@ -263,11 +283,12 @@ impl Adapter { /// or if the caller created the adapter using [`EventLoopProxy`], then /// the [`TreeUpdate`] returned by the provided function must contain /// a full tree. - // TODO choose between the following two implementations based on whether the multitree feature is enabled + // TODO #[cfg(not(feature = "multitree"))] pub fn update_if_active(&mut self, updater: impl FnOnce() -> TreeUpdate) { self.inner.update_if_active(updater); } + #[cfg(feature = "multitree")] pub fn update_subtree_if_active(&mut self, subtree_id: SubtreeId, updater: impl FnOnce() -> TreeUpdate) { self.inner.update_if_active(self.multi_tree_state.rewrite_tree_update(subtree_id, updater)); } From 3cd48b5cbb0c33ac76087cadf7be99afc3a00f3e Mon Sep 17 00:00:00 2001 From: Luke Warlow Date: Thu, 27 Nov 2025 12:08:51 +0000 Subject: [PATCH 14/26] Implement activation_handler wrapper to fix the example application --- platforms/multitree/src/lib.rs | 18 ++++++++++++------ platforms/winit/src/lib.rs | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/platforms/multitree/src/lib.rs b/platforms/multitree/src/lib.rs index 1ad8077e..db5eb92c 100644 --- a/platforms/multitree/src/lib.rs +++ b/platforms/multitree/src/lib.rs @@ -41,16 +41,24 @@ impl MultiTreeAdapterState { ) -> impl 'static + ActivationHandler + Send { struct ActivationHandlerWrapper { inner: H, + // is this actually safe? + adapter_state: NonNull } unsafe impl Send for ActivationHandlerWrapper {} impl ActivationHandler for ActivationHandlerWrapper { fn request_initial_tree(&mut self) -> Option { - // TODO for now we just require users of this adapter to send updates via update_if_active. + let tree_update = self.inner.request_initial_tree(); + if let Some(tree_update) = tree_update { + let adapter_state = unsafe { self.adapter_state.as_mut() }; + // TODO for now only the root subtree is allowed to use request_initial_tree + return Some(adapter_state.rewrite_tree_update(adapter_state.root_subtree_id(), tree_update)) + } None } } + let adapter_state_ptr = NonNull::from(self); - ActivationHandlerWrapper { inner: activation_handler } + ActivationHandlerWrapper { inner: activation_handler, adapter_state: adapter_state_ptr } } pub fn wrap_action_handler( @@ -107,7 +115,7 @@ impl MultiTreeAdapterState { // No need to send a TreeUpdate, the provider of the parent subtree will do it } - pub fn rewrite_tree_update(&mut self, subtree_id: SubtreeId, updater: impl FnOnce() -> TreeUpdate) -> impl FnOnce() -> TreeUpdate { + pub fn rewrite_tree_update(&mut self, subtree_id: SubtreeId, mut subtree_update: TreeUpdate) -> TreeUpdate { // Q: what happens if the graft node (`parent_node_id`) gets removed by the parent subtree? // If we keep the registration, then we need to detect when the graft node gets readded // (if ever), and resend the subtree in full, which requires caching the subtree or telling @@ -119,8 +127,6 @@ impl MultiTreeAdapterState { // - Easy way: we set up our own accesskit_consumer::Tree and ChangeHandler // - Hard way: we plumb through the lower-level Adapters and expose the one in // atspi common / android / windows? macOS has one too, but maybe used differently? - // TODO possibly we don't want to call updater until our return FnOnce is invoked? - let mut subtree_update = updater(); // TODO: rewrite the focus correctly. // We think the model is something like: every subtree has its local idea of the focused // node, but that node may not end up being the focused node globally. The globally focused @@ -154,7 +160,7 @@ impl MultiTreeAdapterState { node.popup_for().map(|node_id| node.set_popup_for(self.map_id(subtree_id, node_id))); // TODO: what do we do about .level()? } - || subtree_update + subtree_update } fn map_id(&mut self, subtree_id: SubtreeId, node_id: NodeId) -> NodeId { diff --git a/platforms/winit/src/lib.rs b/platforms/winit/src/lib.rs index 01664abe..7e8c4bf7 100644 --- a/platforms/winit/src/lib.rs +++ b/platforms/winit/src/lib.rs @@ -290,6 +290,6 @@ impl Adapter { #[cfg(feature = "multitree")] pub fn update_subtree_if_active(&mut self, subtree_id: SubtreeId, updater: impl FnOnce() -> TreeUpdate) { - self.inner.update_if_active(self.multi_tree_state.rewrite_tree_update(subtree_id, updater)); + self.inner.update_if_active(|| self.multi_tree_state.rewrite_tree_update(subtree_id, updater())); } } From ad540e45ab3966a585e9e9e02975792d80010b0f Mon Sep 17 00:00:00 2001 From: Luke Warlow Date: Fri, 28 Nov 2025 14:25:03 +0000 Subject: [PATCH 15/26] Worked on registration of subtrees Co-authored-by: Alice Boxhall --- platforms/multitree/src/lib.rs | 116 +++++++++++++++++++++++++-------- platforms/winit/src/lib.rs | 10 ++- 2 files changed, 97 insertions(+), 29 deletions(-) diff --git a/platforms/multitree/src/lib.rs b/platforms/multitree/src/lib.rs index db5eb92c..4d4274d0 100644 --- a/platforms/multitree/src/lib.rs +++ b/platforms/multitree/src/lib.rs @@ -1,4 +1,4 @@ -use accesskit::{ActionData, ActionHandler, ActionRequest, ActivationHandler, NodeId, TreeUpdate}; +use accesskit::{ActionData, ActionHandler, ActionRequest, ActivationHandler, Node, NodeId, TreeUpdate}; use std::collections::HashMap; use std::ptr::NonNull; @@ -7,6 +7,7 @@ const ROOT_SUBTREE_ID: SubtreeId = SubtreeId(0); pub struct MultiTreeAdapterState { next_subtree_id: SubtreeId, child_subtrees: HashMap, + grafts: HashMap>, id_map: HashMap>, reverse_id_map: HashMap, next_node_id: NodeId, @@ -14,7 +15,10 @@ pub struct MultiTreeAdapterState { pub struct SubtreeInfo { parent_subtree_id: SubtreeId, + // Local Id to the parent tree parent_node_id: NodeId, + // Global id of the root of the child subtree + root_node_id: Option } #[repr(transparent)] @@ -26,6 +30,7 @@ impl MultiTreeAdapterState { let mut result = MultiTreeAdapterState { next_subtree_id: SubtreeId(1), child_subtrees: HashMap::new(), + grafts: HashMap::new(), id_map: HashMap::new(), reverse_id_map: HashMap::new(), next_node_id: NodeId(0), @@ -99,12 +104,35 @@ impl MultiTreeAdapterState { ROOT_SUBTREE_ID } - pub fn register_child_subtree(&mut self, parent_subtree_id: SubtreeId, parent_node_id: NodeId) -> SubtreeId { + pub fn register_child_subtree(&mut self, parent_subtree_id: SubtreeId, parent_node_id: NodeId, child_id: NodeId, parent_node: &mut Node) -> (SubtreeId, TreeUpdate) { let subtree_id = self.next_subtree_id(); assert!(self.subtree_is_registered(parent_subtree_id)); - assert!(self.child_subtrees.insert(subtree_id, SubtreeInfo { parent_subtree_id, parent_node_id }).is_none()); + let grafts_map = self.grafts.get_mut(&parent_subtree_id); + if grafts_map.is_none() { + let map = HashMap::new(); + self.grafts.insert(parent_subtree_id, map); + } + let grafts_map = self.grafts.get_mut(&parent_subtree_id).expect("Must be registered"); + // Maybe store the global id for parent_node? + grafts_map.insert(parent_node_id, subtree_id); + + assert!(self.child_subtrees.insert(subtree_id, SubtreeInfo { parent_subtree_id, parent_node_id, root_node_id: None }).is_none()); assert!(self.id_map.insert(subtree_id, HashMap::default()).is_none()); - subtree_id + let global_id_for_child = self.map_id(subtree_id, child_id); + + let parent_node_global_id = self.rewrite_node(parent_subtree_id, parent_node_id, parent_node); + + let mut nodes: Vec<(NodeId, Node)> = Vec::new(); + parent_node.push_child(global_id_for_child); + nodes.insert(0, (parent_node_global_id, parent_node.clone())); + nodes.insert(1, (global_id_for_child, Node::default())); + let tree_update = TreeUpdate { + nodes, + tree: None, + // Absolutely not correct whatsoever + focus: global_id_for_child + }; + (subtree_id, tree_update) } #[expect(unused)] @@ -116,6 +144,16 @@ impl MultiTreeAdapterState { } pub fn rewrite_tree_update(&mut self, subtree_id: SubtreeId, mut subtree_update: TreeUpdate) -> TreeUpdate { + if let Some(tree) = subtree_update.tree.as_mut() { + let global_id_of_root_of_subtree = self.map_id(subtree_id, tree.root); + tree.root = global_id_of_root_of_subtree; + if subtree_id != self.root_subtree_id() { + let child_subtree_info = self.child_subtrees.get_mut(&subtree_id).expect("Must be registered"); + child_subtree_info.root_node_id = Some(global_id_of_root_of_subtree); + subtree_update.tree = None; + } + } + // Q: what happens if the graft node (`parent_node_id`) gets removed by the parent subtree? // If we keep the registration, then we need to detect when the graft node gets readded // (if ever), and resend the subtree in full, which requires caching the subtree or telling @@ -134,35 +172,45 @@ impl MultiTreeAdapterState { // case it’s the focused node of the child subtree being grafted there (recursively). subtree_update.focus = self.map_id(subtree_id, subtree_update.focus); // TODO: rewrite the root correctly - if let Some(tree) = subtree_update.tree.as_mut() { - tree.root = self.map_id(subtree_id, tree.root); - } for (node_id, node) in subtree_update.nodes.iter_mut() { - *node_id = self.map_id(subtree_id, *node_id); - // Map ids of all node references. - // These correspond to the `node_id_vec_property_methods` and `node_id_property_methods` - // lists in `accesskit/src/lib.rs`. - // TODO: could we make the vec rewrites avoid allocation? - self.map_node_id_vec_property(subtree_id, node.children().to_owned(), |new| node.set_children(new)); - self.map_node_id_vec_property(subtree_id, node.controls().to_owned(), |new| node.set_controls(new)); - self.map_node_id_vec_property(subtree_id, node.details().to_owned(), |new| node.set_details(new)); - self.map_node_id_vec_property(subtree_id, node.described_by().to_owned(), |new| node.set_described_by(new)); - self.map_node_id_vec_property(subtree_id, node.flow_to().to_owned(), |new| node.set_flow_to(new)); - self.map_node_id_vec_property(subtree_id, node.labelled_by().to_owned(), |new| node.set_labelled_by(new)); - self.map_node_id_vec_property(subtree_id, node.owns().to_owned(), |new| node.set_owns(new)); - self.map_node_id_vec_property(subtree_id, node.radio_group().to_owned(), |new| node.set_radio_group(new)); - node.active_descendant().map(|node_id| node.set_active_descendant(self.map_id(subtree_id, node_id))); - node.error_message().map(|node_id| node.set_error_message(self.map_id(subtree_id, node_id))); - node.in_page_link_target().map(|node_id| node.set_in_page_link_target(self.map_id(subtree_id, node_id))); - node.member_of().map(|node_id| node.set_member_of(self.map_id(subtree_id, node_id))); - node.next_on_line().map(|node_id| node.set_next_on_line(self.map_id(subtree_id, node_id))); - node.previous_on_line().map(|node_id| node.set_previous_on_line(self.map_id(subtree_id, node_id))); - node.popup_for().map(|node_id| node.set_popup_for(self.map_id(subtree_id, node_id))); - // TODO: what do we do about .level()? + *node_id = self.rewrite_node(subtree_id, *node_id, node); } + + // TODO We need to ensure that we put the subtree root node id as a child of the parent node id. + subtree_update } + pub fn rewrite_node(&mut self, subtree_id: SubtreeId, node_id: NodeId, node: &mut Node) -> NodeId { + let grafted_node_id: Option = self.get_root_id_for_grafted_subtree(subtree_id, node_id); + let global_node_id = self.map_id(subtree_id, node_id); + // Map ids of all node references. + // These correspond to the `node_id_vec_property_methods` and `node_id_property_methods` + // lists in `accesskit/src/lib.rs`. + // TODO: could we make the vec rewrites avoid allocation? + self.map_node_id_vec_property(subtree_id, node.children().to_owned(), |new| node.set_children(new)); + self.map_node_id_vec_property(subtree_id, node.controls().to_owned(), |new| node.set_controls(new)); + self.map_node_id_vec_property(subtree_id, node.details().to_owned(), |new| node.set_details(new)); + self.map_node_id_vec_property(subtree_id, node.described_by().to_owned(), |new| node.set_described_by(new)); + self.map_node_id_vec_property(subtree_id, node.flow_to().to_owned(), |new| node.set_flow_to(new)); + self.map_node_id_vec_property(subtree_id, node.labelled_by().to_owned(), |new| node.set_labelled_by(new)); + self.map_node_id_vec_property(subtree_id, node.owns().to_owned(), |new| node.set_owns(new)); + self.map_node_id_vec_property(subtree_id, node.radio_group().to_owned(), |new| node.set_radio_group(new)); + node.active_descendant().map(|node_id| node.set_active_descendant(self.map_id(subtree_id, node_id))); + node.error_message().map(|node_id| node.set_error_message(self.map_id(subtree_id, node_id))); + node.in_page_link_target().map(|node_id| node.set_in_page_link_target(self.map_id(subtree_id, node_id))); + node.member_of().map(|node_id| node.set_member_of(self.map_id(subtree_id, node_id))); + node.next_on_line().map(|node_id| node.set_next_on_line(self.map_id(subtree_id, node_id))); + node.previous_on_line().map(|node_id| node.set_previous_on_line(self.map_id(subtree_id, node_id))); + node.popup_for().map(|node_id| node.set_popup_for(self.map_id(subtree_id, node_id))); + // TODO: what do we do about .level()? + + if let Some(grafted_node_id) = grafted_node_id { + node.push_child(grafted_node_id); + } + global_node_id + } + fn map_id(&mut self, subtree_id: SubtreeId, node_id: NodeId) -> NodeId { let map = self.id_map.get_mut(&subtree_id).expect("Subtree not registered"); if let Some(result) = map.get(&node_id) { @@ -213,4 +261,16 @@ impl MultiTreeAdapterState { fn subtree_is_registered(&self, subtree_id: SubtreeId) -> bool { subtree_id == self.root_subtree_id() || self.child_subtrees.contains_key(&subtree_id) } + + fn get_root_id_for_grafted_subtree(&mut self, subtree_id: SubtreeId, local_node_id: NodeId) -> Option { + if let Some(graft_map) = self.grafts.get_mut(&subtree_id) { + if let Some(local_nodes_subtree_id) = graft_map.get_mut(&local_node_id) { + let child_subtree_info = self.child_subtrees.get(&local_nodes_subtree_id).expect("must be registered"); + if let Some(root_node_id) = child_subtree_info.root_node_id { + Some(root_node_id); + } + } + } + None + } } diff --git a/platforms/winit/src/lib.rs b/platforms/winit/src/lib.rs index 7e8c4bf7..75116f47 100644 --- a/platforms/winit/src/lib.rs +++ b/platforms/winit/src/lib.rs @@ -49,7 +49,7 @@ compile_error!( "Both \"rwh_06\" (default) and \"rwh_05\" features cannot be enabled at the same time." ); -use accesskit::{ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler, TreeUpdate}; +use accesskit::{ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler, Node, NodeId, TreeUpdate}; use winit::{ event::WindowEvent as WinitWindowEvent, event_loop::{ActiveEventLoop, EventLoopProxy}, @@ -292,4 +292,12 @@ impl Adapter { pub fn update_subtree_if_active(&mut self, subtree_id: SubtreeId, updater: impl FnOnce() -> TreeUpdate) { self.inner.update_if_active(|| self.multi_tree_state.rewrite_tree_update(subtree_id, updater())); } + + #[cfg(feature = "multitree")] + pub fn register_child_subtree(&mut self, parent_subtree_id: SubtreeId, parent_node_id: NodeId, child_id: NodeId, parent_node: &mut Node) -> SubtreeId { + let (subtree_id, tree_update) = self.multi_tree_state.register_child_subtree(parent_subtree_id, parent_node_id, child_id, parent_node); + self.inner.update_if_active(|| tree_update); + + subtree_id + } } From c336e2ae9e17be8361fd84ca9038f79951361df6 Mon Sep 17 00:00:00 2001 From: Luke Warlow Date: Tue, 2 Dec 2025 11:15:26 +0000 Subject: [PATCH 16/26] Change to using egui internal accesskit adapter - Removes plugin - Also fix subtree registration Co-authored-by: delan azabani --- platforms/multitree/src/lib.rs | 12 +++++------- platforms/winit/src/lib.rs | 5 ++++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/platforms/multitree/src/lib.rs b/platforms/multitree/src/lib.rs index 4d4274d0..0c774098 100644 --- a/platforms/multitree/src/lib.rs +++ b/platforms/multitree/src/lib.rs @@ -104,7 +104,7 @@ impl MultiTreeAdapterState { ROOT_SUBTREE_ID } - pub fn register_child_subtree(&mut self, parent_subtree_id: SubtreeId, parent_node_id: NodeId, child_id: NodeId, parent_node: &mut Node) -> (SubtreeId, TreeUpdate) { + pub fn register_child_subtree(&mut self, parent_subtree_id: SubtreeId, parent_node_id: NodeId, child_id: NodeId, mut parent_node: Node) -> (SubtreeId, TreeUpdate) { let subtree_id = self.next_subtree_id(); assert!(self.subtree_is_registered(parent_subtree_id)); let grafts_map = self.grafts.get_mut(&parent_subtree_id); @@ -115,15 +115,13 @@ impl MultiTreeAdapterState { let grafts_map = self.grafts.get_mut(&parent_subtree_id).expect("Must be registered"); // Maybe store the global id for parent_node? grafts_map.insert(parent_node_id, subtree_id); - - assert!(self.child_subtrees.insert(subtree_id, SubtreeInfo { parent_subtree_id, parent_node_id, root_node_id: None }).is_none()); assert!(self.id_map.insert(subtree_id, HashMap::default()).is_none()); let global_id_for_child = self.map_id(subtree_id, child_id); - - let parent_node_global_id = self.rewrite_node(parent_subtree_id, parent_node_id, parent_node); + assert!(self.child_subtrees.insert(subtree_id, SubtreeInfo { parent_subtree_id, parent_node_id, root_node_id: Some(global_id_for_child) }).is_none()); + let parent_node_global_id = self.rewrite_node(parent_subtree_id, parent_node_id, &mut parent_node); let mut nodes: Vec<(NodeId, Node)> = Vec::new(); - parent_node.push_child(global_id_for_child); + // parent_node.push_child(global_id_for_child); nodes.insert(0, (parent_node_global_id, parent_node.clone())); nodes.insert(1, (global_id_for_child, Node::default())); let tree_update = TreeUpdate { @@ -267,7 +265,7 @@ impl MultiTreeAdapterState { if let Some(local_nodes_subtree_id) = graft_map.get_mut(&local_node_id) { let child_subtree_info = self.child_subtrees.get(&local_nodes_subtree_id).expect("must be registered"); if let Some(root_node_id) = child_subtree_info.root_node_id { - Some(root_node_id); + return Some(root_node_id); } } } diff --git a/platforms/winit/src/lib.rs b/platforms/winit/src/lib.rs index 75116f47..be9354d9 100644 --- a/platforms/winit/src/lib.rs +++ b/platforms/winit/src/lib.rs @@ -285,6 +285,9 @@ impl Adapter { /// a full tree. // TODO #[cfg(not(feature = "multitree"))] pub fn update_if_active(&mut self, updater: impl FnOnce() -> TreeUpdate) { + #[cfg(feature = "multitree")] + self.update_subtree_if_active(self.multi_tree_state.root_subtree_id(), updater); + #[cfg(not(feature = "multitree"))] self.inner.update_if_active(updater); } @@ -294,7 +297,7 @@ impl Adapter { } #[cfg(feature = "multitree")] - pub fn register_child_subtree(&mut self, parent_subtree_id: SubtreeId, parent_node_id: NodeId, child_id: NodeId, parent_node: &mut Node) -> SubtreeId { + pub fn register_child_subtree(&mut self, parent_subtree_id: SubtreeId, parent_node_id: NodeId, child_id: NodeId, parent_node: Node) -> SubtreeId { let (subtree_id, tree_update) = self.multi_tree_state.register_child_subtree(parent_subtree_id, parent_node_id, child_id, parent_node); self.inner.update_if_active(|| tree_update); From 931ed3416dfae17f406a327b63d8617f59fec64e Mon Sep 17 00:00:00 2001 From: Luke Warlow Date: Tue, 2 Dec 2025 12:30:13 +0000 Subject: [PATCH 17/26] Make root_node_id no longer an Option Co-authored-by: delan azabani --- platforms/multitree/src/lib.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/platforms/multitree/src/lib.rs b/platforms/multitree/src/lib.rs index 0c774098..f07ed8a6 100644 --- a/platforms/multitree/src/lib.rs +++ b/platforms/multitree/src/lib.rs @@ -18,7 +18,7 @@ pub struct SubtreeInfo { // Local Id to the parent tree parent_node_id: NodeId, // Global id of the root of the child subtree - root_node_id: Option + root_node_id: NodeId } #[repr(transparent)] @@ -117,7 +117,7 @@ impl MultiTreeAdapterState { grafts_map.insert(parent_node_id, subtree_id); assert!(self.id_map.insert(subtree_id, HashMap::default()).is_none()); let global_id_for_child = self.map_id(subtree_id, child_id); - assert!(self.child_subtrees.insert(subtree_id, SubtreeInfo { parent_subtree_id, parent_node_id, root_node_id: Some(global_id_for_child) }).is_none()); + assert!(self.child_subtrees.insert(subtree_id, SubtreeInfo { parent_subtree_id, parent_node_id, root_node_id: global_id_for_child }).is_none()); let parent_node_global_id = self.rewrite_node(parent_subtree_id, parent_node_id, &mut parent_node); let mut nodes: Vec<(NodeId, Node)> = Vec::new(); @@ -147,7 +147,7 @@ impl MultiTreeAdapterState { tree.root = global_id_of_root_of_subtree; if subtree_id != self.root_subtree_id() { let child_subtree_info = self.child_subtrees.get_mut(&subtree_id).expect("Must be registered"); - child_subtree_info.root_node_id = Some(global_id_of_root_of_subtree); + child_subtree_info.root_node_id = global_id_of_root_of_subtree; subtree_update.tree = None; } } @@ -264,9 +264,7 @@ impl MultiTreeAdapterState { if let Some(graft_map) = self.grafts.get_mut(&subtree_id) { if let Some(local_nodes_subtree_id) = graft_map.get_mut(&local_node_id) { let child_subtree_info = self.child_subtrees.get(&local_nodes_subtree_id).expect("must be registered"); - if let Some(root_node_id) = child_subtree_info.root_node_id { - return Some(root_node_id); - } + return Some(child_subtree_info.root_node_id); } } None From b76325e3c09d71e135486a50492da6f546e1a7b9 Mon Sep 17 00:00:00 2001 From: delan azabani Date: Wed, 3 Dec 2025 16:48:24 +0800 Subject: [PATCH 18/26] Add unit test and remove commented code --- platforms/multitree/src/lib.rs | 89 +++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/platforms/multitree/src/lib.rs b/platforms/multitree/src/lib.rs index f07ed8a6..7f91f83e 100644 --- a/platforms/multitree/src/lib.rs +++ b/platforms/multitree/src/lib.rs @@ -121,7 +121,6 @@ impl MultiTreeAdapterState { let parent_node_global_id = self.rewrite_node(parent_subtree_id, parent_node_id, &mut parent_node); let mut nodes: Vec<(NodeId, Node)> = Vec::new(); - // parent_node.push_child(global_id_for_child); nodes.insert(0, (parent_node_global_id, parent_node.clone())); nodes.insert(1, (global_id_for_child, Node::default())); let tree_update = TreeUpdate { @@ -270,3 +269,91 @@ impl MultiTreeAdapterState { None } } + +#[cfg(test)] +mod test { + use accesskit::{Node, NodeId, Role, Tree, TreeUpdate}; + + use crate::{MultiTreeAdapterState, ROOT_SUBTREE_ID, SubtreeId}; + + fn node(children: impl Into>) -> Node { + let children = children.into(); + let mut result = Node::new(Role::Unknown); + if !children.is_empty() { + result.set_children(children); + } + result + } + + #[test] + fn test_update() { + let mut multitree = MultiTreeAdapterState::new(); + let graft_node = node([]); + assert_eq!( + multitree.rewrite_tree_update(ROOT_SUBTREE_ID, TreeUpdate { + nodes: vec![ + (NodeId(13), node([NodeId(15), NodeId(14)])), + (NodeId(15), graft_node.clone()), + (NodeId(14), node([])), + ], + tree: Some(Tree { + root: NodeId(13), + toolkit_name: None, + toolkit_version: None, + }), + focus: NodeId(13), + }), + TreeUpdate { + nodes: vec![ + (NodeId(0), node([NodeId(1), NodeId(2)])), + (NodeId(1), node([])), + (NodeId(2), node([])), + ], + tree: Some(Tree { + root: NodeId(0), + toolkit_name: None, + toolkit_version: None, + }), + focus: NodeId(0), + }, + ); + let (subtree_id, tree_update) = multitree.register_child_subtree(ROOT_SUBTREE_ID, NodeId(15), NodeId(25), graft_node); + assert_eq!(subtree_id, SubtreeId(1)); + assert_eq!( + tree_update, + TreeUpdate { + nodes: vec![ + (NodeId(1), node([NodeId(3)])), + (NodeId(3), node([])), + ], + tree: None, + // FIXME: assertion failed: actual #3, expected #0 + focus: NodeId(3), + }, + ); + assert_eq!( + multitree.rewrite_tree_update(subtree_id, TreeUpdate { + nodes: vec![ + (NodeId(25), node([NodeId(27), NodeId(26)])), + (NodeId(27), node([])), + (NodeId(26), node([])), + ], + tree: Some(Tree { + root: NodeId(25), + toolkit_name: None, + toolkit_version: None, + }), + focus: NodeId(25), + }), + TreeUpdate { + nodes: vec![ + (NodeId(3), node([NodeId(4), NodeId(5)])), + (NodeId(4), node([])), + (NodeId(5), node([])), + ], + tree: None, + focus: NodeId(3), + }, + ); + } +} From 91f7d1b2c7e1bfc43989fb6ecddb852c37f44cb9 Mon Sep 17 00:00:00 2001 From: delan azabani Date: Wed, 3 Dec 2025 17:04:10 +0800 Subject: [PATCH 19/26] Use entry API for grafts insert --- platforms/multitree/src/lib.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/platforms/multitree/src/lib.rs b/platforms/multitree/src/lib.rs index 7f91f83e..10c496e8 100644 --- a/platforms/multitree/src/lib.rs +++ b/platforms/multitree/src/lib.rs @@ -7,6 +7,7 @@ const ROOT_SUBTREE_ID: SubtreeId = SubtreeId(0); pub struct MultiTreeAdapterState { next_subtree_id: SubtreeId, child_subtrees: HashMap, + /// parent [`SubtreeId`] → parent local [`NodeId`] → child [`SubtreeId`] grafts: HashMap>, id_map: HashMap>, reverse_id_map: HashMap, @@ -107,14 +108,8 @@ impl MultiTreeAdapterState { pub fn register_child_subtree(&mut self, parent_subtree_id: SubtreeId, parent_node_id: NodeId, child_id: NodeId, mut parent_node: Node) -> (SubtreeId, TreeUpdate) { let subtree_id = self.next_subtree_id(); assert!(self.subtree_is_registered(parent_subtree_id)); - let grafts_map = self.grafts.get_mut(&parent_subtree_id); - if grafts_map.is_none() { - let map = HashMap::new(); - self.grafts.insert(parent_subtree_id, map); - } - let grafts_map = self.grafts.get_mut(&parent_subtree_id).expect("Must be registered"); // Maybe store the global id for parent_node? - grafts_map.insert(parent_node_id, subtree_id); + assert!(self.grafts.entry(parent_subtree_id).or_default().insert(parent_node_id, subtree_id).is_none()); assert!(self.id_map.insert(subtree_id, HashMap::default()).is_none()); let global_id_for_child = self.map_id(subtree_id, child_id); assert!(self.child_subtrees.insert(subtree_id, SubtreeInfo { parent_subtree_id, parent_node_id, root_node_id: global_id_for_child }).is_none()); From 239a0d8ec346460c561ecd5c3d3d157404e81cf2 Mon Sep 17 00:00:00 2001 From: delan azabani Date: Wed, 3 Dec 2025 17:10:14 +0800 Subject: [PATCH 20/26] Add doc comments for other map fields --- platforms/multitree/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/platforms/multitree/src/lib.rs b/platforms/multitree/src/lib.rs index 10c496e8..f0a89049 100644 --- a/platforms/multitree/src/lib.rs +++ b/platforms/multitree/src/lib.rs @@ -9,7 +9,9 @@ pub struct MultiTreeAdapterState { child_subtrees: HashMap, /// parent [`SubtreeId`] → parent local [`NodeId`] → child [`SubtreeId`] grafts: HashMap>, + /// [`SubtreeId`] → local [`NodeId`] → global [`NodeId`] id_map: HashMap>, + /// global [`NodeId`] → ([`SubtreeId`], local [`NodeId`]) reverse_id_map: HashMap, next_node_id: NodeId, } From 3f76698ca925f2401d434913adb97604e5a39adf Mon Sep 17 00:00:00 2001 From: delan azabani Date: Wed, 3 Dec 2025 17:23:12 +0800 Subject: [PATCH 21/26] Check final state of multitree instance in unit test --- platforms/multitree/src/lib.rs | 55 +++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/platforms/multitree/src/lib.rs b/platforms/multitree/src/lib.rs index f0a89049..bcfa7053 100644 --- a/platforms/multitree/src/lib.rs +++ b/platforms/multitree/src/lib.rs @@ -4,6 +4,7 @@ use std::ptr::NonNull; const ROOT_SUBTREE_ID: SubtreeId = SubtreeId(0); +#[derive(Debug, PartialEq)] pub struct MultiTreeAdapterState { next_subtree_id: SubtreeId, child_subtrees: HashMap, @@ -16,6 +17,7 @@ pub struct MultiTreeAdapterState { next_node_id: NodeId, } +#[derive(Debug, PartialEq)] pub struct SubtreeInfo { parent_subtree_id: SubtreeId, // Local Id to the parent tree @@ -269,9 +271,11 @@ impl MultiTreeAdapterState { #[cfg(test)] mod test { + use std::{collections::HashMap, hash::Hash}; + use accesskit::{Node, NodeId, Role, Tree, TreeUpdate}; - use crate::{MultiTreeAdapterState, ROOT_SUBTREE_ID, SubtreeId}; + use crate::{MultiTreeAdapterState, ROOT_SUBTREE_ID, SubtreeId, SubtreeInfo}; fn node(children: impl Into>) -> Node { let children = children.into(); @@ -282,6 +286,14 @@ mod test { result } + fn map(entries: impl Into>) -> HashMap { + entries.into() + } + + fn subtree_info(parent_subtree_id: SubtreeId, parent_node_id: NodeId, root_node_id: NodeId) -> SubtreeInfo { + SubtreeInfo { parent_subtree_id, parent_node_id, root_node_id } + } + #[test] fn test_update() { let mut multitree = MultiTreeAdapterState::new(); @@ -314,8 +326,8 @@ mod test { focus: NodeId(0), }, ); - let (subtree_id, tree_update) = multitree.register_child_subtree(ROOT_SUBTREE_ID, NodeId(15), NodeId(25), graft_node); - assert_eq!(subtree_id, SubtreeId(1)); + let (child_subtree_id, tree_update) = multitree.register_child_subtree(ROOT_SUBTREE_ID, NodeId(15), NodeId(25), graft_node); + assert_eq!(child_subtree_id, SubtreeId(1)); assert_eq!( tree_update, TreeUpdate { @@ -329,7 +341,7 @@ mod test { }, ); assert_eq!( - multitree.rewrite_tree_update(subtree_id, TreeUpdate { + multitree.rewrite_tree_update(child_subtree_id, TreeUpdate { nodes: vec![ (NodeId(25), node([NodeId(27), NodeId(26)])), (NodeId(27), node([])), @@ -352,5 +364,40 @@ mod test { focus: NodeId(3), }, ); + assert_eq!( + multitree, + MultiTreeAdapterState { + next_subtree_id: SubtreeId(2), + child_subtrees: map([ + (child_subtree_id, subtree_info(ROOT_SUBTREE_ID, NodeId(15), NodeId(3))), + ]), + grafts: map([ + (ROOT_SUBTREE_ID, map([ + (NodeId(15), child_subtree_id), + ])), + ]), + id_map: map([ + (ROOT_SUBTREE_ID, map([ + (NodeId(13), NodeId(0)), + (NodeId(15), NodeId(1)), + (NodeId(14), NodeId(2)), + ])), + (child_subtree_id, map([ + (NodeId(25), NodeId(3)), + (NodeId(27), NodeId(4)), + (NodeId(26), NodeId(5)), + ])), + ]), + reverse_id_map: map([ + (NodeId(0), (ROOT_SUBTREE_ID, NodeId(13))), + (NodeId(1), (ROOT_SUBTREE_ID, NodeId(15))), + (NodeId(2), (ROOT_SUBTREE_ID, NodeId(14))), + (NodeId(3), (child_subtree_id, NodeId(25))), + (NodeId(4), (child_subtree_id, NodeId(27))), + (NodeId(5), (child_subtree_id, NodeId(26))), + ]), + next_node_id: NodeId(6), + }, + ); } } From 23a3695d9425404b3cc1938bf61ab5a6fb1846c7 Mon Sep 17 00:00:00 2001 From: delan azabani Date: Wed, 3 Dec 2025 17:40:35 +0800 Subject: [PATCH 22/26] Clean up map fields and unused fields --- platforms/multitree/src/lib.rs | 68 +++++++++++++--------------------- 1 file changed, 26 insertions(+), 42 deletions(-) diff --git a/platforms/multitree/src/lib.rs b/platforms/multitree/src/lib.rs index bcfa7053..99b1dfaa 100644 --- a/platforms/multitree/src/lib.rs +++ b/platforms/multitree/src/lib.rs @@ -7,11 +7,12 @@ const ROOT_SUBTREE_ID: SubtreeId = SubtreeId(0); #[derive(Debug, PartialEq)] pub struct MultiTreeAdapterState { next_subtree_id: SubtreeId, + /// child subtree [`SubtreeId`] → child subtree [`SubtreeInfo`] child_subtrees: HashMap, - /// parent [`SubtreeId`] → parent local [`NodeId`] → child [`SubtreeId`] - grafts: HashMap>, - /// [`SubtreeId`] → local [`NodeId`] → global [`NodeId`] - id_map: HashMap>, + /// (parent subtree [`SubtreeId`], parent-subtree-local [`NodeId`]) → child subtree [`SubtreeId`] + grafts: HashMap<(SubtreeId, NodeId), SubtreeId>, + /// ([`SubtreeId`], local [`NodeId`]) → global [`NodeId`] + id_map: HashMap<(SubtreeId, NodeId), NodeId>, /// global [`NodeId`] → ([`SubtreeId`], local [`NodeId`]) reverse_id_map: HashMap, next_node_id: NodeId, @@ -19,11 +20,8 @@ pub struct MultiTreeAdapterState { #[derive(Debug, PartialEq)] pub struct SubtreeInfo { - parent_subtree_id: SubtreeId, - // Local Id to the parent tree - parent_node_id: NodeId, - // Global id of the root of the child subtree - root_node_id: NodeId + /// global [`NodeId`] of root node in child subtree + root_node_id: NodeId, } #[repr(transparent)] @@ -32,17 +30,14 @@ pub struct SubtreeId(u64); impl MultiTreeAdapterState { pub fn new() -> Self { - let mut result = MultiTreeAdapterState { + Self { next_subtree_id: SubtreeId(1), child_subtrees: HashMap::new(), grafts: HashMap::new(), id_map: HashMap::new(), reverse_id_map: HashMap::new(), next_node_id: NodeId(0), - }; - - assert!(result.id_map.insert(result.root_subtree_id(), HashMap::default()).is_none()); - result + } } pub fn wrap_activation_handler( @@ -113,10 +108,9 @@ impl MultiTreeAdapterState { let subtree_id = self.next_subtree_id(); assert!(self.subtree_is_registered(parent_subtree_id)); // Maybe store the global id for parent_node? - assert!(self.grafts.entry(parent_subtree_id).or_default().insert(parent_node_id, subtree_id).is_none()); - assert!(self.id_map.insert(subtree_id, HashMap::default()).is_none()); + assert!(self.grafts.insert((parent_subtree_id, parent_node_id), subtree_id).is_none()); let global_id_for_child = self.map_id(subtree_id, child_id); - assert!(self.child_subtrees.insert(subtree_id, SubtreeInfo { parent_subtree_id, parent_node_id, root_node_id: global_id_for_child }).is_none()); + assert!(self.child_subtrees.insert(subtree_id, SubtreeInfo { root_node_id: global_id_for_child }).is_none()); let parent_node_global_id = self.rewrite_node(parent_subtree_id, parent_node_id, &mut parent_node); let mut nodes: Vec<(NodeId, Node)> = Vec::new(); @@ -208,13 +202,11 @@ impl MultiTreeAdapterState { } fn map_id(&mut self, subtree_id: SubtreeId, node_id: NodeId) -> NodeId { - let map = self.id_map.get_mut(&subtree_id).expect("Subtree not registered"); - if let Some(result) = map.get(&node_id) { + if let Some(result) = self.id_map.get(&(subtree_id, node_id)) { return *result; } let result = self.next_node_id(); - let map = self.id_map.get_mut(&subtree_id).expect("Subtree not registered"); - assert!(map.insert(node_id, result).is_none()); + assert!(self.id_map.insert((subtree_id, node_id), result).is_none()); assert!(self.reverse_id_map.insert(result, (subtree_id, node_id)).is_none()); result } @@ -259,11 +251,9 @@ impl MultiTreeAdapterState { } fn get_root_id_for_grafted_subtree(&mut self, subtree_id: SubtreeId, local_node_id: NodeId) -> Option { - if let Some(graft_map) = self.grafts.get_mut(&subtree_id) { - if let Some(local_nodes_subtree_id) = graft_map.get_mut(&local_node_id) { - let child_subtree_info = self.child_subtrees.get(&local_nodes_subtree_id).expect("must be registered"); - return Some(child_subtree_info.root_node_id); - } + if let Some(local_nodes_subtree_id) = self.grafts.get(&(subtree_id, local_node_id)) { + let child_subtree_info = self.child_subtrees.get(&local_nodes_subtree_id).expect("must be registered"); + return Some(child_subtree_info.root_node_id); } None } @@ -290,8 +280,8 @@ mod test { entries.into() } - fn subtree_info(parent_subtree_id: SubtreeId, parent_node_id: NodeId, root_node_id: NodeId) -> SubtreeInfo { - SubtreeInfo { parent_subtree_id, parent_node_id, root_node_id } + fn subtree_info(root_node_id: NodeId) -> SubtreeInfo { + SubtreeInfo { root_node_id } } #[test] @@ -369,24 +359,18 @@ mod test { MultiTreeAdapterState { next_subtree_id: SubtreeId(2), child_subtrees: map([ - (child_subtree_id, subtree_info(ROOT_SUBTREE_ID, NodeId(15), NodeId(3))), + (child_subtree_id, subtree_info(NodeId(3))), ]), grafts: map([ - (ROOT_SUBTREE_ID, map([ - (NodeId(15), child_subtree_id), - ])), + ((ROOT_SUBTREE_ID, NodeId(15)), child_subtree_id), ]), id_map: map([ - (ROOT_SUBTREE_ID, map([ - (NodeId(13), NodeId(0)), - (NodeId(15), NodeId(1)), - (NodeId(14), NodeId(2)), - ])), - (child_subtree_id, map([ - (NodeId(25), NodeId(3)), - (NodeId(27), NodeId(4)), - (NodeId(26), NodeId(5)), - ])), + ((ROOT_SUBTREE_ID, NodeId(13)), NodeId(0)), + ((ROOT_SUBTREE_ID, NodeId(15)), NodeId(1)), + ((ROOT_SUBTREE_ID, NodeId(14)), NodeId(2)), + ((child_subtree_id, NodeId(25)), NodeId(3)), + ((child_subtree_id, NodeId(27)), NodeId(4)), + ((child_subtree_id, NodeId(26)), NodeId(5)), ]), reverse_id_map: map([ (NodeId(0), (ROOT_SUBTREE_ID, NodeId(13))), From 8db69061ba3997d722999b2c4b5b10292989a2fb Mon Sep 17 00:00:00 2001 From: delan azabani Date: Wed, 3 Dec 2025 18:03:18 +0800 Subject: [PATCH 23/26] Replace `reverse_id_map` with new more general NodeInfo type --- platforms/multitree/src/lib.rs | 57 ++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/platforms/multitree/src/lib.rs b/platforms/multitree/src/lib.rs index 99b1dfaa..7674bb04 100644 --- a/platforms/multitree/src/lib.rs +++ b/platforms/multitree/src/lib.rs @@ -13,8 +13,8 @@ pub struct MultiTreeAdapterState { grafts: HashMap<(SubtreeId, NodeId), SubtreeId>, /// ([`SubtreeId`], local [`NodeId`]) → global [`NodeId`] id_map: HashMap<(SubtreeId, NodeId), NodeId>, - /// global [`NodeId`] → ([`SubtreeId`], local [`NodeId`]) - reverse_id_map: HashMap, + /// global [`NodeId`] → [`NodeInfo`] + node_info: HashMap, next_node_id: NodeId, } @@ -24,6 +24,22 @@ pub struct SubtreeInfo { root_node_id: NodeId, } +#[derive(Debug, PartialEq)] +struct NodeInfo { + /// reverse mapping: [`SubtreeId`] + subtree_id: SubtreeId, + /// reverse mapping: local [`NodeId`] + local_node_id: NodeId, +} +impl NodeInfo { + fn new(subtree_id: SubtreeId, local_node_id: NodeId) -> Self { + Self { + subtree_id, + local_node_id, + } + } +} + #[repr(transparent)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct SubtreeId(u64); @@ -35,7 +51,7 @@ impl MultiTreeAdapterState { child_subtrees: HashMap::new(), grafts: HashMap::new(), id_map: HashMap::new(), - reverse_id_map: HashMap::new(), + node_info: HashMap::new(), next_node_id: NodeId(0), } } @@ -80,14 +96,12 @@ impl MultiTreeAdapterState { fn do_action(&mut self, mut request: ActionRequest) { let adapter_state = unsafe { self.adapter_state.as_ref() }; // Map from the global node id to the local node id and forward to the provided handlers - request.target = adapter_state.reverse_map_id(request.target).1; + request.target = adapter_state.node_info(request.target).local_node_id; if let Some(data) = request.data.as_mut() { match data { ActionData::SetTextSelection(selection) => { - let new_anchor = adapter_state.reverse_map_id(selection.anchor.node).1; - selection.anchor.node = new_anchor; - let new_focus = adapter_state.reverse_map_id(selection.focus.node).1; - selection.focus.node = new_focus; + selection.anchor.node = adapter_state.node_info(selection.anchor.node).local_node_id; + selection.focus.node = adapter_state.node_info(selection.focus.node).local_node_id; } _ => {} } @@ -207,12 +221,17 @@ impl MultiTreeAdapterState { } let result = self.next_node_id(); assert!(self.id_map.insert((subtree_id, node_id), result).is_none()); - assert!(self.reverse_id_map.insert(result, (subtree_id, node_id)).is_none()); + assert!(self.node_info.insert(result, NodeInfo::new(subtree_id, node_id)).is_none()); result } - fn reverse_map_id(&self, global_node_id: NodeId) -> (SubtreeId, NodeId) { - *self.reverse_id_map.get(&global_node_id).expect("Node not registered") + fn node_info(&self, global_node_id: NodeId) -> &NodeInfo { + self.node_info.get(&global_node_id).expect("Node not registered") + } + + #[expect(unused)] + fn node_info_mut(&mut self, global_node_id: NodeId) -> &mut NodeInfo { + self.node_info.get_mut(&global_node_id).expect("Node not registered") } fn map_node_id_vec_property(&mut self, subtree_id: SubtreeId, node_ids: Vec, setter: impl FnOnce(Vec)) { @@ -265,7 +284,7 @@ mod test { use accesskit::{Node, NodeId, Role, Tree, TreeUpdate}; - use crate::{MultiTreeAdapterState, ROOT_SUBTREE_ID, SubtreeId, SubtreeInfo}; + use crate::{MultiTreeAdapterState, NodeInfo, ROOT_SUBTREE_ID, SubtreeId, SubtreeInfo}; fn node(children: impl Into>) -> Node { let children = children.into(); @@ -372,13 +391,13 @@ mod test { ((child_subtree_id, NodeId(27)), NodeId(4)), ((child_subtree_id, NodeId(26)), NodeId(5)), ]), - reverse_id_map: map([ - (NodeId(0), (ROOT_SUBTREE_ID, NodeId(13))), - (NodeId(1), (ROOT_SUBTREE_ID, NodeId(15))), - (NodeId(2), (ROOT_SUBTREE_ID, NodeId(14))), - (NodeId(3), (child_subtree_id, NodeId(25))), - (NodeId(4), (child_subtree_id, NodeId(27))), - (NodeId(5), (child_subtree_id, NodeId(26))), + node_info: map([ + (NodeId(0), NodeInfo::new(ROOT_SUBTREE_ID, NodeId(13))), + (NodeId(1), NodeInfo::new(ROOT_SUBTREE_ID, NodeId(15))), + (NodeId(2), NodeInfo::new(ROOT_SUBTREE_ID, NodeId(14))), + (NodeId(3), NodeInfo::new(child_subtree_id, NodeId(25))), + (NodeId(4), NodeInfo::new(child_subtree_id, NodeId(27))), + (NodeId(5), NodeInfo::new(child_subtree_id, NodeId(26))), ]), next_node_id: NodeId(6), }, From adec1439c9bf1edcd7ca2409e2329bb74bcd828f Mon Sep 17 00:00:00 2001 From: delan azabani Date: Wed, 3 Dec 2025 18:19:55 +0800 Subject: [PATCH 24/26] Store info about root subtree, just like child subtrees --- platforms/multitree/src/lib.rs | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/platforms/multitree/src/lib.rs b/platforms/multitree/src/lib.rs index 7674bb04..40bb24d1 100644 --- a/platforms/multitree/src/lib.rs +++ b/platforms/multitree/src/lib.rs @@ -7,8 +7,8 @@ const ROOT_SUBTREE_ID: SubtreeId = SubtreeId(0); #[derive(Debug, PartialEq)] pub struct MultiTreeAdapterState { next_subtree_id: SubtreeId, - /// child subtree [`SubtreeId`] → child subtree [`SubtreeInfo`] - child_subtrees: HashMap, + /// [`SubtreeId`] → [`SubtreeInfo`] (or None if the root is not yet known) + subtrees: HashMap>, /// (parent subtree [`SubtreeId`], parent-subtree-local [`NodeId`]) → child subtree [`SubtreeId`] grafts: HashMap<(SubtreeId, NodeId), SubtreeId>, /// ([`SubtreeId`], local [`NodeId`]) → global [`NodeId`] @@ -20,7 +20,7 @@ pub struct MultiTreeAdapterState { #[derive(Debug, PartialEq)] pub struct SubtreeInfo { - /// global [`NodeId`] of root node in child subtree + /// global [`NodeId`] of root node in subtree root_node_id: NodeId, } @@ -48,7 +48,7 @@ impl MultiTreeAdapterState { pub fn new() -> Self { Self { next_subtree_id: SubtreeId(1), - child_subtrees: HashMap::new(), + subtrees: [(ROOT_SUBTREE_ID, None)].into(), grafts: HashMap::new(), id_map: HashMap::new(), node_info: HashMap::new(), @@ -124,7 +124,7 @@ impl MultiTreeAdapterState { // Maybe store the global id for parent_node? assert!(self.grafts.insert((parent_subtree_id, parent_node_id), subtree_id).is_none()); let global_id_for_child = self.map_id(subtree_id, child_id); - assert!(self.child_subtrees.insert(subtree_id, SubtreeInfo { root_node_id: global_id_for_child }).is_none()); + assert!(self.subtrees.insert(subtree_id, Some(SubtreeInfo { root_node_id: global_id_for_child })).is_none()); let parent_node_global_id = self.rewrite_node(parent_subtree_id, parent_node_id, &mut parent_node); let mut nodes: Vec<(NodeId, Node)> = Vec::new(); @@ -151,9 +151,13 @@ impl MultiTreeAdapterState { if let Some(tree) = subtree_update.tree.as_mut() { let global_id_of_root_of_subtree = self.map_id(subtree_id, tree.root); tree.root = global_id_of_root_of_subtree; + let subtree_info = self.subtrees.get_mut(&subtree_id).expect("Must be registered"); + if let Some(subtree_info) = subtree_info { + subtree_info.root_node_id = global_id_of_root_of_subtree; + } else { + *subtree_info = Some(SubtreeInfo { root_node_id: global_id_of_root_of_subtree }); + } if subtree_id != self.root_subtree_id() { - let child_subtree_info = self.child_subtrees.get_mut(&subtree_id).expect("Must be registered"); - child_subtree_info.root_node_id = global_id_of_root_of_subtree; subtree_update.tree = None; } } @@ -266,13 +270,13 @@ impl MultiTreeAdapterState { } fn subtree_is_registered(&self, subtree_id: SubtreeId) -> bool { - subtree_id == self.root_subtree_id() || self.child_subtrees.contains_key(&subtree_id) + self.subtrees.contains_key(&subtree_id) } fn get_root_id_for_grafted_subtree(&mut self, subtree_id: SubtreeId, local_node_id: NodeId) -> Option { if let Some(local_nodes_subtree_id) = self.grafts.get(&(subtree_id, local_node_id)) { - let child_subtree_info = self.child_subtrees.get(&local_nodes_subtree_id).expect("must be registered"); - return Some(child_subtree_info.root_node_id); + let subtree_info = self.subtrees.get(&local_nodes_subtree_id).expect("must be registered"); + return subtree_info.as_ref().map(|info| info.root_node_id); } None } @@ -377,8 +381,9 @@ mod test { multitree, MultiTreeAdapterState { next_subtree_id: SubtreeId(2), - child_subtrees: map([ - (child_subtree_id, subtree_info(NodeId(3))), + subtrees: map([ + (ROOT_SUBTREE_ID, Some(subtree_info(NodeId(0)))), + (child_subtree_id, Some(subtree_info(NodeId(3)))), ]), grafts: map([ ((ROOT_SUBTREE_ID, NodeId(15)), child_subtree_id), From 1f068177740d289c7de1db0fb321205c67b0373a Mon Sep 17 00:00:00 2001 From: delan azabani Date: Wed, 3 Dec 2025 19:22:02 +0800 Subject: [PATCH 25/26] Use stable order maps in tests to make failures less confusing --- platforms/multitree/src/lib.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/platforms/multitree/src/lib.rs b/platforms/multitree/src/lib.rs index 40bb24d1..84ea6f1e 100644 --- a/platforms/multitree/src/lib.rs +++ b/platforms/multitree/src/lib.rs @@ -1,20 +1,24 @@ use accesskit::{ActionData, ActionHandler, ActionRequest, ActivationHandler, Node, NodeId, TreeUpdate}; -use std::collections::HashMap; use std::ptr::NonNull; const ROOT_SUBTREE_ID: SubtreeId = SubtreeId(0); +#[cfg(test)] +type Map = std::collections::BTreeMap; +#[cfg(not(test))] +type Map = std::collections::HashMap; + #[derive(Debug, PartialEq)] pub struct MultiTreeAdapterState { next_subtree_id: SubtreeId, /// [`SubtreeId`] → [`SubtreeInfo`] (or None if the root is not yet known) - subtrees: HashMap>, + subtrees: Map>, /// (parent subtree [`SubtreeId`], parent-subtree-local [`NodeId`]) → child subtree [`SubtreeId`] - grafts: HashMap<(SubtreeId, NodeId), SubtreeId>, + grafts: Map<(SubtreeId, NodeId), SubtreeId>, /// ([`SubtreeId`], local [`NodeId`]) → global [`NodeId`] - id_map: HashMap<(SubtreeId, NodeId), NodeId>, + id_map: Map<(SubtreeId, NodeId), NodeId>, /// global [`NodeId`] → [`NodeInfo`] - node_info: HashMap, + node_info: Map, next_node_id: NodeId, } @@ -49,9 +53,9 @@ impl MultiTreeAdapterState { Self { next_subtree_id: SubtreeId(1), subtrees: [(ROOT_SUBTREE_ID, None)].into(), - grafts: HashMap::new(), - id_map: HashMap::new(), - node_info: HashMap::new(), + grafts: Map::new(), + id_map: Map::new(), + node_info: Map::new(), next_node_id: NodeId(0), } } @@ -284,11 +288,9 @@ impl MultiTreeAdapterState { #[cfg(test)] mod test { - use std::{collections::HashMap, hash::Hash}; - use accesskit::{Node, NodeId, Role, Tree, TreeUpdate}; - use crate::{MultiTreeAdapterState, NodeInfo, ROOT_SUBTREE_ID, SubtreeId, SubtreeInfo}; + use crate::{Map, MultiTreeAdapterState, NodeInfo, ROOT_SUBTREE_ID, SubtreeId, SubtreeInfo}; fn node(children: impl Into>) -> Node { let children = children.into(); @@ -299,7 +301,7 @@ mod test { result } - fn map(entries: impl Into>) -> HashMap { + fn map(entries: impl Into>) -> Map { entries.into() } From 1679fcf06eac26b3592709e792fe8e09bfdc14c8 Mon Sep 17 00:00:00 2001 From: delan azabani Date: Wed, 3 Dec 2025 19:20:36 +0800 Subject: [PATCH 26/26] Try to implement garbage collection --- platforms/multitree/src/lib.rs | 135 ++++++++++++++++++++++++++++----- 1 file changed, 118 insertions(+), 17 deletions(-) diff --git a/platforms/multitree/src/lib.rs b/platforms/multitree/src/lib.rs index 84ea6f1e..48cf0ac2 100644 --- a/platforms/multitree/src/lib.rs +++ b/platforms/multitree/src/lib.rs @@ -5,8 +5,12 @@ const ROOT_SUBTREE_ID: SubtreeId = SubtreeId(0); #[cfg(test)] type Map = std::collections::BTreeMap; +#[cfg(test)] +type Set = std::collections::BTreeSet; #[cfg(not(test))] type Map = std::collections::HashMap; +#[cfg(not(test))] +type Set = std::collections::HashSet; #[derive(Debug, PartialEq)] pub struct MultiTreeAdapterState { @@ -34,12 +38,23 @@ struct NodeInfo { subtree_id: SubtreeId, /// reverse mapping: local [`NodeId`] local_node_id: NodeId, + /// global [`NodeId`] of children + children: Vec, } impl NodeInfo { fn new(subtree_id: SubtreeId, local_node_id: NodeId) -> Self { Self { subtree_id, local_node_id, + children: vec![], + } + } + #[cfg(test)] + fn with_children(subtree_id: SubtreeId, local_node_id: NodeId, children: impl Into>) -> Self { + Self { + subtree_id, + local_node_id, + children: children.into(), } } } @@ -152,14 +167,34 @@ impl MultiTreeAdapterState { } pub fn rewrite_tree_update(&mut self, subtree_id: SubtreeId, mut subtree_update: TreeUpdate) -> TreeUpdate { + // global [`NodeId`] of nodes that are no longer referenced. + // initially this is all of the nodes that were previously in the subtree. + // then we remove nodes, as we prove that they are still referenced. + let mut garbage = if let Some(old_root_node_global_id) = self + .subtrees + .get(&subtree_id) + .expect("Must be registered") + .as_ref() + .map(|info| info.root_node_id) + { + Set::from_iter(self.descendants(old_root_node_global_id)) + } else { + Set::new() + }; + if let Some(tree) = subtree_update.tree.as_mut() { - let global_id_of_root_of_subtree = self.map_id(subtree_id, tree.root); - tree.root = global_id_of_root_of_subtree; + let new_root_node_global_id = self.map_id(subtree_id, tree.root); + tree.root = new_root_node_global_id; let subtree_info = self.subtrees.get_mut(&subtree_id).expect("Must be registered"); if let Some(subtree_info) = subtree_info { - subtree_info.root_node_id = global_id_of_root_of_subtree; + let old_root_node_global_id = subtree_info.root_node_id; + if new_root_node_global_id != old_root_node_global_id { + subtree_info.root_node_id = new_root_node_global_id; + // FIXME we also need to update the parent of the root node we were grafted into, + // but this means we need to retain the parent node in its entirety :( + } } else { - *subtree_info = Some(SubtreeInfo { root_node_id: global_id_of_root_of_subtree }); + *subtree_info = Some(SubtreeInfo { root_node_id: new_root_node_global_id }); } if subtree_id != self.root_subtree_id() { subtree_update.tree = None; @@ -184,16 +219,32 @@ impl MultiTreeAdapterState { // case it’s the focused node of the child subtree being grafted there (recursively). subtree_update.focus = self.map_id(subtree_id, subtree_update.focus); // TODO: rewrite the root correctly + // TODO We need to ensure that we put the subtree root node id as a child of the parent node id. for (node_id, node) in subtree_update.nodes.iter_mut() { *node_id = self.rewrite_node(subtree_id, *node_id, node); } - // TODO We need to ensure that we put the subtree root node id as a child of the parent node id. + // Now compute the final set of garbage [`NodeId`], and destroy those nodes. + if let Some(root_node_global_id) = self + .subtrees + .get(&subtree_id) + .expect("Must be registered") + .as_ref() + .map(|info| info.root_node_id) + { + for child_node_global_id in self.descendants(root_node_global_id) { + garbage.remove(&child_node_global_id); + } + } + for garbage_node_global_id in dbg!(garbage) { + let node_info = self.node_info.remove(&garbage_node_global_id).expect("Node must have info"); + self.id_map.remove(&(node_info.subtree_id, node_info.local_node_id)); + } subtree_update } - pub fn rewrite_node(&mut self, subtree_id: SubtreeId, node_id: NodeId, node: &mut Node) -> NodeId { + fn rewrite_node(&mut self, subtree_id: SubtreeId, node_id: NodeId, node: &mut Node) -> NodeId { let grafted_node_id: Option = self.get_root_id_for_grafted_subtree(subtree_id, node_id); let global_node_id = self.map_id(subtree_id, node_id); // Map ids of all node references. @@ -220,6 +271,7 @@ impl MultiTreeAdapterState { if let Some(grafted_node_id) = grafted_node_id { node.push_child(grafted_node_id); } + self.node_info_mut(global_node_id).children = node.children().to_owned(); global_node_id } @@ -237,11 +289,14 @@ impl MultiTreeAdapterState { self.node_info.get(&global_node_id).expect("Node not registered") } - #[expect(unused)] fn node_info_mut(&mut self, global_node_id: NodeId) -> &mut NodeInfo { self.node_info.get_mut(&global_node_id).expect("Node not registered") } + fn descendants(&self, global_node_id: NodeId) -> Descendants<'_> { + Descendants { state: self, stack: vec![global_node_id] } + } + fn map_node_id_vec_property(&mut self, subtree_id: SubtreeId, node_ids: Vec, setter: impl FnOnce(Vec)) { // If node id vec properties return an empty slice from their getters, don’t bother // calling the setters. This may be slightly more efficient, and also works around a @@ -286,6 +341,21 @@ impl MultiTreeAdapterState { } } +/// Iterator over global [`NodeId`] of descendants. +struct Descendants<'state> { + state: &'state MultiTreeAdapterState, + /// next global [`NodeId`] to explore + stack: Vec, +} +impl Iterator for Descendants<'_> { + type Item = NodeId; + fn next(&mut self) -> Option { + let Some(result) = self.stack.pop() else { return None }; + self.stack.extend_from_slice(&self.state.node_info(result).children); + Some(result) + } +} + #[cfg(test)] mod test { use accesskit::{Node, NodeId, Role, Tree, TreeUpdate}; @@ -313,6 +383,7 @@ mod test { fn test_update() { let mut multitree = MultiTreeAdapterState::new(); let graft_node = node([]); + // Check the initial root subtree update. assert_eq!( multitree.rewrite_tree_update(ROOT_SUBTREE_ID, TreeUpdate { nodes: vec![ @@ -341,6 +412,7 @@ mod test { focus: NodeId(0), }, ); + // Register the child subtree, and check the implicit update. let (child_subtree_id, tree_update) = multitree.register_child_subtree(ROOT_SUBTREE_ID, NodeId(15), NodeId(25), graft_node); assert_eq!(child_subtree_id, SubtreeId(1)); assert_eq!( @@ -355,6 +427,7 @@ mod test { focus: NodeId(3), }, ); + // Check the initial child subtree update. assert_eq!( multitree.rewrite_tree_update(child_subtree_id, TreeUpdate { nodes: vec![ @@ -379,13 +452,39 @@ mod test { focus: NodeId(3), }, ); + // Check a subsequent child subtree update that entirely replaces the tree. + assert_eq!( + multitree.rewrite_tree_update(child_subtree_id, TreeUpdate { + nodes: vec![ + (NodeId(35), node([NodeId(37), NodeId(36)])), + (NodeId(37), node([])), + (NodeId(36), node([])), + ], + tree: Some(Tree { + root: NodeId(35), + toolkit_name: None, + toolkit_version: None, + }), + focus: NodeId(35), + }), + TreeUpdate { + nodes: vec![ + (NodeId(6), node([NodeId(7), NodeId(8)])), + (NodeId(7), node([])), + (NodeId(8), node([])), + ], + tree: None, + focus: NodeId(6), + }, + ); + // Check the final state of the instance. assert_eq!( multitree, MultiTreeAdapterState { next_subtree_id: SubtreeId(2), subtrees: map([ (ROOT_SUBTREE_ID, Some(subtree_info(NodeId(0)))), - (child_subtree_id, Some(subtree_info(NodeId(3)))), + (child_subtree_id, Some(subtree_info(NodeId(6)))), ]), grafts: map([ ((ROOT_SUBTREE_ID, NodeId(15)), child_subtree_id), @@ -394,19 +493,21 @@ mod test { ((ROOT_SUBTREE_ID, NodeId(13)), NodeId(0)), ((ROOT_SUBTREE_ID, NodeId(15)), NodeId(1)), ((ROOT_SUBTREE_ID, NodeId(14)), NodeId(2)), - ((child_subtree_id, NodeId(25)), NodeId(3)), - ((child_subtree_id, NodeId(27)), NodeId(4)), - ((child_subtree_id, NodeId(26)), NodeId(5)), + ((child_subtree_id, NodeId(35)), NodeId(6)), + ((child_subtree_id, NodeId(37)), NodeId(7)), + ((child_subtree_id, NodeId(36)), NodeId(8)), ]), node_info: map([ - (NodeId(0), NodeInfo::new(ROOT_SUBTREE_ID, NodeId(13))), - (NodeId(1), NodeInfo::new(ROOT_SUBTREE_ID, NodeId(15))), + (NodeId(0), NodeInfo::with_children(ROOT_SUBTREE_ID, NodeId(13), [NodeId(1), NodeId(2)])), + // FIXME this should have child NodeId(6), not NodeId(3), + // but we don’t emit an update for the parent of a changed graft node yet + (NodeId(1), NodeInfo::with_children(ROOT_SUBTREE_ID, NodeId(15), [NodeId(3)])), (NodeId(2), NodeInfo::new(ROOT_SUBTREE_ID, NodeId(14))), - (NodeId(3), NodeInfo::new(child_subtree_id, NodeId(25))), - (NodeId(4), NodeInfo::new(child_subtree_id, NodeId(27))), - (NodeId(5), NodeInfo::new(child_subtree_id, NodeId(26))), + (NodeId(6), NodeInfo::with_children(child_subtree_id, NodeId(35), [NodeId(7), NodeId(8)])), + (NodeId(7), NodeInfo::new(child_subtree_id, NodeId(37))), + (NodeId(8), NodeInfo::new(child_subtree_id, NodeId(36))), ]), - next_node_id: NodeId(6), + next_node_id: NodeId(9), }, ); }