Skip to content

Commit c16eb80

Browse files
authored
feat!: Multiple tree support (#655)
1 parent 09e2a37 commit c16eb80

File tree

31 files changed

+3202
-590
lines changed

31 files changed

+3202
-590
lines changed

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ pyo3 = { version = "0.26", optional = true }
2020
schemars = { version = "1", optional = true }
2121
serde = { version = "1.0", default-features = false, features = ["alloc", "derive"], optional = true }
2222
serde_json = { version = "1.0", default-features = false, optional = true }
23+
uuid = { version = "1", default-features = false }
2324

2425
[features]
2526
enumn = ["dep:enumn"]
2627
pyo3 = ["dep:pyo3"]
27-
serde = ["dep:serde", "enumn"]
28-
schemars = ["dep:schemars", "dep:serde_json", "serde"]
28+
serde = ["dep:serde", "enumn", "uuid/serde"]
29+
schemars = ["dep:schemars", "dep:serde_json", "serde", "schemars/uuid1"]

common/src/lib.rs

Lines changed: 85 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ use serde::{
2929
#[cfg(feature = "schemars")]
3030
use serde_json::{Map as SchemaMap, Value as SchemaValue};
3131

32+
pub use uuid::Uuid;
33+
3234
mod geometry;
3335
pub use geometry::{Affine, Point, Rect, Size, Vec2};
3436

@@ -626,6 +628,11 @@ pub enum TextDecoration {
626628
pub type NodeIdContent = u64;
627629

628630
/// 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.
629636
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
630637
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
631638
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
@@ -652,6 +659,21 @@ impl fmt::Debug for NodeId {
652659
}
653660
}
654661

662+
/// The stable identity of a [`Tree`].
663+
///
664+
/// Use [`TreeId::ROOT`] for the main/root tree. For subtrees, use a random
665+
/// UUID (version 4) to avoid collisions between independently created trees.
666+
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
667+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
668+
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
669+
#[repr(transparent)]
670+
pub struct TreeId(pub Uuid);
671+
672+
impl TreeId {
673+
/// A reserved tree ID for the root tree. This uses a nil UUID.
674+
pub const ROOT: Self = Self(Uuid::nil());
675+
}
676+
655677
/// Defines a custom action for a UI element.
656678
///
657679
/// For example, a list UI can allow a user to reorder items in the list by dragging the
@@ -774,6 +796,7 @@ enum PropertyValue {
774796
Rect(Rect),
775797
TextSelection(Box<TextSelection>),
776798
CustomActionVec(Vec<CustomAction>),
799+
TreeId(TreeId),
777800
}
778801

779802
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -892,6 +915,7 @@ enum PropertyId {
892915
Bounds,
893916
TextSelection,
894917
CustomActions,
918+
TreeId,
895919

896920
// This MUST be last.
897921
Unset,
@@ -1728,7 +1752,8 @@ copy_type_getters! {
17281752
(get_usize_property, usize, Usize),
17291753
(get_color_property, Color, Color),
17301754
(get_text_decoration_property, TextDecoration, TextDecoration),
1731-
(get_bool_property, bool, Bool)
1755+
(get_bool_property, bool, Bool),
1756+
(get_tree_id_property, TreeId, TreeId)
17321757
}
17331758

17341759
box_type_setters! {
@@ -1747,7 +1772,8 @@ copy_type_setters! {
17471772
(set_usize_property, usize, Usize),
17481773
(set_color_property, Color, Color),
17491774
(set_text_decoration_property, TextDecoration, TextDecoration),
1750-
(set_bool_property, bool, Bool)
1775+
(set_bool_property, bool, Bool),
1776+
(set_tree_id_property, TreeId, TreeId)
17511777
}
17521778

17531779
vec_type_methods! {
@@ -2051,11 +2077,20 @@ property_methods! {
20512077
/// [`transform`]: Node::transform
20522078
(Bounds, bounds, get_rect_property, Option<Rect>, set_bounds, set_rect_property, Rect, clear_bounds),
20532079

2054-
(TextSelection, text_selection, get_text_selection_property, Option<&TextSelection>, set_text_selection, set_text_selection_property, impl Into<Box<TextSelection>>, clear_text_selection)
2080+
(TextSelection, text_selection, get_text_selection_property, Option<&TextSelection>, set_text_selection, set_text_selection_property, impl Into<Box<TextSelection>>, clear_text_selection),
2081+
2082+
/// The tree that this node grafts. When set, this node acts as a graft
2083+
/// point, and its child is the root of the specified subtree.
2084+
///
2085+
/// A graft node must be created before its subtree is pushed.
2086+
///
2087+
/// Removing a graft node or clearing this property removes its subtree,
2088+
/// unless a new graft node is provided in the same update.
2089+
(TreeId, tree_id, get_tree_id_property, Option<TreeId>, set_tree_id, set_tree_id_property, TreeId, clear_tree_id)
20552090
}
20562091

20572092
impl Node {
2058-
option_properties_debug_method! { debug_option_properties, [transform, bounds, text_selection,] }
2093+
option_properties_debug_method! { debug_option_properties, [transform, bounds, text_selection, tree_id,] }
20592094
}
20602095

20612096
#[cfg(test)]
@@ -2160,6 +2195,31 @@ mod text_selection {
21602195
}
21612196
}
21622197

2198+
#[cfg(test)]
2199+
mod tree_id {
2200+
use super::{Node, Role, TreeId, Uuid};
2201+
2202+
#[test]
2203+
fn getter_should_return_default_value() {
2204+
let node = Node::new(Role::GenericContainer);
2205+
assert!(node.tree_id().is_none());
2206+
}
2207+
#[test]
2208+
fn setter_should_update_the_property() {
2209+
let mut node = Node::new(Role::GenericContainer);
2210+
let value = TreeId(Uuid::nil());
2211+
node.set_tree_id(value);
2212+
assert_eq!(node.tree_id(), Some(value));
2213+
}
2214+
#[test]
2215+
fn clearer_should_reset_the_property() {
2216+
let mut node = Node::new(Role::GenericContainer);
2217+
node.set_tree_id(TreeId(Uuid::nil()));
2218+
node.clear_tree_id();
2219+
assert!(node.tree_id().is_none());
2220+
}
2221+
}
2222+
21632223
vec_property_methods! {
21642224
(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)
21652225
}
@@ -2330,7 +2390,8 @@ impl Serialize for Properties {
23302390
Affine,
23312391
Rect,
23322392
TextSelection,
2333-
CustomActionVec
2393+
CustomActionVec,
2394+
TreeId
23342395
});
23352396
}
23362397
map.end()
@@ -2462,7 +2523,8 @@ impl<'de> Visitor<'de> for PropertiesVisitor {
24622523
Affine { Transform },
24632524
Rect { Bounds },
24642525
TextSelection { TextSelection },
2465-
CustomActionVec { CustomActions }
2526+
CustomActionVec { CustomActions },
2527+
TreeId { TreeId }
24662528
});
24672529
}
24682530

@@ -2694,11 +2756,26 @@ pub struct TreeUpdate {
26942756
/// a tree.
26952757
pub tree: Option<Tree>,
26962758

2759+
/// The identifier of the tree that this update applies to.
2760+
///
2761+
/// Use [`TreeId::ROOT`] for the main/root tree. For subtrees, use a unique
2762+
/// [`TreeId`] that identifies the subtree.
2763+
///
2764+
/// When updating a subtree (non-ROOT tree_id):
2765+
/// - A graft node with [`Node::tree_id`] set to this tree's ID must already
2766+
/// exist in the parent tree before the first subtree update.
2767+
/// - The first update for a subtree must include [`tree`](Self::tree) data.
2768+
pub tree_id: TreeId,
2769+
26972770
/// The node within this tree that has keyboard focus when the native
26982771
/// host (e.g. window) has focus. If no specific node within the tree
26992772
/// has keyboard focus, this must be set to the root. The latest focus state
27002773
/// must be provided with every tree update, even if the focus state
27012774
/// didn't change in a given update.
2775+
///
2776+
/// For subtrees, this specifies which node has focus when the subtree
2777+
/// itself is focused (i.e., when focus is on the graft node in the parent
2778+
/// tree).
27022779
pub focus: NodeId,
27032780
}
27042781

@@ -2772,7 +2849,8 @@ pub enum ActionData {
27722849
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
27732850
pub struct ActionRequest {
27742851
pub action: Action,
2775-
pub target: NodeId,
2852+
pub target_tree: TreeId,
2853+
pub target_node: NodeId,
27762854
pub data: Option<ActionData>,
27772855
}
27782856

consumer/src/filters.rs

Lines changed: 43 additions & 3 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);
@@ -95,19 +100,20 @@ pub fn common_filter_with_root_exception(node: &Node) -> FilterResult {
95100

96101
#[cfg(test)]
97102
mod tests {
98-
use accesskit::{Node, NodeId, Rect, Role, Tree, TreeUpdate};
103+
use accesskit::{Node, NodeId, Rect, Role, Tree, TreeId, TreeUpdate};
99104
use alloc::vec;
100105

101106
use super::{
102107
common_filter, common_filter_with_root_exception,
103108
FilterResult::{self, *},
104109
};
110+
use crate::tests::nid;
105111

106112
#[track_caller]
107113
fn assert_filter_result(expected: FilterResult, tree: &crate::Tree, id: NodeId) {
108114
assert_eq!(
109115
expected,
110-
common_filter(&tree.state().node_by_id(id).unwrap())
116+
common_filter(&tree.state().node_by_id(nid(id)).unwrap())
111117
);
112118
}
113119

@@ -123,6 +129,7 @@ mod tests {
123129
(NodeId(1), Node::new(Role::Button)),
124130
],
125131
tree: Some(Tree::new(NodeId(0))),
132+
tree_id: TreeId::ROOT,
126133
focus: NodeId(0),
127134
};
128135
let tree = crate::Tree::new(update, false);
@@ -145,6 +152,7 @@ mod tests {
145152
}),
146153
],
147154
tree: Some(Tree::new(NodeId(0))),
155+
tree_id: TreeId::ROOT,
148156
focus: NodeId(0),
149157
};
150158
let tree = crate::Tree::new(update, false);
@@ -167,6 +175,7 @@ mod tests {
167175
}),
168176
],
169177
tree: Some(Tree::new(NodeId(0))),
178+
tree_id: TreeId::ROOT,
170179
focus: NodeId(1),
171180
};
172181
let tree = crate::Tree::new(update, true);
@@ -185,13 +194,14 @@ mod tests {
185194
(NodeId(1), Node::new(Role::Button)),
186195
],
187196
tree: Some(Tree::new(NodeId(0))),
197+
tree_id: TreeId::ROOT,
188198
focus: NodeId(0),
189199
};
190200
let tree = crate::Tree::new(update, false);
191201
assert_filter_result(ExcludeNode, &tree, NodeId(0));
192202
assert_eq!(
193203
Include,
194-
common_filter_with_root_exception(&tree.state().node_by_id(NodeId(0)).unwrap())
204+
common_filter_with_root_exception(&tree.state().node_by_id(nid(NodeId(0))).unwrap())
195205
);
196206
assert_filter_result(Include, &tree, NodeId(1));
197207
}
@@ -209,6 +219,7 @@ mod tests {
209219
(NodeId(1), Node::new(Role::Button)),
210220
],
211221
tree: Some(Tree::new(NodeId(0))),
222+
tree_id: TreeId::ROOT,
212223
focus: NodeId(0),
213224
};
214225
let tree = crate::Tree::new(update, false);
@@ -229,6 +240,7 @@ mod tests {
229240
(NodeId(1), Node::new(Role::Button)),
230241
],
231242
tree: Some(Tree::new(NodeId(0))),
243+
tree_id: TreeId::ROOT,
232244
focus: NodeId(1),
233245
};
234246
let tree = crate::Tree::new(update, true);
@@ -248,6 +260,7 @@ mod tests {
248260
(NodeId(1), Node::new(Role::TextRun)),
249261
],
250262
tree: Some(Tree::new(NodeId(0))),
263+
tree_id: TreeId::ROOT,
251264
focus: NodeId(0),
252265
};
253266
let tree = crate::Tree::new(update, false);
@@ -333,6 +346,7 @@ mod tests {
333346
}),
334347
],
335348
tree: Some(Tree::new(NodeId(0))),
349+
tree_id: TreeId::ROOT,
336350
focus: NodeId(0),
337351
};
338352
crate::Tree::new(update, false)
@@ -378,4 +392,30 @@ mod tests {
378392
assert_filter_result(ExcludeSubtree, &tree, NodeId(10));
379393
assert_filter_result(ExcludeSubtree, &tree, NodeId(11));
380394
}
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+
}
381421
}

0 commit comments

Comments
 (0)