1use std::{iter::FusedIterator, sync::Arc};
12
13use accesskit::{
14 Action, Affine, DefaultActionVerb, Live, Node as NodeData, NodeId, Orientation, Point, Rect,
15 Role, TextSelection, Toggled,
16};
17
18use crate::filters::FilterResult;
19use crate::iterators::{
20 FilteredChildren, FollowingFilteredSiblings, FollowingSiblings, LabelledBy,
21 PrecedingFilteredSiblings, PrecedingSiblings,
22};
23use crate::tree::State as TreeState;
24
25#[derive(Clone, Copy, PartialEq, Eq)]
26pub(crate) struct ParentAndIndex(pub(crate) NodeId, pub(crate) usize);
27
28#[derive(Clone)]
29pub(crate) struct NodeState {
30 pub(crate) parent_and_index: Option<ParentAndIndex>,
31 pub(crate) data: Arc<NodeData>,
32}
33
34#[derive(Copy, Clone)]
35pub struct Node<'a> {
36 pub tree_state: &'a TreeState,
37 pub(crate) id: NodeId,
38 pub(crate) state: &'a NodeState,
39}
40
41impl<'a> Node<'a> {
42 pub(crate) fn data(&self) -> &NodeData {
43 &self.state.data
44 }
45
46 pub fn is_focused(&self) -> bool {
47 self.tree_state.focus_id() == Some(self.id())
48 }
49
50 pub fn is_focusable(&self) -> bool {
51 self.supports_action(Action::Focus)
52 }
53
54 pub fn is_root(&self) -> bool {
55 self.id() == self.tree_state.root_id()
58 }
59
60 pub fn parent_id(&self) -> Option<NodeId> {
61 self.state
62 .parent_and_index
63 .as_ref()
64 .map(|ParentAndIndex(id, _)| *id)
65 }
66
67 pub fn parent(&self) -> Option<Node<'a>> {
68 self.parent_id()
69 .map(|id| self.tree_state.node_by_id(id).unwrap())
70 }
71
72 pub fn filtered_parent(&self, filter: &impl Fn(&Node) -> FilterResult) -> Option<Node<'a>> {
73 self.parent().and_then(move |parent| {
74 if filter(&parent) == FilterResult::Include {
75 Some(parent)
76 } else {
77 parent.filtered_parent(filter)
78 }
79 })
80 }
81
82 pub fn parent_and_index(self) -> Option<(Node<'a>, usize)> {
83 self.state
84 .parent_and_index
85 .as_ref()
86 .map(|ParentAndIndex(parent, index)| {
87 (self.tree_state.node_by_id(*parent).unwrap(), *index)
88 })
89 }
90
91 pub fn child_ids(
92 &self,
93 ) -> impl DoubleEndedIterator<Item = NodeId>
94 + ExactSizeIterator<Item = NodeId>
95 + FusedIterator<Item = NodeId>
96 + '_ {
97 let data = &self.state.data;
98 data.children().iter().copied()
99 }
100
101 pub fn children(
102 &self,
103 ) -> impl DoubleEndedIterator<Item = Node<'a>>
104 + ExactSizeIterator<Item = Node<'a>>
105 + FusedIterator<Item = Node<'a>>
106 + 'a {
107 let state = self.tree_state;
108 let data = &self.state.data;
109 data.children()
110 .iter()
111 .map(move |id| state.node_by_id(*id).unwrap())
112 }
113
114 pub fn filtered_children(
115 &self,
116 filter: impl Fn(&Node) -> FilterResult + 'a,
117 ) -> impl DoubleEndedIterator<Item = Node<'a>> + FusedIterator<Item = Node<'a>> + 'a {
118 FilteredChildren::new(*self, filter)
119 }
120
121 pub fn following_sibling_ids(
122 &self,
123 ) -> impl DoubleEndedIterator<Item = NodeId>
124 + ExactSizeIterator<Item = NodeId>
125 + FusedIterator<Item = NodeId>
126 + 'a {
127 FollowingSiblings::new(*self)
128 }
129
130 pub fn following_siblings(
131 &self,
132 ) -> impl DoubleEndedIterator<Item = Node<'a>>
133 + ExactSizeIterator<Item = Node<'a>>
134 + FusedIterator<Item = Node<'a>>
135 + 'a {
136 let state = self.tree_state;
137 self.following_sibling_ids()
138 .map(move |id| state.node_by_id(id).unwrap())
139 }
140
141 pub fn following_filtered_siblings(
142 &self,
143 filter: impl Fn(&Node) -> FilterResult + 'a,
144 ) -> impl DoubleEndedIterator<Item = Node<'a>> + FusedIterator<Item = Node<'a>> + 'a {
145 FollowingFilteredSiblings::new(*self, filter)
146 }
147
148 pub fn preceding_sibling_ids(
149 &self,
150 ) -> impl DoubleEndedIterator<Item = NodeId>
151 + ExactSizeIterator<Item = NodeId>
152 + FusedIterator<Item = NodeId>
153 + 'a {
154 PrecedingSiblings::new(*self)
155 }
156
157 pub fn preceding_siblings(
158 &self,
159 ) -> impl DoubleEndedIterator<Item = Node<'a>>
160 + ExactSizeIterator<Item = Node<'a>>
161 + FusedIterator<Item = Node<'a>>
162 + 'a {
163 let state = self.tree_state;
164 self.preceding_sibling_ids()
165 .map(move |id| state.node_by_id(id).unwrap())
166 }
167
168 pub fn preceding_filtered_siblings(
169 &self,
170 filter: impl Fn(&Node) -> FilterResult + 'a,
171 ) -> impl DoubleEndedIterator<Item = Node<'a>> + FusedIterator<Item = Node<'a>> + 'a {
172 PrecedingFilteredSiblings::new(*self, filter)
173 }
174
175 pub fn deepest_first_child(self) -> Option<Node<'a>> {
176 let mut deepest_child = self.children().next()?;
177 while let Some(first_child) = deepest_child.children().next() {
178 deepest_child = first_child;
179 }
180 Some(deepest_child)
181 }
182
183 pub fn deepest_first_filtered_child(
184 &self,
185 filter: &impl Fn(&Node) -> FilterResult,
186 ) -> Option<Node<'a>> {
187 let mut deepest_child = self.first_filtered_child(filter)?;
188 while let Some(first_child) = deepest_child.first_filtered_child(filter) {
189 deepest_child = first_child;
190 }
191 Some(deepest_child)
192 }
193
194 pub fn deepest_last_child(self) -> Option<Node<'a>> {
195 let mut deepest_child = self.children().next_back()?;
196 while let Some(last_child) = deepest_child.children().next_back() {
197 deepest_child = last_child;
198 }
199 Some(deepest_child)
200 }
201
202 pub fn deepest_last_filtered_child(
203 &self,
204 filter: &impl Fn(&Node) -> FilterResult,
205 ) -> Option<Node<'a>> {
206 let mut deepest_child = self.last_filtered_child(filter)?;
207 while let Some(last_child) = deepest_child.last_filtered_child(filter) {
208 deepest_child = last_child;
209 }
210 Some(deepest_child)
211 }
212
213 pub fn is_descendant_of(&self, ancestor: &Node) -> bool {
214 if self.id() == ancestor.id() {
215 return true;
216 }
217 if let Some(parent) = self.parent() {
218 return parent.is_descendant_of(ancestor);
219 }
220 false
221 }
222
223 pub fn direct_transform(&self) -> Affine {
226 self.data()
227 .transform()
228 .map_or(Affine::IDENTITY, |value| *value)
229 }
230
231 pub fn transform(&self) -> Affine {
234 self.parent()
235 .map_or(Affine::IDENTITY, |parent| parent.transform())
236 * self.direct_transform()
237 }
238
239 pub(crate) fn relative_transform(&self, stop_at: &Node) -> Affine {
240 let parent_transform = if let Some(parent) = self.parent() {
241 if parent.id() == stop_at.id() {
242 Affine::IDENTITY
243 } else {
244 parent.relative_transform(stop_at)
245 }
246 } else {
247 Affine::IDENTITY
248 };
249 parent_transform * self.direct_transform()
250 }
251
252 pub fn raw_bounds(&self) -> Option<Rect> {
253 self.data().bounds()
254 }
255
256 pub fn has_bounds(&self) -> bool {
257 self.raw_bounds().is_some()
258 }
259
260 pub fn bounding_box(&self) -> Option<Rect> {
263 self.raw_bounds()
264 .as_ref()
265 .map(|rect| self.transform().transform_rect_bbox(*rect))
266 }
267
268 pub(crate) fn bounding_box_in_coordinate_space(&self, other: &Node) -> Option<Rect> {
269 self.raw_bounds()
270 .as_ref()
271 .map(|rect| self.relative_transform(other).transform_rect_bbox(*rect))
272 }
273
274 pub(crate) fn hit_test(
275 &self,
276 point: Point,
277 filter: &impl Fn(&Node) -> FilterResult,
278 ) -> Option<(Node<'a>, Point)> {
279 let filter_result = filter(self);
280
281 if filter_result == FilterResult::ExcludeSubtree {
282 return None;
283 }
284
285 for child in self.children().rev() {
286 let point = child.direct_transform().inverse() * point;
287 if let Some(result) = child.hit_test(point, filter) {
288 return Some(result);
289 }
290 }
291
292 if filter_result == FilterResult::Include {
293 if let Some(rect) = &self.raw_bounds() {
294 if rect.contains(point) {
295 return Some((*self, point));
296 }
297 }
298 }
299
300 None
301 }
302
303 pub fn node_at_point(
306 &self,
307 point: Point,
308 filter: &impl Fn(&Node) -> FilterResult,
309 ) -> Option<Node<'a>> {
310 self.hit_test(point, filter).map(|(node, _)| node)
311 }
312
313 pub fn id(&self) -> NodeId {
314 self.id
315 }
316
317 pub fn role(&self) -> Role {
318 self.data().role()
319 }
320
321 pub fn role_description(&self) -> Option<String> {
322 self.data().role_description().map(String::from)
323 }
324
325 pub fn has_role_description(&self) -> bool {
326 self.data().role_description().is_some()
327 }
328
329 pub fn is_hidden(&self) -> bool {
330 self.data().is_hidden()
331 }
332
333 pub fn is_disabled(&self) -> bool {
334 self.data().is_disabled()
335 }
336
337 pub fn is_read_only(&self) -> bool {
338 let data = self.data();
339 if data.is_read_only() {
340 true
341 } else {
342 self.should_have_read_only_state_by_default() || !self.is_read_only_supported()
343 }
344 }
345
346 pub fn is_read_only_or_disabled(&self) -> bool {
347 self.is_read_only() || self.is_disabled()
348 }
349
350 pub fn toggled(&self) -> Option<Toggled> {
351 self.data().toggled()
352 }
353
354 pub fn numeric_value(&self) -> Option<f64> {
355 self.data().numeric_value()
356 }
357
358 pub fn min_numeric_value(&self) -> Option<f64> {
359 self.data().min_numeric_value()
360 }
361
362 pub fn max_numeric_value(&self) -> Option<f64> {
363 self.data().max_numeric_value()
364 }
365
366 pub fn numeric_value_step(&self) -> Option<f64> {
367 self.data().numeric_value_step()
368 }
369
370 pub fn numeric_value_jump(&self) -> Option<f64> {
371 self.data().numeric_value_jump()
372 }
373
374 pub fn is_text_input(&self) -> bool {
375 matches!(
376 self.role(),
377 Role::TextInput
378 | Role::MultilineTextInput
379 | Role::SearchInput
380 | Role::DateInput
381 | Role::DateTimeInput
382 | Role::WeekInput
383 | Role::MonthInput
384 | Role::TimeInput
385 | Role::EmailInput
386 | Role::NumberInput
387 | Role::PasswordInput
388 | Role::PhoneNumberInput
389 | Role::UrlInput
390 | Role::EditableComboBox
391 | Role::SpinButton
392 )
393 }
394
395 pub fn is_multiline(&self) -> bool {
396 self.role() == Role::MultilineTextInput
397 }
398
399 pub fn orientation(&self) -> Option<Orientation> {
400 self.data().orientation()
401 }
402
403 pub fn default_action_verb(&self) -> Option<DefaultActionVerb> {
404 self.data().default_action_verb()
405 }
406
407 pub fn is_clickable(&self) -> bool {
417 if let Some(verb) = self.default_action_verb() {
422 if verb != DefaultActionVerb::ClickAncestor {
423 return true;
424 }
425 }
426
427 false
428 }
429
430 pub fn supports_toggle(&self) -> bool {
431 self.toggled().is_some()
432 }
433
434 pub fn supports_expand_collapse(&self) -> bool {
435 self.data().is_expanded().is_some()
436 }
437
438 pub fn is_invocable(&self) -> bool {
439 self.is_clickable()
448 && !matches!(
449 self.default_action_verb(),
450 Some(
451 DefaultActionVerb::Focus
452 | DefaultActionVerb::Select
453 | DefaultActionVerb::Unselect
454 )
455 )
456 && !self.supports_toggle()
457 && !self.supports_expand_collapse()
458 }
459
460 fn supports_action(&self, action: Action) -> bool {
463 self.data().supports_action(action)
464 }
465
466 pub fn supports_increment(&self) -> bool {
467 self.supports_action(Action::Increment)
468 }
469
470 pub fn supports_decrement(&self) -> bool {
471 self.supports_action(Action::Decrement)
472 }
473}
474
475fn descendant_label_filter(node: &Node) -> FilterResult {
476 match node.role() {
477 Role::Label | Role::Image => FilterResult::Include,
478 Role::GenericContainer => FilterResult::ExcludeNode,
479 _ => FilterResult::ExcludeSubtree,
480 }
481}
482
483impl<'a> Node<'a> {
484 pub fn labelled_by(
485 &self,
486 ) -> impl DoubleEndedIterator<Item = Node<'a>> + FusedIterator<Item = Node<'a>> + 'a {
487 let explicit = &self.state.data.labelled_by();
488 if explicit.is_empty()
489 && matches!(self.role(), Role::Button | Role::DefaultButton | Role::Link)
490 {
491 LabelledBy::FromDescendants(FilteredChildren::new(*self, &descendant_label_filter))
492 } else {
493 LabelledBy::Explicit {
494 ids: explicit.iter(),
495 tree_state: self.tree_state,
496 }
497 }
498 }
499
500 pub fn name(&self) -> Option<String> {
501 if let Some(name) = &self.data().name() {
502 Some(name.to_string())
503 } else {
504 let names = self
505 .labelled_by()
506 .filter_map(|node| node.name())
507 .collect::<Vec<String>>();
508 (!names.is_empty()).then(move || names.join(" "))
509 }
510 }
511
512 pub fn description(&self) -> Option<String> {
513 self.data()
514 .description()
515 .map(|description| description.to_string())
516 }
517
518 pub fn placeholder(&self) -> Option<String> {
519 self.data()
520 .placeholder()
521 .map(|placeholder| placeholder.to_string())
522 }
523
524 pub fn value(&self) -> Option<String> {
525 if let Some(value) = &self.data().value() {
526 Some(value.to_string())
527 } else if self.supports_text_ranges() && !self.is_multiline() {
528 Some(self.document_range().text())
529 } else {
530 None
531 }
532 }
533
534 pub fn has_value(&self) -> bool {
535 self.data().value().is_some() || (self.supports_text_ranges() && !self.is_multiline())
536 }
537
538 pub fn is_read_only_supported(&self) -> bool {
539 self.is_text_input()
540 || matches!(
541 self.role(),
542 Role::CheckBox
543 | Role::ColorWell
544 | Role::ComboBox
545 | Role::Grid
546 | Role::ListBox
547 | Role::MenuItemCheckBox
548 | Role::MenuItemRadio
549 | Role::MenuListPopup
550 | Role::RadioButton
551 | Role::RadioGroup
552 | Role::Slider
553 | Role::Switch
554 | Role::TreeGrid
555 )
556 }
557
558 pub fn should_have_read_only_state_by_default(&self) -> bool {
559 matches!(
560 self.role(),
561 Role::Article
562 | Role::Definition
563 | Role::DescriptionList
564 | Role::DescriptionListTerm
565 | Role::Directory
566 | Role::Document
567 | Role::GraphicsDocument
568 | Role::Image
569 | Role::List
570 | Role::ListItem
571 | Role::PdfRoot
572 | Role::ProgressIndicator
573 | Role::RootWebArea
574 | Role::Term
575 | Role::Timer
576 | Role::Toolbar
577 | Role::Tooltip
578 )
579 }
580
581 pub fn live(&self) -> Live {
582 self.data()
583 .live()
584 .unwrap_or_else(|| self.parent().map_or(Live::Off, |parent| parent.live()))
585 }
586
587 pub fn is_selected(&self) -> Option<bool> {
588 self.data().is_selected()
589 }
590
591 pub fn raw_text_selection(&self) -> Option<&TextSelection> {
592 self.data().text_selection()
593 }
594
595 pub fn raw_value(&self) -> Option<&str> {
596 self.data().value()
597 }
598
599 pub fn author_id(&self) -> Option<&str> {
600 self.data().author_id()
601 }
602
603 pub fn class_name(&self) -> Option<&str> {
604 self.data().class_name()
605 }
606
607 pub fn index_path(&self) -> Vec<usize> {
608 self.relative_index_path(self.tree_state.root_id())
609 }
610
611 pub fn relative_index_path(&self, ancestor_id: NodeId) -> Vec<usize> {
612 let mut result = Vec::new();
613 let mut current = *self;
614 while current.id() != ancestor_id {
615 let (parent, index) = current.parent_and_index().unwrap();
616 result.push(index);
617 current = parent;
618 }
619 result.reverse();
620 result
621 }
622
623 pub(crate) fn first_filtered_child(
624 &self,
625 filter: &impl Fn(&Node) -> FilterResult,
626 ) -> Option<Node<'a>> {
627 for child in self.children() {
628 let result = filter(&child);
629 if result == FilterResult::Include {
630 return Some(child);
631 }
632 if result == FilterResult::ExcludeNode {
633 if let Some(descendant) = child.first_filtered_child(filter) {
634 return Some(descendant);
635 }
636 }
637 }
638 None
639 }
640
641 pub(crate) fn last_filtered_child(
642 &self,
643 filter: &impl Fn(&Node) -> FilterResult,
644 ) -> Option<Node<'a>> {
645 for child in self.children().rev() {
646 let result = filter(&child);
647 if result == FilterResult::Include {
648 return Some(child);
649 }
650 if result == FilterResult::ExcludeNode {
651 if let Some(descendant) = child.last_filtered_child(filter) {
652 return Some(descendant);
653 }
654 }
655 }
656 None
657 }
658}
659
660#[cfg(test)]
661mod tests {
662 use accesskit::{NodeBuilder, NodeId, Point, Rect, Role, Tree, TreeUpdate};
663
664 use crate::tests::*;
665
666 #[test]
667 fn parent_and_index() {
668 let tree = test_tree();
669 assert!(tree.state().root().parent_and_index().is_none());
670 assert_eq!(
671 Some((ROOT_ID, 0)),
672 tree.state()
673 .node_by_id(PARAGRAPH_0_ID)
674 .unwrap()
675 .parent_and_index()
676 .map(|(parent, index)| (parent.id(), index))
677 );
678 assert_eq!(
679 Some((PARAGRAPH_0_ID, 0)),
680 tree.state()
681 .node_by_id(LABEL_0_0_IGNORED_ID)
682 .unwrap()
683 .parent_and_index()
684 .map(|(parent, index)| (parent.id(), index))
685 );
686 assert_eq!(
687 Some((ROOT_ID, 1)),
688 tree.state()
689 .node_by_id(PARAGRAPH_1_IGNORED_ID)
690 .unwrap()
691 .parent_and_index()
692 .map(|(parent, index)| (parent.id(), index))
693 );
694 }
695
696 #[test]
697 fn deepest_first_child() {
698 let tree = test_tree();
699 assert_eq!(
700 LABEL_0_0_IGNORED_ID,
701 tree.state().root().deepest_first_child().unwrap().id()
702 );
703 assert_eq!(
704 LABEL_0_0_IGNORED_ID,
705 tree.state()
706 .node_by_id(PARAGRAPH_0_ID)
707 .unwrap()
708 .deepest_first_child()
709 .unwrap()
710 .id()
711 );
712 assert!(tree
713 .state()
714 .node_by_id(LABEL_0_0_IGNORED_ID)
715 .unwrap()
716 .deepest_first_child()
717 .is_none());
718 }
719
720 #[test]
721 fn filtered_parent() {
722 let tree = test_tree();
723 assert_eq!(
724 ROOT_ID,
725 tree.state()
726 .node_by_id(LABEL_1_1_ID)
727 .unwrap()
728 .filtered_parent(&test_tree_filter)
729 .unwrap()
730 .id()
731 );
732 assert!(tree
733 .state()
734 .root()
735 .filtered_parent(&test_tree_filter)
736 .is_none());
737 }
738
739 #[test]
740 fn deepest_first_filtered_child() {
741 let tree = test_tree();
742 assert_eq!(
743 PARAGRAPH_0_ID,
744 tree.state()
745 .root()
746 .deepest_first_filtered_child(&test_tree_filter)
747 .unwrap()
748 .id()
749 );
750 assert!(tree
751 .state()
752 .node_by_id(PARAGRAPH_0_ID)
753 .unwrap()
754 .deepest_first_filtered_child(&test_tree_filter)
755 .is_none());
756 assert!(tree
757 .state()
758 .node_by_id(LABEL_0_0_IGNORED_ID)
759 .unwrap()
760 .deepest_first_filtered_child(&test_tree_filter)
761 .is_none());
762 }
763
764 #[test]
765 fn deepest_last_child() {
766 let tree = test_tree();
767 assert_eq!(
768 EMPTY_CONTAINER_3_3_IGNORED_ID,
769 tree.state().root().deepest_last_child().unwrap().id()
770 );
771 assert_eq!(
772 EMPTY_CONTAINER_3_3_IGNORED_ID,
773 tree.state()
774 .node_by_id(PARAGRAPH_3_IGNORED_ID)
775 .unwrap()
776 .deepest_last_child()
777 .unwrap()
778 .id()
779 );
780 assert!(tree
781 .state()
782 .node_by_id(BUTTON_3_2_ID)
783 .unwrap()
784 .deepest_last_child()
785 .is_none());
786 }
787
788 #[test]
789 fn deepest_last_filtered_child() {
790 let tree = test_tree();
791 assert_eq!(
792 BUTTON_3_2_ID,
793 tree.state()
794 .root()
795 .deepest_last_filtered_child(&test_tree_filter)
796 .unwrap()
797 .id()
798 );
799 assert_eq!(
800 BUTTON_3_2_ID,
801 tree.state()
802 .node_by_id(PARAGRAPH_3_IGNORED_ID)
803 .unwrap()
804 .deepest_last_filtered_child(&test_tree_filter)
805 .unwrap()
806 .id()
807 );
808 assert!(tree
809 .state()
810 .node_by_id(BUTTON_3_2_ID)
811 .unwrap()
812 .deepest_last_filtered_child(&test_tree_filter)
813 .is_none());
814 assert!(tree
815 .state()
816 .node_by_id(PARAGRAPH_0_ID)
817 .unwrap()
818 .deepest_last_filtered_child(&test_tree_filter)
819 .is_none());
820 }
821
822 #[test]
823 fn is_descendant_of() {
824 let tree = test_tree();
825 assert!(tree
826 .state()
827 .node_by_id(PARAGRAPH_0_ID)
828 .unwrap()
829 .is_descendant_of(&tree.state().root()));
830 assert!(tree
831 .state()
832 .node_by_id(LABEL_0_0_IGNORED_ID)
833 .unwrap()
834 .is_descendant_of(&tree.state().root()));
835 assert!(tree
836 .state()
837 .node_by_id(LABEL_0_0_IGNORED_ID)
838 .unwrap()
839 .is_descendant_of(&tree.state().node_by_id(PARAGRAPH_0_ID).unwrap()));
840 assert!(!tree
841 .state()
842 .node_by_id(LABEL_0_0_IGNORED_ID)
843 .unwrap()
844 .is_descendant_of(&tree.state().node_by_id(PARAGRAPH_2_ID).unwrap()));
845 assert!(!tree
846 .state()
847 .node_by_id(PARAGRAPH_0_ID)
848 .unwrap()
849 .is_descendant_of(&tree.state().node_by_id(PARAGRAPH_2_ID).unwrap()));
850 }
851
852 #[test]
853 fn is_root() {
854 let tree = test_tree();
855 assert!(tree.state().node_by_id(ROOT_ID).unwrap().is_root());
856 assert!(!tree.state().node_by_id(PARAGRAPH_0_ID).unwrap().is_root());
857 }
858
859 #[test]
860 fn bounding_box() {
861 let tree = test_tree();
862 assert!(tree
863 .state()
864 .node_by_id(ROOT_ID)
865 .unwrap()
866 .bounding_box()
867 .is_none());
868 assert_eq!(
869 Some(Rect {
870 x0: 10.0,
871 y0: 40.0,
872 x1: 810.0,
873 y1: 80.0,
874 }),
875 tree.state()
876 .node_by_id(PARAGRAPH_1_IGNORED_ID)
877 .unwrap()
878 .bounding_box()
879 );
880 assert_eq!(
881 Some(Rect {
882 x0: 20.0,
883 y0: 50.0,
884 x1: 100.0,
885 y1: 70.0,
886 }),
887 tree.state()
888 .node_by_id(LABEL_1_1_ID)
889 .unwrap()
890 .bounding_box()
891 );
892 }
893
894 #[test]
895 fn node_at_point() {
896 let tree = test_tree();
897 assert!(tree
898 .state()
899 .root()
900 .node_at_point(Point::new(10.0, 40.0), &test_tree_filter)
901 .is_none());
902 assert_eq!(
903 Some(LABEL_1_1_ID),
904 tree.state()
905 .root()
906 .node_at_point(Point::new(20.0, 50.0), &test_tree_filter)
907 .map(|node| node.id())
908 );
909 assert_eq!(
910 Some(LABEL_1_1_ID),
911 tree.state()
912 .root()
913 .node_at_point(Point::new(50.0, 60.0), &test_tree_filter)
914 .map(|node| node.id())
915 );
916 assert!(tree
917 .state()
918 .root()
919 .node_at_point(Point::new(100.0, 70.0), &test_tree_filter)
920 .is_none());
921 }
922
923 #[test]
924 fn no_name_or_labelled_by() {
925 let update = TreeUpdate {
926 nodes: vec![
927 (NodeId(0), {
928 let mut builder = NodeBuilder::new(Role::Window);
929 builder.set_children(vec![NodeId(1)]);
930 builder.build()
931 }),
932 (NodeId(1), NodeBuilder::new(Role::Button).build()),
933 ],
934 tree: Some(Tree::new(NodeId(0))),
935 focus: NodeId(0),
936 };
937 let tree = crate::Tree::new(update, false);
938 assert_eq!(None, tree.state().node_by_id(NodeId(1)).unwrap().name());
939 }
940
941 #[test]
942 fn name_from_labelled_by() {
943 const LABEL_1: &str = "Check email every";
946 const LABEL_2: &str = "minutes";
947
948 let update = TreeUpdate {
949 nodes: vec![
950 (NodeId(0), {
951 let mut builder = NodeBuilder::new(Role::Window);
952 builder.set_children(vec![NodeId(1), NodeId(2), NodeId(3), NodeId(4)]);
953 builder.build()
954 }),
955 (NodeId(1), {
956 let mut builder = NodeBuilder::new(Role::CheckBox);
957 builder.set_labelled_by(vec![NodeId(2), NodeId(4)]);
958 builder.build()
959 }),
960 (NodeId(2), {
961 let mut builder = NodeBuilder::new(Role::Label);
962 builder.set_name(LABEL_1);
963 builder.build()
964 }),
965 (NodeId(3), {
966 let mut builder = NodeBuilder::new(Role::TextInput);
967 builder.push_labelled_by(NodeId(4));
968 builder.build()
969 }),
970 (NodeId(4), {
971 let mut builder = NodeBuilder::new(Role::Label);
972 builder.set_name(LABEL_2);
973 builder.build()
974 }),
975 ],
976 tree: Some(Tree::new(NodeId(0))),
977 focus: NodeId(0),
978 };
979 let tree = crate::Tree::new(update, false);
980 assert_eq!(
981 Some([LABEL_1, LABEL_2].join(" ")),
982 tree.state().node_by_id(NodeId(1)).unwrap().name()
983 );
984 assert_eq!(
985 Some(LABEL_2.into()),
986 tree.state().node_by_id(NodeId(3)).unwrap().name()
987 );
988 }
989
990 #[test]
991 fn name_from_descendant_label() {
992 const BUTTON_LABEL: &str = "Play";
993 const LINK_LABEL: &str = "Watch in browser";
994
995 let update = TreeUpdate {
996 nodes: vec![
997 (NodeId(0), {
998 let mut builder = NodeBuilder::new(Role::Window);
999 builder.set_children(vec![NodeId(1), NodeId(3)]);
1000 builder.build()
1001 }),
1002 (NodeId(1), {
1003 let mut builder = NodeBuilder::new(Role::Button);
1004 builder.push_child(NodeId(2));
1005 builder.build()
1006 }),
1007 (NodeId(2), {
1008 let mut builder = NodeBuilder::new(Role::Image);
1009 builder.set_name(BUTTON_LABEL);
1010 builder.build()
1011 }),
1012 (NodeId(3), {
1013 let mut builder = NodeBuilder::new(Role::Link);
1014 builder.push_child(NodeId(4));
1015 builder.build()
1016 }),
1017 (NodeId(4), {
1018 let mut builder = NodeBuilder::new(Role::GenericContainer);
1019 builder.push_child(NodeId(5));
1020 builder.build()
1021 }),
1022 (NodeId(5), {
1023 let mut builder = NodeBuilder::new(Role::Label);
1024 builder.set_name(LINK_LABEL);
1025 builder.build()
1026 }),
1027 ],
1028 tree: Some(Tree::new(NodeId(0))),
1029 focus: NodeId(0),
1030 };
1031 let tree = crate::Tree::new(update, false);
1032 assert_eq!(
1033 Some(BUTTON_LABEL.into()),
1034 tree.state().node_by_id(NodeId(1)).unwrap().name()
1035 );
1036 assert_eq!(
1037 Some(LINK_LABEL.into()),
1038 tree.state().node_by_id(NodeId(3)).unwrap().name()
1039 );
1040 }
1041}