Skip to content

Commit 4094dec

Browse files
authored
feat: Consumer support for scrolling and clipping children (#574)
1 parent d38776a commit 4094dec

File tree

2 files changed

+233
-50
lines changed

2 files changed

+233
-50
lines changed

consumer/src/filters.rs

Lines changed: 205 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// the LICENSE-APACHE file) or the MIT license (found in
44
// the LICENSE-MIT file), at your option.
55

6-
use accesskit::Role;
6+
use accesskit::{Rect, Role};
77

88
use crate::node::Node;
99

@@ -14,13 +14,41 @@ pub enum FilterResult {
1414
ExcludeSubtree,
1515
}
1616

17-
pub fn common_filter(node: &Node) -> FilterResult {
17+
fn common_filter_base(node: &Node) -> Option<FilterResult> {
1818
if node.is_focused() {
19-
return FilterResult::Include;
19+
return Some(FilterResult::Include);
2020
}
2121

2222
if node.is_hidden() {
23-
return FilterResult::ExcludeSubtree;
23+
return Some(FilterResult::ExcludeSubtree);
24+
}
25+
26+
let role = node.role();
27+
if role == Role::GenericContainer || role == Role::TextRun {
28+
return Some(FilterResult::ExcludeNode);
29+
}
30+
31+
None
32+
}
33+
34+
fn common_filter_without_parent_checks(node: &Node) -> FilterResult {
35+
common_filter_base(node).unwrap_or(FilterResult::Include)
36+
}
37+
38+
fn is_first_sibling_in_parent_bbox<'a>(
39+
mut siblings: impl Iterator<Item = Node<'a>>,
40+
parent_bbox: Rect,
41+
) -> bool {
42+
siblings.next().is_some_and(|sibling| {
43+
sibling
44+
.bounding_box()
45+
.is_some_and(|bbox| !bbox.intersect(parent_bbox).is_empty())
46+
})
47+
}
48+
49+
pub fn common_filter(node: &Node) -> FilterResult {
50+
if let Some(result) = common_filter_base(node) {
51+
return result;
2452
}
2553

2654
if let Some(parent) = node.parent() {
@@ -29,9 +57,30 @@ pub fn common_filter(node: &Node) -> FilterResult {
2957
}
3058
}
3159

32-
let role = node.role();
33-
if role == Role::GenericContainer || role == Role::TextRun {
34-
return FilterResult::ExcludeNode;
60+
if let Some(parent) = node.filtered_parent(&common_filter_without_parent_checks) {
61+
if parent.clips_children() {
62+
// If the parent clips its children, then exclude this subtree
63+
// if this child's bounding box isn't inside the parent's bounding
64+
// box, and if the previous or next filtered sibling isn't inside
65+
// the parent's bounding box either. The latter condition is meant
66+
// to allow off-screen items to be seen by consumers so they can be
67+
// scrolled into view.
68+
if let Some(bbox) = node.bounding_box() {
69+
if let Some(parent_bbox) = parent.bounding_box() {
70+
if bbox.intersect(parent_bbox).is_empty()
71+
&& !(is_first_sibling_in_parent_bbox(
72+
node.following_filtered_siblings(&common_filter_without_parent_checks),
73+
parent_bbox,
74+
) || is_first_sibling_in_parent_bbox(
75+
node.preceding_filtered_siblings(&common_filter_without_parent_checks),
76+
parent_bbox,
77+
))
78+
{
79+
return FilterResult::ExcludeSubtree;
80+
}
81+
}
82+
}
83+
}
3584
}
3685

3786
FilterResult::Include
@@ -46,10 +95,21 @@ pub fn common_filter_with_root_exception(node: &Node) -> FilterResult {
4695

4796
#[cfg(test)]
4897
mod tests {
49-
use accesskit::{Node, NodeId, Role, Tree, TreeUpdate};
98+
use accesskit::{Node, NodeId, Rect, Role, Tree, TreeUpdate};
5099
use alloc::vec;
51100

52-
use super::{common_filter, common_filter_with_root_exception, FilterResult};
101+
use super::{
102+
common_filter, common_filter_with_root_exception,
103+
FilterResult::{self, *},
104+
};
105+
106+
#[track_caller]
107+
fn assert_filter_result(expected: FilterResult, tree: &crate::Tree, id: NodeId) {
108+
assert_eq!(
109+
expected,
110+
common_filter(&tree.state().node_by_id(id).unwrap())
111+
);
112+
}
53113

54114
#[test]
55115
fn normal() {
@@ -66,10 +126,7 @@ mod tests {
66126
focus: NodeId(0),
67127
};
68128
let tree = crate::Tree::new(update, false);
69-
assert_eq!(
70-
FilterResult::Include,
71-
common_filter(&tree.state().node_by_id(NodeId(1)).unwrap())
72-
);
129+
assert_filter_result(Include, &tree, NodeId(1));
73130
}
74131

75132
#[test]
@@ -91,10 +148,7 @@ mod tests {
91148
focus: NodeId(0),
92149
};
93150
let tree = crate::Tree::new(update, false);
94-
assert_eq!(
95-
FilterResult::ExcludeSubtree,
96-
common_filter(&tree.state().node_by_id(NodeId(1)).unwrap())
97-
);
151+
assert_filter_result(ExcludeSubtree, &tree, NodeId(1));
98152
}
99153

100154
#[test]
@@ -116,10 +170,7 @@ mod tests {
116170
focus: NodeId(1),
117171
};
118172
let tree = crate::Tree::new(update, true);
119-
assert_eq!(
120-
FilterResult::Include,
121-
common_filter(&tree.state().node_by_id(NodeId(1)).unwrap())
122-
);
173+
assert_filter_result(Include, &tree, NodeId(1));
123174
}
124175

125176
#[test]
@@ -137,18 +188,12 @@ mod tests {
137188
focus: NodeId(0),
138189
};
139190
let tree = crate::Tree::new(update, false);
191+
assert_filter_result(ExcludeNode, &tree, NodeId(0));
140192
assert_eq!(
141-
FilterResult::ExcludeNode,
142-
common_filter(&tree.state().node_by_id(NodeId(0)).unwrap())
143-
);
144-
assert_eq!(
145-
FilterResult::Include,
193+
Include,
146194
common_filter_with_root_exception(&tree.state().node_by_id(NodeId(0)).unwrap())
147195
);
148-
assert_eq!(
149-
FilterResult::Include,
150-
common_filter(&tree.state().node_by_id(NodeId(1)).unwrap())
151-
);
196+
assert_filter_result(Include, &tree, NodeId(1));
152197
}
153198

154199
#[test]
@@ -167,14 +212,8 @@ mod tests {
167212
focus: NodeId(0),
168213
};
169214
let tree = crate::Tree::new(update, false);
170-
assert_eq!(
171-
FilterResult::ExcludeSubtree,
172-
common_filter(&tree.state().node_by_id(NodeId(0)).unwrap())
173-
);
174-
assert_eq!(
175-
FilterResult::ExcludeSubtree,
176-
common_filter(&tree.state().node_by_id(NodeId(1)).unwrap())
177-
);
215+
assert_filter_result(ExcludeSubtree, &tree, NodeId(0));
216+
assert_filter_result(ExcludeSubtree, &tree, NodeId(1));
178217
}
179218

180219
#[test]
@@ -193,14 +232,8 @@ mod tests {
193232
focus: NodeId(1),
194233
};
195234
let tree = crate::Tree::new(update, true);
196-
assert_eq!(
197-
FilterResult::ExcludeSubtree,
198-
common_filter(&tree.state().node_by_id(NodeId(0)).unwrap())
199-
);
200-
assert_eq!(
201-
FilterResult::Include,
202-
common_filter(&tree.state().node_by_id(NodeId(1)).unwrap())
203-
);
235+
assert_filter_result(ExcludeSubtree, &tree, NodeId(0));
236+
assert_filter_result(Include, &tree, NodeId(1));
204237
}
205238

206239
#[test]
@@ -218,9 +251,131 @@ mod tests {
218251
focus: NodeId(0),
219252
};
220253
let tree = crate::Tree::new(update, false);
221-
assert_eq!(
222-
FilterResult::ExcludeNode,
223-
common_filter(&tree.state().node_by_id(NodeId(1)).unwrap())
224-
);
254+
assert_filter_result(ExcludeNode, &tree, NodeId(1));
255+
}
256+
257+
fn clipped_children_test_tree() -> crate::Tree {
258+
let update = TreeUpdate {
259+
nodes: vec![
260+
(NodeId(0), {
261+
let mut node = Node::new(Role::ScrollView);
262+
node.set_clips_children();
263+
node.set_bounds(Rect::new(0.0, 0.0, 30.0, 30.0));
264+
node.set_children(vec![
265+
NodeId(1),
266+
NodeId(2),
267+
NodeId(3),
268+
NodeId(4),
269+
NodeId(5),
270+
NodeId(6),
271+
NodeId(7),
272+
NodeId(8),
273+
NodeId(9),
274+
NodeId(10),
275+
NodeId(11),
276+
]);
277+
node
278+
}),
279+
(NodeId(1), {
280+
let mut node = Node::new(Role::Unknown);
281+
node.set_bounds(Rect::new(0.0, -30.0, 30.0, -20.0));
282+
node
283+
}),
284+
(NodeId(2), {
285+
let mut node = Node::new(Role::Unknown);
286+
node.set_bounds(Rect::new(0.0, -20.0, 30.0, -10.0));
287+
node
288+
}),
289+
(NodeId(3), {
290+
let mut node = Node::new(Role::Unknown);
291+
node.set_bounds(Rect::new(0.0, -10.0, 30.0, 0.0));
292+
node
293+
}),
294+
(NodeId(4), {
295+
let mut node = Node::new(Role::Unknown);
296+
node.set_hidden();
297+
node
298+
}),
299+
(NodeId(5), {
300+
let mut node = Node::new(Role::Unknown);
301+
node.set_bounds(Rect::new(0.0, 0.0, 30.0, 10.0));
302+
node
303+
}),
304+
(NodeId(6), {
305+
let mut node = Node::new(Role::Unknown);
306+
node.set_bounds(Rect::new(0.0, 10.0, 30.0, 20.0));
307+
node
308+
}),
309+
(NodeId(7), {
310+
let mut node = Node::new(Role::Unknown);
311+
node.set_bounds(Rect::new(0.0, 20.0, 30.0, 30.0));
312+
node
313+
}),
314+
(NodeId(8), {
315+
let mut node = Node::new(Role::Unknown);
316+
node.set_hidden();
317+
node
318+
}),
319+
(NodeId(9), {
320+
let mut node = Node::new(Role::Unknown);
321+
node.set_bounds(Rect::new(0.0, 30.0, 30.0, 40.0));
322+
node
323+
}),
324+
(NodeId(10), {
325+
let mut node = Node::new(Role::Unknown);
326+
node.set_bounds(Rect::new(0.0, 40.0, 30.0, 50.0));
327+
node
328+
}),
329+
(NodeId(11), {
330+
let mut node = Node::new(Role::Unknown);
331+
node.set_bounds(Rect::new(0.0, 50.0, 30.0, 60.0));
332+
node
333+
}),
334+
],
335+
tree: Some(Tree::new(NodeId(0))),
336+
focus: NodeId(0),
337+
};
338+
crate::Tree::new(update, false)
339+
}
340+
341+
#[test]
342+
fn clipped_children_excluded_above() {
343+
let tree = clipped_children_test_tree();
344+
assert_filter_result(ExcludeSubtree, &tree, NodeId(1));
345+
assert_filter_result(ExcludeSubtree, &tree, NodeId(2));
346+
}
347+
348+
#[test]
349+
fn clipped_children_included_above() {
350+
let tree = clipped_children_test_tree();
351+
assert_filter_result(Include, &tree, NodeId(3));
352+
}
353+
354+
#[test]
355+
fn clipped_children_hidden() {
356+
let tree = clipped_children_test_tree();
357+
assert_filter_result(ExcludeSubtree, &tree, NodeId(4));
358+
assert_filter_result(ExcludeSubtree, &tree, NodeId(8));
359+
}
360+
361+
#[test]
362+
fn clipped_children_visible() {
363+
let tree = clipped_children_test_tree();
364+
assert_filter_result(Include, &tree, NodeId(5));
365+
assert_filter_result(Include, &tree, NodeId(6));
366+
assert_filter_result(Include, &tree, NodeId(7));
367+
}
368+
369+
#[test]
370+
fn clipped_children_included_below() {
371+
let tree = clipped_children_test_tree();
372+
assert_filter_result(Include, &tree, NodeId(9));
373+
}
374+
375+
#[test]
376+
fn clipped_children_excluded_below() {
377+
let tree = clipped_children_test_tree();
378+
assert_filter_result(ExcludeSubtree, &tree, NodeId(10));
379+
assert_filter_result(ExcludeSubtree, &tree, NodeId(11));
225380
}
226381
}

consumer/src/node.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,34 @@ impl<'a> Node<'a> {
378378
self.data().numeric_value_jump()
379379
}
380380

381+
pub fn clips_children(&self) -> bool {
382+
self.data().clips_children()
383+
}
384+
385+
pub fn scroll_x(&self) -> Option<f64> {
386+
self.data().scroll_x()
387+
}
388+
389+
pub fn scroll_x_min(&self) -> Option<f64> {
390+
self.data().scroll_x_min()
391+
}
392+
393+
pub fn scroll_x_max(&self) -> Option<f64> {
394+
self.data().scroll_x_max()
395+
}
396+
397+
pub fn scroll_y(&self) -> Option<f64> {
398+
self.data().scroll_y()
399+
}
400+
401+
pub fn scroll_y_min(&self) -> Option<f64> {
402+
self.data().scroll_y_min()
403+
}
404+
405+
pub fn scroll_y_max(&self) -> Option<f64> {
406+
self.data().scroll_y_max()
407+
}
408+
381409
pub fn is_text_input(&self) -> bool {
382410
matches!(
383411
self.role(),

0 commit comments

Comments
 (0)