Skip to content

Commit 3dd4748

Browse files
committed
Add Node::tree_id property for graft support
1 parent 1525151 commit 3dd4748

File tree

5 files changed

+2042
-46
lines changed

5 files changed

+2042
-46
lines changed

common/src/lib.rs

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,11 @@ pub enum TextDecoration {
628628
pub type NodeIdContent = u64;
629629

630630
/// The stable identity of a [`Node`], unique within the node's tree.
631+
///
632+
/// Each tree (root or subtree) has its own independent ID space. The same
633+
/// `NodeId` value can exist in different trees without conflict. When working
634+
/// with multiple trees, the combination of `NodeId` and [`TreeId`] uniquely
635+
/// identifies a node.
631636
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
632637
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
633638
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
@@ -778,6 +783,7 @@ enum PropertyValue {
778783
Rect(Rect),
779784
TextSelection(Box<TextSelection>),
780785
CustomActionVec(Vec<CustomAction>),
786+
TreeId(TreeId),
781787
}
782788

783789
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -894,6 +900,7 @@ enum PropertyId {
894900
Bounds,
895901
TextSelection,
896902
CustomActions,
903+
TreeId,
897904

898905
// This MUST be last.
899906
Unset,
@@ -1695,7 +1702,8 @@ copy_type_getters! {
16951702
(get_usize_property, usize, Usize),
16961703
(get_color_property, u32, Color),
16971704
(get_text_decoration_property, TextDecoration, TextDecoration),
1698-
(get_bool_property, bool, Bool)
1705+
(get_bool_property, bool, Bool),
1706+
(get_tree_id_property, TreeId, TreeId)
16991707
}
17001708

17011709
box_type_setters! {
@@ -1713,7 +1721,8 @@ copy_type_setters! {
17131721
(set_usize_property, usize, Usize),
17141722
(set_color_property, u32, Color),
17151723
(set_text_decoration_property, TextDecoration, TextDecoration),
1716-
(set_bool_property, bool, Bool)
1724+
(set_bool_property, bool, Bool),
1725+
(set_tree_id_property, TreeId, TreeId)
17171726
}
17181727

17191728
vec_type_methods! {
@@ -2014,11 +2023,20 @@ property_methods! {
20142023
/// [`transform`]: Node::transform
20152024
(Bounds, bounds, get_rect_property, Option<Rect>, set_bounds, set_rect_property, Rect, clear_bounds),
20162025

2017-
(TextSelection, text_selection, get_text_selection_property, Option<&TextSelection>, set_text_selection, set_text_selection_property, impl Into<Box<TextSelection>>, clear_text_selection)
2026+
(TextSelection, text_selection, get_text_selection_property, Option<&TextSelection>, set_text_selection, set_text_selection_property, impl Into<Box<TextSelection>>, clear_text_selection),
2027+
2028+
/// The tree that this node grafts. When set, this node acts as a graft
2029+
/// point, and its child is the root of the specified subtree.
2030+
///
2031+
/// A graft node must be created before its subtree is pushed.
2032+
///
2033+
/// Removing a graft node or clearing this property removes its subtree,
2034+
/// unless a new graft node is provided in the same update.
2035+
(TreeId, tree_id, get_tree_id_property, Option<TreeId>, set_tree_id, set_tree_id_property, TreeId, clear_tree_id)
20182036
}
20192037

20202038
impl Node {
2021-
option_properties_debug_method! { debug_option_properties, [transform, bounds, text_selection,] }
2039+
option_properties_debug_method! { debug_option_properties, [transform, bounds, text_selection, tree_id,] }
20222040
}
20232041

20242042
#[cfg(test)]
@@ -2123,6 +2141,31 @@ mod text_selection {
21232141
}
21242142
}
21252143

2144+
#[cfg(test)]
2145+
mod tree_id {
2146+
use super::{Node, Role, TreeId, Uuid};
2147+
2148+
#[test]
2149+
fn getter_should_return_default_value() {
2150+
let node = Node::new(Role::GenericContainer);
2151+
assert!(node.tree_id().is_none());
2152+
}
2153+
#[test]
2154+
fn setter_should_update_the_property() {
2155+
let mut node = Node::new(Role::GenericContainer);
2156+
let value = TreeId(Uuid::nil());
2157+
node.set_tree_id(value);
2158+
assert_eq!(node.tree_id(), Some(value));
2159+
}
2160+
#[test]
2161+
fn clearer_should_reset_the_property() {
2162+
let mut node = Node::new(Role::GenericContainer);
2163+
node.set_tree_id(TreeId(Uuid::nil()));
2164+
node.clear_tree_id();
2165+
assert!(node.tree_id().is_none());
2166+
}
2167+
}
2168+
21262169
vec_property_methods! {
21272170
(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)
21282171
}
@@ -2291,7 +2334,8 @@ impl Serialize for Properties {
22912334
Affine,
22922335
Rect,
22932336
TextSelection,
2294-
CustomActionVec
2337+
CustomActionVec,
2338+
TreeId
22952339
});
22962340
}
22972341
map.end()
@@ -2421,7 +2465,8 @@ impl<'de> Visitor<'de> for PropertiesVisitor {
24212465
Affine { Transform },
24222466
Rect { Bounds },
24232467
TextSelection { TextSelection },
2424-
CustomActionVec { CustomActions }
2468+
CustomActionVec { CustomActions },
2469+
TreeId { TreeId }
24252470
});
24262471
}
24272472

@@ -2651,14 +2696,26 @@ pub struct TreeUpdate {
26512696
/// a tree.
26522697
pub tree: Option<Tree>,
26532698

2654-
/// The identifier of the tree.
2699+
/// The identifier of the tree that this update applies to.
2700+
///
2701+
/// Use [`TreeId::ROOT`] for the main/root tree. For subtrees, use a unique
2702+
/// [`TreeId`] that identifies the subtree.
2703+
///
2704+
/// When updating a subtree (non-ROOT tree_id):
2705+
/// - A graft node with [`Node::tree_id`] set to this tree's ID must already
2706+
/// exist in the parent tree before the first subtree update.
2707+
/// - The first update for a subtree must include [`tree`](Self::tree) data.
26552708
pub tree_id: TreeId,
26562709

26572710
/// The node within this tree that has keyboard focus when the native
26582711
/// host (e.g. window) has focus. If no specific node within the tree
26592712
/// has keyboard focus, this must be set to the root. The latest focus state
26602713
/// must be provided with every tree update, even if the focus state
26612714
/// didn't change in a given update.
2715+
///
2716+
/// For subtrees, this specifies which node has focus when the subtree
2717+
/// itself is focused (i.e., when focus is on the graft node in the parent
2718+
/// tree).
26622719
pub focus: NodeId,
26632720
}
26642721

consumer/src/filters.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ fn common_filter_base(node: &Node) -> Option<FilterResult> {
2323
return Some(FilterResult::ExcludeSubtree);
2424
}
2525

26+
// Graft nodes are transparent containers pointing to a subtree
27+
if node.is_graft() {
28+
return Some(FilterResult::ExcludeNode);
29+
}
30+
2631
let role = node.role();
2732
if role == Role::GenericContainer || role == Role::TextRun {
2833
return Some(FilterResult::ExcludeNode);
@@ -387,4 +392,30 @@ mod tests {
387392
assert_filter_result(ExcludeSubtree, &tree, NodeId(10));
388393
assert_filter_result(ExcludeSubtree, &tree, NodeId(11));
389394
}
395+
396+
#[test]
397+
fn graft_node() {
398+
use accesskit::Uuid;
399+
400+
let subtree_id = TreeId(Uuid::from_u128(1));
401+
let update = TreeUpdate {
402+
nodes: vec![
403+
(NodeId(0), {
404+
let mut node = Node::new(Role::Window);
405+
node.set_children(vec![NodeId(1)]);
406+
node
407+
}),
408+
(NodeId(1), {
409+
let mut node = Node::new(Role::GenericContainer);
410+
node.set_tree_id(subtree_id);
411+
node
412+
}),
413+
],
414+
tree: Some(Tree::new(NodeId(0))),
415+
tree_id: TreeId::ROOT,
416+
focus: NodeId(0),
417+
};
418+
let tree = crate::Tree::new(update, false);
419+
assert_filter_result(ExcludeNode, &tree, NodeId(1));
420+
}
390421
}

consumer/src/iterators.rs

Lines changed: 125 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@ use crate::{
1818
tree::State as TreeState,
1919
};
2020

21-
/// Iterator over child NodeIds.
21+
/// Iterator over child NodeIds, handling both normal nodes and graft nodes.
2222
pub enum ChildIds<'a> {
2323
Normal {
2424
parent_id: NodeId,
2525
children: core::slice::Iter<'a, LocalNodeId>,
2626
},
27+
Graft(Option<NodeId>),
2728
}
2829

2930
impl Iterator for ChildIds<'_> {
@@ -37,6 +38,7 @@ impl Iterator for ChildIds<'_> {
3738
} => children
3839
.next()
3940
.map(|child| parent_id.with_same_tree(*child)),
41+
Self::Graft(id) => id.take(),
4042
}
4143
}
4244

@@ -55,6 +57,7 @@ impl DoubleEndedIterator for ChildIds<'_> {
5557
} => children
5658
.next_back()
5759
.map(|child| parent_id.with_same_tree(*child)),
60+
Self::Graft(id) => id.take(),
5861
}
5962
}
6063
}
@@ -63,6 +66,7 @@ impl ExactSizeIterator for ChildIds<'_> {
6366
fn len(&self) -> usize {
6467
match self {
6568
Self::Normal { children, .. } => children.len(),
69+
Self::Graft(id) => usize::from(id.is_some()),
6670
}
6771
}
6872
}
@@ -85,13 +89,18 @@ impl<'a> FollowingSiblings<'a> {
8589
let parent_and_index = node.parent_and_index();
8690
let (back_position, front_position, done) =
8791
if let Some((ref parent, index)) = parent_and_index {
88-
let back_position = parent.data().children().len() - 1;
89-
let front_position = index + 1;
90-
(
91-
back_position,
92-
front_position,
93-
front_position > back_position,
94-
)
92+
// Graft nodes have only one child (the subtree root)
93+
if parent.is_graft() {
94+
(0, 0, true)
95+
} else {
96+
let back_position = parent.data().children().len() - 1;
97+
let front_position = index + 1;
98+
(
99+
back_position,
100+
front_position,
101+
front_position > back_position,
102+
)
103+
}
95104
} else {
96105
(0, 0, true)
97106
};
@@ -169,12 +178,18 @@ pub struct PrecedingSiblings<'a> {
169178
impl<'a> PrecedingSiblings<'a> {
170179
pub(crate) fn new(node: Node<'a>) -> Self {
171180
let parent_and_index = node.parent_and_index();
172-
let (back_position, front_position, done) = if let Some((_, index)) = parent_and_index {
173-
let front_position = index.saturating_sub(1);
174-
(0, front_position, index == 0)
175-
} else {
176-
(0, 0, true)
177-
};
181+
let (back_position, front_position, done) =
182+
if let Some((ref parent, index)) = parent_and_index {
183+
// Graft nodes have only one child (the subtree root)
184+
if parent.is_graft() {
185+
(0, 0, true)
186+
} else {
187+
let front_position = index.saturating_sub(1);
188+
(0, front_position, index == 0)
189+
}
190+
} else {
191+
(0, 0, true)
192+
};
178193
Self {
179194
back_position,
180195
done,
@@ -567,9 +582,14 @@ impl<Filter: Fn(&Node) -> FilterResult> FusedIterator for LabelledBy<'_, Filter>
567582

568583
#[cfg(test)]
569584
mod tests {
570-
use crate::tests::*;
571-
use accesskit::NodeId as LocalNodeId;
572-
use alloc::vec::Vec;
585+
use crate::{
586+
filters::common_filter,
587+
tests::*,
588+
tree::{ChangeHandler, TreeIndex},
589+
NodeId,
590+
};
591+
use accesskit::{Node, NodeId as LocalNodeId, Role, Tree, TreeId, TreeUpdate, Uuid};
592+
use alloc::{vec, vec::Vec};
573593

574594
#[test]
575595
fn following_siblings() {
@@ -927,4 +947,92 @@ mod tests {
927947
.next_back()
928948
.is_none());
929949
}
950+
951+
#[test]
952+
fn graft_node_without_subtree_has_no_filtered_children() {
953+
let subtree_id = TreeId(Uuid::from_u128(1));
954+
955+
let update = TreeUpdate {
956+
nodes: vec![
957+
(LocalNodeId(0), {
958+
let mut node = Node::new(Role::Window);
959+
node.set_children(vec![LocalNodeId(1)]);
960+
node
961+
}),
962+
(LocalNodeId(1), {
963+
let mut node = Node::new(Role::GenericContainer);
964+
node.set_tree_id(subtree_id);
965+
node
966+
}),
967+
],
968+
tree: Some(Tree::new(LocalNodeId(0))),
969+
tree_id: TreeId::ROOT,
970+
focus: LocalNodeId(0),
971+
};
972+
let tree = crate::Tree::new(update, false);
973+
974+
let graft_node_id = NodeId::new(LocalNodeId(1), TreeIndex(0));
975+
let graft_node = tree.state().node_by_id(graft_node_id).unwrap();
976+
assert!(graft_node.filtered_children(common_filter).next().is_none());
977+
}
978+
979+
#[test]
980+
fn filtered_children_crosses_subtree_boundary() {
981+
struct NoOpHandler;
982+
impl ChangeHandler for NoOpHandler {
983+
fn node_added(&mut self, _: &crate::Node) {}
984+
fn node_updated(&mut self, _: &crate::Node, _: &crate::Node) {}
985+
fn focus_moved(&mut self, _: Option<&crate::Node>, _: Option<&crate::Node>) {}
986+
fn node_removed(&mut self, _: &crate::Node) {}
987+
}
988+
989+
let subtree_id = TreeId(Uuid::from_u128(1));
990+
991+
let update = TreeUpdate {
992+
nodes: vec![
993+
(LocalNodeId(0), {
994+
let mut node = Node::new(Role::Window);
995+
node.set_children(vec![LocalNodeId(1)]);
996+
node
997+
}),
998+
(LocalNodeId(1), {
999+
let mut node = Node::new(Role::GenericContainer);
1000+
node.set_tree_id(subtree_id);
1001+
node
1002+
}),
1003+
],
1004+
tree: Some(Tree::new(LocalNodeId(0))),
1005+
tree_id: TreeId::ROOT,
1006+
focus: LocalNodeId(0),
1007+
};
1008+
let mut tree = crate::Tree::new(update, false);
1009+
1010+
let subtree_update = TreeUpdate {
1011+
nodes: vec![
1012+
(LocalNodeId(0), {
1013+
let mut node = Node::new(Role::Document);
1014+
node.set_children(vec![LocalNodeId(1)]);
1015+
node
1016+
}),
1017+
(LocalNodeId(1), Node::new(Role::Button)),
1018+
],
1019+
tree: Some(Tree::new(LocalNodeId(0))),
1020+
tree_id: subtree_id,
1021+
focus: LocalNodeId(0),
1022+
};
1023+
tree.update_and_process_changes(subtree_update, &mut NoOpHandler);
1024+
1025+
let root = tree.state().root();
1026+
let filtered_children: Vec<_> = root.filtered_children(common_filter).collect();
1027+
1028+
assert_eq!(1, filtered_children.len());
1029+
let subtree_root_id = NodeId::new(LocalNodeId(0), TreeIndex(1));
1030+
assert_eq!(subtree_root_id, filtered_children[0].id());
1031+
1032+
let document = &filtered_children[0];
1033+
let doc_children: Vec<_> = document.filtered_children(common_filter).collect();
1034+
assert_eq!(1, doc_children.len());
1035+
let button_id = NodeId::new(LocalNodeId(1), TreeIndex(1));
1036+
assert_eq!(button_id, doc_children[0].id());
1037+
}
9301038
}

0 commit comments

Comments
 (0)