diff --git a/Cargo.lock b/Cargo.lock index 24ce4213..b1900eb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,6 +27,7 @@ dependencies = [ "schemars", "serde", "serde_json", + "uuid", ] [[package]] @@ -1677,6 +1678,7 @@ dependencies = [ "schemars_derive", "serde", "serde_json", + "uuid", ] [[package]] @@ -2101,6 +2103,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "serde", +] + [[package]] name = "version_check" version = "0.9.4" diff --git a/common/Cargo.toml b/common/Cargo.toml index d61bb7b6..a2f595ba 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -20,9 +20,10 @@ pyo3 = { version = "0.26", optional = true } schemars = { version = "1", optional = true } serde = { version = "1.0", default-features = false, features = ["alloc", "derive"], optional = true } serde_json = { version = "1.0", default-features = false, optional = true } +uuid = { version = "1", default-features = false } [features] enumn = ["dep:enumn"] pyo3 = ["dep:pyo3"] -serde = ["dep:serde", "enumn"] -schemars = ["dep:schemars", "dep:serde_json", "serde"] +serde = ["dep:serde", "enumn", "uuid/serde"] +schemars = ["dep:schemars", "dep:serde_json", "serde", "schemars/uuid1"] diff --git a/common/src/lib.rs b/common/src/lib.rs index 1ba8b2c2..974227a0 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -29,6 +29,8 @@ use serde::{ #[cfg(feature = "schemars")] use serde_json::{Map as SchemaMap, Value as SchemaValue}; +pub use uuid::Uuid; + mod geometry; pub use geometry::{Affine, Point, Rect, Size, Vec2}; @@ -626,6 +628,11 @@ pub enum TextDecoration { pub type NodeIdContent = u64; /// The stable identity of a [`Node`], unique within the node's tree. +/// +/// Each tree (root or subtree) has its own independent ID space. The same +/// `NodeId` value can exist in different trees without conflict. When working +/// with multiple trees, the combination of `NodeId` and [`TreeId`] uniquely +/// identifies a node. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] @@ -652,6 +659,21 @@ impl fmt::Debug for NodeId { } } +/// The stable identity of a [`Tree`]. +/// +/// Use [`TreeId::ROOT`] for the main/root tree. For subtrees, use a random +/// UUID (version 4) to avoid collisions between independently created trees. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "schemars", derive(JsonSchema))] +#[repr(transparent)] +pub struct TreeId(pub Uuid); + +impl TreeId { + /// A reserved tree ID for the root tree. This uses a nil UUID. + pub const ROOT: Self = Self(Uuid::nil()); +} + /// Defines a custom action for a UI element. /// /// For example, a list UI can allow a user to reorder items in the list by dragging the @@ -761,6 +783,7 @@ enum PropertyValue { Rect(Rect), TextSelection(Box), CustomActionVec(Vec), + TreeId(TreeId), } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -877,6 +900,7 @@ enum PropertyId { Bounds, TextSelection, CustomActions, + TreeId, // This MUST be last. Unset, @@ -1678,7 +1702,8 @@ copy_type_getters! { (get_usize_property, usize, Usize), (get_color_property, u32, Color), (get_text_decoration_property, TextDecoration, TextDecoration), - (get_bool_property, bool, Bool) + (get_bool_property, bool, Bool), + (get_tree_id_property, TreeId, TreeId) } box_type_setters! { @@ -1696,7 +1721,8 @@ copy_type_setters! { (set_usize_property, usize, Usize), (set_color_property, u32, Color), (set_text_decoration_property, TextDecoration, TextDecoration), - (set_bool_property, bool, Bool) + (set_bool_property, bool, Bool), + (set_tree_id_property, TreeId, TreeId) } vec_type_methods! { @@ -1997,11 +2023,20 @@ property_methods! { /// [`transform`]: Node::transform (Bounds, bounds, get_rect_property, Option, set_bounds, set_rect_property, Rect, clear_bounds), - (TextSelection, text_selection, get_text_selection_property, Option<&TextSelection>, set_text_selection, set_text_selection_property, impl Into>, clear_text_selection) + (TextSelection, text_selection, get_text_selection_property, Option<&TextSelection>, set_text_selection, set_text_selection_property, impl Into>, clear_text_selection), + + /// The tree that this node grafts. When set, this node acts as a graft + /// point, and its child is the root of the specified subtree. + /// + /// A graft node must be created before its subtree is pushed. + /// + /// Removing a graft node or clearing this property removes its subtree, + /// unless a new graft node is provided in the same update. + (TreeId, tree_id, get_tree_id_property, Option, set_tree_id, set_tree_id_property, TreeId, clear_tree_id) } impl Node { - option_properties_debug_method! { debug_option_properties, [transform, bounds, text_selection,] } + option_properties_debug_method! { debug_option_properties, [transform, bounds, text_selection, tree_id,] } } #[cfg(test)] @@ -2106,6 +2141,31 @@ mod text_selection { } } +#[cfg(test)] +mod tree_id { + use super::{Node, Role, TreeId, Uuid}; + + #[test] + fn getter_should_return_default_value() { + let node = Node::new(Role::GenericContainer); + assert!(node.tree_id().is_none()); + } + #[test] + fn setter_should_update_the_property() { + let mut node = Node::new(Role::GenericContainer); + let value = TreeId(Uuid::nil()); + node.set_tree_id(value); + assert_eq!(node.tree_id(), Some(value)); + } + #[test] + fn clearer_should_reset_the_property() { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(TreeId(Uuid::nil())); + node.clear_tree_id(); + assert!(node.tree_id().is_none()); + } +} + vec_property_methods! { (CustomActions, CustomAction, custom_actions, get_custom_action_vec, set_custom_actions, set_custom_action_vec, push_custom_action, push_to_custom_action_vec, clear_custom_actions) } @@ -2274,7 +2334,8 @@ impl Serialize for Properties { Affine, Rect, TextSelection, - CustomActionVec + CustomActionVec, + TreeId }); } map.end() @@ -2404,7 +2465,8 @@ impl<'de> Visitor<'de> for PropertiesVisitor { Affine { Transform }, Rect { Bounds }, TextSelection { TextSelection }, - CustomActionVec { CustomActions } + CustomActionVec { CustomActions }, + TreeId { TreeId } }); } @@ -2634,11 +2696,26 @@ pub struct TreeUpdate { /// a tree. pub tree: Option, + /// The identifier of the tree that this update applies to. + /// + /// Use [`TreeId::ROOT`] for the main/root tree. For subtrees, use a unique + /// [`TreeId`] that identifies the subtree. + /// + /// When updating a subtree (non-ROOT tree_id): + /// - A graft node with [`Node::tree_id`] set to this tree's ID must already + /// exist in the parent tree before the first subtree update. + /// - The first update for a subtree must include [`tree`](Self::tree) data. + pub tree_id: TreeId, + /// The node within this tree that has keyboard focus when the native /// host (e.g. window) has focus. If no specific node within the tree /// has keyboard focus, this must be set to the root. The latest focus state /// must be provided with every tree update, even if the focus state /// didn't change in a given update. + /// + /// For subtrees, this specifies which node has focus when the subtree + /// itself is focused (i.e., when focus is on the graft node in the parent + /// tree). pub focus: NodeId, } @@ -2712,7 +2789,8 @@ pub enum ActionData { #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct ActionRequest { pub action: Action, - pub target: NodeId, + pub target_tree: TreeId, + pub target_node: NodeId, pub data: Option, } diff --git a/consumer/src/filters.rs b/consumer/src/filters.rs index df9025a4..8905bef3 100644 --- a/consumer/src/filters.rs +++ b/consumer/src/filters.rs @@ -23,6 +23,11 @@ fn common_filter_base(node: &Node) -> Option { return Some(FilterResult::ExcludeSubtree); } + // Graft nodes are transparent containers pointing to a subtree + if node.is_graft() { + return Some(FilterResult::ExcludeNode); + } + let role = node.role(); if role == Role::GenericContainer || role == Role::TextRun { return Some(FilterResult::ExcludeNode); @@ -95,19 +100,20 @@ pub fn common_filter_with_root_exception(node: &Node) -> FilterResult { #[cfg(test)] mod tests { - use accesskit::{Node, NodeId, Rect, Role, Tree, TreeUpdate}; + use accesskit::{Node, NodeId, Rect, Role, Tree, TreeId, TreeUpdate}; use alloc::vec; use super::{ common_filter, common_filter_with_root_exception, FilterResult::{self, *}, }; + use crate::tests::nid; #[track_caller] fn assert_filter_result(expected: FilterResult, tree: &crate::Tree, id: NodeId) { assert_eq!( expected, - common_filter(&tree.state().node_by_id(id).unwrap()) + common_filter(&tree.state().node_by_id(nid(id)).unwrap()) ); } @@ -123,6 +129,7 @@ mod tests { (NodeId(1), Node::new(Role::Button)), ], tree: Some(Tree::new(NodeId(0))), + tree_id: TreeId::ROOT, focus: NodeId(0), }; let tree = crate::Tree::new(update, false); @@ -145,6 +152,7 @@ mod tests { }), ], tree: Some(Tree::new(NodeId(0))), + tree_id: TreeId::ROOT, focus: NodeId(0), }; let tree = crate::Tree::new(update, false); @@ -167,6 +175,7 @@ mod tests { }), ], tree: Some(Tree::new(NodeId(0))), + tree_id: TreeId::ROOT, focus: NodeId(1), }; let tree = crate::Tree::new(update, true); @@ -185,13 +194,14 @@ mod tests { (NodeId(1), Node::new(Role::Button)), ], tree: Some(Tree::new(NodeId(0))), + tree_id: TreeId::ROOT, focus: NodeId(0), }; let tree = crate::Tree::new(update, false); assert_filter_result(ExcludeNode, &tree, NodeId(0)); assert_eq!( Include, - common_filter_with_root_exception(&tree.state().node_by_id(NodeId(0)).unwrap()) + common_filter_with_root_exception(&tree.state().node_by_id(nid(NodeId(0))).unwrap()) ); assert_filter_result(Include, &tree, NodeId(1)); } @@ -209,6 +219,7 @@ mod tests { (NodeId(1), Node::new(Role::Button)), ], tree: Some(Tree::new(NodeId(0))), + tree_id: TreeId::ROOT, focus: NodeId(0), }; let tree = crate::Tree::new(update, false); @@ -229,6 +240,7 @@ mod tests { (NodeId(1), Node::new(Role::Button)), ], tree: Some(Tree::new(NodeId(0))), + tree_id: TreeId::ROOT, focus: NodeId(1), }; let tree = crate::Tree::new(update, true); @@ -248,6 +260,7 @@ mod tests { (NodeId(1), Node::new(Role::TextRun)), ], tree: Some(Tree::new(NodeId(0))), + tree_id: TreeId::ROOT, focus: NodeId(0), }; let tree = crate::Tree::new(update, false); @@ -333,6 +346,7 @@ mod tests { }), ], tree: Some(Tree::new(NodeId(0))), + tree_id: TreeId::ROOT, focus: NodeId(0), }; crate::Tree::new(update, false) @@ -378,4 +392,30 @@ mod tests { assert_filter_result(ExcludeSubtree, &tree, NodeId(10)); assert_filter_result(ExcludeSubtree, &tree, NodeId(11)); } + + #[test] + fn graft_node() { + use accesskit::Uuid; + + let subtree_id = TreeId(Uuid::from_u128(1)); + let update = TreeUpdate { + nodes: vec![ + (NodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![NodeId(1)]); + node + }), + (NodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id); + node + }), + ], + tree: Some(Tree::new(NodeId(0))), + tree_id: TreeId::ROOT, + focus: NodeId(0), + }; + let tree = crate::Tree::new(update, false); + assert_filter_result(ExcludeNode, &tree, NodeId(1)); + } } diff --git a/consumer/src/iterators.rs b/consumer/src/iterators.rs index 30faaac3..f9783f13 100644 --- a/consumer/src/iterators.rs +++ b/consumer/src/iterators.rs @@ -10,9 +10,68 @@ use core::iter::FusedIterator; -use accesskit::NodeId; +use accesskit::NodeId as LocalNodeId; -use crate::{filters::FilterResult, node::Node, tree::State as TreeState}; +use crate::{ + filters::FilterResult, + node::{Node, NodeId}, + tree::State as TreeState, +}; + +/// Iterator over child NodeIds, handling both normal nodes and graft nodes. +pub enum ChildIds<'a> { + Normal { + parent_id: NodeId, + children: core::slice::Iter<'a, LocalNodeId>, + }, + Graft(Option), +} + +impl Iterator for ChildIds<'_> { + type Item = NodeId; + + fn next(&mut self) -> Option { + match self { + Self::Normal { + parent_id, + children, + } => children + .next() + .map(|child| parent_id.with_same_tree(*child)), + Self::Graft(id) => id.take(), + } + } + + fn size_hint(&self) -> (usize, Option) { + let len = self.len(); + (len, Some(len)) + } +} + +impl DoubleEndedIterator for ChildIds<'_> { + fn next_back(&mut self) -> Option { + match self { + Self::Normal { + parent_id, + children, + } => children + .next_back() + .map(|child| parent_id.with_same_tree(*child)), + Self::Graft(id) => id.take(), + } + } +} + +impl ExactSizeIterator for ChildIds<'_> { + fn len(&self) -> usize { + match self { + Self::Normal { children, .. } => children.len(), + Self::Graft(id) => usize::from(id.is_some()), + } + } +} + +impl FusedIterator for ChildIds<'_> {} /// An iterator that yields following siblings of a node. /// @@ -22,6 +81,7 @@ pub struct FollowingSiblings<'a> { done: bool, front_position: usize, parent: Option>, + node_id: NodeId, } impl<'a> FollowingSiblings<'a> { @@ -29,13 +89,18 @@ impl<'a> FollowingSiblings<'a> { let parent_and_index = node.parent_and_index(); let (back_position, front_position, done) = if let Some((ref parent, index)) = parent_and_index { - let back_position = parent.data().children().len() - 1; - let front_position = index + 1; - ( - back_position, - front_position, - front_position > back_position, - ) + // Graft nodes have only one child (the subtree root) + if parent.is_graft() { + (0, 0, true) + } else { + let back_position = parent.data().children().len() - 1; + let front_position = index + 1; + ( + back_position, + front_position, + front_position > back_position, + ) + } } else { (0, 0, true) }; @@ -44,6 +109,7 @@ impl<'a> FollowingSiblings<'a> { done, front_position, parent: parent_and_index.map(|(parent, _)| parent), + node_id: node.id, } } } @@ -63,7 +129,7 @@ impl Iterator for FollowingSiblings<'_> { .children() .get(self.front_position)?; self.front_position += 1; - Some(*child) + Some(self.node_id.with_same_tree(*child)) } } @@ -89,7 +155,7 @@ impl DoubleEndedIterator for FollowingSiblings<'_> { .children() .get(self.back_position)?; self.back_position -= 1; - Some(*child) + Some(self.node_id.with_same_tree(*child)) } } } @@ -106,22 +172,30 @@ pub struct PrecedingSiblings<'a> { done: bool, front_position: usize, parent: Option>, + node_id: NodeId, } impl<'a> PrecedingSiblings<'a> { pub(crate) fn new(node: Node<'a>) -> Self { let parent_and_index = node.parent_and_index(); - let (back_position, front_position, done) = if let Some((_, index)) = parent_and_index { - let front_position = index.saturating_sub(1); - (0, front_position, index == 0) - } else { - (0, 0, true) - }; + let (back_position, front_position, done) = + if let Some((ref parent, index)) = parent_and_index { + // Graft nodes have only one child (the subtree root) + if parent.is_graft() { + (0, 0, true) + } else { + let front_position = index.saturating_sub(1); + (0, front_position, index == 0) + } + } else { + (0, 0, true) + }; Self { back_position, done, front_position, parent: parent_and_index.map(|(parent, _)| parent), + node_id: node.id, } } } @@ -143,7 +217,7 @@ impl Iterator for PrecedingSiblings<'_> { if !self.done { self.front_position -= 1; } - Some(*child) + Some(self.node_id.with_same_tree(*child)) } } @@ -169,7 +243,7 @@ impl DoubleEndedIterator for PrecedingSiblings<'_> { .children() .get(self.back_position)?; self.back_position += 1; - Some(*child) + Some(self.node_id.with_same_tree(*child)) } } } @@ -459,8 +533,9 @@ impl FilterResult> FusedIterator for FilteredChildren<'_, F pub(crate) enum LabelledBy<'a, Filter: Fn(&Node) -> FilterResult> { FromDescendants(FilteredChildren<'a, Filter>), Explicit { - ids: core::slice::Iter<'a, NodeId>, + ids: core::slice::Iter<'a, LocalNodeId>, tree_state: &'a TreeState, + node_id: NodeId, }, } @@ -470,9 +545,13 @@ impl<'a, Filter: Fn(&Node) -> FilterResult> Iterator for LabelledBy<'a, Filter> fn next(&mut self) -> Option { match self { Self::FromDescendants(iter) => iter.next(), - Self::Explicit { ids, tree_state } => { - ids.next().map(|id| tree_state.node_by_id(*id).unwrap()) - } + Self::Explicit { + ids, + tree_state, + node_id, + } => ids + .next() + .map(|id| tree_state.node_by_id(node_id.with_same_tree(*id)).unwrap()), } } @@ -488,9 +567,13 @@ impl FilterResult> DoubleEndedIterator for LabelledBy<'_, F fn next_back(&mut self) -> Option { match self { Self::FromDescendants(iter) => iter.next_back(), - Self::Explicit { ids, tree_state } => ids + Self::Explicit { + ids, + tree_state, + node_id, + } => ids .next_back() - .map(|id| tree_state.node_by_id(*id).unwrap()), + .map(|id| tree_state.node_by_id(node_id.with_same_tree(*id)).unwrap()), } } } @@ -499,9 +582,14 @@ impl FilterResult> FusedIterator for LabelledBy<'_, Filter> #[cfg(test)] mod tests { - use crate::tests::*; - use accesskit::NodeId; - use alloc::vec::Vec; + use crate::{ + filters::common_filter, + tests::*, + tree::{ChangeHandler, TreeIndex}, + NodeId, + }; + use accesskit::{Node, NodeId as LocalNodeId, Role, Tree, TreeId, TreeUpdate, Uuid}; + use alloc::{vec, vec::Vec}; #[test] fn following_siblings() { @@ -515,23 +603,23 @@ mod tests { PARAGRAPH_3_IGNORED_ID ], tree.state() - .node_by_id(PARAGRAPH_0_ID) + .node_by_id(nid(PARAGRAPH_0_ID)) .unwrap() - .following_siblings() - .map(|node| node.id()) - .collect::>()[..] + .following_sibling_ids() + .map(|id| id.to_components().0) + .collect::>()[..] ); assert_eq!( 3, tree.state() - .node_by_id(PARAGRAPH_0_ID) + .node_by_id(nid(PARAGRAPH_0_ID)) .unwrap() .following_siblings() .len() ); assert!(tree .state() - .node_by_id(PARAGRAPH_3_IGNORED_ID) + .node_by_id(nid(PARAGRAPH_3_IGNORED_ID)) .unwrap() .following_siblings() .next() @@ -539,7 +627,7 @@ mod tests { assert_eq!( 0, tree.state() - .node_by_id(PARAGRAPH_3_IGNORED_ID) + .node_by_id(nid(PARAGRAPH_3_IGNORED_ID)) .unwrap() .following_siblings() .len() @@ -562,16 +650,16 @@ mod tests { PARAGRAPH_1_IGNORED_ID ], tree.state() - .node_by_id(PARAGRAPH_0_ID) + .node_by_id(nid(PARAGRAPH_0_ID)) .unwrap() - .following_siblings() + .following_sibling_ids() .rev() - .map(|node| node.id()) - .collect::>()[..] + .map(|id| id.to_components().0) + .collect::>()[..] ); assert!(tree .state() - .node_by_id(PARAGRAPH_3_IGNORED_ID) + .node_by_id(nid(PARAGRAPH_3_IGNORED_ID)) .unwrap() .following_siblings() .next_back() @@ -586,23 +674,23 @@ mod tests { assert_eq!( [PARAGRAPH_2_ID, PARAGRAPH_1_IGNORED_ID, PARAGRAPH_0_ID], tree.state() - .node_by_id(PARAGRAPH_3_IGNORED_ID) + .node_by_id(nid(PARAGRAPH_3_IGNORED_ID)) .unwrap() - .preceding_siblings() - .map(|node| node.id()) - .collect::>()[..] + .preceding_sibling_ids() + .map(|id| id.to_components().0) + .collect::>()[..] ); assert_eq!( 3, tree.state() - .node_by_id(PARAGRAPH_3_IGNORED_ID) + .node_by_id(nid(PARAGRAPH_3_IGNORED_ID)) .unwrap() .preceding_siblings() .len() ); assert!(tree .state() - .node_by_id(PARAGRAPH_0_ID) + .node_by_id(nid(PARAGRAPH_0_ID)) .unwrap() .preceding_siblings() .next() @@ -610,7 +698,7 @@ mod tests { assert_eq!( 0, tree.state() - .node_by_id(PARAGRAPH_0_ID) + .node_by_id(nid(PARAGRAPH_0_ID)) .unwrap() .preceding_siblings() .len() @@ -629,16 +717,16 @@ mod tests { assert_eq!( [PARAGRAPH_0_ID, PARAGRAPH_1_IGNORED_ID, PARAGRAPH_2_ID], tree.state() - .node_by_id(PARAGRAPH_3_IGNORED_ID) + .node_by_id(nid(PARAGRAPH_3_IGNORED_ID)) .unwrap() - .preceding_siblings() + .preceding_sibling_ids() .rev() - .map(|node| node.id()) - .collect::>()[..] + .map(|id| id.to_components().0) + .collect::>()[..] ); assert!(tree .state() - .node_by_id(PARAGRAPH_0_ID) + .node_by_id(nid(PARAGRAPH_0_ID)) .unwrap() .preceding_siblings() .next_back() @@ -657,24 +745,24 @@ mod tests { assert_eq!( [LABEL_1_1_ID, PARAGRAPH_2_ID, LABEL_3_1_0_ID, BUTTON_3_2_ID], tree.state() - .node_by_id(PARAGRAPH_0_ID) + .node_by_id(nid(PARAGRAPH_0_ID)) .unwrap() .following_filtered_siblings(test_tree_filter) - .map(|node| node.id()) - .collect::>()[..] + .map(|node| node.id().to_components().0) + .collect::>()[..] ); assert_eq!( [BUTTON_3_2_ID], tree.state() - .node_by_id(LABEL_3_1_0_ID) + .node_by_id(nid(LABEL_3_1_0_ID)) .unwrap() .following_filtered_siblings(test_tree_filter) - .map(|node| node.id()) - .collect::>()[..] + .map(|node| node.id().to_components().0) + .collect::>()[..] ); assert!(tree .state() - .node_by_id(PARAGRAPH_3_IGNORED_ID) + .node_by_id(nid(PARAGRAPH_3_IGNORED_ID)) .unwrap() .following_filtered_siblings(test_tree_filter) .next() @@ -693,26 +781,26 @@ mod tests { assert_eq!( [BUTTON_3_2_ID, LABEL_3_1_0_ID, PARAGRAPH_2_ID, LABEL_1_1_ID], tree.state() - .node_by_id(PARAGRAPH_0_ID) + .node_by_id(nid(PARAGRAPH_0_ID)) .unwrap() .following_filtered_siblings(test_tree_filter) .rev() - .map(|node| node.id()) - .collect::>()[..] + .map(|node| node.id().to_components().0) + .collect::>()[..] ); assert_eq!( [BUTTON_3_2_ID,], tree.state() - .node_by_id(LABEL_3_1_0_ID) + .node_by_id(nid(LABEL_3_1_0_ID)) .unwrap() .following_filtered_siblings(test_tree_filter) .rev() - .map(|node| node.id()) - .collect::>()[..] + .map(|node| node.id().to_components().0) + .collect::>()[..] ); assert!(tree .state() - .node_by_id(PARAGRAPH_3_IGNORED_ID) + .node_by_id(nid(PARAGRAPH_3_IGNORED_ID)) .unwrap() .following_filtered_siblings(test_tree_filter) .next_back() @@ -731,24 +819,24 @@ mod tests { assert_eq!( [PARAGRAPH_2_ID, LABEL_1_1_ID, PARAGRAPH_0_ID], tree.state() - .node_by_id(PARAGRAPH_3_IGNORED_ID) + .node_by_id(nid(PARAGRAPH_3_IGNORED_ID)) .unwrap() .preceding_filtered_siblings(test_tree_filter) - .map(|node| node.id()) - .collect::>()[..] + .map(|node| node.id().to_components().0) + .collect::>()[..] ); assert_eq!( [PARAGRAPH_2_ID, LABEL_1_1_ID, PARAGRAPH_0_ID], tree.state() - .node_by_id(LABEL_3_1_0_ID) + .node_by_id(nid(LABEL_3_1_0_ID)) .unwrap() .preceding_filtered_siblings(test_tree_filter) - .map(|node| node.id()) - .collect::>()[..] + .map(|node| node.id().to_components().0) + .collect::>()[..] ); assert!(tree .state() - .node_by_id(PARAGRAPH_0_ID) + .node_by_id(nid(PARAGRAPH_0_ID)) .unwrap() .preceding_filtered_siblings(test_tree_filter) .next() @@ -767,26 +855,26 @@ mod tests { assert_eq!( [PARAGRAPH_0_ID, LABEL_1_1_ID, PARAGRAPH_2_ID], tree.state() - .node_by_id(PARAGRAPH_3_IGNORED_ID) + .node_by_id(nid(PARAGRAPH_3_IGNORED_ID)) .unwrap() .preceding_filtered_siblings(test_tree_filter) .rev() - .map(|node| node.id()) - .collect::>()[..] + .map(|node| node.id().to_components().0) + .collect::>()[..] ); assert_eq!( [PARAGRAPH_0_ID, LABEL_1_1_ID, PARAGRAPH_2_ID], tree.state() - .node_by_id(LABEL_3_1_0_ID) + .node_by_id(nid(LABEL_3_1_0_ID)) .unwrap() .preceding_filtered_siblings(test_tree_filter) .rev() - .map(|node| node.id()) - .collect::>()[..] + .map(|node| node.id().to_components().0) + .collect::>()[..] ); assert!(tree .state() - .node_by_id(PARAGRAPH_0_ID) + .node_by_id(nid(PARAGRAPH_0_ID)) .unwrap() .preceding_filtered_siblings(test_tree_filter) .next_back() @@ -807,19 +895,19 @@ mod tests { tree.state() .root() .filtered_children(test_tree_filter) - .map(|node| node.id()) - .collect::>()[..] + .map(|node| node.id().to_components().0) + .collect::>()[..] ); assert!(tree .state() - .node_by_id(PARAGRAPH_0_ID) + .node_by_id(nid(PARAGRAPH_0_ID)) .unwrap() .filtered_children(test_tree_filter) .next() .is_none()); assert!(tree .state() - .node_by_id(LABEL_0_0_IGNORED_ID) + .node_by_id(nid(LABEL_0_0_IGNORED_ID)) .unwrap() .filtered_children(test_tree_filter) .next() @@ -841,22 +929,110 @@ mod tests { .root() .filtered_children(test_tree_filter) .rev() - .map(|node| node.id()) - .collect::>()[..] + .map(|node| node.id().to_components().0) + .collect::>()[..] ); assert!(tree .state() - .node_by_id(PARAGRAPH_0_ID) + .node_by_id(nid(PARAGRAPH_0_ID)) .unwrap() .filtered_children(test_tree_filter) .next_back() .is_none()); assert!(tree .state() - .node_by_id(LABEL_0_0_IGNORED_ID) + .node_by_id(nid(LABEL_0_0_IGNORED_ID)) .unwrap() .filtered_children(test_tree_filter) .next_back() .is_none()); } + + #[test] + fn graft_node_without_subtree_has_no_filtered_children() { + let subtree_id = TreeId(Uuid::from_u128(1)); + + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let tree = crate::Tree::new(update, false); + + let graft_node_id = NodeId::new(LocalNodeId(1), TreeIndex(0)); + let graft_node = tree.state().node_by_id(graft_node_id).unwrap(); + assert!(graft_node.filtered_children(common_filter).next().is_none()); + } + + #[test] + fn filtered_children_crosses_subtree_boundary() { + struct NoOpHandler; + impl ChangeHandler for NoOpHandler { + fn node_added(&mut self, _: &crate::Node) {} + fn node_updated(&mut self, _: &crate::Node, _: &crate::Node) {} + fn focus_moved(&mut self, _: Option<&crate::Node>, _: Option<&crate::Node>) {} + fn node_removed(&mut self, _: &crate::Node) {} + } + + let subtree_id = TreeId(Uuid::from_u128(1)); + + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let mut tree = crate::Tree::new(update, false); + + let subtree_update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Document); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), Node::new(Role::Button)), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: subtree_id, + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(subtree_update, &mut NoOpHandler); + + let root = tree.state().root(); + let filtered_children: Vec<_> = root.filtered_children(common_filter).collect(); + + assert_eq!(1, filtered_children.len()); + let subtree_root_id = NodeId::new(LocalNodeId(0), TreeIndex(1)); + assert_eq!(subtree_root_id, filtered_children[0].id()); + + let document = &filtered_children[0]; + let doc_children: Vec<_> = document.filtered_children(common_filter).collect(); + assert_eq!(1, doc_children.len()); + let button_id = NodeId::new(LocalNodeId(1), TreeIndex(1)); + assert_eq!(button_id, doc_children[0].id()); + } } diff --git a/consumer/src/lib.rs b/consumer/src/lib.rs index dcb54fba..6e277040 100644 --- a/consumer/src/lib.rs +++ b/consumer/src/lib.rs @@ -11,7 +11,7 @@ pub(crate) mod tree; pub use tree::{ChangeHandler as TreeChangeHandler, State as TreeState, Tree}; pub(crate) mod node; -pub use node::Node; +pub use node::{Node, NodeId}; pub(crate) mod filters; pub use filters::{common_filter, common_filter_with_root_exception, FilterResult}; @@ -26,28 +26,36 @@ pub use text::{ #[cfg(test)] mod tests { - use accesskit::{Affine, Node, NodeId, Rect, Role, Tree, TreeUpdate, Vec2}; + use accesskit::{ + Affine, Node, NodeId as LocalNodeId, Rect, Role, Tree, TreeId, TreeUpdate, Vec2, + }; use alloc::vec; + use crate::node::NodeId; + use crate::tree::TreeIndex; use crate::FilterResult; - pub const ROOT_ID: NodeId = NodeId(0); - pub const PARAGRAPH_0_ID: NodeId = NodeId(1); - pub const LABEL_0_0_IGNORED_ID: NodeId = NodeId(2); - pub const PARAGRAPH_1_IGNORED_ID: NodeId = NodeId(3); - pub const BUTTON_1_0_HIDDEN_ID: NodeId = NodeId(4); - pub const CONTAINER_1_0_0_HIDDEN_ID: NodeId = NodeId(5); - pub const LABEL_1_1_ID: NodeId = NodeId(6); - pub const BUTTON_1_2_HIDDEN_ID: NodeId = NodeId(7); - pub const CONTAINER_1_2_0_HIDDEN_ID: NodeId = NodeId(8); - pub const PARAGRAPH_2_ID: NodeId = NodeId(9); - pub const LABEL_2_0_ID: NodeId = NodeId(10); - pub const PARAGRAPH_3_IGNORED_ID: NodeId = NodeId(11); - pub const EMPTY_CONTAINER_3_0_IGNORED_ID: NodeId = NodeId(12); - pub const LINK_3_1_IGNORED_ID: NodeId = NodeId(13); - pub const LABEL_3_1_0_ID: NodeId = NodeId(14); - pub const BUTTON_3_2_ID: NodeId = NodeId(15); - pub const EMPTY_CONTAINER_3_3_IGNORED_ID: NodeId = NodeId(16); + pub fn nid(id: LocalNodeId) -> NodeId { + NodeId::new(id, TreeIndex(0)) + } + + pub const ROOT_ID: LocalNodeId = LocalNodeId(0); + pub const PARAGRAPH_0_ID: LocalNodeId = LocalNodeId(1); + pub const LABEL_0_0_IGNORED_ID: LocalNodeId = LocalNodeId(2); + pub const PARAGRAPH_1_IGNORED_ID: LocalNodeId = LocalNodeId(3); + pub const BUTTON_1_0_HIDDEN_ID: LocalNodeId = LocalNodeId(4); + pub const CONTAINER_1_0_0_HIDDEN_ID: LocalNodeId = LocalNodeId(5); + pub const LABEL_1_1_ID: LocalNodeId = LocalNodeId(6); + pub const BUTTON_1_2_HIDDEN_ID: LocalNodeId = LocalNodeId(7); + pub const CONTAINER_1_2_0_HIDDEN_ID: LocalNodeId = LocalNodeId(8); + pub const PARAGRAPH_2_ID: LocalNodeId = LocalNodeId(9); + pub const LABEL_2_0_ID: LocalNodeId = LocalNodeId(10); + pub const PARAGRAPH_3_IGNORED_ID: LocalNodeId = LocalNodeId(11); + pub const EMPTY_CONTAINER_3_0_IGNORED_ID: LocalNodeId = LocalNodeId(12); + pub const LINK_3_1_IGNORED_ID: LocalNodeId = LocalNodeId(13); + pub const LABEL_3_1_0_ID: LocalNodeId = LocalNodeId(14); + pub const BUTTON_3_2_ID: LocalNodeId = LocalNodeId(15); + pub const EMPTY_CONTAINER_3_3_IGNORED_ID: LocalNodeId = LocalNodeId(16); pub fn test_tree() -> crate::tree::Tree { let root = { @@ -179,6 +187,7 @@ mod tests { (EMPTY_CONTAINER_3_3_IGNORED_ID, empty_container_3_3_ignored), ], tree: Some(Tree::new(ROOT_ID)), + tree_id: TreeId::ROOT, focus: ROOT_ID, }; crate::tree::Tree::new(initial_update, false) @@ -188,12 +197,12 @@ mod tests { let id = node.id(); if node.is_hidden() { FilterResult::ExcludeSubtree - } else if id == LABEL_0_0_IGNORED_ID - || id == PARAGRAPH_1_IGNORED_ID - || id == PARAGRAPH_3_IGNORED_ID - || id == EMPTY_CONTAINER_3_0_IGNORED_ID - || id == LINK_3_1_IGNORED_ID - || id == EMPTY_CONTAINER_3_3_IGNORED_ID + } else if id == nid(LABEL_0_0_IGNORED_ID) + || id == nid(PARAGRAPH_1_IGNORED_ID) + || id == nid(PARAGRAPH_3_IGNORED_ID) + || id == nid(EMPTY_CONTAINER_3_0_IGNORED_ID) + || id == nid(LINK_3_1_IGNORED_ID) + || id == nid(EMPTY_CONTAINER_3_3_IGNORED_ID) { FilterResult::ExcludeNode } else { diff --git a/consumer/src/node.rs b/consumer/src/node.rs index 2f9df237..339876b2 100644 --- a/consumer/src/node.rs +++ b/consumer/src/node.rs @@ -9,8 +9,8 @@ // found in the LICENSE.chromium file. use accesskit::{ - Action, Affine, AriaCurrent, HasPopup, Live, Node as NodeData, NodeId, Orientation, Point, - Rect, Role, SortDirection, TextSelection, Toggled, + Action, Affine, AriaCurrent, HasPopup, Live, Node as NodeData, NodeId as LocalNodeId, + Orientation, Point, Rect, Role, SortDirection, TextSelection, Toggled, }; use alloc::{ string::{String, ToString}, @@ -20,10 +20,35 @@ use core::{fmt, iter::FusedIterator}; use crate::filters::FilterResult; use crate::iterators::{ - FilteredChildren, FollowingFilteredSiblings, FollowingSiblings, LabelledBy, + ChildIds, FilteredChildren, FollowingFilteredSiblings, FollowingSiblings, LabelledBy, PrecedingFilteredSiblings, PrecedingSiblings, }; -use crate::tree::State as TreeState; +use crate::tree::{State as TreeState, TreeIndex}; + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct NodeId(TreeIndex, LocalNodeId); + +impl NodeId { + pub(crate) fn new(local_id: LocalNodeId, tree_index: TreeIndex) -> Self { + Self(tree_index, local_id) + } + + pub(crate) fn with_same_tree(&self, local_id: LocalNodeId) -> Self { + Self(self.0, local_id) + } + + pub(crate) fn to_components(self) -> (LocalNodeId, TreeIndex) { + (self.1, self.0) + } +} + +impl From for u128 { + fn from(id: NodeId) -> Self { + let tree_index = id.0 .0 as u128; + let local_id = id.1 .0 as u128; + (local_id << 64) | tree_index + } +} #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub(crate) struct ParentAndIndex(pub(crate) NodeId, pub(crate) usize); @@ -64,6 +89,11 @@ impl<'a> Node<'a> { self.id() == self.tree_state.root_id() } + /// Returns true if this node is a graft node (has a tree_id property set). + pub fn is_graft(&self) -> bool { + self.state.data.tree_id().is_some() + } + pub fn parent_id(&self) -> Option { self.state .parent_and_index @@ -95,14 +125,28 @@ impl<'a> Node<'a> { }) } + /// Returns the single child of a graft node (the subtree root), if available. + fn graft_child_id(&self) -> Option { + self.state + .data + .tree_id() + .and_then(|tree_id| self.tree_state.subtree_root(tree_id)) + } + pub fn child_ids( &self, ) -> impl DoubleEndedIterator + ExactSizeIterator + FusedIterator - + '_ { - let data = &self.state.data; - data.children().iter().copied() + + 'a { + if self.is_graft() { + ChildIds::Graft(self.graft_child_id()) + } else { + ChildIds::Normal { + parent_id: self.id, + children: self.state.data.children().iter(), + } + } } pub fn children( @@ -112,10 +156,8 @@ impl<'a> Node<'a> { + FusedIterator> + 'a { let state = self.tree_state; - let data = &self.state.data; - data.children() - .iter() - .map(move |id| state.node_by_id(*id).unwrap()) + self.child_ids() + .map(move |id| state.node_by_id(id).unwrap()) } pub fn filtered_children( @@ -632,6 +674,7 @@ impl<'a> Node<'a> { LabelledBy::Explicit { ids: explicit.iter(), tree_state: self.tree_state, + node_id: self.id, } } } @@ -817,10 +860,11 @@ impl<'a> Node<'a> { &self, ) -> impl DoubleEndedIterator> + FusedIterator> + 'a { let state = self.tree_state; + let id = self.id; let data = &self.state.data; data.controls() .iter() - .map(move |id| state.node_by_id(*id).unwrap()) + .map(move |control_id| state.node_by_id(id.with_same_tree(*control_id)).unwrap()) } pub fn raw_text_selection(&self) -> Option<&TextSelection> { @@ -944,7 +988,7 @@ impl fmt::Write for SpacePrefixingWriter { mod tests { use accesskit::{ Action, Node, NodeId, Point, Rect, Role, TextDirection, TextPosition, TextSelection, Tree, - TreeUpdate, + TreeId, TreeUpdate, }; use alloc::vec; @@ -957,26 +1001,26 @@ mod tests { assert_eq!( Some((ROOT_ID, 0)), tree.state() - .node_by_id(PARAGRAPH_0_ID) + .node_by_id(nid(PARAGRAPH_0_ID)) .unwrap() .parent_and_index() - .map(|(parent, index)| (parent.id(), index)) + .map(|(parent, index)| (parent.id().to_components().0, index)) ); assert_eq!( Some((PARAGRAPH_0_ID, 0)), tree.state() - .node_by_id(LABEL_0_0_IGNORED_ID) + .node_by_id(nid(LABEL_0_0_IGNORED_ID)) .unwrap() .parent_and_index() - .map(|(parent, index)| (parent.id(), index)) + .map(|(parent, index)| (parent.id().to_components().0, index)) ); assert_eq!( Some((ROOT_ID, 1)), tree.state() - .node_by_id(PARAGRAPH_1_IGNORED_ID) + .node_by_id(nid(PARAGRAPH_1_IGNORED_ID)) .unwrap() .parent_and_index() - .map(|(parent, index)| (parent.id(), index)) + .map(|(parent, index)| (parent.id().to_components().0, index)) ); } @@ -985,20 +1029,28 @@ mod tests { let tree = test_tree(); assert_eq!( LABEL_0_0_IGNORED_ID, - tree.state().root().deepest_first_child().unwrap().id() + tree.state() + .root() + .deepest_first_child() + .unwrap() + .id() + .to_components() + .0 ); assert_eq!( LABEL_0_0_IGNORED_ID, tree.state() - .node_by_id(PARAGRAPH_0_ID) + .node_by_id(nid(PARAGRAPH_0_ID)) .unwrap() .deepest_first_child() .unwrap() .id() + .to_components() + .0 ); assert!(tree .state() - .node_by_id(LABEL_0_0_IGNORED_ID) + .node_by_id(nid(LABEL_0_0_IGNORED_ID)) .unwrap() .deepest_first_child() .is_none()); @@ -1010,11 +1062,13 @@ mod tests { assert_eq!( ROOT_ID, tree.state() - .node_by_id(LABEL_1_1_ID) + .node_by_id(nid(LABEL_1_1_ID)) .unwrap() .filtered_parent(&test_tree_filter) .unwrap() .id() + .to_components() + .0 ); assert!(tree .state() @@ -1033,16 +1087,18 @@ mod tests { .deepest_first_filtered_child(&test_tree_filter) .unwrap() .id() + .to_components() + .0 ); assert!(tree .state() - .node_by_id(PARAGRAPH_0_ID) + .node_by_id(nid(PARAGRAPH_0_ID)) .unwrap() .deepest_first_filtered_child(&test_tree_filter) .is_none()); assert!(tree .state() - .node_by_id(LABEL_0_0_IGNORED_ID) + .node_by_id(nid(LABEL_0_0_IGNORED_ID)) .unwrap() .deepest_first_filtered_child(&test_tree_filter) .is_none()); @@ -1053,20 +1109,28 @@ mod tests { let tree = test_tree(); assert_eq!( EMPTY_CONTAINER_3_3_IGNORED_ID, - tree.state().root().deepest_last_child().unwrap().id() + tree.state() + .root() + .deepest_last_child() + .unwrap() + .id() + .to_components() + .0 ); assert_eq!( EMPTY_CONTAINER_3_3_IGNORED_ID, tree.state() - .node_by_id(PARAGRAPH_3_IGNORED_ID) + .node_by_id(nid(PARAGRAPH_3_IGNORED_ID)) .unwrap() .deepest_last_child() .unwrap() .id() + .to_components() + .0 ); assert!(tree .state() - .node_by_id(BUTTON_3_2_ID) + .node_by_id(nid(BUTTON_3_2_ID)) .unwrap() .deepest_last_child() .is_none()); @@ -1082,25 +1146,29 @@ mod tests { .deepest_last_filtered_child(&test_tree_filter) .unwrap() .id() + .to_components() + .0 ); assert_eq!( BUTTON_3_2_ID, tree.state() - .node_by_id(PARAGRAPH_3_IGNORED_ID) + .node_by_id(nid(PARAGRAPH_3_IGNORED_ID)) .unwrap() .deepest_last_filtered_child(&test_tree_filter) .unwrap() .id() + .to_components() + .0 ); assert!(tree .state() - .node_by_id(BUTTON_3_2_ID) + .node_by_id(nid(BUTTON_3_2_ID)) .unwrap() .deepest_last_filtered_child(&test_tree_filter) .is_none()); assert!(tree .state() - .node_by_id(PARAGRAPH_0_ID) + .node_by_id(nid(PARAGRAPH_0_ID)) .unwrap() .deepest_last_filtered_child(&test_tree_filter) .is_none()); @@ -1111,36 +1179,40 @@ mod tests { let tree = test_tree(); assert!(tree .state() - .node_by_id(PARAGRAPH_0_ID) + .node_by_id(nid(PARAGRAPH_0_ID)) .unwrap() .is_descendant_of(&tree.state().root())); assert!(tree .state() - .node_by_id(LABEL_0_0_IGNORED_ID) + .node_by_id(nid(LABEL_0_0_IGNORED_ID)) .unwrap() .is_descendant_of(&tree.state().root())); assert!(tree .state() - .node_by_id(LABEL_0_0_IGNORED_ID) + .node_by_id(nid(LABEL_0_0_IGNORED_ID)) .unwrap() - .is_descendant_of(&tree.state().node_by_id(PARAGRAPH_0_ID).unwrap())); + .is_descendant_of(&tree.state().node_by_id(nid(PARAGRAPH_0_ID)).unwrap())); assert!(!tree .state() - .node_by_id(LABEL_0_0_IGNORED_ID) + .node_by_id(nid(LABEL_0_0_IGNORED_ID)) .unwrap() - .is_descendant_of(&tree.state().node_by_id(PARAGRAPH_2_ID).unwrap())); + .is_descendant_of(&tree.state().node_by_id(nid(PARAGRAPH_2_ID)).unwrap())); assert!(!tree .state() - .node_by_id(PARAGRAPH_0_ID) + .node_by_id(nid(PARAGRAPH_0_ID)) .unwrap() - .is_descendant_of(&tree.state().node_by_id(PARAGRAPH_2_ID).unwrap())); + .is_descendant_of(&tree.state().node_by_id(nid(PARAGRAPH_2_ID)).unwrap())); } #[test] fn is_root() { let tree = test_tree(); - assert!(tree.state().node_by_id(ROOT_ID).unwrap().is_root()); - assert!(!tree.state().node_by_id(PARAGRAPH_0_ID).unwrap().is_root()); + assert!(tree.state().node_by_id(nid(ROOT_ID)).unwrap().is_root()); + assert!(!tree + .state() + .node_by_id(nid(PARAGRAPH_0_ID)) + .unwrap() + .is_root()); } #[test] @@ -1148,7 +1220,7 @@ mod tests { let tree = test_tree(); assert!(tree .state() - .node_by_id(ROOT_ID) + .node_by_id(nid(ROOT_ID)) .unwrap() .bounding_box() .is_none()); @@ -1160,7 +1232,7 @@ mod tests { y1: 80.0, }), tree.state() - .node_by_id(PARAGRAPH_1_IGNORED_ID) + .node_by_id(nid(PARAGRAPH_1_IGNORED_ID)) .unwrap() .bounding_box() ); @@ -1172,7 +1244,7 @@ mod tests { y1: 70.0, }), tree.state() - .node_by_id(LABEL_1_1_ID) + .node_by_id(nid(LABEL_1_1_ID)) .unwrap() .bounding_box() ); @@ -1187,14 +1259,14 @@ mod tests { .node_at_point(Point::new(10.0, 40.0), &test_tree_filter) .is_none()); assert_eq!( - Some(LABEL_1_1_ID), + Some(nid(LABEL_1_1_ID)), tree.state() .root() .node_at_point(Point::new(20.0, 50.0), &test_tree_filter) .map(|node| node.id()) ); assert_eq!( - Some(LABEL_1_1_ID), + Some(nid(LABEL_1_1_ID)), tree.state() .root() .node_at_point(Point::new(50.0, 60.0), &test_tree_filter) @@ -1219,10 +1291,14 @@ mod tests { (NodeId(1), Node::new(Role::Button)), ], tree: Some(Tree::new(NodeId(0))), + tree_id: TreeId::ROOT, focus: NodeId(0), }; let tree = crate::Tree::new(update, false); - assert_eq!(None, tree.state().node_by_id(NodeId(1)).unwrap().label()); + assert_eq!( + None, + tree.state().node_by_id(nid(NodeId(1))).unwrap().label() + ); } #[test] @@ -1261,16 +1337,17 @@ mod tests { }), ], tree: Some(Tree::new(NodeId(0))), + tree_id: TreeId::ROOT, focus: NodeId(0), }; let tree = crate::Tree::new(update, false); assert_eq!( Some([LABEL_1, LABEL_2].join(" ")), - tree.state().node_by_id(NodeId(1)).unwrap().label() + tree.state().node_by_id(nid(NodeId(1))).unwrap().label() ); assert_eq!( Some(LABEL_2.into()), - tree.state().node_by_id(NodeId(3)).unwrap().label() + tree.state().node_by_id(nid(NodeId(3))).unwrap().label() ); } @@ -1411,43 +1488,56 @@ mod tests { }), ], tree: Some(Tree::new(ROOT_ID)), + tree_id: TreeId::ROOT, focus: ROOT_ID, }; let tree = crate::Tree::new(update, false); assert_eq!( Some(DEFAULT_BUTTON_LABEL.into()), - tree.state().node_by_id(DEFAULT_BUTTON_ID).unwrap().label() + tree.state() + .node_by_id(nid(DEFAULT_BUTTON_ID)) + .unwrap() + .label() ); assert_eq!( Some(LINK_LABEL.into()), - tree.state().node_by_id(LINK_ID).unwrap().label() + tree.state().node_by_id(nid(LINK_ID)).unwrap().label() ); assert_eq!( Some(CHECKBOX_LABEL.into()), - tree.state().node_by_id(CHECKBOX_ID).unwrap().label() + tree.state().node_by_id(nid(CHECKBOX_ID)).unwrap().label() ); assert_eq!( Some(RADIO_BUTTON_LABEL.into()), - tree.state().node_by_id(RADIO_BUTTON_ID).unwrap().label() + tree.state() + .node_by_id(nid(RADIO_BUTTON_ID)) + .unwrap() + .label() ); assert_eq!( Some(MENU_BUTTON_LABEL.into()), - tree.state().node_by_id(MENU_BUTTON_ID).unwrap().label() + tree.state() + .node_by_id(nid(MENU_BUTTON_ID)) + .unwrap() + .label() ); assert_eq!( Some(MENU_ITEM_LABEL.into()), - tree.state().node_by_id(MENU_ITEM_ID).unwrap().label() + tree.state().node_by_id(nid(MENU_ITEM_ID)).unwrap().label() ); assert_eq!( Some(MENU_ITEM_CHECKBOX_LABEL.into()), tree.state() - .node_by_id(MENU_ITEM_CHECKBOX_ID) + .node_by_id(nid(MENU_ITEM_CHECKBOX_ID)) .unwrap() .label() ); assert_eq!( Some(MENU_ITEM_RADIO_LABEL.into()), - tree.state().node_by_id(MENU_ITEM_RADIO_ID).unwrap().label() + tree.state() + .node_by_id(nid(MENU_ITEM_RADIO_ID)) + .unwrap() + .label() ); } @@ -1506,13 +1596,14 @@ mod tests { }), ], tree: Some(Tree::new(ROOT_ID)), + tree_id: TreeId::ROOT, focus: TEXT_INPUT_ID, }; let tree = crate::Tree::new(update, false); assert_eq!( Some(PLACEHOLDER), tree.state() - .node_by_id(TEXT_INPUT_ID) + .node_by_id(nid(TEXT_INPUT_ID)) .unwrap() .placeholder() ); @@ -1574,15 +1665,68 @@ mod tests { }), ], tree: Some(Tree::new(ROOT_ID)), + tree_id: TreeId::ROOT, focus: TEXT_INPUT_ID, }; let tree = crate::Tree::new(update, false); assert_eq!( None, tree.state() - .node_by_id(TEXT_INPUT_ID) + .node_by_id(nid(TEXT_INPUT_ID)) .unwrap() .placeholder() ); } + + mod node_id { + use super::NodeId as LocalNodeId; + use crate::node::NodeId; + use crate::tree::TreeIndex; + + #[test] + fn new_and_to_components_round_trip() { + let node_id = LocalNodeId(42); + let tree_index = TreeIndex(7); + let id = NodeId::new(node_id, tree_index); + let (extracted_node_id, extracted_tree_index) = id.to_components(); + assert_eq!(node_id, extracted_node_id); + assert_eq!(tree_index, extracted_tree_index); + } + + #[test] + fn with_same_tree_preserves_tree_index() { + let original_node_id = LocalNodeId(100); + let tree_index = TreeIndex(5); + let id = NodeId::new(original_node_id, tree_index); + + let new_node_id = LocalNodeId(200); + let new_id = id.with_same_tree(new_node_id); + + let (extracted_node_id, extracted_tree_index) = new_id.to_components(); + assert_eq!(new_node_id, extracted_node_id); + assert_eq!(tree_index, extracted_tree_index); + } + + #[test] + fn into_u128() { + let node_id = LocalNodeId(12345); + let tree_index = TreeIndex(67); + let id = NodeId::new(node_id, tree_index); + let (extracted_node_id, extracted_tree_index) = id.to_components(); + assert_eq!(node_id, extracted_node_id); + assert_eq!(tree_index, extracted_tree_index); + } + + #[test] + fn equality() { + let id1 = NodeId::new(LocalNodeId(1), TreeIndex(2)); + let id2 = NodeId::new(LocalNodeId(1), TreeIndex(2)); + let id3 = NodeId::new(LocalNodeId(1), TreeIndex(3)); + let id4 = NodeId::new(LocalNodeId(2), TreeIndex(2)); + + assert_eq!(id1, id2); + assert_ne!(id1, id3); + assert_ne!(id1, id4); + } + } } diff --git a/consumer/src/text.rs b/consumer/src/text.rs index e1c6f70e..b18e3740 100644 --- a/consumer/src/text.rs +++ b/consumer/src/text.rs @@ -4,13 +4,13 @@ // the LICENSE-MIT file), at your option. use accesskit::{ - Node as NodeData, NodeId, Point, Rect, Role, TextAlign, TextDecoration, TextDirection, + Node as NodeData, Point, Rect, Role, TextAlign, TextDecoration, TextDirection, TextPosition as WeakPosition, TextSelection, VerticalOffset, }; use alloc::{string::String, vec::Vec}; use core::{cmp::Ordering, fmt, iter::FusedIterator}; -use crate::{FilterResult, Node, TreeState}; +use crate::{node::NodeId, FilterResult, Node, TreeState}; #[derive(Clone, Copy, Debug)] pub(crate) struct InnerPosition<'a> { @@ -19,8 +19,8 @@ pub(crate) struct InnerPosition<'a> { } impl<'a> InnerPosition<'a> { - fn upgrade(tree_state: &'a TreeState, weak: WeakPosition) -> Option { - let node = tree_state.node_by_id(weak.node)?; + fn upgrade(tree_state: &'a TreeState, weak: WeakPosition, node_id: NodeId) -> Option { + let node = tree_state.node_by_id(node_id.with_same_tree(weak.node))?; if node.role() != Role::TextRun { return None; } @@ -34,8 +34,12 @@ impl<'a> InnerPosition<'a> { }) } - fn clamped_upgrade(tree_state: &'a TreeState, weak: WeakPosition) -> Option { - let node = tree_state.node_by_id(weak.node)?; + fn clamped_upgrade( + tree_state: &'a TreeState, + weak: WeakPosition, + node_id: NodeId, + ) -> Option { + let node = tree_state.node_by_id(node_id.with_same_tree(weak.node))?; if node.role() != Role::TextRun { return None; } @@ -111,7 +115,10 @@ impl<'a> InnerPosition<'a> { fn line_start(&self) -> Self { let mut node = self.node; while let Some(id) = node.data().previous_on_line() { - node = node.tree_state.node_by_id(id).unwrap(); + node = node + .tree_state + .node_by_id(node.id.with_same_tree(id)) + .unwrap(); } Self { node, @@ -122,7 +129,10 @@ impl<'a> InnerPosition<'a> { fn line_end(&self) -> Self { let mut node = self.node; while let Some(id) = node.data().next_on_line() { - node = node.tree_state.node_by_id(id).unwrap(); + node = node + .tree_state + .node_by_id(node.id.with_same_tree(id)) + .unwrap(); } Self { node, @@ -131,8 +141,9 @@ impl<'a> InnerPosition<'a> { } pub(crate) fn downgrade(&self) -> WeakPosition { + let (local_node_id, _) = self.node.id.to_components(); WeakPosition { - node: self.node.id(), + node: local_node_id, character_index: self.character_index, } } @@ -905,8 +916,8 @@ impl WeakRange { pub fn upgrade<'a>(&self, tree_state: &'a TreeState) -> Option> { let node = self.upgrade_node(tree_state)?; - let start = InnerPosition::upgrade(tree_state, self.start)?; - let end = InnerPosition::upgrade(tree_state, self.end)?; + let start = InnerPosition::upgrade(tree_state, self.start, self.node_id)?; + let end = InnerPosition::upgrade(tree_state, self.end, self.node_id)?; Some(Range { node, start, end }) } } @@ -978,9 +989,10 @@ macro_rules! inherited_properties { } $(#[cfg(test)] mod $getter { - use accesskit::{Node, NodeId, Role, Tree, TreeUpdate}; + use accesskit::{Node, NodeId, Role, Tree, TreeId, TreeUpdate}; use alloc::vec; use super::RangePropertyValue; + use crate::tests::nid; #[test] fn directly_set() { let update = TreeUpdate { @@ -999,11 +1011,12 @@ macro_rules! inherited_properties { }), ], tree: Some(Tree::new(NodeId(0))), + tree_id: TreeId::ROOT, focus: NodeId(0), }; let tree = crate::Tree::new(update, false); let state = tree.state(); - let node = state.node_by_id(NodeId(0)).unwrap(); + let node = state.node_by_id(nid(NodeId(0))).unwrap(); let pos = node.document_start(); assert_eq!(pos.$getter(), Some($test_value_1)); let range = node.document_range(); @@ -1027,11 +1040,12 @@ macro_rules! inherited_properties { }), ], tree: Some(Tree::new(NodeId(0))), + tree_id: TreeId::ROOT, focus: NodeId(0), }; let tree = crate::Tree::new(update, false); let state = tree.state(); - let node = state.node_by_id(NodeId(0)).unwrap(); + let node = state.node_by_id(nid(NodeId(0))).unwrap(); let pos = node.document_start(); assert_eq!(pos.$getter(), Some($test_value_1)); let range = node.document_range(); @@ -1056,11 +1070,12 @@ macro_rules! inherited_properties { }), ], tree: Some(Tree::new(NodeId(0))), + tree_id: TreeId::ROOT, focus: NodeId(0), }; let tree = crate::Tree::new(update, false); let state = tree.state(); - let node = state.node_by_id(NodeId(0)).unwrap(); + let node = state.node_by_id(nid(NodeId(0))).unwrap(); assert_eq!(node.$getter(), Some($test_value_1)); let pos = node.document_start(); assert_eq!(pos.$getter(), Some($test_value_2)); @@ -1084,11 +1099,12 @@ macro_rules! inherited_properties { }), ], tree: Some(Tree::new(NodeId(0))), + tree_id: TreeId::ROOT, focus: NodeId(0), }; let tree = crate::Tree::new(update, false); let state = tree.state(); - let node = state.node_by_id(NodeId(0)).unwrap(); + let node = state.node_by_id(nid(NodeId(0))).unwrap(); let pos = node.document_start(); assert_eq!(pos.$getter(), None); let range = node.document_range(); @@ -1118,11 +1134,12 @@ macro_rules! inherited_properties { }), ], tree: Some(Tree::new(NodeId(0))), + tree_id: TreeId::ROOT, focus: NodeId(0), }; let tree = crate::Tree::new(update, false); let state = tree.state(); - let node = state.node_by_id(NodeId(0)).unwrap(); + let node = state.node_by_id(nid(NodeId(0))).unwrap(); let range = node.document_range(); assert_eq!(range.$getter(), RangePropertyValue::Mixed); } @@ -1151,11 +1168,12 @@ macro_rules! inherited_properties { }), ], tree: Some(Tree::new(NodeId(0))), + tree_id: TreeId::ROOT, focus: NodeId(0), }; let tree = crate::Tree::new(update, false); let state = tree.state(); - let node = state.node_by_id(NodeId(0)).unwrap(); + let node = state.node_by_id(nid(NodeId(0))).unwrap(); assert_eq!(node.$getter(), Some($test_value_1)); let start = node.document_start(); assert_eq!(start.$getter(), Some($test_value_2)); @@ -1273,16 +1291,21 @@ impl<'a> Node<'a> { } pub fn text_selection(&self) -> Option> { + let id = self.id; self.data().text_selection().map(|selection| { - let anchor = InnerPosition::clamped_upgrade(self.tree_state, selection.anchor).unwrap(); - let focus = InnerPosition::clamped_upgrade(self.tree_state, selection.focus).unwrap(); + let anchor = + InnerPosition::clamped_upgrade(self.tree_state, selection.anchor, id).unwrap(); + let focus = + InnerPosition::clamped_upgrade(self.tree_state, selection.focus, id).unwrap(); Range::new(*self, anchor, focus) }) } pub fn text_selection_anchor(&self) -> Option> { + let id = self.id; self.data().text_selection().map(|selection| { - let anchor = InnerPosition::clamped_upgrade(self.tree_state, selection.anchor).unwrap(); + let anchor = + InnerPosition::clamped_upgrade(self.tree_state, selection.anchor, id).unwrap(); Position { root_node: *self, inner: anchor, @@ -1291,8 +1314,10 @@ impl<'a> Node<'a> { } pub fn text_selection_focus(&self) -> Option> { + let id = self.id; self.data().text_selection().map(|selection| { - let focus = InnerPosition::clamped_upgrade(self.tree_state, selection.focus).unwrap(); + let focus = + InnerPosition::clamped_upgrade(self.tree_state, selection.focus, id).unwrap(); Position { root_node: *self, inner: focus, @@ -1469,13 +1494,14 @@ impl<'a> Node<'a> { #[cfg(test)] mod tests { + use crate::tests::nid; use accesskit::{NodeId, Point, Rect, TextDecoration, TextSelection}; use alloc::vec; // This was originally based on an actual tree produced by egui but // has since been heavily modified by hand to cover various test cases. fn main_multiline_tree(selection: Option) -> crate::Tree { - use accesskit::{Action, Affine, Node, Role, TextDirection, Tree, TreeUpdate}; + use accesskit::{Action, Affine, Node, Role, TextDirection, Tree, TreeId, TreeUpdate}; let update = TreeUpdate { nodes: vec![ @@ -1686,6 +1712,7 @@ mod tests { }), ], tree: Some(Tree::new(NodeId(0))), + tree_id: TreeId::ROOT, focus: NodeId(1), }; @@ -1771,15 +1798,21 @@ mod tests { fn supports_text_ranges() { let tree = main_multiline_tree(None); let state = tree.state(); - assert!(!state.node_by_id(NodeId(0)).unwrap().supports_text_ranges()); - assert!(state.node_by_id(NodeId(1)).unwrap().supports_text_ranges()); + assert!(!state + .node_by_id(nid(NodeId(0))) + .unwrap() + .supports_text_ranges()); + assert!(state + .node_by_id(nid(NodeId(1))) + .unwrap() + .supports_text_ranges()); } #[test] fn multiline_document_range() { let tree = main_multiline_tree(None); let state = tree.state(); - let node = state.node_by_id(NodeId(1)).unwrap(); + let node = state.node_by_id(nid(NodeId(1))).unwrap(); let range = node.document_range(); let start = range.start(); assert!(start.is_word_start()); @@ -1850,7 +1883,7 @@ mod tests { fn multiline_document_range_to_first_format_change() { let tree = main_multiline_tree(None); let state = tree.state(); - let node = state.node_by_id(NodeId(1)).unwrap(); + let node = state.node_by_id(nid(NodeId(1))).unwrap(); let mut range = node.document_range(); range.set_end(range.start().forward_to_format_end()); assert_eq!( @@ -1880,7 +1913,7 @@ mod tests { fn multiline_document_range_from_last_format_change() { let tree = main_multiline_tree(None); let state = tree.state(); - let node = state.node_by_id(NodeId(1)).unwrap(); + let node = state.node_by_id(nid(NodeId(1))).unwrap(); let mut range = node.document_range(); range.set_start(range.end().backward_to_format_start()); assert_eq!( @@ -1922,7 +1955,7 @@ mod tests { fn multiline_end_degenerate_range() { let tree = main_multiline_tree(Some(multiline_end_selection())); let state = tree.state(); - let node = state.node_by_id(NodeId(1)).unwrap(); + let node = state.node_by_id(nid(NodeId(1))).unwrap(); let range = node.text_selection().unwrap(); assert!(range.is_degenerate()); let pos = range.start(); @@ -1948,7 +1981,7 @@ mod tests { fn multiline_wrapped_line_end_range() { let tree = main_multiline_tree(Some(multiline_wrapped_line_end_selection())); let state = tree.state(); - let node = state.node_by_id(NodeId(1)).unwrap(); + let node = state.node_by_id(nid(NodeId(1))).unwrap(); let range = node.text_selection().unwrap(); assert!(range.is_degenerate()); let pos = range.start(); @@ -2010,7 +2043,7 @@ mod tests { fn multiline_find_line_ends_from_middle() { let tree = main_multiline_tree(Some(multiline_second_line_middle_selection())); let state = tree.state(); - let node = state.node_by_id(NodeId(1)).unwrap(); + let node = state.node_by_id(nid(NodeId(1))).unwrap(); let mut range = node.text_selection().unwrap(); assert!(range.is_degenerate()); let pos = range.start(); @@ -2056,7 +2089,7 @@ mod tests { fn multiline_find_wrapped_line_ends_from_middle() { let tree = main_multiline_tree(Some(multiline_first_line_middle_selection())); let state = tree.state(); - let node = state.node_by_id(NodeId(1)).unwrap(); + let node = state.node_by_id(nid(NodeId(1))).unwrap(); let mut range = node.text_selection().unwrap(); assert!(range.is_degenerate()); let pos = range.start(); @@ -2088,7 +2121,7 @@ mod tests { fn multiline_find_paragraph_ends_from_middle() { let tree = main_multiline_tree(Some(multiline_second_line_middle_selection())); let state = tree.state(); - let node = state.node_by_id(NodeId(1)).unwrap(); + let node = state.node_by_id(nid(NodeId(1))).unwrap(); let mut range = node.text_selection().unwrap(); assert!(range.is_degenerate()); let pos = range.start(); @@ -2144,7 +2177,7 @@ mod tests { fn multiline_find_format_ends_from_middle() { let tree = main_multiline_tree(Some(multiline_second_line_middle_selection())); let state = tree.state(); - let node = state.node_by_id(NodeId(1)).unwrap(); + let node = state.node_by_id(nid(NodeId(1))).unwrap(); let mut range = node.text_selection().unwrap(); assert!(range.is_degenerate()); let pos = range.start(); @@ -2172,7 +2205,7 @@ mod tests { fn multiline_find_word_ends_from_middle() { let tree = main_multiline_tree(Some(multiline_second_line_middle_selection())); let state = tree.state(); - let node = state.node_by_id(NodeId(1)).unwrap(); + let node = state.node_by_id(nid(NodeId(1))).unwrap(); let mut range = node.text_selection().unwrap(); assert!(range.is_degenerate()); let pos = range.start(); @@ -2212,7 +2245,7 @@ mod tests { fn text_position_at_point() { let tree = main_multiline_tree(None); let state = tree.state(); - let node = state.node_by_id(NodeId(1)).unwrap(); + let node = state.node_by_id(nid(NodeId(1))).unwrap(); { let pos = node.text_position_at_point(Point::new(8.0, 31.666664123535156)); @@ -2304,7 +2337,7 @@ mod tests { fn to_global_usv_index() { let tree = main_multiline_tree(None); let state = tree.state(); - let node = state.node_by_id(NodeId(1)).unwrap(); + let node = state.node_by_id(nid(NodeId(1))).unwrap(); { let range = node.document_range(); @@ -2329,7 +2362,7 @@ mod tests { fn to_global_utf16_index() { let tree = main_multiline_tree(None); let state = tree.state(); - let node = state.node_by_id(NodeId(1)).unwrap(); + let node = state.node_by_id(nid(NodeId(1))).unwrap(); { let range = node.document_range(); @@ -2354,7 +2387,7 @@ mod tests { fn to_line_index() { let tree = main_multiline_tree(None); let state = tree.state(); - let node = state.node_by_id(NodeId(1)).unwrap(); + let node = state.node_by_id(nid(NodeId(1))).unwrap(); { let range = node.document_range(); @@ -2380,7 +2413,7 @@ mod tests { fn line_range_from_index() { let tree = main_multiline_tree(None); let state = tree.state(); - let node = state.node_by_id(NodeId(1)).unwrap(); + let node = state.node_by_id(nid(NodeId(1))).unwrap(); { let range = node.line_range_from_index(0).unwrap(); @@ -2419,7 +2452,7 @@ mod tests { fn text_position_from_global_usv_index() { let tree = main_multiline_tree(None); let state = tree.state(); - let node = state.node_by_id(NodeId(1)).unwrap(); + let node = state.node_by_id(nid(NodeId(1))).unwrap(); { let pos = node.text_position_from_global_usv_index(0).unwrap(); @@ -2498,7 +2531,7 @@ mod tests { fn text_position_from_global_utf16_index() { let tree = main_multiline_tree(None); let state = tree.state(); - let node = state.node_by_id(NodeId(1)).unwrap(); + let node = state.node_by_id(nid(NodeId(1))).unwrap(); { let pos = node.text_position_from_global_utf16_index(0).unwrap(); @@ -2577,7 +2610,7 @@ mod tests { fn multiline_selection_clamping() { let tree = main_multiline_tree(Some(multiline_past_end_selection())); let state = tree.state(); - let node = state.node_by_id(NodeId(1)).unwrap(); + let node = state.node_by_id(nid(NodeId(1))).unwrap(); let _ = node.text_selection().unwrap(); } diff --git a/consumer/src/tree.rs b/consumer/src/tree.rs index d781e603..91d3fab6 100644 --- a/consumer/src/tree.rs +++ b/consumer/src/tree.rs @@ -3,19 +3,55 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit::{Node as NodeData, NodeId, Tree as TreeData, TreeUpdate}; -use alloc::vec; +use accesskit::{Node as NodeData, NodeId as LocalNodeId, Tree as TreeData, TreeId, TreeUpdate}; +use alloc::{vec, vec::Vec}; use core::fmt; use hashbrown::{HashMap, HashSet}; -use crate::node::{Node, NodeState, ParentAndIndex}; +use crate::node::{Node, NodeId, NodeState, ParentAndIndex}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub(crate) struct TreeIndex(pub(crate) u32); + +#[derive(Debug, Default)] +struct TreeIndexMap { + id_to_index: HashMap, + index_to_id: HashMap, + next: u32, +} + +impl TreeIndexMap { + fn get_index(&mut self, id: TreeId) -> TreeIndex { + *self.id_to_index.entry(id).or_insert_with(|| { + let tree_index = TreeIndex(self.next); + self.next += 1; + self.index_to_id.insert(tree_index, id); + tree_index + }) + } + + fn get_id(&self, index: TreeIndex) -> Option { + self.index_to_id.get(&index).copied() + } +} + +/// State for a subtree, including its root node and current focus. +#[derive(Clone, Debug)] +pub(crate) struct SubtreeState { + pub(crate) root: NodeId, + pub(crate) focus: NodeId, +} #[derive(Clone, Debug)] pub struct State { pub(crate) nodes: HashMap, pub(crate) data: TreeData, + pub(crate) root: NodeId, pub(crate) focus: NodeId, is_host_focused: bool, + pub(crate) subtrees: HashMap, + pub(crate) graft_parents: HashMap, } #[derive(Default)] @@ -27,12 +63,44 @@ struct InternalChanges { impl State { fn validate_global(&self) { - if !self.nodes.contains_key(&self.data.root) { + if !self.nodes.contains_key(&self.root) { panic!("Root ID {:?} is not in the node list", self.data.root); } if !self.nodes.contains_key(&self.focus) { - panic!("Focused ID {:?} is not in the node list", self.focus); + panic!( + "Focused ID {:?} is not in the node list", + self.focus.to_components().0 + ); + } + } + + /// Computes the effective focus by following the graft chain from ROOT. + /// If ROOT's focus is on a graft node, follows through to that subtree's focus, + /// and continues recursively until reaching a non-graft node. + fn compute_effective_focus(&self) -> NodeId { + let Some(root_subtree) = self.subtrees.get(&TreeId::ROOT) else { + return self.focus; + }; + + let mut current_focus = root_subtree.focus; + loop { + let Some(node_state) = self.nodes.get(¤t_focus) else { + break; + }; + let Some(subtree_id) = node_state.data.tree_id() else { + break; + }; + let subtree = self.subtrees.get(&subtree_id).unwrap_or_else(|| { + panic!( + "Focus is on graft node {:?} but subtree {:?} does not exist. \ + Graft nodes cannot be focused without their subtree.", + current_focus.to_components().0, + subtree_id + ); + }); + current_focus = subtree.focus; } + current_focus } fn update( @@ -40,28 +108,101 @@ impl State { update: TreeUpdate, is_host_focused: bool, mut changes: Option<&mut InternalChanges>, + tree_index: TreeIndex, ) { + let map_id = |id: LocalNodeId| NodeId::new(id, tree_index); + let mut unreachable = HashSet::new(); let mut seen_child_ids = HashSet::new(); - if let Some(tree) = update.tree { - if tree.root != self.data.root { - unreachable.insert(self.data.root); + let tree_id = update.tree_id; + if tree_id != TreeId::ROOT { + let subtree_exists = self.subtrees.contains_key(&tree_id); + if update.tree.is_some() && !self.graft_parents.contains_key(&tree_id) { + panic!( + "Cannot push subtree {:?}: no graft node exists for this tree. \ + Push the graft node (with tree_id property set) before pushing the subtree.", + tree_id + ); + } + if !subtree_exists && update.tree.is_none() { + panic!( + "Cannot update subtree {:?}: subtree does not exist. \ + The first update for a subtree must include tree data.", + tree_id + ); } - self.data = tree; } - let root = self.data.root; + let new_tree_root = if let Some(tree) = update.tree { + let new_root = map_id(tree.root); + if tree_id == TreeId::ROOT { + if tree.root != self.data.root { + unreachable.insert(self.root); + } + self.root = new_root; + self.data = tree; + } else if let Some(subtree) = self.subtrees.get(&tree_id) { + if subtree.root != new_root { + unreachable.insert(subtree.root); + } + } + Some(new_root) + } else { + None + }; + + let root = new_tree_root + .map(|r| r.to_components().0) + .unwrap_or_else(|| { + self.subtrees + .get(&tree_id) + .map(|s| s.root.to_components().0) + .unwrap_or(self.data.root) + }); + let mut pending_nodes: HashMap = HashMap::new(); let mut pending_children = HashMap::new(); + let mut pending_grafts: HashMap = HashMap::new(); + let mut grafts_to_remove: HashSet = HashSet::new(); + + fn record_graft( + pending_grafts: &mut HashMap, + subtree_id: TreeId, + graft_node_id: NodeId, + ) { + if subtree_id == TreeId::ROOT { + panic!("Cannot graft the root tree"); + } + if let Some(existing_graft) = pending_grafts.get(&subtree_id) { + panic!( + "Subtree {:?} already has a graft parent {:?}, cannot assign to {:?}", + subtree_id, + existing_graft.to_components().0, + graft_node_id.to_components().0 + ); + } + pending_grafts.insert(subtree_id, graft_node_id); + } fn add_node( nodes: &mut HashMap, + pending_grafts: &mut HashMap, changes: &mut Option<&mut InternalChanges>, parent_and_index: Option, id: NodeId, data: NodeData, ) { + if let Some(subtree_id) = data.tree_id() { + if !data.children().is_empty() { + panic!( + "Node {:?} has both tree_id and children. \ + A graft node's only child comes from its subtree.", + id.to_components().0 + ); + } + record_graft(pending_grafts, subtree_id, id); + } let state = NodeState { parent_and_index, data, @@ -72,46 +213,66 @@ impl State { } } - for (node_id, node_data) in update.nodes { + for (local_node_id, node_data) in update.nodes { + let node_id = map_id(local_node_id); unreachable.remove(&node_id); for (child_index, child_id) in node_data.children().iter().enumerate() { - if seen_child_ids.contains(child_id) { + let mapped_child_id = map_id(*child_id); + if !seen_child_ids.insert(mapped_child_id) { panic!("TreeUpdate includes duplicate child {:?}", child_id); } - seen_child_ids.insert(*child_id); - unreachable.remove(child_id); + unreachable.remove(&mapped_child_id); let parent_and_index = ParentAndIndex(node_id, child_index); - if let Some(child_state) = self.nodes.get_mut(child_id) { + if let Some(child_state) = self.nodes.get_mut(&mapped_child_id) { if child_state.parent_and_index != Some(parent_and_index) { child_state.parent_and_index = Some(parent_and_index); if let Some(changes) = &mut changes { - changes.updated_node_ids.insert(*child_id); + changes.updated_node_ids.insert(mapped_child_id); } } - } else if let Some(child_data) = pending_nodes.remove(child_id) { + } else if let Some(child_data) = pending_nodes.remove(&mapped_child_id) { add_node( &mut self.nodes, + &mut pending_grafts, &mut changes, Some(parent_and_index), - *child_id, + mapped_child_id, child_data, ); } else { - pending_children.insert(*child_id, parent_and_index); + pending_children.insert(mapped_child_id, parent_and_index); } } if let Some(node_state) = self.nodes.get_mut(&node_id) { - if node_id == root { + if local_node_id == root { node_state.parent_and_index = None; } for child_id in node_state.data.children().iter() { - if !seen_child_ids.contains(child_id) { - unreachable.insert(*child_id); + let mapped_existing_child_id = map_id(*child_id); + if !seen_child_ids.contains(&mapped_existing_child_id) { + unreachable.insert(mapped_existing_child_id); } } if node_state.data != node_data { + if node_data.tree_id().is_some() && !node_data.children().is_empty() { + panic!( + "Node {:?} has both tree_id and children. \ + A graft node's only child comes from its subtree.", + node_id.to_components().0 + ); + } + let old_tree_id = node_state.data.tree_id(); + let new_tree_id = node_data.tree_id(); + if old_tree_id != new_tree_id { + if let Some(old_subtree_id) = old_tree_id { + grafts_to_remove.insert(old_subtree_id); + } + if let Some(new_subtree_id) = new_tree_id { + record_graft(&mut pending_grafts, new_subtree_id, node_id); + } + } node_state.data.clone_from(&node_data); if let Some(changes) = &mut changes { changes.updated_node_ids.insert(node_id); @@ -120,51 +281,203 @@ impl State { } else if let Some(parent_and_index) = pending_children.remove(&node_id) { add_node( &mut self.nodes, + &mut pending_grafts, &mut changes, Some(parent_and_index), node_id, node_data, ); - } else if node_id == root { - add_node(&mut self.nodes, &mut changes, None, node_id, node_data); + } else if local_node_id == root { + add_node( + &mut self.nodes, + &mut pending_grafts, + &mut changes, + None, + node_id, + node_data, + ); } else { pending_nodes.insert(node_id, node_data); } } if !pending_nodes.is_empty() { - panic!("TreeUpdate includes {} nodes which are neither in the current tree nor a child of another node from the update: {}", pending_nodes.len(), ShortNodeList(&pending_nodes)); + panic!( + "TreeUpdate includes {} nodes which are neither in the current tree nor a child of another node from the update: {}", + pending_nodes.len(), + ShortNodeList(&pending_nodes) + ); } if !pending_children.is_empty() { - panic!("TreeUpdate's nodes include {} children ids which are neither in the current tree nor the ID of another node from the update: {}", pending_children.len(), ShortNodeList(&pending_children)); + panic!( + "TreeUpdate's nodes include {} children ids which are neither in the current tree nor the ID of another node from the update: {}", + pending_children.len(), + ShortNodeList(&pending_children) + ); + } + + let tree_focus = map_id(update.focus); + if let Some(new_root) = new_tree_root { + self.subtrees.insert( + tree_id, + SubtreeState { + root: new_root, + focus: tree_focus, + }, + ); + } else if let Some(subtree) = self.subtrees.get_mut(&tree_id) { + subtree.focus = tree_focus; + } else if tree_id == TreeId::ROOT { + self.subtrees.insert( + tree_id, + SubtreeState { + root: self.root, + focus: tree_focus, + }, + ); } - self.focus = update.focus; self.is_host_focused = is_host_focused; if !unreachable.is_empty() { fn traverse_unreachable( nodes: &mut HashMap, + grafts_to_remove: &mut HashSet, changes: &mut Option<&mut InternalChanges>, seen_child_ids: &HashSet, + new_tree_root: Option, id: NodeId, ) { if let Some(changes) = changes { changes.removed_node_ids.insert(id); } let node = nodes.remove(&id).unwrap(); + if let Some(subtree_id) = node.data.tree_id() { + grafts_to_remove.insert(subtree_id); + } + let (_, tree_index) = id.to_components(); for child_id in node.data.children().iter() { - if !seen_child_ids.contains(child_id) { - traverse_unreachable(nodes, changes, seen_child_ids, *child_id); + let child_node_id = NodeId::new(*child_id, tree_index); + if !seen_child_ids.contains(&child_node_id) + && new_tree_root != Some(child_node_id) + { + traverse_unreachable( + nodes, + grafts_to_remove, + changes, + seen_child_ids, + new_tree_root, + child_node_id, + ); } } } for id in unreachable { - traverse_unreachable(&mut self.nodes, &mut changes, &seen_child_ids, id); + traverse_unreachable( + &mut self.nodes, + &mut grafts_to_remove, + &mut changes, + &seen_child_ids, + new_tree_root, + id, + ); + } + } + + fn traverse_subtree( + nodes: &mut HashMap, + subtrees_to_remove: &mut Vec, + subtrees_queued: &mut HashSet, + changes: &mut Option<&mut InternalChanges>, + id: NodeId, + ) { + let Some(node) = nodes.remove(&id) else { + return; + }; + if let Some(changes) = changes { + changes.removed_node_ids.insert(id); + } + if let Some(nested_subtree_id) = node.data.tree_id() { + if subtrees_queued.insert(nested_subtree_id) { + subtrees_to_remove.push(nested_subtree_id); + } + } + let (_, tree_index) = id.to_components(); + for child_id in node.data.children().iter() { + traverse_subtree( + nodes, + subtrees_to_remove, + subtrees_queued, + changes, + NodeId::new(*child_id, tree_index), + ); + } + } + + let mut subtrees_queued: HashSet = grafts_to_remove; + let mut subtrees_to_remove: Vec = subtrees_queued.iter().copied().collect(); + let mut i = 0; + while i < subtrees_to_remove.len() { + let subtree_id = subtrees_to_remove[i]; + i += 1; + + if self.graft_parents.remove(&subtree_id).is_none() { + continue; + } + + if pending_grafts.contains_key(&subtree_id) { + continue; + } + if let Some(subtree) = self.subtrees.remove(&subtree_id) { + traverse_subtree( + &mut self.nodes, + &mut subtrees_to_remove, + &mut subtrees_queued, + &mut changes, + subtree.root, + ); + } + } + + for (subtree_id, node_id) in pending_grafts { + if let Some(&existing_graft) = self.graft_parents.get(&subtree_id) { + panic!( + "Subtree {:?} already has a graft parent {:?}, cannot assign to {:?}", + subtree_id, + existing_graft.to_components().0, + node_id.to_components().0 + ); + } + self.graft_parents.insert(subtree_id, node_id); + if let Some(subtree) = self.subtrees.get(&subtree_id) { + let subtree_root_id = subtree.root; + if let Some(root_state) = self.nodes.get_mut(&subtree_root_id) { + root_state.parent_and_index = Some(ParentAndIndex(node_id, 0)); + if let Some(changes) = &mut changes { + if !changes.added_node_ids.contains(&subtree_root_id) { + changes.updated_node_ids.insert(subtree_root_id); + } + } + } + } + } + + if let Some(new_root_id) = new_tree_root { + if let Some(&graft_node_id) = self.graft_parents.get(&tree_id) { + if let Some(root_state) = self.nodes.get_mut(&new_root_id) { + root_state.parent_and_index = Some(ParentAndIndex(graft_node_id, 0)); + if let Some(changes) = &mut changes { + if !changes.added_node_ids.contains(&new_root_id) { + changes.updated_node_ids.insert(new_root_id); + } + } + } } } + self.focus = self.compute_effective_focus(); + self.validate_global(); } @@ -173,16 +486,18 @@ impl State { is_host_focused: bool, changes: Option<&mut InternalChanges>, ) { + let (focus, _) = self.focus.to_components(); let update = TreeUpdate { nodes: vec![], tree: None, - focus: self.focus, + tree_id: TreeId::ROOT, + focus, }; - self.update(update, is_host_focused, changes); + self.update(update, is_host_focused, changes, TreeIndex(0)); } pub fn has_node(&self, id: NodeId) -> bool { - self.nodes.get(&id).is_some() + self.nodes.contains_key(&id) } pub fn node_by_id(&self, id: NodeId) -> Option> { @@ -194,13 +509,18 @@ impl State { } pub fn root_id(&self) -> NodeId { - self.data.root + self.root } pub fn root(&self) -> Node<'_> { self.node_by_id(self.root_id()).unwrap() } + /// Returns the root NodeId of the subtree with the given TreeId, if it exists. + pub fn subtree_root(&self, tree_id: TreeId) -> Option { + self.subtrees.get(&tree_id).map(|s| s.root) + } + pub fn is_host_focused(&self) -> bool { self.is_host_focused } @@ -252,23 +572,35 @@ pub trait ChangeHandler { pub struct Tree { state: State, next_state: State, + tree_index_map: TreeIndexMap, } impl Tree { pub fn new(mut initial_state: TreeUpdate, is_host_focused: bool) -> Self { let Some(tree) = initial_state.tree.take() else { - panic!("Tried to initialize the accessibility tree without a root tree. TreeUpdate::tree must be Some."); + panic!( + "Tried to initialize the accessibility tree without a root tree. TreeUpdate::tree must be Some." + ); }; + if initial_state.tree_id != TreeId::ROOT { + panic!("Cannot initialize with a subtree. TreeUpdate::tree_id must be TreeId::ROOT."); + } + let mut tree_index_map = TreeIndexMap::default(); + let tree_index = tree_index_map.get_index(initial_state.tree_id); let mut state = State { nodes: HashMap::new(), + root: NodeId::new(tree.root, tree_index), data: tree, - focus: initial_state.focus, + focus: NodeId::new(initial_state.focus, tree_index), is_host_focused, + subtrees: HashMap::new(), + graft_parents: HashMap::new(), }; - state.update(initial_state, is_host_focused, None); + state.update(initial_state, is_host_focused, None, tree_index); Self { next_state: state.clone(), state, + tree_index_map, } } @@ -277,9 +609,14 @@ impl Tree { update: TreeUpdate, handler: &mut impl ChangeHandler, ) { + let tree_index = self.tree_index_map.get_index(update.tree_id); let mut changes = InternalChanges::default(); - self.next_state - .update(update, self.state.is_host_focused, Some(&mut changes)); + self.next_state.update( + update, + self.state.is_host_focused, + Some(&mut changes), + tree_index, + ); self.process_changes(changes, handler); } @@ -350,13 +687,28 @@ impl Tree { if self.state.data != self.next_state.data { self.state.data.clone_from(&self.next_state.data); } + self.state.root = self.next_state.root; self.state.focus = self.next_state.focus; self.state.is_host_focused = self.next_state.is_host_focused; + self.state.subtrees.clone_from(&self.next_state.subtrees); + self.state + .graft_parents + .clone_from(&self.next_state.graft_parents); } pub fn state(&self) -> &State { &self.state } + + pub fn locate_node(&self, node_id: NodeId) -> Option<(LocalNodeId, TreeId)> { + if !self.state.has_node(node_id) { + return None; + } + let (local_id, tree_index) = node_id.to_components(); + self.tree_index_map + .get_id(tree_index) + .map(|tree_id| (local_id, tree_id)) + } } struct ShortNodeList<'a, T>(&'a HashMap); @@ -372,7 +724,7 @@ impl fmt::Display for ShortNodeList<'_, T> { if i != 0 { write!(f, ", ")?; } - write!(f, "{id:?}")?; + write!(f, "{:?}", id.to_components().0)?; } if iter.next().is_some() { write!(f, " ...")?; @@ -383,46 +735,124 @@ impl fmt::Display for ShortNodeList<'_, T> { #[cfg(test)] mod tests { - use accesskit::{Node, NodeId, Role, Tree, TreeUpdate}; + use accesskit::{Node, NodeId as LocalNodeId, Role, Tree, TreeId, TreeUpdate, Uuid}; use alloc::{vec, vec::Vec}; + use super::{TreeIndex, TreeIndexMap}; + use crate::node::NodeId; + + struct NoOpHandler; + impl super::ChangeHandler for NoOpHandler { + fn node_added(&mut self, _: &crate::Node) {} + fn node_updated(&mut self, _: &crate::Node, _: &crate::Node) {} + fn focus_moved(&mut self, _: Option<&crate::Node>, _: Option<&crate::Node>) {} + fn node_removed(&mut self, _: &crate::Node) {} + } + + fn node_id(n: u64) -> NodeId { + NodeId::new(LocalNodeId(n), TreeIndex(0)) + } + + #[test] + fn tree_index_map_assigns_sequential_indices() { + let mut map = TreeIndexMap::default(); + let id1 = TreeId::ROOT; + let id2 = TreeId(Uuid::from_u128(1)); + let id3 = TreeId(Uuid::from_u128(2)); + + let index1 = map.get_index(id1); + let index2 = map.get_index(id2); + let index3 = map.get_index(id3); + + assert_eq!(index1, TreeIndex(0)); + assert_eq!(index2, TreeIndex(1)); + assert_eq!(index3, TreeIndex(2)); + } + + #[test] + fn tree_index_map_returns_same_index_for_same_id() { + let mut map = TreeIndexMap::default(); + let id = TreeId::ROOT; + + let index1 = map.get_index(id); + let index2 = map.get_index(id); + + assert_eq!(index1, index2); + } + + #[test] + fn tree_index_map_get_id_returns_correct_id() { + let mut map = TreeIndexMap::default(); + let id1 = TreeId::ROOT; + let id2 = TreeId(Uuid::from_u128(1)); + + let index1 = map.get_index(id1); + let index2 = map.get_index(id2); + + assert_eq!(map.get_id(index1), Some(id1)); + assert_eq!(map.get_id(index2), Some(id2)); + } + + #[test] + fn tree_index_map_get_id_returns_none_for_unknown_index() { + let map = TreeIndexMap::default(); + assert_eq!(map.get_id(TreeIndex(0)), None); + assert_eq!(map.get_id(TreeIndex(999)), None); + } + #[test] fn init_tree_with_root_node() { let update = TreeUpdate { - nodes: vec![(NodeId(0), Node::new(Role::Window))], - tree: Some(Tree::new(NodeId(0))), - focus: NodeId(0), + nodes: vec![(LocalNodeId(0), Node::new(Role::Window))], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), }; let tree = super::Tree::new(update, false); - assert_eq!(NodeId(0), tree.state().root().id()); + assert_eq!(node_id(0), tree.state().root().id()); assert_eq!(Role::Window, tree.state().root().role()); assert!(tree.state().root().parent().is_none()); } + #[test] + #[should_panic( + expected = "Cannot initialize with a subtree. TreeUpdate::tree_id must be TreeId::ROOT." + )] + fn init_tree_with_non_root_tree_id_panics() { + let update = TreeUpdate { + nodes: vec![(LocalNodeId(0), Node::new(Role::Window))], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId(Uuid::from_u128(1)), + focus: LocalNodeId(0), + }; + let _ = super::Tree::new(update, false); + } + #[test] fn root_node_has_children() { let update = TreeUpdate { nodes: vec![ - (NodeId(0), { + (LocalNodeId(0), { let mut node = Node::new(Role::Window); - node.set_children(vec![NodeId(1), NodeId(2)]); + node.set_children(vec![LocalNodeId(1), LocalNodeId(2)]); node }), - (NodeId(1), Node::new(Role::Button)), - (NodeId(2), Node::new(Role::Button)), + (LocalNodeId(1), Node::new(Role::Button)), + (LocalNodeId(2), Node::new(Role::Button)), ], - tree: Some(Tree::new(NodeId(0))), - focus: NodeId(0), + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), }; let tree = super::Tree::new(update, false); let state = tree.state(); assert_eq!( - NodeId(0), - state.node_by_id(NodeId(1)).unwrap().parent().unwrap().id() + node_id(0), + state.node_by_id(node_id(1)).unwrap().parent().unwrap().id() ); assert_eq!( - NodeId(0), - state.node_by_id(NodeId(2)).unwrap().parent().unwrap().id() + node_id(0), + state.node_by_id(node_id(2)).unwrap().parent().unwrap().id() ); assert_eq!(2, state.root().children().count()); } @@ -431,23 +861,25 @@ mod tests { fn add_child_to_root_node() { let root_node = Node::new(Role::Window); let first_update = TreeUpdate { - nodes: vec![(NodeId(0), root_node.clone())], - tree: Some(Tree::new(NodeId(0))), - focus: NodeId(0), + nodes: vec![(LocalNodeId(0), root_node.clone())], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), }; let mut tree = super::Tree::new(first_update, false); assert_eq!(0, tree.state().root().children().count()); let second_update = TreeUpdate { nodes: vec![ - (NodeId(0), { + (LocalNodeId(0), { let mut node = root_node; - node.push_child(NodeId(1)); + node.push_child(LocalNodeId(1)); node }), - (NodeId(1), Node::new(Role::RootWebArea)), + (LocalNodeId(1), Node::new(Role::RootWebArea)), ], tree: None, - focus: NodeId(0), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), }; struct Handler { got_new_child_node: bool, @@ -458,16 +890,16 @@ mod tests { } impl super::ChangeHandler for Handler { fn node_added(&mut self, node: &crate::Node) { - if node.id() == NodeId(1) { + if node.id() == node_id(1) { self.got_new_child_node = true; return; } unexpected_change(); } fn node_updated(&mut self, old_node: &crate::Node, new_node: &crate::Node) { - if new_node.id() == NodeId(0) + if new_node.id() == node_id(0) && old_node.data().children().is_empty() - && new_node.data().children() == [NodeId(1)] + && new_node.data().children() == [LocalNodeId(1)] { self.got_updated_root_node = true; return; @@ -494,10 +926,10 @@ mod tests { assert!(handler.got_updated_root_node); let state = tree.state(); assert_eq!(1, state.root().children().count()); - assert_eq!(NodeId(1), state.root().children().next().unwrap().id()); + assert_eq!(node_id(1), state.root().children().next().unwrap().id()); assert_eq!( - NodeId(0), - state.node_by_id(NodeId(1)).unwrap().parent().unwrap().id() + node_id(0), + state.node_by_id(node_id(1)).unwrap().parent().unwrap().id() ); } @@ -506,22 +938,24 @@ mod tests { let root_node = Node::new(Role::Window); let first_update = TreeUpdate { nodes: vec![ - (NodeId(0), { + (LocalNodeId(0), { let mut node = root_node.clone(); - node.push_child(NodeId(1)); + node.push_child(LocalNodeId(1)); node }), - (NodeId(1), Node::new(Role::RootWebArea)), + (LocalNodeId(1), Node::new(Role::RootWebArea)), ], - tree: Some(Tree::new(NodeId(0))), - focus: NodeId(0), + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), }; let mut tree = super::Tree::new(first_update, false); assert_eq!(1, tree.state().root().children().count()); let second_update = TreeUpdate { - nodes: vec![(NodeId(0), root_node)], + nodes: vec![(LocalNodeId(0), root_node)], tree: None, - focus: NodeId(0), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), }; struct Handler { got_updated_root_node: bool, @@ -535,8 +969,8 @@ mod tests { unexpected_change(); } fn node_updated(&mut self, old_node: &crate::Node, new_node: &crate::Node) { - if new_node.id() == NodeId(0) - && old_node.data().children() == [NodeId(1)] + if new_node.id() == node_id(0) + && old_node.data().children() == [LocalNodeId(1)] && new_node.data().children().is_empty() { self.got_updated_root_node = true; @@ -552,7 +986,7 @@ mod tests { unexpected_change(); } fn node_removed(&mut self, node: &crate::Node) { - if node.id() == NodeId(1) { + if node.id() == node_id(1) { self.got_removed_child_node = true; return; } @@ -567,30 +1001,32 @@ mod tests { assert!(handler.got_updated_root_node); assert!(handler.got_removed_child_node); assert_eq!(0, tree.state().root().children().count()); - assert!(tree.state().node_by_id(NodeId(1)).is_none()); + assert!(tree.state().node_by_id(node_id(1)).is_none()); } #[test] fn move_focus_between_siblings() { let first_update = TreeUpdate { nodes: vec![ - (NodeId(0), { + (LocalNodeId(0), { let mut node = Node::new(Role::Window); - node.set_children(vec![NodeId(1), NodeId(2)]); + node.set_children(vec![LocalNodeId(1), LocalNodeId(2)]); node }), - (NodeId(1), Node::new(Role::Button)), - (NodeId(2), Node::new(Role::Button)), + (LocalNodeId(1), Node::new(Role::Button)), + (LocalNodeId(2), Node::new(Role::Button)), ], - tree: Some(Tree::new(NodeId(0))), - focus: NodeId(1), + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(1), }; let mut tree = super::Tree::new(first_update, true); - assert!(tree.state().node_by_id(NodeId(1)).unwrap().is_focused()); + assert!(tree.state().node_by_id(node_id(1)).unwrap().is_focused()); let second_update = TreeUpdate { nodes: vec![], tree: None, - focus: NodeId(2), + tree_id: TreeId::ROOT, + focus: LocalNodeId(2), }; struct Handler { got_old_focus_node_update: bool, @@ -605,16 +1041,16 @@ mod tests { unexpected_change(); } fn node_updated(&mut self, old_node: &crate::Node, new_node: &crate::Node) { - if old_node.id() == NodeId(1) - && new_node.id() == NodeId(1) + if old_node.id() == node_id(1) + && new_node.id() == node_id(1) && old_node.is_focused() && !new_node.is_focused() { self.got_old_focus_node_update = true; return; } - if old_node.id() == NodeId(2) - && new_node.id() == NodeId(2) + if old_node.id() == node_id(2) + && new_node.id() == node_id(2) && !old_node.is_focused() && new_node.is_focused() { @@ -629,7 +1065,7 @@ mod tests { new_node: Option<&crate::Node>, ) { if let (Some(old_node), Some(new_node)) = (old_node, new_node) { - if old_node.id() == NodeId(1) && new_node.id() == NodeId(2) { + if old_node.id() == node_id(1) && new_node.id() == node_id(2) { self.got_focus_change = true; return; } @@ -649,8 +1085,8 @@ mod tests { assert!(handler.got_old_focus_node_update); assert!(handler.got_new_focus_node_update); assert!(handler.got_focus_change); - assert!(tree.state().node_by_id(NodeId(2)).unwrap().is_focused()); - assert!(!tree.state().node_by_id(NodeId(1)).unwrap().is_focused()); + assert!(tree.state().node_by_id(node_id(2)).unwrap().is_focused()); + assert!(!tree.state().node_by_id(node_id(1)).unwrap().is_focused()); } #[test] @@ -658,33 +1094,35 @@ mod tests { let child_node = Node::new(Role::Button); let first_update = TreeUpdate { nodes: vec![ - (NodeId(0), { + (LocalNodeId(0), { let mut node = Node::new(Role::Window); - node.set_children(vec![NodeId(1)]); + node.set_children(vec![LocalNodeId(1)]); node }), - (NodeId(1), { + (LocalNodeId(1), { let mut node = child_node.clone(); node.set_label("foo"); node }), ], - tree: Some(Tree::new(NodeId(0))), - focus: NodeId(0), + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), }; let mut tree = super::Tree::new(first_update, false); assert_eq!( Some("foo".into()), - tree.state().node_by_id(NodeId(1)).unwrap().label() + tree.state().node_by_id(node_id(1)).unwrap().label() ); let second_update = TreeUpdate { - nodes: vec![(NodeId(1), { + nodes: vec![(LocalNodeId(1), { let mut node = child_node; node.set_label("bar"); node })], tree: None, - focus: NodeId(0), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), }; struct Handler { got_updated_child_node: bool, @@ -697,7 +1135,7 @@ mod tests { unexpected_change(); } fn node_updated(&mut self, old_node: &crate::Node, new_node: &crate::Node) { - if new_node.id() == NodeId(1) + if new_node.id() == node_id(1) && old_node.label() == Some("foo".into()) && new_node.label() == Some("bar".into()) { @@ -724,7 +1162,7 @@ mod tests { assert!(handler.got_updated_child_node); assert_eq!( Some("bar".into()), - tree.state().node_by_id(NodeId(1)).unwrap().label() + tree.state().node_by_id(node_id(1)).unwrap().label() ); } @@ -736,19 +1174,20 @@ mod tests { fn no_change_update() { let update = TreeUpdate { nodes: vec![ - (NodeId(0), { + (LocalNodeId(0), { let mut node = Node::new(Role::Window); - node.set_children(vec![NodeId(1)]); + node.set_children(vec![LocalNodeId(1)]); node }), - (NodeId(1), { + (LocalNodeId(1), { let mut node = Node::new(Role::Button); node.set_label("foo"); node }), ], - tree: Some(Tree::new(NodeId(0))), - focus: NodeId(0), + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), }; let mut tree = super::Tree::new(update.clone(), false); struct Handler; @@ -794,16 +1233,16 @@ mod tests { unexpected_change(); } fn node_updated(&mut self, old_node: &crate::Node, new_node: &crate::Node) { - if new_node.id() == NodeId(0) - && old_node.child_ids().collect::>() == vec![NodeId(1)] - && new_node.child_ids().collect::>() == vec![NodeId(2)] + if new_node.id() == node_id(0) + && old_node.child_ids().collect::>() == vec![node_id(1)] + && new_node.child_ids().collect::>() == vec![node_id(2)] { self.got_updated_root = true; return; } - if new_node.id() == NodeId(2) - && old_node.parent_id() == Some(NodeId(1)) - && new_node.parent_id() == Some(NodeId(0)) + if new_node.id() == node_id(2) + && old_node.parent_id() == Some(node_id(1)) + && new_node.parent_id() == Some(node_id(0)) { self.got_updated_child = true; return; @@ -818,7 +1257,7 @@ mod tests { unexpected_change(); } fn node_removed(&mut self, node: &crate::Node) { - if node.id() == NodeId(1) { + if node.id() == node_id(1) { self.got_removed_container = true; return; } @@ -827,20 +1266,21 @@ mod tests { } let mut root = Node::new(Role::Window); - root.set_children([NodeId(1)]); + root.set_children([LocalNodeId(1)]); let mut container = Node::new(Role::GenericContainer); - container.set_children([NodeId(2)]); + container.set_children([LocalNodeId(2)]); let update = TreeUpdate { nodes: vec![ - (NodeId(0), root.clone()), - (NodeId(1), container), - (NodeId(2), Node::new(Role::Button)), + (LocalNodeId(0), root.clone()), + (LocalNodeId(1), container), + (LocalNodeId(2), Node::new(Role::Button)), ], - tree: Some(Tree::new(NodeId(0))), - focus: NodeId(0), + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), }; let mut tree = crate::Tree::new(update, false); - root.set_children([NodeId(2)]); + root.set_children([LocalNodeId(2)]); let mut handler = Handler { got_updated_root: false, got_updated_child: false, @@ -848,9 +1288,10 @@ mod tests { }; tree.update_and_process_changes( TreeUpdate { - nodes: vec![(NodeId(0), root)], + nodes: vec![(LocalNodeId(0), root)], tree: None, - focus: NodeId(0), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), }, &mut handler, ); @@ -859,16 +1300,1571 @@ mod tests { assert!(handler.got_removed_container); assert_eq!( tree.state() - .node_by_id(NodeId(0)) + .node_by_id(node_id(0)) .unwrap() .child_ids() .collect::>(), - vec![NodeId(2)] + vec![node_id(2)] + ); + assert!(tree.state().node_by_id(node_id(1)).is_none()); + assert_eq!( + tree.state().node_by_id(node_id(2)).unwrap().parent_id(), + Some(node_id(0)) + ); + } + + fn subtree_id() -> TreeId { + TreeId(Uuid::from_u128(1)) + } + + #[test] + fn graft_node_tracks_subtree() { + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let tree = super::Tree::new(update, false); + assert_eq!( + tree.state().graft_parents.get(&subtree_id()), + Some(&node_id(1)) + ); + } + + #[test] + #[should_panic(expected = "already has a graft parent")] + fn duplicate_graft_parent_panics() { + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1), LocalNodeId(2)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + (LocalNodeId(2), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let _ = super::Tree::new(update, false); + } + + #[test] + fn reparent_subtree_by_removing_old_graft() { + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1), LocalNodeId(2)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + (LocalNodeId(2), Node::new(Role::GenericContainer)), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let mut tree = super::Tree::new(update, false); + assert_eq!( + tree.state().graft_parents.get(&subtree_id()), + Some(&node_id(1)) + ); + + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(2)]); + node + }), + (LocalNodeId(2), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + ], + tree: None, + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(update, &mut NoOpHandler); + assert_eq!( + tree.state().graft_parents.get(&subtree_id()), + Some(&node_id(2)) + ); + } + + #[test] + fn reparent_subtree_by_clearing_old_graft_tree_id() { + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1), LocalNodeId(2)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + (LocalNodeId(2), Node::new(Role::GenericContainer)), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let mut tree = super::Tree::new(update, false); + assert_eq!( + tree.state().graft_parents.get(&subtree_id()), + Some(&node_id(1)) ); - assert!(tree.state().node_by_id(NodeId(1)).is_none()); + + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(1), Node::new(Role::GenericContainer)), + (LocalNodeId(2), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + ], + tree: None, + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(update, &mut NoOpHandler); assert_eq!( - tree.state().node_by_id(NodeId(2)).unwrap().parent_id(), - Some(NodeId(0)) + tree.state().graft_parents.get(&subtree_id()), + Some(&node_id(2)) ); } + + #[test] + #[should_panic(expected = "already has a graft parent")] + fn duplicate_graft_parent_on_update_panics() { + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1), LocalNodeId(2)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + (LocalNodeId(2), Node::new(Role::GenericContainer)), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let mut tree = super::Tree::new(update, false); + + let update = TreeUpdate { + nodes: vec![(LocalNodeId(2), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + })], + tree: None, + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(update, &mut NoOpHandler); + } + + #[test] + #[should_panic(expected = "Cannot graft the root tree")] + fn graft_root_tree_panics() { + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(TreeId::ROOT); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let _ = super::Tree::new(update, false); + } + + #[test] + #[should_panic(expected = "Cannot graft the root tree")] + fn graft_root_tree_on_update_panics() { + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), Node::new(Role::GenericContainer)), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let mut tree = super::Tree::new(update, false); + + let update = TreeUpdate { + nodes: vec![(LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(TreeId::ROOT); + node + })], + tree: None, + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(update, &mut NoOpHandler); + } + + fn subtree_node_id(id: u64) -> NodeId { + NodeId::new(LocalNodeId(id), TreeIndex(1)) + } + + #[test] + fn subtree_root_parent_is_graft_when_graft_exists_first() { + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let mut tree = super::Tree::new(update, false); + + let subtree_update = TreeUpdate { + nodes: vec![(LocalNodeId(0), Node::new(Role::Document))], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: subtree_id(), + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(subtree_update, &mut NoOpHandler); + + let subtree_root = tree.state().node_by_id(subtree_node_id(0)).unwrap(); + assert_eq!(subtree_root.parent_id(), Some(node_id(1))); + + let graft_node = tree.state().node_by_id(node_id(1)).unwrap(); + let children: Vec<_> = graft_node.child_ids().collect(); + assert_eq!(children.len(), 1); + assert_eq!(children[0], subtree_node_id(0)); + } + + #[test] + #[should_panic(expected = "no graft node exists for this tree")] + fn subtree_push_without_graft_panics() { + let update = TreeUpdate { + nodes: vec![(LocalNodeId(0), Node::new(Role::Window))], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let mut tree = super::Tree::new(update, false); + + let subtree_update = TreeUpdate { + nodes: vec![(LocalNodeId(0), Node::new(Role::Document))], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: subtree_id(), + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(subtree_update, &mut NoOpHandler); + } + + #[test] + #[should_panic(expected = "subtree does not exist")] + fn subtree_update_without_tree_data_panics() { + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let mut tree = super::Tree::new(update, false); + + let subtree_update = TreeUpdate { + nodes: vec![(LocalNodeId(0), Node::new(Role::Document))], + tree: None, + tree_id: subtree_id(), + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(subtree_update, &mut NoOpHandler); + } + + #[test] + fn subtree_nodes_removed_when_graft_removed() { + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let mut tree = super::Tree::new(update, false); + + let subtree_update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Document); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(nested_subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: subtree_id(), + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(subtree_update, &mut NoOpHandler); + + let nested_update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Document); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), Node::new(Role::Paragraph)), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: nested_subtree_id(), + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(nested_update, &mut NoOpHandler); + + assert!(tree.state().node_by_id(subtree_node_id(0)).is_some()); + assert!(tree.state().node_by_id(subtree_node_id(1)).is_some()); + assert!(tree.state().node_by_id(nested_subtree_node_id(0)).is_some()); + assert!(tree.state().node_by_id(nested_subtree_node_id(1)).is_some()); + + let update = TreeUpdate { + nodes: vec![(LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![]); + node + })], + tree: None, + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(update, &mut NoOpHandler); + + assert!(tree.state().node_by_id(subtree_node_id(0)).is_none()); + assert!(tree.state().node_by_id(subtree_node_id(1)).is_none()); + assert!(tree.state().node_by_id(nested_subtree_node_id(0)).is_none()); + assert!(tree.state().node_by_id(nested_subtree_node_id(1)).is_none()); + assert!(tree.state().subtrees.get(&subtree_id()).is_none()); + assert!(tree.state().subtrees.get(&nested_subtree_id()).is_none()); + } + + #[test] + fn subtree_nodes_removed_when_graft_tree_id_cleared() { + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let mut tree = super::Tree::new(update, false); + + let subtree_update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Document); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), Node::new(Role::Paragraph)), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: subtree_id(), + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(subtree_update, &mut NoOpHandler); + + assert!(tree.state().node_by_id(subtree_node_id(0)).is_some()); + assert!(tree.state().node_by_id(subtree_node_id(1)).is_some()); + + let update = TreeUpdate { + nodes: vec![(LocalNodeId(1), Node::new(Role::GenericContainer))], + tree: None, + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(update, &mut NoOpHandler); + + assert!(tree.state().node_by_id(subtree_node_id(0)).is_none()); + assert!(tree.state().node_by_id(subtree_node_id(1)).is_none()); + assert!(tree.state().subtrees.get(&subtree_id()).is_none()); + } + + #[test] + fn graft_node_has_no_children_when_subtree_not_pushed() { + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let tree = super::Tree::new(update, false); + + let graft_node = tree.state().node_by_id(node_id(1)).unwrap(); + assert_eq!(graft_node.child_ids().count(), 0); + assert_eq!(graft_node.children().count(), 0); + } + + #[test] + #[should_panic(expected = "has both tree_id")] + fn graft_node_with_children_panics() { + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node.set_children(vec![LocalNodeId(2)]); + node + }), + (LocalNodeId(2), Node::new(Role::Button)), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + super::Tree::new(update, false); + } + + #[test] + fn node_added_called_when_subtree_pushed() { + struct Handler { + added_nodes: Vec, + } + impl super::ChangeHandler for Handler { + fn node_added(&mut self, node: &crate::Node) { + self.added_nodes.push(node.id()); + } + fn node_updated(&mut self, _: &crate::Node, _: &crate::Node) {} + fn focus_moved(&mut self, _: Option<&crate::Node>, _: Option<&crate::Node>) {} + fn node_removed(&mut self, _: &crate::Node) {} + } + + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let mut tree = super::Tree::new(update, false); + + let mut handler = Handler { + added_nodes: Vec::new(), + }; + + let subtree_update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Document); + node.set_children(vec![LocalNodeId(1), LocalNodeId(2)]); + node + }), + (LocalNodeId(1), Node::new(Role::Paragraph)), + (LocalNodeId(2), Node::new(Role::Button)), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: subtree_id(), + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(subtree_update, &mut handler); + + assert_eq!(handler.added_nodes.len(), 3,); + assert!(handler.added_nodes.contains(&subtree_node_id(0)),); + assert!(handler.added_nodes.contains(&subtree_node_id(1)),); + assert!(handler.added_nodes.contains(&subtree_node_id(2)),); + } + + #[test] + fn node_removed_called_when_graft_removed() { + struct Handler { + removed_nodes: Vec, + } + impl super::ChangeHandler for Handler { + fn node_added(&mut self, _: &crate::Node) {} + fn node_updated(&mut self, _: &crate::Node, _: &crate::Node) {} + fn focus_moved(&mut self, _: Option<&crate::Node>, _: Option<&crate::Node>) {} + fn node_removed(&mut self, node: &crate::Node) { + self.removed_nodes.push(node.id()); + } + } + + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let mut tree = super::Tree::new(update, false); + + let subtree_update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Document); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), Node::new(Role::Paragraph)), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: subtree_id(), + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(subtree_update, &mut NoOpHandler); + + assert!(tree.state().node_by_id(subtree_node_id(0)).is_some()); + assert!(tree.state().node_by_id(subtree_node_id(1)).is_some()); + + let mut handler = Handler { + removed_nodes: Vec::new(), + }; + + let update = TreeUpdate { + nodes: vec![(LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![]); + node + })], + tree: None, + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(update, &mut handler); + + assert!(handler.removed_nodes.contains(&node_id(1)),); + assert!(handler.removed_nodes.contains(&subtree_node_id(0)),); + assert!(handler.removed_nodes.contains(&subtree_node_id(1)),); + assert_eq!(handler.removed_nodes.len(), 3,); + } + + #[test] + fn node_updated_called_when_subtree_reparented() { + struct Handler { + updated_nodes: Vec, + } + impl super::ChangeHandler for Handler { + fn node_added(&mut self, _: &crate::Node) {} + fn node_updated(&mut self, _old: &crate::Node, new: &crate::Node) { + self.updated_nodes.push(new.id()); + } + fn focus_moved(&mut self, _: Option<&crate::Node>, _: Option<&crate::Node>) {} + fn node_removed(&mut self, _: &crate::Node) {} + } + + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1), LocalNodeId(2)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + (LocalNodeId(2), Node::new(Role::GenericContainer)), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let mut tree = super::Tree::new(update, false); + + let subtree_update = TreeUpdate { + nodes: vec![(LocalNodeId(0), Node::new(Role::Document))], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: subtree_id(), + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(subtree_update, &mut NoOpHandler); + + let subtree_root = tree.state().node_by_id(subtree_node_id(0)).unwrap(); + assert_eq!(subtree_root.parent().unwrap().id(), node_id(1)); + + let mut handler = Handler { + updated_nodes: Vec::new(), + }; + + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(1), Node::new(Role::GenericContainer)), + (LocalNodeId(2), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + ], + tree: None, + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(update, &mut handler); + + assert!(handler.updated_nodes.contains(&subtree_node_id(0)),); + + let subtree_root = tree.state().node_by_id(subtree_node_id(0)).unwrap(); + assert_eq!(subtree_root.parent().unwrap().id(), node_id(2)); + } + + #[test] + fn focus_moved_called_when_focus_moves_to_subtree() { + struct Handler { + focus_moves: Vec<(Option, Option)>, + } + impl super::ChangeHandler for Handler { + fn node_added(&mut self, _: &crate::Node) {} + fn node_updated(&mut self, _: &crate::Node, _: &crate::Node) {} + fn focus_moved(&mut self, old: Option<&crate::Node>, new: Option<&crate::Node>) { + self.focus_moves + .push((old.map(|n| n.id()), new.map(|n| n.id()))); + } + fn node_removed(&mut self, _: &crate::Node) {} + } + + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let mut tree = super::Tree::new(update, true); + + let subtree_update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Document); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), Node::new(Role::Button)), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: subtree_id(), + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(subtree_update, &mut NoOpHandler); + + let mut handler = Handler { + focus_moves: Vec::new(), + }; + + let update = TreeUpdate { + nodes: vec![], + tree: None, + tree_id: TreeId::ROOT, + focus: LocalNodeId(1), + }; + tree.update_and_process_changes(update, &mut handler); + + assert_eq!(handler.focus_moves.len(), 1,); + let (old_focus, new_focus) = &handler.focus_moves[0]; + assert_eq!(*old_focus, Some(node_id(0)),); + assert_eq!(*new_focus, Some(subtree_node_id(0)),); + } + + #[test] + fn focus_moved_called_when_subtree_focus_changes() { + struct Handler { + focus_moves: Vec<(Option, Option)>, + } + impl super::ChangeHandler for Handler { + fn node_added(&mut self, _: &crate::Node) {} + fn node_updated(&mut self, _: &crate::Node, _: &crate::Node) {} + fn focus_moved(&mut self, old: Option<&crate::Node>, new: Option<&crate::Node>) { + self.focus_moves + .push((old.map(|n| n.id()), new.map(|n| n.id()))); + } + fn node_removed(&mut self, _: &crate::Node) {} + } + + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let mut tree = super::Tree::new(update, true); + + let subtree_update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Document); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), Node::new(Role::Button)), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: subtree_id(), + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(subtree_update, &mut NoOpHandler); + + let root_update = TreeUpdate { + nodes: vec![], + tree: None, + tree_id: TreeId::ROOT, + focus: LocalNodeId(1), + }; + tree.update_and_process_changes(root_update, &mut NoOpHandler); + + let mut handler = Handler { + focus_moves: Vec::new(), + }; + + let subtree_update = TreeUpdate { + nodes: vec![], + tree: None, + tree_id: subtree_id(), + focus: LocalNodeId(1), + }; + tree.update_and_process_changes(subtree_update, &mut handler); + + assert_eq!(handler.focus_moves.len(), 1,); + let (old_focus, new_focus) = &handler.focus_moves[0]; + assert_eq!(*old_focus, Some(subtree_node_id(0)),); + assert_eq!(*new_focus, Some(subtree_node_id(1)),); + } + + fn nested_subtree_id() -> TreeId { + TreeId(Uuid::from_u128(2)) + } + + fn nested_subtree_node_id(n: u64) -> NodeId { + NodeId::new(LocalNodeId(n), TreeIndex(2)) + } + + #[test] + fn nested_subtree_focus_follows_graft_chain() { + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let mut tree = super::Tree::new(update, true); + + let subtree_update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Document); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(nested_subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: subtree_id(), + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(subtree_update, &mut NoOpHandler); + + let nested_update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Group); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), Node::new(Role::Button)), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: nested_subtree_id(), + focus: LocalNodeId(1), + }; + tree.update_and_process_changes(nested_update, &mut NoOpHandler); + + let update = TreeUpdate { + nodes: vec![], + tree: None, + tree_id: TreeId::ROOT, + focus: LocalNodeId(1), + }; + tree.update_and_process_changes(update, &mut NoOpHandler); + + let update = TreeUpdate { + nodes: vec![], + tree: None, + tree_id: subtree_id(), + focus: LocalNodeId(1), + }; + tree.update_and_process_changes(update, &mut NoOpHandler); + + assert_eq!(tree.state().focus_id(), Some(nested_subtree_node_id(1)),); + } + + #[test] + fn nested_subtree_focus_update_changes_effective_focus() { + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let mut tree = super::Tree::new(update, true); + + let subtree_update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Document); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(nested_subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: subtree_id(), + focus: LocalNodeId(1), + }; + tree.update_and_process_changes(subtree_update, &mut NoOpHandler); + + let nested_update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Group); + node.set_children(vec![LocalNodeId(1), LocalNodeId(2)]); + node + }), + (LocalNodeId(1), Node::new(Role::Button)), + (LocalNodeId(2), Node::new(Role::Button)), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: nested_subtree_id(), + focus: LocalNodeId(1), + }; + tree.update_and_process_changes(nested_update, &mut NoOpHandler); + + let root_update = TreeUpdate { + nodes: vec![], + tree: None, + tree_id: TreeId::ROOT, + focus: LocalNodeId(1), + }; + tree.update_and_process_changes(root_update, &mut NoOpHandler); + + assert_eq!(tree.state().focus_id(), Some(nested_subtree_node_id(1))); + + let update = TreeUpdate { + nodes: vec![], + tree: None, + tree_id: nested_subtree_id(), + focus: LocalNodeId(2), + }; + tree.update_and_process_changes(update, &mut NoOpHandler); + + assert_eq!(tree.state().focus_id(), Some(nested_subtree_node_id(2)),); + } + + #[test] + #[should_panic(expected = "Graft nodes cannot be focused without their subtree")] + fn removing_nested_subtree_while_intermediate_focus_on_graft_panics() { + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(1), + }; + let mut tree = super::Tree::new(update, true); + + let subtree_update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Document); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(nested_subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: subtree_id(), + focus: LocalNodeId(1), + }; + tree.update_and_process_changes(subtree_update, &mut NoOpHandler); + + let nested_update = TreeUpdate { + nodes: vec![(LocalNodeId(0), Node::new(Role::Button))], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: nested_subtree_id(), + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(nested_update, &mut NoOpHandler); + + let update = TreeUpdate { + nodes: vec![(LocalNodeId(1), Node::new(Role::GenericContainer))], + tree: None, + tree_id: subtree_id(), + focus: LocalNodeId(1), + }; + tree.update_and_process_changes(update, &mut NoOpHandler); + } + + #[test] + fn nested_subtree_root_lookup_for_focus_only_update() { + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let mut tree = super::Tree::new(update, true); + + let subtree_update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Document); + node.set_children(vec![LocalNodeId(1), LocalNodeId(2)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(nested_subtree_id()); + node + }), + (LocalNodeId(2), Node::new(Role::Button)), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: subtree_id(), + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(subtree_update, &mut NoOpHandler); + + let nested_update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Group); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), Node::new(Role::Button)), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: nested_subtree_id(), + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(nested_update, &mut NoOpHandler); + + let update = TreeUpdate { + nodes: vec![], + tree: None, + tree_id: subtree_id(), + focus: LocalNodeId(2), + }; + tree.update_and_process_changes(update, &mut NoOpHandler); + + assert_eq!( + tree.state().subtrees.get(&subtree_id()).unwrap().focus, + subtree_node_id(2), + ); + } + + #[test] + fn subtree_root_change_updates_graft_and_parent() { + struct Handler { + updated_nodes: Vec, + added_nodes: Vec, + removed_nodes: Vec, + } + impl super::ChangeHandler for Handler { + fn node_added(&mut self, node: &crate::Node) { + self.added_nodes.push(node.id()); + } + fn node_updated(&mut self, _old: &crate::Node, new: &crate::Node) { + self.updated_nodes.push(new.id()); + } + fn focus_moved(&mut self, _: Option<&crate::Node>, _: Option<&crate::Node>) {} + fn node_removed(&mut self, node: &crate::Node) { + self.removed_nodes.push(node.id()); + } + } + + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let mut tree = super::Tree::new(update, false); + + let subtree_update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Document); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), Node::new(Role::Paragraph)), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: subtree_id(), + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(subtree_update, &mut NoOpHandler); + + let mut handler = Handler { + updated_nodes: Vec::new(), + added_nodes: Vec::new(), + removed_nodes: Vec::new(), + }; + + let subtree_update = TreeUpdate { + nodes: vec![ + (LocalNodeId(2), { + let mut node = Node::new(Role::Article); + node.set_children(vec![LocalNodeId(3)]); + node + }), + (LocalNodeId(3), Node::new(Role::Button)), + ], + tree: Some(Tree::new(LocalNodeId(2))), + tree_id: subtree_id(), + focus: LocalNodeId(2), + }; + tree.update_and_process_changes(subtree_update, &mut handler); + + let graft_node = tree.state().node_by_id(node_id(1)).unwrap(); + let children: Vec<_> = graft_node.child_ids().collect(); + assert_eq!(children.len(), 1); + assert_eq!(children[0], subtree_node_id(2)); + + let new_subtree_root = tree.state().node_by_id(subtree_node_id(2)).unwrap(); + assert_eq!(new_subtree_root.parent_id(), Some(node_id(1))); + assert_eq!(new_subtree_root.role(), Role::Article); + + assert!(tree.state().node_by_id(subtree_node_id(0)).is_none()); + assert!(tree.state().node_by_id(subtree_node_id(1)).is_none()); + + assert!(tree.state().node_by_id(subtree_node_id(2)).is_some()); + assert!(tree.state().node_by_id(subtree_node_id(3)).is_some()); + + assert!(handler.removed_nodes.contains(&subtree_node_id(0)),); + assert!(handler.removed_nodes.contains(&subtree_node_id(1)),); + assert!(handler.added_nodes.contains(&subtree_node_id(2)),); + assert!(handler.added_nodes.contains(&subtree_node_id(3)),); + } + + #[test] + fn subtree_root_change_to_existing_child() { + struct Handler { + updated_nodes: Vec, + added_nodes: Vec, + removed_nodes: Vec, + } + impl super::ChangeHandler for Handler { + fn node_added(&mut self, node: &crate::Node) { + self.added_nodes.push(node.id()); + } + fn node_updated(&mut self, _old: &crate::Node, new: &crate::Node) { + self.updated_nodes.push(new.id()); + } + fn focus_moved(&mut self, _: Option<&crate::Node>, _: Option<&crate::Node>) {} + fn node_removed(&mut self, node: &crate::Node) { + self.removed_nodes.push(node.id()); + } + } + + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let mut tree = super::Tree::new(update, false); + + let subtree_update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Document); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::Article); + node.set_children(vec![LocalNodeId(2)]); + node + }), + (LocalNodeId(2), Node::new(Role::Paragraph)), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: subtree_id(), + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(subtree_update, &mut NoOpHandler); + + let graft_node = tree.state().node_by_id(node_id(1)).unwrap(); + assert_eq!(graft_node.child_ids().next(), Some(subtree_node_id(0))); + + let old_root = tree.state().node_by_id(subtree_node_id(0)).unwrap(); + assert_eq!(old_root.role(), Role::Document); + assert_eq!(old_root.parent_id(), Some(node_id(1))); + + let child = tree.state().node_by_id(subtree_node_id(1)).unwrap(); + assert_eq!(child.role(), Role::Article); + assert_eq!(child.parent_id(), Some(subtree_node_id(0))); + + let grandchild = tree.state().node_by_id(subtree_node_id(2)).unwrap(); + assert_eq!(grandchild.parent_id(), Some(subtree_node_id(1))); + + let mut handler = Handler { + updated_nodes: Vec::new(), + added_nodes: Vec::new(), + removed_nodes: Vec::new(), + }; + + let subtree_update = TreeUpdate { + nodes: vec![ + (LocalNodeId(1), { + let mut node = Node::new(Role::Article); + node.set_children(vec![LocalNodeId(2)]); + node + }), + (LocalNodeId(2), Node::new(Role::Paragraph)), + ], + tree: Some(Tree::new(LocalNodeId(1))), + tree_id: subtree_id(), + focus: LocalNodeId(1), + }; + tree.update_and_process_changes(subtree_update, &mut handler); + + let graft_node = tree.state().node_by_id(node_id(1)).unwrap(); + let children: Vec<_> = graft_node.child_ids().collect(); + assert_eq!(children.len(), 1); + assert_eq!(children[0], subtree_node_id(1)); + + let new_root = tree.state().node_by_id(subtree_node_id(1)).unwrap(); + assert_eq!(new_root.parent_id(), Some(node_id(1))); + assert_eq!(new_root.role(), Role::Article); + + assert!(tree.state().node_by_id(subtree_node_id(0)).is_none(),); + + let grandchild = tree.state().node_by_id(subtree_node_id(2)).unwrap(); + assert_eq!(grandchild.parent_id(), Some(subtree_node_id(1))); + + assert!(handler.removed_nodes.contains(&subtree_node_id(0)),); + assert!(handler.updated_nodes.contains(&subtree_node_id(1)),); + assert!(!handler.added_nodes.contains(&subtree_node_id(1)),); + assert!(!handler.added_nodes.contains(&subtree_node_id(2)),); + } + + #[test] + fn subtree_root_change_to_new_parent_of_old_root() { + struct Handler { + updated_nodes: Vec, + added_nodes: Vec, + removed_nodes: Vec, + } + impl super::ChangeHandler for Handler { + fn node_added(&mut self, node: &crate::Node) { + self.added_nodes.push(node.id()); + } + fn node_updated(&mut self, _old: &crate::Node, new: &crate::Node) { + self.updated_nodes.push(new.id()); + } + fn focus_moved(&mut self, _: Option<&crate::Node>, _: Option<&crate::Node>) {} + fn node_removed(&mut self, node: &crate::Node) { + self.removed_nodes.push(node.id()); + } + } + + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let mut tree = super::Tree::new(update, false); + + let subtree_update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Document); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), Node::new(Role::Paragraph)), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: subtree_id(), + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(subtree_update, &mut NoOpHandler); + + let mut handler = Handler { + updated_nodes: Vec::new(), + added_nodes: Vec::new(), + removed_nodes: Vec::new(), + }; + + let subtree_update = TreeUpdate { + nodes: vec![ + (LocalNodeId(2), { + let mut node = Node::new(Role::Article); + node.set_children(vec![LocalNodeId(0)]); + node + }), + (LocalNodeId(0), { + let mut node = Node::new(Role::Document); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), Node::new(Role::Paragraph)), + ], + tree: Some(Tree::new(LocalNodeId(2))), + tree_id: subtree_id(), + focus: LocalNodeId(2), + }; + tree.update_and_process_changes(subtree_update, &mut handler); + + let graft_node = tree.state().node_by_id(node_id(1)).unwrap(); + let children: Vec<_> = graft_node.child_ids().collect(); + assert_eq!(children.len(), 1); + assert_eq!(children[0], subtree_node_id(2)); + + let new_root = tree.state().node_by_id(subtree_node_id(2)).unwrap(); + assert_eq!(new_root.parent_id(), Some(node_id(1))); + assert_eq!(new_root.role(), Role::Article); + + let old_root = tree.state().node_by_id(subtree_node_id(0)).unwrap(); + assert_eq!(old_root.parent_id(), Some(subtree_node_id(2))); + assert_eq!(old_root.role(), Role::Document); + + let grandchild = tree.state().node_by_id(subtree_node_id(1)).unwrap(); + assert_eq!(grandchild.parent_id(), Some(subtree_node_id(0))); + + assert!(handler.added_nodes.contains(&subtree_node_id(2))); + assert!(handler.updated_nodes.contains(&subtree_node_id(0))); + assert!(!handler.removed_nodes.contains(&subtree_node_id(0))); + assert!(!handler.removed_nodes.contains(&subtree_node_id(1))); + } + + #[test] + fn subtree_update_without_tree_preserves_root() { + struct Handler { + updated_nodes: Vec, + added_nodes: Vec, + removed_nodes: Vec, + } + impl super::ChangeHandler for Handler { + fn node_added(&mut self, node: &crate::Node) { + self.added_nodes.push(node.id()); + } + fn node_updated(&mut self, _old: &crate::Node, new: &crate::Node) { + self.updated_nodes.push(new.id()); + } + fn focus_moved(&mut self, _: Option<&crate::Node>, _: Option<&crate::Node>) {} + fn node_removed(&mut self, node: &crate::Node) { + self.removed_nodes.push(node.id()); + } + } + + let update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Window); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::GenericContainer); + node.set_tree_id(subtree_id()); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: TreeId::ROOT, + focus: LocalNodeId(0), + }; + let mut tree = super::Tree::new(update, false); + + let subtree_update = TreeUpdate { + nodes: vec![ + (LocalNodeId(0), { + let mut node = Node::new(Role::Document); + node.set_children(vec![LocalNodeId(1)]); + node + }), + (LocalNodeId(1), { + let mut node = Node::new(Role::Paragraph); + node.set_label("original"); + node + }), + ], + tree: Some(Tree::new(LocalNodeId(0))), + tree_id: subtree_id(), + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(subtree_update, &mut NoOpHandler); + + let mut handler = Handler { + updated_nodes: Vec::new(), + added_nodes: Vec::new(), + removed_nodes: Vec::new(), + }; + + let subtree_update = TreeUpdate { + nodes: vec![(LocalNodeId(1), { + let mut node = Node::new(Role::Paragraph); + node.set_label("modified"); + node + })], + tree: None, + tree_id: subtree_id(), + focus: LocalNodeId(0), + }; + tree.update_and_process_changes(subtree_update, &mut handler); + + let subtree_root = tree.state().node_by_id(subtree_node_id(0)).unwrap(); + assert_eq!(subtree_root.role(), Role::Document); + assert_eq!(subtree_root.parent_id(), Some(node_id(1))); + + let graft_node = tree.state().node_by_id(node_id(1)).unwrap(); + assert_eq!(graft_node.child_ids().next(), Some(subtree_node_id(0))); + + let child = tree.state().node_by_id(subtree_node_id(1)).unwrap(); + assert_eq!(child.label().as_deref(), Some("modified")); + + assert!(handler.removed_nodes.is_empty(),); + assert!(handler.added_nodes.is_empty()); + assert!(handler.updated_nodes.contains(&subtree_node_id(1)),); + assert!(!handler.updated_nodes.contains(&subtree_node_id(0)),); + } } diff --git a/platforms/android/src/adapter.rs b/platforms/android/src/adapter.rs index 3b7d52cb..a39d56fa 100644 --- a/platforms/android/src/adapter.rs +++ b/platforms/android/src/adapter.rs @@ -9,8 +9,9 @@ // found in the LICENSE.chromium file. use accesskit::{ - Action, ActionData, ActionHandler, ActionRequest, ActivationHandler, Node as NodeData, NodeId, - Orientation, Point, Role, ScrollUnit, TextSelection, Tree as TreeData, TreeUpdate, + Action, ActionData, ActionHandler, ActionRequest, ActivationHandler, Node as NodeData, + NodeId as LocalNodeId, Orientation, Point, Role, ScrollUnit, TextSelection, Tree as TreeData, + TreeId, TreeUpdate, }; use accesskit_consumer::{FilterResult, Node, TextPosition, Tree, TreeChangeHandler}; use jni::{ @@ -156,7 +157,7 @@ impl TreeChangeHandler for AdapterChangeHandler<'_> { } } -const PLACEHOLDER_ROOT_ID: NodeId = NodeId(0); +const PLACEHOLDER_ROOT_ID: LocalNodeId = LocalNodeId(0); #[derive(Debug, Default)] enum State { @@ -179,6 +180,7 @@ impl State { let placeholder_update = TreeUpdate { nodes: vec![(PLACEHOLDER_ROOT_ID, NodeData::new(Role::Window))], tree: Some(TreeData::new(PLACEHOLDER_ROOT_ID)), + tree_id: TreeId::ROOT, focus: PLACEHOLDER_ROOT_ID, }; Self::Placeholder(Tree::new(placeholder_update, true)) @@ -382,6 +384,7 @@ impl Adapter { } else { self.node_id_map.get_accesskit_id(virtual_view_id)? }; + let (target_node, target_tree) = tree.locate_node(target)?; let mut events = Vec::new(); let request = match action { ACTION_CLICK => ActionRequest { @@ -396,12 +399,14 @@ impl Adapter { Action::Click } }, - target, + target_tree, + target_node, data: None, }, ACTION_FOCUS => ActionRequest { action: Action::Focus, - target, + target_tree, + target_node, data: None, }, ACTION_SCROLL_BACKWARD | ACTION_SCROLL_FORWARD => ActionRequest { @@ -436,7 +441,8 @@ impl Adapter { Action::ScrollRight } }, - target, + target_tree, + target_node, data: Some(ActionData::ScrollUnit(ScrollUnit::Page)), }, ACTION_ACCESSIBILITY_FOCUS => { @@ -491,7 +497,8 @@ impl Adapter { let id = self.node_id_map.get_accesskit_id(virtual_view_id)?; tree_state.node_by_id(id).unwrap() }; - let target = node.id(); + let (node_id, tree_id) = tree.locate_node(node.id())?; + let (focus_id, _) = tree.locate_node(tree_state.focus_id_in_tree())?; // TalkBack expects the text selection change to take effect // immediately, so we optimistically update the node. // But don't be *too* optimistic. @@ -506,14 +513,16 @@ impl Adapter { let mut new_node = node.data().clone(); new_node.set_text_selection(selection); let update = TreeUpdate { - nodes: vec![(node.id(), new_node)], + nodes: vec![(node_id, new_node)], tree: None, - focus: tree_state.focus_id_in_tree(), + tree_id, + focus: focus_id, }; update_tree(events, &mut self.node_id_map, tree, update); let request = ActionRequest { - target, action: Action::SetTextSelection, + target_tree: tree_id, + target_node: node_id, data: Some(ActionData::SetTextSelection(selection)), }; action_handler.do_action(request); diff --git a/platforms/android/src/util.rs b/platforms/android/src/util.rs index 283f4b91..0f9c708e 100644 --- a/platforms/android/src/util.rs +++ b/platforms/android/src/util.rs @@ -3,8 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit::NodeId; -use accesskit_consumer::Node; +use accesskit_consumer::{Node, NodeId}; use jni::{objects::JObject, sys::jint, JNIEnv}; use std::collections::HashMap; diff --git a/platforms/atspi-common/src/adapter.rs b/platforms/atspi-common/src/adapter.rs index a602e62a..0406fae4 100644 --- a/platforms/atspi-common/src/adapter.rs +++ b/platforms/atspi-common/src/adapter.rs @@ -15,8 +15,8 @@ use crate::{ util::WindowBounds, AdapterCallback, Event, ObjectEvent, WindowEvent, }; -use accesskit::{ActionHandler, NodeId, Role, TreeUpdate}; -use accesskit_consumer::{FilterResult, Node, Tree, TreeChangeHandler, TreeState}; +use accesskit::{ActionHandler, Role, TreeUpdate}; +use accesskit_consumer::{FilterResult, Node, NodeId, Tree, TreeChangeHandler, TreeState}; use atspi_common::{InterfaceSet, Politeness, State}; use std::fmt::{Debug, Formatter}; use std::{ diff --git a/platforms/atspi-common/src/callback.rs b/platforms/atspi-common/src/callback.rs index deb1df82..81dce9b9 100644 --- a/platforms/atspi-common/src/callback.rs +++ b/platforms/atspi-common/src/callback.rs @@ -3,7 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit::NodeId; +use accesskit_consumer::NodeId; use atspi_common::InterfaceSet; use crate::{Adapter, Event}; diff --git a/platforms/atspi-common/src/events.rs b/platforms/atspi-common/src/events.rs index 8e8cd6ee..ae031ed6 100644 --- a/platforms/atspi-common/src/events.rs +++ b/platforms/atspi-common/src/events.rs @@ -3,7 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit::NodeId; +use accesskit_consumer::NodeId; use atspi_common::{Politeness, Role, State}; use crate::{NodeIdOrRoot, Rect}; diff --git a/platforms/atspi-common/src/lib.rs b/platforms/atspi-common/src/lib.rs index 8680944d..ece8ef0d 100644 --- a/platforms/atspi-common/src/lib.rs +++ b/platforms/atspi-common/src/lib.rs @@ -16,6 +16,7 @@ mod rect; pub mod simplified; mod util; +pub use accesskit_consumer::NodeId; pub use atspi_common::{ CoordType, Granularity, InterfaceSet, Layer, RelationType, Role, ScrollType, State, StateSet, }; diff --git a/platforms/atspi-common/src/node.rs b/platforms/atspi-common/src/node.rs index 33aa2279..ad59a618 100644 --- a/platforms/atspi-common/src/node.rs +++ b/platforms/atspi-common/src/node.rs @@ -9,10 +9,10 @@ // found in the LICENSE.chromium file. use accesskit::{ - Action, ActionData, ActionRequest, Affine, Live, NodeId, Orientation, Point, Rect, Role, - Toggled, + Action, ActionData, ActionRequest, Affine, Live, NodeId as LocalNodeId, Orientation, Point, + Rect, Role, Toggled, TreeId, }; -use accesskit_consumer::{FilterResult, Node, TreeState}; +use accesskit_consumer::{FilterResult, Node, NodeId, Tree, TreeState}; use atspi_common::{ CoordType, Granularity, Interface, InterfaceSet, Layer, Politeness, RelationType, Role as AtspiRole, ScrollType, State, StateSet, @@ -655,22 +655,22 @@ impl PlatformNode { f(tree.state()) } - fn with_tree_state_and_context(&self, f: F) -> Result + fn with_tree_and_context(&self, f: F) -> Result where - F: FnOnce(&TreeState, &Context) -> Result, + F: FnOnce(&Tree, &Context) -> Result, { let context = self.upgrade_context()?; let tree = context.read_tree(); - f(tree.state(), &context) + f(&tree, &context) } fn resolve_with_context(&self, f: F) -> Result where - for<'a> F: FnOnce(Node<'a>, &Context) -> Result, + for<'a> F: FnOnce(Node<'a>, &'a Tree, &Context) -> Result, { - self.with_tree_state_and_context(|state, context| { - if let Some(node) = state.node_by_id(self.id) { - f(node, context) + self.with_tree_and_context(|tree, context| { + if let Some(node) = tree.state().node_by_id(self.id) { + f(node, tree, context) } else { Err(Error::Defunct) } @@ -679,12 +679,12 @@ impl PlatformNode { fn resolve_for_selection_with_context(&self, f: F) -> Result where - for<'a> F: FnOnce(Node<'a>, &Context) -> Result, + for<'a> F: FnOnce(Node<'a>, &'a Tree, &Context) -> Result, { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, tree, context| { let wrapper = NodeWrapper(&node); if wrapper.supports_selection() { - f(node, context) + f(node, tree, context) } else { Err(Error::UnsupportedInterface) } @@ -693,12 +693,12 @@ impl PlatformNode { fn resolve_for_text_with_context(&self, f: F) -> Result where - for<'a> F: FnOnce(Node<'a>, &Context) -> Result, + for<'a> F: FnOnce(Node<'a>, &'a Tree, &Context) -> Result, { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, tree, context| { let wrapper = NodeWrapper(&node); if wrapper.supports_text() { - f(node, context) + f(node, tree, context) } else { Err(Error::UnsupportedInterface) } @@ -709,7 +709,7 @@ impl PlatformNode { where for<'a> F: FnOnce(Node<'a>) -> Result, { - self.resolve_with_context(|node, _| f(node)) + self.resolve_with_context(|node, _, _| f(node)) } fn resolve_for_selection(&self, f: F) -> Result @@ -730,17 +730,17 @@ impl PlatformNode { where for<'a> F: FnOnce(Node<'a>) -> Result, { - self.resolve_for_text_with_context(|node, _| f(node)) + self.resolve_for_text_with_context(|node, _, _| f(node)) } - fn do_action_internal(&self, f: F) -> Result<()> + fn do_action_internal(&self, target: NodeId, f: F) -> Result<()> where - F: FnOnce(&TreeState, &Context) -> ActionRequest, + F: FnOnce(&TreeState, &Context, LocalNodeId, TreeId) -> ActionRequest, { let context = self.upgrade_context()?; let tree = context.read_tree(); - if tree.state().has_node(self.id) { - let request = f(tree.state(), &context); + if let Some((target_node, target_tree)) = tree.locate_node(target) { + let request = f(tree.state(), &context, target_node, target_tree); drop(tree); context.do_action(request); Ok(()) @@ -880,9 +880,9 @@ impl PlatformNode { } pub fn state(&self) -> StateSet { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, tree, _| { let wrapper = NodeWrapper(&node); - Ok(wrapper.state(context.read_tree().state().focus_id().is_some())) + Ok(wrapper.state(tree.state().focus_id().is_some())) }) .unwrap_or(State::Defunct.into()) } @@ -970,16 +970,17 @@ impl PlatformNode { if index != 0 { return Ok(false); } - self.do_action_internal(|_, _| ActionRequest { + self.do_action_internal(self.id, |_, _, target_node, target_tree| ActionRequest { action: Action::Click, - target: self.id, + target_tree, + target_node, data: None, })?; Ok(true) } pub fn contains(&self, x: i32, y: i32, coord_type: CoordType) -> Result { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, _, context| { let window_bounds = context.read_root_window_bounds(); let wrapper = NodeWrapper(&node); if let Some(extents) = wrapper.extents(&window_bounds, coord_type) { @@ -996,7 +997,7 @@ impl PlatformNode { y: i32, coord_type: CoordType, ) -> Result> { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, _, context| { let window_bounds = context.read_root_window_bounds(); let point = window_bounds.atspi_point_to_accesskit_point( Point::new(x.into(), y.into()), @@ -1009,7 +1010,7 @@ impl PlatformNode { } pub fn extents(&self, coord_type: CoordType) -> Result { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, _, context| { let window_bounds = context.read_root_window_bounds(); let wrapper = NodeWrapper(&node); Ok(wrapper @@ -1030,34 +1031,38 @@ impl PlatformNode { } pub fn grab_focus(&self) -> Result { - self.do_action_internal(|_, _| ActionRequest { + self.do_action_internal(self.id, |_, _, target_node, target_tree| ActionRequest { action: Action::Focus, - target: self.id, + target_tree, + target_node, data: None, })?; Ok(true) } pub fn scroll_to(&self, scroll_type: ScrollType) -> Result { - self.do_action_internal(|_, _| ActionRequest { + self.do_action_internal(self.id, |_, _, target_node, target_tree| ActionRequest { action: Action::ScrollIntoView, - target: self.id, + target_tree, + target_node, data: atspi_scroll_type_to_scroll_hint(scroll_type).map(ActionData::ScrollHint), })?; Ok(true) } pub fn scroll_to_point(&self, coord_type: CoordType, x: i32, y: i32) -> Result { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, tree, context| { let window_bounds = context.read_root_window_bounds(); let point = window_bounds.atspi_point_to_accesskit_point( Point::new(x.into(), y.into()), node.filtered_parent(&filter), coord_type, ); + let (target_node, target_tree) = tree.locate_node(self.id).ok_or(Error::Defunct)?; context.do_action(ActionRequest { action: Action::ScrollToPoint, - target: self.id, + target_tree, + target_node, data: Some(ActionData::ScrollToPoint(point)), }); Ok(()) @@ -1086,14 +1091,17 @@ impl PlatformNode { } pub fn select_child(&self, child_index: usize) -> Result { - self.resolve_for_selection_with_context(|node, context| { + self.resolve_for_selection_with_context(|node, tree, context| { if let Some(child) = node.filtered_children(filter).nth(child_index) { if let Some(true) = child.is_selected() { Ok(true) } else if child.is_selectable() && child.is_clickable(&filter) { + let (target_node, target_tree) = + tree.locate_node(child.id()).ok_or(Error::Defunct)?; context.do_action(ActionRequest { action: Action::Click, - target: child.id(), + target_tree, + target_node, data: None, }); Ok(true) @@ -1107,16 +1115,19 @@ impl PlatformNode { } pub fn deselect_selected_child(&self, selected_child_index: usize) -> Result { - self.resolve_for_selection_with_context(|node, context| { + self.resolve_for_selection_with_context(|node, tree, context| { if let Some(child) = node .items(filter) .filter(|c| c.is_selected() == Some(true)) .nth(selected_child_index) { if child.is_clickable(&filter) { + let (target_node, target_tree) = + tree.locate_node(child.id()).ok_or(Error::Defunct)?; context.do_action(ActionRequest { action: Action::Click, - target: child.id(), + target_tree, + target_node, data: None, }); Ok(true) @@ -1149,14 +1160,17 @@ impl PlatformNode { } pub fn deselect_child(&self, child_index: usize) -> Result { - self.resolve_for_selection_with_context(|node, context| { + self.resolve_for_selection_with_context(|node, tree, context| { if let Some(child) = node.filtered_children(filter).nth(child_index) { if let Some(false) = child.is_selected() { Ok(true) } else if child.is_selectable() && child.is_clickable(&filter) { + let (target_node, target_tree) = + tree.locate_node(child.id()).ok_or(Error::Defunct)?; context.do_action(ActionRequest { action: Action::Click, - target: child.id(), + target_tree, + target_node, data: None, }); Ok(true) @@ -1222,11 +1236,13 @@ impl PlatformNode { } pub fn set_caret_offset(&self, offset: i32) -> Result { - self.resolve_for_text_with_context(|node, context| { + self.resolve_for_text_with_context(|node, tree, context| { let offset = text_position_from_offset(&node, offset).ok_or(Error::IndexOutOfRange)?; + let (target_node, target_tree) = tree.locate_node(node.id()).ok_or(Error::Defunct)?; context.do_action(ActionRequest { action: Action::SetTextSelection, - target: node.id(), + target_tree, + target_node, data: Some(ActionData::SetTextSelection( offset.to_degenerate_range().to_text_selection(), )), @@ -1251,7 +1267,7 @@ impl PlatformNode { } pub fn character_extents(&self, offset: i32, coord_type: CoordType) -> Result { - self.resolve_for_text_with_context(|node, context| { + self.resolve_for_text_with_context(|node, _, context| { let range = text_range_from_offset(&node, offset, Granularity::Char)?; if let Some(bounds) = range.bounding_boxes().first() { let window_bounds = context.read_root_window_bounds(); @@ -1268,7 +1284,7 @@ impl PlatformNode { } pub fn offset_at_point(&self, x: i32, y: i32, coord_type: CoordType) -> Result { - self.resolve_for_text_with_context(|node, context| { + self.resolve_for_text_with_context(|node, _, context| { let window_bounds = context.read_root_window_bounds(); let point = window_bounds.atspi_point_to_accesskit_point( Point::new(x.into(), y.into()), @@ -1327,15 +1343,17 @@ impl PlatformNode { return Ok(false); } - self.resolve_for_text_with_context(|node, context| { + self.resolve_for_text_with_context(|node, tree, context| { // Simply collapse the selection to the position of the caret if a caret is // visible, otherwise set the selection to 0. let selection_end = node .text_selection_focus() .unwrap_or_else(|| node.document_range().start()); + let (target_node, target_tree) = tree.locate_node(node.id()).ok_or(Error::Defunct)?; context.do_action(ActionRequest { action: Action::SetTextSelection, - target: node.id(), + target_tree, + target_node, data: Some(ActionData::SetTextSelection( selection_end.to_degenerate_range().to_text_selection(), )), @@ -1354,12 +1372,14 @@ impl PlatformNode { return Ok(false); } - self.resolve_for_text_with_context(|node, context| { + self.resolve_for_text_with_context(|node, tree, context| { let range = text_range_from_offsets(&node, start_offset, end_offset) .ok_or(Error::IndexOutOfRange)?; + let (target_node, target_tree) = tree.locate_node(node.id()).ok_or(Error::Defunct)?; context.do_action(ActionRequest { action: Action::SetTextSelection, - target: node.id(), + target_tree, + target_node, data: Some(ActionData::SetTextSelection(range.to_text_selection())), }); Ok(true) @@ -1372,7 +1392,7 @@ impl PlatformNode { end_offset: i32, coord_type: CoordType, ) -> Result { - self.resolve_for_text_with_context(|node, context| { + self.resolve_for_text_with_context(|node, _, context| { if let Some(rect) = text_range_bounds_from_offsets(&node, start_offset, end_offset) { let window_bounds = context.read_root_window_bounds(); let new_origin = window_bounds.accesskit_point_to_atspi_point( @@ -1405,7 +1425,7 @@ impl PlatformNode { end_offset: i32, scroll_type: ScrollType, ) -> Result { - self.resolve_for_text_with_context(|node, context| { + self.resolve_for_text_with_context(|node, tree, context| { if let Some(range) = text_range_from_offsets(&node, start_offset, end_offset) { let position = if matches!( scroll_type, @@ -1415,9 +1435,13 @@ impl PlatformNode { } else { range.start() }; + let (target_node, target_tree) = tree + .locate_node(position.inner_node().id()) + .ok_or(Error::Defunct)?; context.do_action(ActionRequest { action: Action::ScrollIntoView, - target: position.inner_node().id(), + target_tree, + target_node, data: atspi_scroll_type_to_scroll_hint(scroll_type).map(ActionData::ScrollHint), }); Ok(true) @@ -1435,7 +1459,7 @@ impl PlatformNode { x: i32, y: i32, ) -> Result { - self.resolve_for_text_with_context(|node, context| { + self.resolve_for_text_with_context(|node, tree, context| { let window_bounds = context.read_root_window_bounds(); let target_point = window_bounds.atspi_point_to_accesskit_point( Point::new(x.into(), y.into()), @@ -1445,9 +1469,12 @@ impl PlatformNode { if let Some(rect) = text_range_bounds_from_offsets(&node, start_offset, end_offset) { let point = Point::new(target_point.x - rect.x0, target_point.y - rect.y0); + let (target_node, target_tree) = + tree.locate_node(node.id()).ok_or(Error::Defunct)?; context.do_action(ActionRequest { action: Action::ScrollToPoint, - target: node.id(), + target_tree, + target_node, data: Some(ActionData::ScrollToPoint(point)), }); return Ok(true); @@ -1476,9 +1503,10 @@ impl PlatformNode { } pub fn set_current_value(&self, value: f64) -> Result<()> { - self.do_action_internal(|_, _| ActionRequest { + self.do_action_internal(self.id, |_, _, target_node, target_tree| ActionRequest { action: Action::SetValue, - target: self.id, + target_tree, + target_node, data: Some(ActionData::NumericValue(value)), }) } diff --git a/platforms/macos/src/adapter.rs b/platforms/macos/src/adapter.rs index 2de5dbaf..6c8c934c 100644 --- a/platforms/macos/src/adapter.rs +++ b/platforms/macos/src/adapter.rs @@ -11,8 +11,8 @@ use crate::{ util::*, }; use accesskit::{ - ActionHandler, ActionRequest, ActivationHandler, Node as NodeProvider, NodeId, Role, - Tree as TreeData, TreeUpdate, + ActionHandler, ActionRequest, ActivationHandler, Node as NodeProvider, NodeId as LocalNodeId, + Role, Tree as TreeData, TreeId, TreeUpdate, }; use accesskit_consumer::{FilterResult, Tree}; use objc2::rc::{Id, WeakId}; @@ -21,7 +21,7 @@ use objc2_foundation::{MainThreadMarker, NSArray, NSObject, NSPoint}; use std::fmt::{Debug, Formatter}; use std::{ffi::c_void, ptr::null_mut, rc::Rc}; -const PLACEHOLDER_ROOT_ID: NodeId = NodeId(0); +const PLACEHOLDER_ROOT_ID: LocalNodeId = LocalNodeId(0); enum State { Inactive { @@ -196,6 +196,7 @@ impl Adapter { let placeholder_update = TreeUpdate { nodes: vec![(PLACEHOLDER_ROOT_ID, NodeProvider::new(Role::Window))], tree: Some(TreeData::new(PLACEHOLDER_ROOT_ID)), + tree_id: TreeId::ROOT, focus: PLACEHOLDER_ROOT_ID, }; let placeholder_tree = Tree::new(placeholder_update, false); diff --git a/platforms/macos/src/context.rs b/platforms/macos/src/context.rs index b3d51fa2..687fcbc6 100644 --- a/platforms/macos/src/context.rs +++ b/platforms/macos/src/context.rs @@ -4,8 +4,8 @@ // the LICENSE-MIT file), at your option. use crate::node::PlatformNode; -use accesskit::{ActionHandler, ActionRequest, NodeId}; -use accesskit_consumer::Tree; +use accesskit::{ActionHandler, ActionRequest}; +use accesskit_consumer::{NodeId, Tree}; use hashbrown::HashMap; use objc2::rc::{Id, WeakId}; use objc2_app_kit::*; diff --git a/platforms/macos/src/event.rs b/platforms/macos/src/event.rs index 6758b55e..5a1cdc41 100644 --- a/platforms/macos/src/event.rs +++ b/platforms/macos/src/event.rs @@ -3,8 +3,8 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit::{Live, NodeId, Role}; -use accesskit_consumer::{FilterResult, Node, TreeChangeHandler}; +use accesskit::{Live, Role}; +use accesskit_consumer::{FilterResult, Node, NodeId, TreeChangeHandler}; use hashbrown::HashSet; use objc2::runtime::{AnyObject, ProtocolObject}; use objc2_app_kit::*; diff --git a/platforms/macos/src/node.rs b/platforms/macos/src/node.rs index 095e8547..0502592f 100644 --- a/platforms/macos/src/node.rs +++ b/platforms/macos/src/node.rs @@ -10,10 +10,8 @@ #![allow(non_upper_case_globals)] -use accesskit::{ - Action, ActionData, ActionRequest, NodeId, Orientation, Role, TextSelection, Toggled, -}; -use accesskit_consumer::{FilterResult, Node}; +use accesskit::{Action, ActionData, ActionRequest, Orientation, Role, TextSelection, Toggled}; +use accesskit_consumer::{FilterResult, Node, NodeId, Tree}; use objc2::{ declare_class, msg_send_id, mutability::InteriorMutable, @@ -384,7 +382,7 @@ declare_class!( unsafe impl PlatformNode { #[method_id(accessibilityParent)] fn parent(&self) -> Option> { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, _, context| { if let Some(parent) = node.filtered_parent(&filter) { Some(Id::into_super(Id::into_super(Id::into_super(context.get_or_create_platform_node(parent.id()))))) } else { @@ -399,7 +397,7 @@ declare_class!( #[method_id(accessibilityWindow)] fn window(&self) -> Option> { - self.resolve_with_context(|_, context| { + self.resolve_with_context(|_, _, context| { context .view .load() @@ -410,7 +408,7 @@ declare_class!( #[method_id(accessibilityTopLevelUIElement)] fn top_level(&self) -> Option> { - self.resolve_with_context(|_, context| { + self.resolve_with_context(|_, _, context| { context .view .load() @@ -432,7 +430,7 @@ declare_class!( #[method_id(accessibilitySelectedChildren)] fn selected_children(&self) -> Option>> { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, _, context| { let wrapper = NodeWrapper(node); if !wrapper.is_container_with_selectable_children() { return None; @@ -449,7 +447,7 @@ declare_class!( #[method(accessibilityFrame)] fn frame(&self) -> NSRect { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, _, context| { let view = match context.view.load() { Some(view) => view, None => { @@ -554,20 +552,26 @@ declare_class!( #[method(setAccessibilityValue:)] fn set_value(&self, value: &NSObject) { if let Some(string) = downcast_ref::(value) { - self.resolve_with_context(|node, context| { - context.do_action(ActionRequest { - action: Action::SetValue, - target: node.id(), - data: Some(ActionData::Value(string.to_string().into())), - }); + self.resolve_with_context(|node, tree, context| { + if let Some((target_node, target_tree)) = tree.locate_node(node.id()) { + context.do_action(ActionRequest { + action: Action::SetValue, + target_tree, + target_node, + data: Some(ActionData::Value(string.to_string().into())), + }); + } }); } else if let Some(number) = downcast_ref::(value) { - self.resolve_with_context(|node, context| { - context.do_action(ActionRequest { - action: Action::SetValue, - target: node.id(), - data: Some(ActionData::NumericValue(number.doubleValue())), - }); + self.resolve_with_context(|node, tree, context| { + if let Some((target_node, target_tree)) = tree.locate_node(node.id()) { + context.do_action(ActionRequest { + action: Action::SetValue, + target_tree, + target_node, + data: Some(ActionData::NumericValue(number.doubleValue())), + }); + } }); } } @@ -619,23 +623,29 @@ declare_class!( #[method(setAccessibilityFocused:)] fn set_focused(&self, focused: bool) { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, tree, context| { if focused { if node.is_focusable(&filter) { - context.do_action(ActionRequest { - action: Action::Focus, - target: node.id(), - data: None, - }); + if let Some((target_node, target_tree)) = tree.locate_node(node.id()) { + context.do_action(ActionRequest { + action: Action::Focus, + target_tree, + target_node, + data: None, + }); + } } } else { - let root = node.tree_state.root(); + let root = tree.state().root(); if root.is_focusable(&filter) { - context.do_action(ActionRequest { - action: Action::Focus, - target: root.id(), - data: None, - }); + if let Some((target_node, target_tree)) = tree.locate_node(root.id()) { + context.do_action(ActionRequest { + action: Action::Focus, + target_tree, + target_node, + data: None, + }); + } } } }); @@ -643,14 +653,17 @@ declare_class!( #[method(accessibilityPerformPress)] fn press(&self) -> bool { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, tree, context| { let clickable = node.is_clickable(&filter); if clickable { - context.do_action(ActionRequest { - action: Action::Click, - target: node.id(), - data: None, - }); + if let Some((target_node, target_tree)) = tree.locate_node(node.id()) { + context.do_action(ActionRequest { + action: Action::Click, + target_tree, + target_node, + data: None, + }); + } } clickable }) @@ -659,14 +672,17 @@ declare_class!( #[method(accessibilityPerformIncrement)] fn increment(&self) -> bool { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, tree, context| { let supports_increment = node.supports_increment(&filter); if supports_increment { - context.do_action(ActionRequest { - action: Action::Increment, - target: node.id(), - data: None, - }); + if let Some((target_node, target_tree)) = tree.locate_node(node.id()) { + context.do_action(ActionRequest { + action: Action::Increment, + target_tree, + target_node, + data: None, + }); + } } supports_increment }) @@ -675,14 +691,17 @@ declare_class!( #[method(accessibilityPerformDecrement)] fn decrement(&self) -> bool { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, tree, context| { let supports_decrement = node.supports_decrement(&filter); if supports_decrement { - context.do_action(ActionRequest { - action: Action::Decrement, - target: node.id(), - data: None, - }); + if let Some((target_node, target_tree)) = tree.locate_node(node.id()) { + context.do_action(ActionRequest { + action: Action::Decrement, + target_tree, + target_node, + data: None, + }); + } } supports_decrement }) @@ -761,7 +780,7 @@ declare_class!( #[method(accessibilityRangeForPosition:)] fn range_for_position(&self, point: NSPoint) -> NSRange { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, _, context| { let view = match context.view.load() { Some(view) => view, None => { @@ -795,7 +814,7 @@ declare_class!( #[method(accessibilityFrameForRange:)] fn frame_for_range(&self, range: NSRange) -> NSRect { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, _, context| { let view = match context.view.load() { Some(view) => view, None => { @@ -846,14 +865,17 @@ declare_class!( #[method(setAccessibilitySelectedTextRange:)] fn set_selected_text_range(&self, range: NSRange) { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, tree, context| { if node.supports_text_ranges() { if let Some(range) = from_ns_range(node, range) { - context.do_action(ActionRequest { - action: Action::SetTextSelection, - target: node.id(), - data: Some(ActionData::SetTextSelection(range.to_text_selection())), - }); + if let Some((target_node, target_tree)) = tree.locate_node(node.id()) { + context.do_action(ActionRequest { + action: Action::SetTextSelection, + target_tree, + target_node, + data: Some(ActionData::SetTextSelection(range.to_text_selection())), + }); + } } } }); @@ -878,7 +900,7 @@ declare_class!( #[method(setAccessibilitySelected:)] fn set_selected(&self, selected: bool) { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, tree, context| { let wrapper = NodeWrapper(node); if !node.is_clickable(&filter) || !wrapper.is_item_like() @@ -889,11 +911,14 @@ declare_class!( if node.is_selected() == Some(selected) { return; } - context.do_action(ActionRequest { - action: Action::Click, - target: node.id(), - data: None, - }); + if let Some((target_node, target_tree)) = tree.locate_node(node.id()) { + context.do_action(ActionRequest { + action: Action::Click, + target_tree, + target_node, + data: None, + }); + } }); } @@ -913,7 +938,7 @@ declare_class!( #[method_id(accessibilityRows)] fn rows(&self) -> Option>> { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, _, context| { let wrapper = NodeWrapper(node); if !wrapper.is_container_with_selectable_children() { return None; @@ -929,7 +954,7 @@ declare_class!( #[method_id(accessibilitySelectedRows)] fn selected_rows(&self) -> Option>> { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, _, context| { let wrapper = NodeWrapper(node); if !wrapper.is_container_with_selectable_children() { return None; @@ -946,17 +971,20 @@ declare_class!( #[method(accessibilityPerformPick)] fn pick(&self) -> bool { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, tree, context| { let wrapper = NodeWrapper(node); let selectable = node.is_clickable(&filter) && wrapper.is_item_like() && node.is_selectable(); if selectable { - context.do_action(ActionRequest { - action: Action::Click, - target: node.id(), - data: None, - }); + if let Some((target_node, target_tree)) = tree.locate_node(node.id()) { + context.do_action(ActionRequest { + action: Action::Click, + target_tree, + target_node, + data: None, + }); + } } selectable }) @@ -965,7 +993,7 @@ declare_class!( #[method_id(accessibilityLinkedUIElements)] fn linked_ui_elements(&self) -> Option>> { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, _, context| { let platform_nodes: Vec> = node .controls() .filter(|controlled| filter(controlled) == FilterResult::Include) @@ -982,7 +1010,7 @@ declare_class!( #[method_id(accessibilityTabs)] fn tabs(&self) -> Option>> { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, _, context| { if node.role() != Role::TabList { return None; } @@ -1022,13 +1050,16 @@ declare_class!( #[method(accessibilityPerformAction:)] fn perform_action(&self, action: &NSString) { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, tree, context| { if action == ns_string!(SCROLL_TO_VISIBLE_ACTION) { - context.do_action(ActionRequest { - action: Action::ScrollIntoView, - target: node.id(), - data: None, - }); + if let Some((target_node, target_tree)) = tree.locate_node(node.id()) { + context.do_action(ActionRequest { + action: Action::ScrollIntoView, + target_tree, + target_node, + data: None, + }); + } } }); } @@ -1134,24 +1165,24 @@ impl PlatformNode { fn resolve_with_context(&self, f: F) -> Option where - F: FnOnce(&Node, &Rc) -> T, + F: FnOnce(&Node, &Tree, &Rc) -> T, { let context = self.ivars().context.upgrade()?; let tree = context.tree.borrow(); let state = tree.state(); let node = state.node_by_id(self.ivars().node_id)?; - Some(f(&node, &context)) + Some(f(&node, &tree, &context)) } fn resolve(&self, f: F) -> Option where F: FnOnce(&Node) -> T, { - self.resolve_with_context(|node, _| f(node)) + self.resolve_with_context(|node, _, _| f(node)) } fn children_internal(&self) -> Option>> { - self.resolve_with_context(|node, context| { + self.resolve_with_context(|node, _, context| { let platform_nodes = node .filtered_children(filter) .map(|child| context.get_or_create_platform_node(child.id())) diff --git a/platforms/unix/src/adapter.rs b/platforms/unix/src/adapter.rs index a7c44d58..66bfa9b8 100644 --- a/platforms/unix/src/adapter.rs +++ b/platforms/unix/src/adapter.rs @@ -3,10 +3,10 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit::{ActionHandler, ActivationHandler, DeactivationHandler, NodeId, Rect, TreeUpdate}; +use accesskit::{ActionHandler, ActivationHandler, DeactivationHandler, Rect, TreeUpdate}; use accesskit_atspi_common::{ next_adapter_id, ActionHandlerNoMut, ActionHandlerWrapper, Adapter as AdapterImpl, - AdapterCallback, Event, PlatformNode, WindowBounds, + AdapterCallback, Event, NodeId, PlatformNode, WindowBounds, }; #[cfg(not(feature = "tokio"))] use async_channel::Sender; diff --git a/platforms/unix/src/atspi/bus.rs b/platforms/unix/src/atspi/bus.rs index afeb9d09..2e8fef47 100644 --- a/platforms/unix/src/atspi/bus.rs +++ b/platforms/unix/src/atspi/bus.rs @@ -8,9 +8,8 @@ use crate::{ context::get_or_init_app_context, executor::{Executor, Task}, }; -use accesskit::NodeId; use accesskit_atspi_common::{ - NodeIdOrRoot, ObjectEvent, PlatformNode, PlatformRoot, Property, WindowEvent, + NodeId, NodeIdOrRoot, ObjectEvent, PlatformNode, PlatformRoot, Property, WindowEvent, }; use atspi::{ events::EventBodyBorrowed, diff --git a/platforms/unix/src/atspi/object_id.rs b/platforms/unix/src/atspi/object_id.rs index d291cf62..e89dde9d 100644 --- a/platforms/unix/src/atspi/object_id.rs +++ b/platforms/unix/src/atspi/object_id.rs @@ -4,8 +4,7 @@ // the LICENSE-MIT file), at your option. use crate::atspi::OwnedObjectAddress; -use accesskit::NodeId; -use accesskit_atspi_common::PlatformNode; +use accesskit_atspi_common::{NodeId, PlatformNode}; use serde::{Serialize, Serializer}; use zbus::{ names::UniqueName, @@ -31,7 +30,9 @@ impl ObjectId { Self::Root => ObjectPath::from_str_unchecked(ROOT_PATH), Self::Node { adapter, node } => ObjectPath::from_string_unchecked(format!( "{}{}/{}", - ACCESSIBLE_PATH_PREFIX, adapter, node.0 + ACCESSIBLE_PATH_PREFIX, + adapter, + u128::from(*node) )), } .into() @@ -45,7 +46,7 @@ impl Serialize for ObjectId { { match self { Self::Root => serializer.serialize_str("root"), - Self::Node { node, .. } => serializer.serialize_str(&node.0.to_string()), + Self::Node { node, .. } => serializer.serialize_str(&u128::from(*node).to_string()), } } } @@ -58,7 +59,7 @@ impl From for Structure<'_> { fn from(id: ObjectId) -> Self { Self::from((match id { ObjectId::Root => "root".into(), - ObjectId::Node { node, .. } => node.0.to_string(), + ObjectId::Node { node, .. } => u128::from(node).to_string(), },)) } } diff --git a/platforms/windows/examples/hello_world.rs b/platforms/windows/examples/hello_world.rs index 7d06dc20..33a71a4f 100644 --- a/platforms/windows/examples/hello_world.rs +++ b/platforms/windows/examples/hello_world.rs @@ -2,7 +2,7 @@ use accesskit::{ Action, ActionHandler, ActionRequest, ActivationHandler, Live, Node, NodeId, Rect, Role, Tree, - TreeUpdate, + TreeId, TreeUpdate, }; use accesskit_windows::Adapter; use once_cell::sync::Lazy; @@ -115,6 +115,7 @@ impl ActivationHandler for InnerWindowState { (BUTTON_2_ID, button_2), ], tree: Some(tree), + tree_id: TreeId::ROOT, focus: self.focus, }; if let Some(announcement) = &self.announcement { @@ -138,6 +139,7 @@ impl WindowState { if let Some(events) = adapter.update_if_active(|| TreeUpdate { nodes: vec![], tree: None, + tree_id: TreeId::ROOT, focus, }) { drop(adapter); @@ -160,6 +162,7 @@ impl WindowState { TreeUpdate { nodes: vec![(ANNOUNCEMENT_ID, announcement), (WINDOW_ID, root)], tree: None, + tree_id: TreeId::ROOT, focus: inner_state.focus, } }) { @@ -201,7 +204,7 @@ impl ActionHandler for SimpleActionHandler { Some(self.window), SET_FOCUS_MSG, WPARAM(0), - LPARAM(request.target.0 as _), + LPARAM(request.target_node.0 as _), ) } .unwrap(); @@ -212,7 +215,7 @@ impl ActionHandler for SimpleActionHandler { Some(self.window), CLICK_MSG, WPARAM(0), - LPARAM(request.target.0 as _), + LPARAM(request.target_node.0 as _), ) } .unwrap(); diff --git a/platforms/windows/src/adapter.rs b/platforms/windows/src/adapter.rs index 0c889f34..b9ce64f1 100644 --- a/platforms/windows/src/adapter.rs +++ b/platforms/windows/src/adapter.rs @@ -4,10 +4,10 @@ // the LICENSE-MIT file), at your option. use accesskit::{ - ActionHandler, ActivationHandler, Live, Node as NodeProvider, NodeId, Role, Tree as TreeData, - TreeUpdate, + ActionHandler, ActivationHandler, Live, Node as NodeProvider, NodeId as LocalNodeId, Role, + Tree as TreeData, TreeId, TreeUpdate, }; -use accesskit_consumer::{FilterResult, Node, Tree, TreeChangeHandler}; +use accesskit_consumer::{FilterResult, Node, NodeId, Tree, TreeChangeHandler}; use hashbrown::{HashMap, HashSet}; use std::fmt::{Debug, Formatter}; use std::sync::{atomic::Ordering, Arc}; @@ -323,7 +323,7 @@ impl TreeChangeHandler for AdapterChangeHandler<'_> { // TODO: handle other events (#20) } -const PLACEHOLDER_ROOT_ID: NodeId = NodeId(0); +const PLACEHOLDER_ROOT_ID: LocalNodeId = LocalNodeId(0); enum State { Inactive { @@ -518,6 +518,7 @@ impl Adapter { let placeholder_update = TreeUpdate { nodes: vec![(PLACEHOLDER_ROOT_ID, NodeProvider::new(Role::Window))], tree: Some(TreeData::new(PLACEHOLDER_ROOT_ID)), + tree_id: TreeId::ROOT, focus: PLACEHOLDER_ROOT_ID, }; let placeholder_tree = Tree::new(placeholder_update, *is_window_focused); diff --git a/platforms/windows/src/node.rs b/platforms/windows/src/node.rs index ee8f37ca..d0d1c7a3 100644 --- a/platforms/windows/src/node.rs +++ b/platforms/windows/src/node.rs @@ -11,10 +11,10 @@ #![allow(non_upper_case_globals)] use accesskit::{ - Action, ActionData, ActionRequest, AriaCurrent, HasPopup, Live, NodeId, NodeIdContent, - Orientation, Point, Role, SortDirection, Toggled, + Action, ActionData, ActionRequest, AriaCurrent, HasPopup, Live, NodeId as LocalNodeId, + Orientation, Point, Role, SortDirection, Toggled, TreeId, }; -use accesskit_consumer::{FilterResult, Node, TreeState}; +use accesskit_consumer::{FilterResult, Node, NodeId, Tree, TreeState}; use std::sync::{atomic::Ordering, Arc, Weak}; use windows::{ core::*, @@ -32,13 +32,15 @@ use crate::{ util::*, }; -const RUNTIME_ID_SIZE: usize = 3; +const RUNTIME_ID_SIZE: usize = 5; fn runtime_id_from_node_id(id: NodeId) -> [i32; RUNTIME_ID_SIZE] { - static_assertions::assert_eq_size!(NodeIdContent, u64); - let id = id.0; + static_assertions::assert_eq_size!(NodeId, u128); + let id: u128 = id.into(); [ UiaAppendRuntimeId as _, + ((id >> 96) & 0xFFFFFFFF) as _, + ((id >> 64) & 0xFFFFFFFF) as _, ((id >> 32) & 0xFFFFFFFF) as _, (id & 0xFFFFFFFF) as _, ] @@ -779,10 +781,17 @@ impl PlatformNode { fn with_tree_state_and_context(&self, f: F) -> Result where F: FnOnce(&TreeState, &Context) -> Result, + { + self.with_tree_and_context(|tree, context| f(tree.state(), context)) + } + + fn with_tree_and_context(&self, f: F) -> Result + where + F: FnOnce(&Tree, &Context) -> Result, { let context = self.upgrade_context()?; let tree = context.read_tree(); - f(tree.state(), &context) + f(&tree, &context) } fn with_tree_state(&self, f: F) -> Result @@ -792,7 +801,8 @@ impl PlatformNode { self.with_tree_state_and_context(|state, _| f(state)) } - fn node<'a>(&self, state: &'a TreeState) -> Result> { + fn node<'a>(&self, tree: &'a Tree) -> Result> { + let state = tree.state(); if let Some(id) = self.node_id { if let Some(node) = state.node_by_id(id) { Ok(node) @@ -804,12 +814,20 @@ impl PlatformNode { } } + fn node_with_location<'a>(&self, tree: &'a Tree) -> Result<(Node<'a>, LocalNodeId, TreeId)> { + let node = self.node(tree)?; + let (local_id, tree_id) = tree + .locate_node(node.id()) + .ok_or_else(element_not_available)?; + Ok((node, local_id, tree_id)) + } + fn resolve_with_context(&self, f: F) -> Result where for<'a> F: FnOnce(Node<'a>, &Context) -> Result, { - self.with_tree_state_and_context(|state, context| { - let node = self.node(state)?; + self.with_tree_and_context(|tree, context| { + let node = self.node(tree)?; f(node, context) }) } @@ -818,9 +836,9 @@ impl PlatformNode { where for<'a> F: FnOnce(Node<'a>, &TreeState, &Context) -> Result, { - self.with_tree_state_and_context(|state, context| { - let node = self.node(state)?; - f(node, state, context) + self.with_tree_and_context(|tree, context| { + let node = self.node(tree)?; + f(node, tree.state(), context) }) } @@ -835,8 +853,8 @@ impl PlatformNode { where for<'a> F: FnOnce(Node<'a>, &Context) -> Result, { - self.with_tree_state_and_context(|state, context| { - let node = self.node(state)?; + self.with_tree_and_context(|tree, context| { + let node = self.node(tree)?; if node.supports_text_ranges() { f(node, context) } else { @@ -854,16 +872,15 @@ impl PlatformNode { fn do_complex_action(&self, f: F) -> Result<()> where - for<'a> F: FnOnce(Node<'a>) -> Result>, + for<'a> F: FnOnce(Node<'a>, LocalNodeId, TreeId) -> Result>, { let context = self.upgrade_context()?; if context.is_placeholder.load(Ordering::SeqCst) { return Err(element_not_enabled()); } let tree = context.read_tree(); - let state = tree.state(); - let node = self.node(state)?; - if let Some(request) = f(node)? { + let (node, target_node, target_tree) = self.node_with_location(&tree)?; + if let Some(request) = f(node, target_node, target_tree)? { drop(tree); context.do_action(request); } @@ -874,14 +891,15 @@ impl PlatformNode { where F: FnOnce() -> (Action, Option), { - self.do_complex_action(|node| { + self.do_complex_action(|node, target_node, target_tree| { if node.is_disabled() { return Err(element_not_enabled()); } let (action, data) = f(); Ok(Some(ActionRequest { - target: node.id(), action, + target_tree, + target_node, data, })) }) @@ -892,7 +910,7 @@ impl PlatformNode { } fn set_selected(&self, selected: bool) -> Result<()> { - self.do_complex_action(|node| { + self.do_complex_action(|node, target_node, target_tree| { if node.is_disabled() { return Err(element_not_enabled()); } @@ -902,7 +920,8 @@ impl PlatformNode { } Ok(Some(ActionRequest { action: Action::Click, - target: node.id(), + target_tree, + target_node, data: None, })) }) @@ -1266,10 +1285,11 @@ patterns! { )), (UIA_ScrollItemPatternId, IScrollItemProvider, IScrollItemProvider_Impl, is_scroll_item_pattern_supported, (), ( fn ScrollIntoView(&self) -> Result<()> { - self.do_complex_action(|node| { + self.do_complex_action(|_node, target_node, target_tree| { Ok(Some(ActionRequest { - target: node.id(), action: Action::ScrollIntoView, + target_tree, + target_node, data: None, })) }) diff --git a/platforms/windows/src/tests/simple.rs b/platforms/windows/src/tests/simple.rs index 382545cb..6c33cbf3 100644 --- a/platforms/windows/src/tests/simple.rs +++ b/platforms/windows/src/tests/simple.rs @@ -4,7 +4,8 @@ // the LICENSE-MIT file), at your option. use accesskit::{ - Action, ActionHandler, ActionRequest, ActivationHandler, Node, NodeId, Role, Tree, TreeUpdate, + Action, ActionHandler, ActionRequest, ActivationHandler, Node, NodeId, Role, Tree, TreeId, + TreeUpdate, }; use windows::{core::*, Win32::UI::Accessibility::*}; @@ -35,6 +36,7 @@ fn get_initial_state() -> TreeUpdate { (BUTTON_2_ID, button_2), ], tree: Some(Tree::new(WINDOW_ID)), + tree_id: TreeId::ROOT, focus: BUTTON_1_ID, } } diff --git a/platforms/windows/src/tests/subclassed.rs b/platforms/windows/src/tests/subclassed.rs index b9eeb4c7..d81236c8 100644 --- a/platforms/windows/src/tests/subclassed.rs +++ b/platforms/windows/src/tests/subclassed.rs @@ -4,7 +4,8 @@ // the LICENSE-MIT file), at your option. use accesskit::{ - Action, ActionHandler, ActionRequest, ActivationHandler, Node, NodeId, Role, Tree, TreeUpdate, + Action, ActionHandler, ActionRequest, ActivationHandler, Node, NodeId, Role, Tree, TreeId, + TreeUpdate, }; use once_cell::sync::Lazy; use windows::{ @@ -52,6 +53,7 @@ fn get_initial_state() -> TreeUpdate { (BUTTON_2_ID, button_2), ], tree: Some(Tree::new(WINDOW_ID)), + tree_id: TreeId::ROOT, focus: BUTTON_1_ID, } } diff --git a/platforms/windows/src/text.rs b/platforms/windows/src/text.rs index 39404936..2b2d0df4 100644 --- a/platforms/windows/src/text.rs +++ b/platforms/windows/src/text.rs @@ -7,7 +7,7 @@ use accesskit::{Action, ActionData, ActionRequest, ScrollHint}; use accesskit_consumer::{ - Node, TextPosition as Position, TextRange as Range, TreeState, WeakTextRange as WeakRange, + Node, TextPosition as Position, TextRange as Range, Tree, TreeState, WeakTextRange as WeakRange, }; use std::sync::{Arc, RwLock, Weak}; use windows::{ @@ -291,12 +291,12 @@ impl PlatformRange { fn do_action(&self, f: F) -> Result<()> where - for<'a> F: FnOnce(Range<'a>) -> ActionRequest, + for<'a> F: FnOnce(Range<'a>, &Tree) -> ActionRequest, { let context = self.upgrade_context()?; let tree = context.read_tree(); let range = self.upgrade_for_read(tree.state())?; - let request = f(range); + let request = f(range, &tree); drop(tree); context.do_action(request); Ok(()) @@ -557,10 +557,14 @@ impl ITextRangeProvider_Impl for PlatformRange_Impl { } fn Select(&self) -> Result<()> { - self.do_action(|range| ActionRequest { - action: Action::SetTextSelection, - target: range.node().id(), - data: Some(ActionData::SetTextSelection(range.to_text_selection())), + self.do_action(|range, tree| { + let (target_node, target_tree) = tree.locate_node(range.node().id()).unwrap(); + ActionRequest { + action: Action::SetTextSelection, + target_tree, + target_node, + data: Some(ActionData::SetTextSelection(range.to_text_selection())), + } }) } @@ -575,15 +579,17 @@ impl ITextRangeProvider_Impl for PlatformRange_Impl { } fn ScrollIntoView(&self, align_to_top: BOOL) -> Result<()> { - self.do_action(|range| { + self.do_action(|range, tree| { let position = if align_to_top.into() { range.start() } else { range.end() }; + let (target_node, target_tree) = tree.locate_node(position.inner_node().id()).unwrap(); ActionRequest { - target: position.inner_node().id(), action: Action::ScrollIntoView, + target_tree, + target_node, data: Some(ActionData::ScrollHint(if align_to_top.into() { ScrollHint::TopEdge } else { diff --git a/platforms/winit/examples/mixed_handlers.rs b/platforms/winit/examples/mixed_handlers.rs index b3a454b3..8ed575bd 100644 --- a/platforms/winit/examples/mixed_handlers.rs +++ b/platforms/winit/examples/mixed_handlers.rs @@ -2,7 +2,8 @@ mod fill; use accesskit::{ - Action, ActionRequest, ActivationHandler, Live, Node, NodeId, Rect, Role, Tree, TreeUpdate, + Action, ActionRequest, ActivationHandler, Live, Node, NodeId, Rect, Role, Tree, TreeId, + TreeUpdate, }; use accesskit_winit::{Adapter, Event as AccessKitEvent, WindowEvent as AccessKitWindowEvent}; use std::{ @@ -96,6 +97,7 @@ impl UiState { (BUTTON_2_ID, button_2), ], tree: Some(tree), + tree_id: TreeId::ROOT, focus: self.focus, }; if let Some(announcement) = &self.announcement { @@ -111,6 +113,7 @@ impl UiState { adapter.update_if_active(|| TreeUpdate { nodes: vec![], tree: None, + tree_id: TreeId::ROOT, focus, }); } @@ -128,6 +131,7 @@ impl UiState { TreeUpdate { nodes: vec![(ANNOUNCEMENT_ID, announcement), (WINDOW_ID, root)], tree: None, + tree_id: TreeId::ROOT, focus: self.focus, } }); @@ -258,15 +262,19 @@ impl ApplicationHandler for Application { match user_event.window_event { AccessKitWindowEvent::InitialTreeRequested => unreachable!(), - AccessKitWindowEvent::ActionRequested(ActionRequest { action, target, .. }) => { - if target == BUTTON_1_ID || target == BUTTON_2_ID { + AccessKitWindowEvent::ActionRequested(ActionRequest { + action, + target_node, + .. + }) => { + if target_node == BUTTON_1_ID || target_node == BUTTON_2_ID { let mut state = state.lock().unwrap(); match action { Action::Focus => { - state.set_focus(adapter, target); + state.set_focus(adapter, target_node); } Action::Click => { - state.press_button(adapter, target); + state.press_button(adapter, target_node); } _ => (), } diff --git a/platforms/winit/examples/simple.rs b/platforms/winit/examples/simple.rs index f4ab4a63..c390454b 100644 --- a/platforms/winit/examples/simple.rs +++ b/platforms/winit/examples/simple.rs @@ -1,7 +1,7 @@ #[path = "util/fill.rs"] mod fill; -use accesskit::{Action, ActionRequest, Live, Node, NodeId, Rect, Role, Tree, TreeUpdate}; +use accesskit::{Action, ActionRequest, Live, Node, NodeId, Rect, Role, Tree, TreeId, TreeUpdate}; use accesskit_winit::{Adapter, Event as AccessKitEvent, WindowEvent as AccessKitWindowEvent}; use std::error::Error; use winit::{ @@ -91,6 +91,7 @@ impl UiState { (BUTTON_2_ID, button_2), ], tree: Some(tree), + tree_id: TreeId::ROOT, focus: self.focus, }; if let Some(announcement) = &self.announcement { @@ -106,6 +107,7 @@ impl UiState { adapter.update_if_active(|| TreeUpdate { nodes: vec![], tree: None, + tree_id: TreeId::ROOT, focus, }); } @@ -123,6 +125,7 @@ impl UiState { TreeUpdate { nodes: vec![(ANNOUNCEMENT_ID, announcement), (WINDOW_ID, root)], tree: None, + tree_id: TreeId::ROOT, focus: self.focus, } }); @@ -235,14 +238,18 @@ impl ApplicationHandler for Application { AccessKitWindowEvent::InitialTreeRequested => { adapter.update_if_active(|| state.build_initial_tree()); } - AccessKitWindowEvent::ActionRequested(ActionRequest { action, target, .. }) => { - if target == BUTTON_1_ID || target == BUTTON_2_ID { + AccessKitWindowEvent::ActionRequested(ActionRequest { + action, + target_node, + .. + }) => { + if target_node == BUTTON_1_ID || target_node == BUTTON_2_ID { match action { Action::Focus => { - state.set_focus(adapter, target); + state.set_focus(adapter, target_node); } Action::Click => { - state.press_button(adapter, target); + state.press_button(adapter, target_node); } _ => (), }