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