1use accesskit::{
12 Action, Affine, AriaCurrent, HasPopup, Live, Node as NodeData, NodeId as LocalNodeId,
13 Orientation, Point, Rect, Role, SortDirection, TextSelection, Toggled,
14};
15use alloc::{
16 string::{String, ToString},
17 vec::Vec,
18};
19use core::{fmt, iter::FusedIterator};
20
21use crate::filters::FilterResult;
22use crate::iterators::{
23 ChildIds, FilteredChildren, FollowingFilteredSiblings, FollowingSiblings, LabelledBy,
24 PrecedingFilteredSiblings, PrecedingSiblings,
25};
26use crate::tree::{State as TreeState, TreeIndex};
27
28#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
29pub struct NodeId(TreeIndex, LocalNodeId);
30
31impl NodeId {
32 pub(crate) fn new(local_id: LocalNodeId, tree_index: TreeIndex) -> Self {
33 Self(tree_index, local_id)
34 }
35
36 pub(crate) fn with_same_tree(&self, local_id: LocalNodeId) -> Self {
37 Self(self.0, local_id)
38 }
39
40 pub(crate) fn to_components(self) -> (LocalNodeId, TreeIndex) {
41 (self.1, self.0)
42 }
43}
44
45impl From<NodeId> for u128 {
46 fn from(id: NodeId) -> Self {
47 let tree_index = id.0 .0 as u128;
48 let local_id = id.1 .0 as u128;
49 (local_id << 64) | tree_index
50 }
51}
52
53#[derive(Clone, Copy, PartialEq, Eq, Debug)]
54pub(crate) struct ParentAndIndex(pub(crate) NodeId, pub(crate) usize);
55
56#[derive(Clone, Debug)]
57pub(crate) struct NodeState {
58 pub(crate) parent_and_index: Option<ParentAndIndex>,
59 pub(crate) data: NodeData,
60}
61
62#[derive(Copy, Clone, Debug)]
63pub struct Node<'a> {
64 pub tree_state: &'a TreeState,
65 pub(crate) id: NodeId,
66 pub(crate) state: &'a NodeState,
67}
68
69impl<'a> Node<'a> {
70 pub fn data(&self) -> &'a NodeData {
71 &self.state.data
72 }
73
74 pub fn is_focused(&self) -> bool {
75 let dominated_by_active_descendant = |node_id| {
76 self.tree_state
77 .node_by_id(node_id)
78 .and_then(|node| node.active_descendant())
79 .is_some()
80 };
81 match self.tree_state.focus_id() {
82 Some(focus_id) if focus_id == self.id() => !dominated_by_active_descendant(focus_id),
83 Some(focus_id) => self
84 .tree_state
85 .node_by_id(focus_id)
86 .and_then(|focused| focused.active_descendant())
87 .is_some_and(|active_descendant| active_descendant.id() == self.id()),
88 None => false,
89 }
90 }
91
92 pub fn is_focused_in_tree(&self) -> bool {
93 self.tree_state.focus == self.id()
94 }
95
96 pub fn is_focusable(&self, parent_filter: &impl Fn(&Node) -> FilterResult) -> bool {
97 self.supports_action(Action::Focus, parent_filter) || self.is_focused_in_tree()
98 }
99
100 pub fn is_root(&self) -> bool {
101 self.id() == self.tree_state.root_id()
104 }
105
106 pub fn is_graft(&self) -> bool {
108 self.state.data.tree_id().is_some()
109 }
110
111 pub fn parent_id(&self) -> Option<NodeId> {
112 self.state
113 .parent_and_index
114 .as_ref()
115 .map(|ParentAndIndex(id, _)| *id)
116 }
117
118 pub fn parent(&self) -> Option<Node<'a>> {
119 self.parent_id()
120 .map(|id| self.tree_state.node_by_id(id).unwrap())
121 }
122
123 pub fn filtered_parent(&self, filter: &impl Fn(&Node) -> FilterResult) -> Option<Node<'a>> {
124 self.parent().and_then(move |parent| {
125 if filter(&parent) == FilterResult::Include {
126 Some(parent)
127 } else {
128 parent.filtered_parent(filter)
129 }
130 })
131 }
132
133 pub fn parent_and_index(self) -> Option<(Node<'a>, usize)> {
134 self.state
135 .parent_and_index
136 .as_ref()
137 .map(|ParentAndIndex(parent, index)| {
138 (self.tree_state.node_by_id(*parent).unwrap(), *index)
139 })
140 }
141
142 fn graft_child_id(&self) -> Option<NodeId> {
144 self.state
145 .data
146 .tree_id()
147 .and_then(|tree_id| self.tree_state.subtree_root(tree_id))
148 }
149
150 pub fn child_ids(
151 &self,
152 ) -> impl DoubleEndedIterator<Item = NodeId>
153 + ExactSizeIterator<Item = NodeId>
154 + FusedIterator<Item = NodeId>
155 + 'a {
156 if self.is_graft() {
157 ChildIds::Graft(self.graft_child_id())
158 } else {
159 ChildIds::Normal {
160 parent_id: self.id,
161 children: self.state.data.children().iter(),
162 }
163 }
164 }
165
166 pub fn children(
167 &self,
168 ) -> impl DoubleEndedIterator<Item = Node<'a>>
169 + ExactSizeIterator<Item = Node<'a>>
170 + FusedIterator<Item = Node<'a>>
171 + 'a {
172 let state = self.tree_state;
173 self.child_ids()
174 .map(move |id| state.node_by_id(id).unwrap())
175 }
176
177 pub fn filtered_children(
178 &self,
179 filter: impl Fn(&Node) -> FilterResult + 'a,
180 ) -> impl DoubleEndedIterator<Item = Node<'a>> + FusedIterator<Item = Node<'a>> + 'a {
181 FilteredChildren::new(*self, filter)
182 }
183
184 pub fn following_sibling_ids(
185 &self,
186 ) -> impl DoubleEndedIterator<Item = NodeId>
187 + ExactSizeIterator<Item = NodeId>
188 + FusedIterator<Item = NodeId>
189 + 'a {
190 FollowingSiblings::new(*self)
191 }
192
193 pub fn following_siblings(
194 &self,
195 ) -> impl DoubleEndedIterator<Item = Node<'a>>
196 + ExactSizeIterator<Item = Node<'a>>
197 + FusedIterator<Item = Node<'a>>
198 + 'a {
199 let state = self.tree_state;
200 self.following_sibling_ids()
201 .map(move |id| state.node_by_id(id).unwrap())
202 }
203
204 pub fn following_filtered_siblings(
205 &self,
206 filter: impl Fn(&Node) -> FilterResult + 'a,
207 ) -> impl DoubleEndedIterator<Item = Node<'a>> + FusedIterator<Item = Node<'a>> + 'a {
208 FollowingFilteredSiblings::new(*self, filter)
209 }
210
211 pub fn preceding_sibling_ids(
212 &self,
213 ) -> impl DoubleEndedIterator<Item = NodeId>
214 + ExactSizeIterator<Item = NodeId>
215 + FusedIterator<Item = NodeId>
216 + 'a {
217 PrecedingSiblings::new(*self)
218 }
219
220 pub fn preceding_siblings(
221 &self,
222 ) -> impl DoubleEndedIterator<Item = Node<'a>>
223 + ExactSizeIterator<Item = Node<'a>>
224 + FusedIterator<Item = Node<'a>>
225 + 'a {
226 let state = self.tree_state;
227 self.preceding_sibling_ids()
228 .map(move |id| state.node_by_id(id).unwrap())
229 }
230
231 pub fn preceding_filtered_siblings(
232 &self,
233 filter: impl Fn(&Node) -> FilterResult + 'a,
234 ) -> impl DoubleEndedIterator<Item = Node<'a>> + FusedIterator<Item = Node<'a>> + 'a {
235 PrecedingFilteredSiblings::new(*self, filter)
236 }
237
238 pub fn deepest_first_child(self) -> Option<Node<'a>> {
239 let mut deepest_child = self.children().next()?;
240 while let Some(first_child) = deepest_child.children().next() {
241 deepest_child = first_child;
242 }
243 Some(deepest_child)
244 }
245
246 pub fn deepest_first_filtered_child(
247 &self,
248 filter: &impl Fn(&Node) -> FilterResult,
249 ) -> Option<Node<'a>> {
250 let mut deepest_child = self.first_filtered_child(filter)?;
251 while let Some(first_child) = deepest_child.first_filtered_child(filter) {
252 deepest_child = first_child;
253 }
254 Some(deepest_child)
255 }
256
257 pub fn deepest_last_child(self) -> Option<Node<'a>> {
258 let mut deepest_child = self.children().next_back()?;
259 while let Some(last_child) = deepest_child.children().next_back() {
260 deepest_child = last_child;
261 }
262 Some(deepest_child)
263 }
264
265 pub fn deepest_last_filtered_child(
266 &self,
267 filter: &impl Fn(&Node) -> FilterResult,
268 ) -> Option<Node<'a>> {
269 let mut deepest_child = self.last_filtered_child(filter)?;
270 while let Some(last_child) = deepest_child.last_filtered_child(filter) {
271 deepest_child = last_child;
272 }
273 Some(deepest_child)
274 }
275
276 pub fn is_descendant_of(&self, ancestor: &Node) -> bool {
277 if self.id() == ancestor.id() {
278 return true;
279 }
280 if let Some(parent) = self.parent() {
281 return parent.is_descendant_of(ancestor);
282 }
283 false
284 }
285
286 pub fn direct_transform(&self) -> Affine {
289 self.data()
290 .transform()
291 .map_or(Affine::IDENTITY, |value| *value)
292 }
293
294 pub fn transform(&self) -> Affine {
297 self.parent()
298 .map_or(Affine::IDENTITY, |parent| parent.transform())
299 * self.direct_transform()
300 }
301
302 pub(crate) fn relative_transform(&self, stop_at: &Node) -> Affine {
303 let parent_transform = if let Some(parent) = self.parent() {
304 if parent.id() == stop_at.id() {
305 Affine::IDENTITY
306 } else {
307 parent.relative_transform(stop_at)
308 }
309 } else {
310 Affine::IDENTITY
311 };
312 parent_transform * self.direct_transform()
313 }
314
315 pub fn raw_bounds(&self) -> Option<Rect> {
316 self.data().bounds()
317 }
318
319 pub fn has_bounds(&self) -> bool {
320 self.raw_bounds().is_some()
321 }
322
323 pub fn bounding_box(&self) -> Option<Rect> {
326 self.raw_bounds()
327 .as_ref()
328 .map(|rect| self.transform().transform_rect_bbox(*rect))
329 }
330
331 pub(crate) fn bounding_box_in_coordinate_space(&self, other: &Node) -> Option<Rect> {
332 self.raw_bounds()
333 .as_ref()
334 .map(|rect| self.relative_transform(other).transform_rect_bbox(*rect))
335 }
336
337 pub(crate) fn hit_test(
338 &self,
339 point: Point,
340 filter: &impl Fn(&Node) -> FilterResult,
341 ) -> Option<(Node<'a>, Point)> {
342 let filter_result = filter(self);
343
344 if filter_result == FilterResult::ExcludeSubtree {
345 return None;
346 }
347
348 for child in self.children().rev() {
349 let point = child.direct_transform().inverse() * point;
350 if let Some(result) = child.hit_test(point, filter) {
351 return Some(result);
352 }
353 }
354
355 if filter_result == FilterResult::Include {
356 if let Some(rect) = &self.raw_bounds() {
357 if rect.contains(point) {
358 return Some((*self, point));
359 }
360 }
361 }
362
363 None
364 }
365
366 pub fn node_at_point(
369 &self,
370 point: Point,
371 filter: &impl Fn(&Node) -> FilterResult,
372 ) -> Option<Node<'a>> {
373 self.hit_test(point, filter).map(|(node, _)| node)
374 }
375
376 pub fn id(&self) -> NodeId {
377 self.id
378 }
379
380 pub fn role(&self) -> Role {
381 self.data().role()
382 }
383
384 pub fn role_description(&self) -> Option<&str> {
385 self.data().role_description()
386 }
387
388 pub fn has_role_description(&self) -> bool {
389 self.data().role_description().is_some()
390 }
391
392 pub fn is_live_atomic(&self) -> bool {
393 self.data().is_live_atomic()
394 }
395
396 pub fn is_busy(&self) -> bool {
397 self.data().is_busy()
398 }
399
400 pub fn column_index_text(&self) -> Option<&str> {
401 self.data().column_index_text()
402 }
403
404 pub fn row_index_text(&self) -> Option<&str> {
405 self.data().row_index_text()
406 }
407
408 pub fn braille_label(&self) -> Option<&str> {
409 self.data().braille_label()
410 }
411
412 pub fn has_braille_label(&self) -> bool {
413 self.data().braille_label().is_some()
414 }
415
416 pub fn braille_role_description(&self) -> Option<&str> {
417 self.data().braille_role_description()
418 }
419
420 pub fn has_braille_role_description(&self) -> bool {
421 self.data().braille_role_description().is_some()
422 }
423
424 pub fn aria_current(&self) -> Option<AriaCurrent> {
425 self.data().aria_current()
426 }
427
428 pub fn has_popup(&self) -> Option<HasPopup> {
429 self.data().has_popup()
430 }
431
432 pub fn is_hidden(&self) -> bool {
433 self.fetch_inherited_flag(NodeData::is_hidden)
434 }
435
436 pub fn level(&self) -> Option<usize> {
437 self.data().level()
438 }
439
440 pub fn is_disabled(&self) -> bool {
441 self.data().is_disabled()
442 }
443
444 pub fn is_read_only(&self) -> bool {
445 let data = self.data();
446 if data.is_read_only() {
447 true
448 } else {
449 self.should_have_read_only_state_by_default() || !self.is_read_only_supported()
450 }
451 }
452
453 pub fn is_read_only_or_disabled(&self) -> bool {
454 self.is_read_only() || self.is_disabled()
455 }
456
457 pub fn toggled(&self) -> Option<Toggled> {
458 self.data().toggled()
459 }
460
461 pub fn numeric_value(&self) -> Option<f64> {
462 self.data().numeric_value()
463 }
464
465 pub fn min_numeric_value(&self) -> Option<f64> {
466 self.data().min_numeric_value()
467 }
468
469 pub fn max_numeric_value(&self) -> Option<f64> {
470 self.data().max_numeric_value()
471 }
472
473 pub fn numeric_value_step(&self) -> Option<f64> {
474 self.data().numeric_value_step()
475 }
476
477 pub fn numeric_value_jump(&self) -> Option<f64> {
478 self.data().numeric_value_jump()
479 }
480
481 pub fn clips_children(&self) -> bool {
482 self.data().clips_children()
483 }
484
485 pub fn scroll_x(&self) -> Option<f64> {
486 self.data().scroll_x()
487 }
488
489 pub fn scroll_x_min(&self) -> Option<f64> {
490 self.data().scroll_x_min()
491 }
492
493 pub fn scroll_x_max(&self) -> Option<f64> {
494 self.data().scroll_x_max()
495 }
496
497 pub fn scroll_y(&self) -> Option<f64> {
498 self.data().scroll_y()
499 }
500
501 pub fn scroll_y_min(&self) -> Option<f64> {
502 self.data().scroll_y_min()
503 }
504
505 pub fn scroll_y_max(&self) -> Option<f64> {
506 self.data().scroll_y_max()
507 }
508
509 pub(crate) fn fetch_inherited_property<T>(
510 &self,
511 getter: fn(&'a NodeData) -> Option<T>,
512 ) -> Option<T> {
513 let mut node = *self;
514 loop {
515 let value = getter(node.data());
516 if value.is_some() {
517 return value;
518 }
519 node = node.parent()?;
520 }
521 }
522
523 pub(crate) fn fetch_inherited_flag(&self, getter: fn(&'a NodeData) -> bool) -> bool {
524 let mut node = *self;
525 loop {
526 if getter(node.data()) {
527 return true;
528 }
529 if let Some(parent) = node.parent() {
530 node = parent;
531 } else {
532 return false;
533 }
534 }
535 }
536
537 pub fn is_text_input(&self) -> bool {
538 matches!(
539 self.role(),
540 Role::TextInput
541 | Role::MultilineTextInput
542 | Role::SearchInput
543 | Role::DateInput
544 | Role::DateTimeInput
545 | Role::WeekInput
546 | Role::MonthInput
547 | Role::TimeInput
548 | Role::EmailInput
549 | Role::NumberInput
550 | Role::PasswordInput
551 | Role::PhoneNumberInput
552 | Role::UrlInput
553 | Role::EditableComboBox
554 | Role::SpinButton
555 )
556 }
557
558 pub fn is_multiline(&self) -> bool {
559 self.role() == Role::MultilineTextInput
560 }
561
562 pub fn orientation(&self) -> Option<Orientation> {
563 self.data().orientation().or_else(|| {
564 if self.role() == Role::ListBox {
565 Some(Orientation::Vertical)
566 } else if self.role() == Role::TabList {
567 Some(Orientation::Horizontal)
568 } else {
569 None
570 }
571 })
572 }
573
574 pub fn is_dialog(&self) -> bool {
575 matches!(self.role(), Role::AlertDialog | Role::Dialog)
576 }
577
578 pub fn is_modal(&self) -> bool {
579 self.data().is_modal()
580 }
581
582 pub fn is_clickable(&self, parent_filter: &impl Fn(&Node) -> FilterResult) -> bool {
592 self.supports_action(Action::Click, parent_filter)
593 }
594
595 pub fn is_selectable(&self) -> bool {
596 self.is_selected().is_some() && !self.is_disabled()
598 }
599
600 pub fn is_multiselectable(&self) -> bool {
601 self.data().is_multiselectable()
602 }
603
604 pub fn size_of_set_from_container(
605 &self,
606 filter: &impl Fn(&Node) -> FilterResult,
607 ) -> Option<usize> {
608 self.selection_container(filter)
609 .and_then(|c| c.size_of_set())
610 }
611
612 pub fn size_of_set(&self) -> Option<usize> {
613 self.data().size_of_set()
615 }
616
617 pub fn position_in_set(&self) -> Option<usize> {
618 self.data().position_in_set()
620 }
621
622 pub fn sort_direction(&self) -> Option<SortDirection> {
623 self.data().sort_direction()
624 }
625
626 pub fn supports_toggle(&self) -> bool {
627 self.toggled().is_some()
628 }
629
630 pub fn supports_expand_collapse(&self) -> bool {
631 self.data().is_expanded().is_some()
632 }
633
634 pub fn is_invocable(&self, parent_filter: &impl Fn(&Node) -> FilterResult) -> bool {
635 self.is_clickable(parent_filter)
644 && !self.is_text_input()
645 && !matches!(self.role(), Role::Document | Role::Terminal)
646 && !self.supports_toggle()
647 && !self.supports_expand_collapse()
648 && self.is_selected().is_none()
649 }
650
651 pub fn supports_action(
652 &self,
653 action: Action,
654 parent_filter: &impl Fn(&Node) -> FilterResult,
655 ) -> bool {
656 if self.data().supports_action(action) {
657 return true;
658 }
659 if let Some(parent) = self.filtered_parent(parent_filter) {
660 return parent.data().child_supports_action(action);
661 }
662 false
663 }
664
665 pub fn supports_increment(&self, parent_filter: &impl Fn(&Node) -> FilterResult) -> bool {
666 self.supports_action(Action::Increment, parent_filter)
667 }
668
669 pub fn supports_decrement(&self, parent_filter: &impl Fn(&Node) -> FilterResult) -> bool {
670 self.supports_action(Action::Decrement, parent_filter)
671 }
672}
673
674fn descendant_label_filter(node: &Node) -> FilterResult {
675 match node.role() {
676 Role::Label | Role::Image => FilterResult::Include,
677 Role::GenericContainer => FilterResult::ExcludeNode,
678 _ => FilterResult::ExcludeSubtree,
679 }
680}
681
682impl<'a> Node<'a> {
683 pub fn labelled_by(
684 &self,
685 ) -> impl DoubleEndedIterator<Item = Node<'a>> + FusedIterator<Item = Node<'a>> + 'a {
686 let explicit = &self.state.data.labelled_by();
687 if explicit.is_empty()
688 && matches!(
689 self.role(),
690 Role::Button
691 | Role::CheckBox
692 | Role::DefaultButton
693 | Role::Link
694 | Role::MenuItem
695 | Role::MenuItemCheckBox
696 | Role::MenuItemRadio
697 | Role::RadioButton
698 )
699 {
700 LabelledBy::FromDescendants(FilteredChildren::new(*self, &descendant_label_filter))
701 } else {
702 LabelledBy::Explicit {
703 ids: explicit.iter(),
704 tree_state: self.tree_state,
705 node_id: self.id,
706 }
707 }
708 }
709
710 pub fn label_comes_from_value(&self) -> bool {
711 self.role() == Role::Label
712 }
713
714 pub fn label(&self) -> Option<String> {
715 let mut result = String::new();
716 self.write_label(&mut result).unwrap().then_some(result)
717 }
718
719 fn write_label_direct<W: fmt::Write>(&self, mut writer: W) -> Result<bool, fmt::Error> {
720 if let Some(label) = &self.data().label() {
721 writer.write_str(label)?;
722 Ok(true)
723 } else {
724 Ok(false)
725 }
726 }
727
728 pub fn write_label<W: fmt::Write>(&self, mut writer: W) -> Result<bool, fmt::Error> {
729 if self.write_label_direct(&mut writer)? {
730 Ok(true)
731 } else {
732 let mut wrote_one = false;
733 for node in self.labelled_by() {
734 let writer = SpacePrefixingWriter {
735 inner: &mut writer,
736 need_prefix: wrote_one,
737 };
738 let wrote_this_time = if node.label_comes_from_value() {
739 node.write_value(writer)
740 } else {
741 node.write_label_direct(writer)
742 }?;
743 wrote_one = wrote_one || wrote_this_time;
744 }
745 Ok(wrote_one)
746 }
747 }
748
749 pub fn description(&self) -> Option<String> {
750 self.data()
751 .description()
752 .map(|description| description.to_string())
753 }
754
755 pub fn url(&self) -> Option<&str> {
756 self.data().url()
757 }
758
759 pub fn supports_url(&self) -> bool {
760 matches!(
761 self.role(),
762 Role::Link
763 | Role::DocBackLink
764 | Role::DocBiblioRef
765 | Role::DocGlossRef
766 | Role::DocNoteRef
767 ) && self.url().is_some()
768 }
769
770 fn is_empty_text_input(&self) -> bool {
771 let mut text_runs = self.text_runs();
772 if let Some(first_text_run) = text_runs.next() {
773 first_text_run
774 .data()
775 .value()
776 .is_none_or(|value| value.is_empty())
777 && text_runs.next().is_none()
778 } else {
779 true
780 }
781 }
782
783 pub fn placeholder(&self) -> Option<&str> {
784 self.data()
785 .placeholder()
786 .filter(|_| self.is_text_input() && self.is_empty_text_input())
787 }
788
789 pub fn value(&self) -> Option<String> {
790 let mut result = String::new();
791 self.write_value(&mut result).unwrap().then_some(result)
792 }
793
794 pub fn write_value<W: fmt::Write>(&self, mut writer: W) -> Result<bool, fmt::Error> {
795 if let Some(value) = &self.data().value() {
796 writer.write_str(value)?;
797 Ok(true)
798 } else if self.supports_text_ranges() && !self.is_multiline() {
799 self.document_range().write_text(writer)?;
800 Ok(true)
801 } else {
802 Ok(false)
803 }
804 }
805
806 pub fn has_value(&self) -> bool {
807 self.data().value().is_some() || (self.supports_text_ranges() && !self.is_multiline())
808 }
809
810 pub fn is_read_only_supported(&self) -> bool {
811 self.is_text_input()
812 || matches!(
813 self.role(),
814 Role::CheckBox
815 | Role::ColorWell
816 | Role::ComboBox
817 | Role::Grid
818 | Role::ListBox
819 | Role::MenuItemCheckBox
820 | Role::MenuItemRadio
821 | Role::MenuListPopup
822 | Role::RadioButton
823 | Role::RadioGroup
824 | Role::Slider
825 | Role::Switch
826 | Role::TreeGrid
827 )
828 }
829
830 pub fn should_have_read_only_state_by_default(&self) -> bool {
831 matches!(
832 self.role(),
833 Role::Article
834 | Role::Definition
835 | Role::DescriptionList
836 | Role::Document
837 | Role::GraphicsDocument
838 | Role::Image
839 | Role::List
840 | Role::ListItem
841 | Role::PdfRoot
842 | Role::ProgressIndicator
843 | Role::RootWebArea
844 | Role::Term
845 | Role::Timer
846 | Role::Toolbar
847 | Role::Tooltip
848 )
849 }
850
851 pub fn is_required(&self) -> bool {
852 self.data().is_required()
853 }
854
855 pub fn live(&self) -> Live {
856 self.data()
857 .live()
858 .unwrap_or_else(|| self.parent().map_or(Live::Off, |parent| parent.live()))
859 }
860
861 pub fn is_selected(&self) -> Option<bool> {
862 self.data().is_selected()
863 }
864
865 pub fn is_item_like(&self) -> bool {
866 matches!(
867 self.role(),
868 Role::Article
869 | Role::Comment
870 | Role::ListItem
871 | Role::MenuItem
872 | Role::MenuItemRadio
873 | Role::Tab
874 | Role::MenuItemCheckBox
875 | Role::TreeItem
876 | Role::ListBoxOption
877 | Role::MenuListOption
878 | Role::RadioButton
879 | Role::Term
880 )
881 }
882
883 pub fn is_container_with_selectable_children(&self) -> bool {
884 matches!(
885 self.role(),
886 Role::ComboBox
887 | Role::EditableComboBox
888 | Role::Grid
889 | Role::ListBox
890 | Role::ListGrid
891 | Role::Menu
892 | Role::MenuBar
893 | Role::MenuListPopup
894 | Role::RadioGroup
895 | Role::TabList
896 | Role::Toolbar
897 | Role::Tree
898 | Role::TreeGrid
899 )
900 }
901
902 pub fn controls(
903 &self,
904 ) -> impl DoubleEndedIterator<Item = Node<'a>> + FusedIterator<Item = Node<'a>> + 'a {
905 let state = self.tree_state;
906 let id = self.id;
907 let data = &self.state.data;
908 data.controls()
909 .iter()
910 .map(move |control_id| state.node_by_id(id.with_same_tree(*control_id)).unwrap())
911 }
912
913 pub fn active_descendant(&self) -> Option<Node<'a>> {
914 self.state
915 .data
916 .active_descendant()
917 .and_then(|id| self.tree_state.node_by_id(self.id.with_same_tree(id)))
918 }
919
920 pub fn raw_text_selection(&self) -> Option<&TextSelection> {
921 self.data().text_selection()
922 }
923
924 pub fn raw_value(&self) -> Option<&str> {
925 self.data().value()
926 }
927
928 pub fn author_id(&self) -> Option<&str> {
929 self.data().author_id()
930 }
931
932 pub fn class_name(&self) -> Option<&str> {
933 self.data().class_name()
934 }
935
936 pub fn index_path(&self) -> Vec<usize> {
937 self.relative_index_path(self.tree_state.root_id())
938 }
939
940 pub fn relative_index_path(&self, ancestor_id: NodeId) -> Vec<usize> {
941 let mut result = Vec::new();
942 let mut current = *self;
943 while current.id() != ancestor_id {
944 let (parent, index) = current.parent_and_index().unwrap();
945 result.push(index);
946 current = parent;
947 }
948 result.reverse();
949 result
950 }
951
952 pub(crate) fn first_filtered_child(
953 &self,
954 filter: &impl Fn(&Node) -> FilterResult,
955 ) -> Option<Node<'a>> {
956 for child in self.children() {
957 let result = filter(&child);
958 if result == FilterResult::Include {
959 return Some(child);
960 }
961 if result == FilterResult::ExcludeNode {
962 if let Some(descendant) = child.first_filtered_child(filter) {
963 return Some(descendant);
964 }
965 }
966 }
967 None
968 }
969
970 pub(crate) fn last_filtered_child(
971 &self,
972 filter: &impl Fn(&Node) -> FilterResult,
973 ) -> Option<Node<'a>> {
974 for child in self.children().rev() {
975 let result = filter(&child);
976 if result == FilterResult::Include {
977 return Some(child);
978 }
979 if result == FilterResult::ExcludeNode {
980 if let Some(descendant) = child.last_filtered_child(filter) {
981 return Some(descendant);
982 }
983 }
984 }
985 None
986 }
987
988 pub fn selection_container(&self, filter: &impl Fn(&Node) -> FilterResult) -> Option<Node<'a>> {
989 self.filtered_parent(&|parent| match filter(parent) {
990 FilterResult::Include if parent.is_container_with_selectable_children() => {
991 FilterResult::Include
992 }
993 FilterResult::Include => FilterResult::ExcludeNode,
994 filter_result => filter_result,
995 })
996 }
997
998 pub fn items(
999 &self,
1000 filter: impl Fn(&Node) -> FilterResult + 'a,
1001 ) -> impl DoubleEndedIterator<Item = Node<'a>> + FusedIterator<Item = Node<'a>> + 'a {
1002 self.filtered_children(move |child| match filter(child) {
1003 FilterResult::Include if child.is_item_like() => FilterResult::Include,
1004 FilterResult::Include => FilterResult::ExcludeNode,
1005 filter_result => filter_result,
1006 })
1007 }
1008}
1009
1010struct SpacePrefixingWriter<W: fmt::Write> {
1011 inner: W,
1012 need_prefix: bool,
1013}
1014
1015impl<W: fmt::Write> SpacePrefixingWriter<W> {
1016 fn write_prefix_if_needed(&mut self) -> fmt::Result {
1017 if self.need_prefix {
1018 self.inner.write_char(' ')?;
1019 self.need_prefix = false;
1020 }
1021 Ok(())
1022 }
1023}
1024
1025impl<W: fmt::Write> fmt::Write for SpacePrefixingWriter<W> {
1026 fn write_str(&mut self, s: &str) -> fmt::Result {
1027 self.write_prefix_if_needed()?;
1028 self.inner.write_str(s)
1029 }
1030
1031 fn write_char(&mut self, c: char) -> fmt::Result {
1032 self.write_prefix_if_needed()?;
1033 self.inner.write_char(c)
1034 }
1035}
1036
1037#[cfg(test)]
1038mod tests {
1039 use accesskit::{
1040 Action, Node, NodeId, Point, Rect, Role, TextDirection, TextPosition, TextSelection, Tree,
1041 TreeId, TreeUpdate,
1042 };
1043 use alloc::vec;
1044
1045 use crate::tests::*;
1046
1047 #[test]
1048 fn parent_and_index() {
1049 let tree = test_tree();
1050 assert!(tree.state().root().parent_and_index().is_none());
1051 assert_eq!(
1052 Some((ROOT_ID, 0)),
1053 tree.state()
1054 .node_by_id(nid(PARAGRAPH_0_ID))
1055 .unwrap()
1056 .parent_and_index()
1057 .map(|(parent, index)| (parent.id().to_components().0, index))
1058 );
1059 assert_eq!(
1060 Some((PARAGRAPH_0_ID, 0)),
1061 tree.state()
1062 .node_by_id(nid(LABEL_0_0_IGNORED_ID))
1063 .unwrap()
1064 .parent_and_index()
1065 .map(|(parent, index)| (parent.id().to_components().0, index))
1066 );
1067 assert_eq!(
1068 Some((ROOT_ID, 1)),
1069 tree.state()
1070 .node_by_id(nid(PARAGRAPH_1_IGNORED_ID))
1071 .unwrap()
1072 .parent_and_index()
1073 .map(|(parent, index)| (parent.id().to_components().0, index))
1074 );
1075 }
1076
1077 #[test]
1078 fn deepest_first_child() {
1079 let tree = test_tree();
1080 assert_eq!(
1081 LABEL_0_0_IGNORED_ID,
1082 tree.state()
1083 .root()
1084 .deepest_first_child()
1085 .unwrap()
1086 .id()
1087 .to_components()
1088 .0
1089 );
1090 assert_eq!(
1091 LABEL_0_0_IGNORED_ID,
1092 tree.state()
1093 .node_by_id(nid(PARAGRAPH_0_ID))
1094 .unwrap()
1095 .deepest_first_child()
1096 .unwrap()
1097 .id()
1098 .to_components()
1099 .0
1100 );
1101 assert!(tree
1102 .state()
1103 .node_by_id(nid(LABEL_0_0_IGNORED_ID))
1104 .unwrap()
1105 .deepest_first_child()
1106 .is_none());
1107 }
1108
1109 #[test]
1110 fn filtered_parent() {
1111 let tree = test_tree();
1112 assert_eq!(
1113 ROOT_ID,
1114 tree.state()
1115 .node_by_id(nid(LABEL_1_1_ID))
1116 .unwrap()
1117 .filtered_parent(&test_tree_filter)
1118 .unwrap()
1119 .id()
1120 .to_components()
1121 .0
1122 );
1123 assert!(tree
1124 .state()
1125 .root()
1126 .filtered_parent(&test_tree_filter)
1127 .is_none());
1128 }
1129
1130 #[test]
1131 fn deepest_first_filtered_child() {
1132 let tree = test_tree();
1133 assert_eq!(
1134 PARAGRAPH_0_ID,
1135 tree.state()
1136 .root()
1137 .deepest_first_filtered_child(&test_tree_filter)
1138 .unwrap()
1139 .id()
1140 .to_components()
1141 .0
1142 );
1143 assert!(tree
1144 .state()
1145 .node_by_id(nid(PARAGRAPH_0_ID))
1146 .unwrap()
1147 .deepest_first_filtered_child(&test_tree_filter)
1148 .is_none());
1149 assert!(tree
1150 .state()
1151 .node_by_id(nid(LABEL_0_0_IGNORED_ID))
1152 .unwrap()
1153 .deepest_first_filtered_child(&test_tree_filter)
1154 .is_none());
1155 }
1156
1157 #[test]
1158 fn deepest_last_child() {
1159 let tree = test_tree();
1160 assert_eq!(
1161 EMPTY_CONTAINER_3_3_IGNORED_ID,
1162 tree.state()
1163 .root()
1164 .deepest_last_child()
1165 .unwrap()
1166 .id()
1167 .to_components()
1168 .0
1169 );
1170 assert_eq!(
1171 EMPTY_CONTAINER_3_3_IGNORED_ID,
1172 tree.state()
1173 .node_by_id(nid(PARAGRAPH_3_IGNORED_ID))
1174 .unwrap()
1175 .deepest_last_child()
1176 .unwrap()
1177 .id()
1178 .to_components()
1179 .0
1180 );
1181 assert!(tree
1182 .state()
1183 .node_by_id(nid(BUTTON_3_2_ID))
1184 .unwrap()
1185 .deepest_last_child()
1186 .is_none());
1187 }
1188
1189 #[test]
1190 fn deepest_last_filtered_child() {
1191 let tree = test_tree();
1192 assert_eq!(
1193 BUTTON_3_2_ID,
1194 tree.state()
1195 .root()
1196 .deepest_last_filtered_child(&test_tree_filter)
1197 .unwrap()
1198 .id()
1199 .to_components()
1200 .0
1201 );
1202 assert_eq!(
1203 BUTTON_3_2_ID,
1204 tree.state()
1205 .node_by_id(nid(PARAGRAPH_3_IGNORED_ID))
1206 .unwrap()
1207 .deepest_last_filtered_child(&test_tree_filter)
1208 .unwrap()
1209 .id()
1210 .to_components()
1211 .0
1212 );
1213 assert!(tree
1214 .state()
1215 .node_by_id(nid(BUTTON_3_2_ID))
1216 .unwrap()
1217 .deepest_last_filtered_child(&test_tree_filter)
1218 .is_none());
1219 assert!(tree
1220 .state()
1221 .node_by_id(nid(PARAGRAPH_0_ID))
1222 .unwrap()
1223 .deepest_last_filtered_child(&test_tree_filter)
1224 .is_none());
1225 }
1226
1227 #[test]
1228 fn is_descendant_of() {
1229 let tree = test_tree();
1230 assert!(tree
1231 .state()
1232 .node_by_id(nid(PARAGRAPH_0_ID))
1233 .unwrap()
1234 .is_descendant_of(&tree.state().root()));
1235 assert!(tree
1236 .state()
1237 .node_by_id(nid(LABEL_0_0_IGNORED_ID))
1238 .unwrap()
1239 .is_descendant_of(&tree.state().root()));
1240 assert!(tree
1241 .state()
1242 .node_by_id(nid(LABEL_0_0_IGNORED_ID))
1243 .unwrap()
1244 .is_descendant_of(&tree.state().node_by_id(nid(PARAGRAPH_0_ID)).unwrap()));
1245 assert!(!tree
1246 .state()
1247 .node_by_id(nid(LABEL_0_0_IGNORED_ID))
1248 .unwrap()
1249 .is_descendant_of(&tree.state().node_by_id(nid(PARAGRAPH_2_ID)).unwrap()));
1250 assert!(!tree
1251 .state()
1252 .node_by_id(nid(PARAGRAPH_0_ID))
1253 .unwrap()
1254 .is_descendant_of(&tree.state().node_by_id(nid(PARAGRAPH_2_ID)).unwrap()));
1255 }
1256
1257 #[test]
1258 fn is_root() {
1259 let tree = test_tree();
1260 assert!(tree.state().node_by_id(nid(ROOT_ID)).unwrap().is_root());
1261 assert!(!tree
1262 .state()
1263 .node_by_id(nid(PARAGRAPH_0_ID))
1264 .unwrap()
1265 .is_root());
1266 }
1267
1268 #[test]
1269 fn bounding_box() {
1270 let tree = test_tree();
1271 assert!(tree
1272 .state()
1273 .node_by_id(nid(ROOT_ID))
1274 .unwrap()
1275 .bounding_box()
1276 .is_none());
1277 assert_eq!(
1278 Some(Rect {
1279 x0: 10.0,
1280 y0: 40.0,
1281 x1: 810.0,
1282 y1: 80.0,
1283 }),
1284 tree.state()
1285 .node_by_id(nid(PARAGRAPH_1_IGNORED_ID))
1286 .unwrap()
1287 .bounding_box()
1288 );
1289 assert_eq!(
1290 Some(Rect {
1291 x0: 20.0,
1292 y0: 50.0,
1293 x1: 100.0,
1294 y1: 70.0,
1295 }),
1296 tree.state()
1297 .node_by_id(nid(LABEL_1_1_ID))
1298 .unwrap()
1299 .bounding_box()
1300 );
1301 }
1302
1303 #[test]
1304 fn node_at_point() {
1305 let tree = test_tree();
1306 assert!(tree
1307 .state()
1308 .root()
1309 .node_at_point(Point::new(10.0, 40.0), &test_tree_filter)
1310 .is_none());
1311 assert_eq!(
1312 Some(nid(LABEL_1_1_ID)),
1313 tree.state()
1314 .root()
1315 .node_at_point(Point::new(20.0, 50.0), &test_tree_filter)
1316 .map(|node| node.id())
1317 );
1318 assert_eq!(
1319 Some(nid(LABEL_1_1_ID)),
1320 tree.state()
1321 .root()
1322 .node_at_point(Point::new(50.0, 60.0), &test_tree_filter)
1323 .map(|node| node.id())
1324 );
1325 assert!(tree
1326 .state()
1327 .root()
1328 .node_at_point(Point::new(100.0, 70.0), &test_tree_filter)
1329 .is_none());
1330 }
1331
1332 #[test]
1333 fn no_label_or_labelled_by() {
1334 let update = TreeUpdate {
1335 nodes: vec![
1336 (NodeId(0), {
1337 let mut node = Node::new(Role::Window);
1338 node.set_children(vec![NodeId(1)]);
1339 node
1340 }),
1341 (NodeId(1), Node::new(Role::Button)),
1342 ],
1343 tree: Some(Tree::new(NodeId(0))),
1344 tree_id: TreeId::ROOT,
1345 focus: NodeId(0),
1346 };
1347 let tree = crate::Tree::new(update, false);
1348 assert_eq!(
1349 None,
1350 tree.state().node_by_id(nid(NodeId(1))).unwrap().label()
1351 );
1352 }
1353
1354 #[test]
1355 fn label_from_labelled_by() {
1356 const LABEL_1: &str = "Check email every";
1359 const LABEL_2: &str = "minutes";
1360
1361 let update = TreeUpdate {
1362 nodes: vec![
1363 (NodeId(0), {
1364 let mut node = Node::new(Role::Window);
1365 node.set_children(vec![NodeId(1), NodeId(2), NodeId(3), NodeId(4)]);
1366 node
1367 }),
1368 (NodeId(1), {
1369 let mut node = Node::new(Role::CheckBox);
1370 node.set_labelled_by(vec![NodeId(2), NodeId(4)]);
1371 node
1372 }),
1373 (NodeId(2), {
1374 let mut node = Node::new(Role::Label);
1375 node.set_value(LABEL_1);
1376 node
1377 }),
1378 (NodeId(3), {
1379 let mut node = Node::new(Role::TextInput);
1380 node.push_labelled_by(NodeId(4));
1381 node
1382 }),
1383 (NodeId(4), {
1384 let mut node = Node::new(Role::Label);
1385 node.set_value(LABEL_2);
1386 node
1387 }),
1388 ],
1389 tree: Some(Tree::new(NodeId(0))),
1390 tree_id: TreeId::ROOT,
1391 focus: NodeId(0),
1392 };
1393 let tree = crate::Tree::new(update, false);
1394 assert_eq!(
1395 Some([LABEL_1, LABEL_2].join(" ")),
1396 tree.state().node_by_id(nid(NodeId(1))).unwrap().label()
1397 );
1398 assert_eq!(
1399 Some(LABEL_2.into()),
1400 tree.state().node_by_id(nid(NodeId(3))).unwrap().label()
1401 );
1402 }
1403
1404 #[test]
1405 fn label_from_descendant_label() {
1406 const ROOT_ID: NodeId = NodeId(0);
1407 const DEFAULT_BUTTON_ID: NodeId = NodeId(1);
1408 const DEFAULT_BUTTON_LABEL_ID: NodeId = NodeId(2);
1409 const LINK_ID: NodeId = NodeId(3);
1410 const LINK_LABEL_CONTAINER_ID: NodeId = NodeId(4);
1411 const LINK_LABEL_ID: NodeId = NodeId(5);
1412 const CHECKBOX_ID: NodeId = NodeId(6);
1413 const CHECKBOX_LABEL_ID: NodeId = NodeId(7);
1414 const RADIO_BUTTON_ID: NodeId = NodeId(8);
1415 const RADIO_BUTTON_LABEL_ID: NodeId = NodeId(9);
1416 const MENU_BUTTON_ID: NodeId = NodeId(10);
1417 const MENU_BUTTON_LABEL_ID: NodeId = NodeId(11);
1418 const MENU_ID: NodeId = NodeId(12);
1419 const MENU_ITEM_ID: NodeId = NodeId(13);
1420 const MENU_ITEM_LABEL_ID: NodeId = NodeId(14);
1421 const MENU_ITEM_CHECKBOX_ID: NodeId = NodeId(15);
1422 const MENU_ITEM_CHECKBOX_LABEL_ID: NodeId = NodeId(16);
1423 const MENU_ITEM_RADIO_ID: NodeId = NodeId(17);
1424 const MENU_ITEM_RADIO_LABEL_ID: NodeId = NodeId(18);
1425
1426 const DEFAULT_BUTTON_LABEL: &str = "Play";
1427 const LINK_LABEL: &str = "Watch in browser";
1428 const CHECKBOX_LABEL: &str = "Resume from previous position";
1429 const RADIO_BUTTON_LABEL: &str = "Normal speed";
1430 const MENU_BUTTON_LABEL: &str = "More";
1431 const MENU_ITEM_LABEL: &str = "Share";
1432 const MENU_ITEM_CHECKBOX_LABEL: &str = "Apply volume processing";
1433 const MENU_ITEM_RADIO_LABEL: &str = "Maximize loudness for noisy environment";
1434
1435 let update = TreeUpdate {
1436 nodes: vec![
1437 (ROOT_ID, {
1438 let mut node = Node::new(Role::Window);
1439 node.set_children(vec![
1440 DEFAULT_BUTTON_ID,
1441 LINK_ID,
1442 CHECKBOX_ID,
1443 RADIO_BUTTON_ID,
1444 MENU_BUTTON_ID,
1445 MENU_ID,
1446 ]);
1447 node
1448 }),
1449 (DEFAULT_BUTTON_ID, {
1450 let mut node = Node::new(Role::DefaultButton);
1451 node.push_child(DEFAULT_BUTTON_LABEL_ID);
1452 node
1453 }),
1454 (DEFAULT_BUTTON_LABEL_ID, {
1455 let mut node = Node::new(Role::Image);
1456 node.set_label(DEFAULT_BUTTON_LABEL);
1457 node
1458 }),
1459 (LINK_ID, {
1460 let mut node = Node::new(Role::Link);
1461 node.push_child(LINK_LABEL_CONTAINER_ID);
1462 node
1463 }),
1464 (LINK_LABEL_CONTAINER_ID, {
1465 let mut node = Node::new(Role::GenericContainer);
1466 node.push_child(LINK_LABEL_ID);
1467 node
1468 }),
1469 (LINK_LABEL_ID, {
1470 let mut node = Node::new(Role::Label);
1471 node.set_value(LINK_LABEL);
1472 node
1473 }),
1474 (CHECKBOX_ID, {
1475 let mut node = Node::new(Role::CheckBox);
1476 node.push_child(CHECKBOX_LABEL_ID);
1477 node
1478 }),
1479 (CHECKBOX_LABEL_ID, {
1480 let mut node = Node::new(Role::Label);
1481 node.set_value(CHECKBOX_LABEL);
1482 node
1483 }),
1484 (RADIO_BUTTON_ID, {
1485 let mut node = Node::new(Role::RadioButton);
1486 node.push_child(RADIO_BUTTON_LABEL_ID);
1487 node
1488 }),
1489 (RADIO_BUTTON_LABEL_ID, {
1490 let mut node = Node::new(Role::Label);
1491 node.set_value(RADIO_BUTTON_LABEL);
1492 node
1493 }),
1494 (MENU_BUTTON_ID, {
1495 let mut node = Node::new(Role::Button);
1496 node.push_child(MENU_BUTTON_LABEL_ID);
1497 node
1498 }),
1499 (MENU_BUTTON_LABEL_ID, {
1500 let mut node = Node::new(Role::Label);
1501 node.set_value(MENU_BUTTON_LABEL);
1502 node
1503 }),
1504 (MENU_ID, {
1505 let mut node = Node::new(Role::Menu);
1506 node.set_children([MENU_ITEM_ID, MENU_ITEM_CHECKBOX_ID, MENU_ITEM_RADIO_ID]);
1507 node
1508 }),
1509 (MENU_ITEM_ID, {
1510 let mut node = Node::new(Role::MenuItem);
1511 node.push_child(MENU_ITEM_LABEL_ID);
1512 node
1513 }),
1514 (MENU_ITEM_LABEL_ID, {
1515 let mut node = Node::new(Role::Label);
1516 node.set_value(MENU_ITEM_LABEL);
1517 node
1518 }),
1519 (MENU_ITEM_CHECKBOX_ID, {
1520 let mut node = Node::new(Role::MenuItemCheckBox);
1521 node.push_child(MENU_ITEM_CHECKBOX_LABEL_ID);
1522 node
1523 }),
1524 (MENU_ITEM_CHECKBOX_LABEL_ID, {
1525 let mut node = Node::new(Role::Label);
1526 node.set_value(MENU_ITEM_CHECKBOX_LABEL);
1527 node
1528 }),
1529 (MENU_ITEM_RADIO_ID, {
1530 let mut node = Node::new(Role::MenuItemRadio);
1531 node.push_child(MENU_ITEM_RADIO_LABEL_ID);
1532 node
1533 }),
1534 (MENU_ITEM_RADIO_LABEL_ID, {
1535 let mut node = Node::new(Role::Label);
1536 node.set_value(MENU_ITEM_RADIO_LABEL);
1537 node
1538 }),
1539 ],
1540 tree: Some(Tree::new(ROOT_ID)),
1541 tree_id: TreeId::ROOT,
1542 focus: ROOT_ID,
1543 };
1544 let tree = crate::Tree::new(update, false);
1545 assert_eq!(
1546 Some(DEFAULT_BUTTON_LABEL.into()),
1547 tree.state()
1548 .node_by_id(nid(DEFAULT_BUTTON_ID))
1549 .unwrap()
1550 .label()
1551 );
1552 assert_eq!(
1553 Some(LINK_LABEL.into()),
1554 tree.state().node_by_id(nid(LINK_ID)).unwrap().label()
1555 );
1556 assert_eq!(
1557 Some(CHECKBOX_LABEL.into()),
1558 tree.state().node_by_id(nid(CHECKBOX_ID)).unwrap().label()
1559 );
1560 assert_eq!(
1561 Some(RADIO_BUTTON_LABEL.into()),
1562 tree.state()
1563 .node_by_id(nid(RADIO_BUTTON_ID))
1564 .unwrap()
1565 .label()
1566 );
1567 assert_eq!(
1568 Some(MENU_BUTTON_LABEL.into()),
1569 tree.state()
1570 .node_by_id(nid(MENU_BUTTON_ID))
1571 .unwrap()
1572 .label()
1573 );
1574 assert_eq!(
1575 Some(MENU_ITEM_LABEL.into()),
1576 tree.state().node_by_id(nid(MENU_ITEM_ID)).unwrap().label()
1577 );
1578 assert_eq!(
1579 Some(MENU_ITEM_CHECKBOX_LABEL.into()),
1580 tree.state()
1581 .node_by_id(nid(MENU_ITEM_CHECKBOX_ID))
1582 .unwrap()
1583 .label()
1584 );
1585 assert_eq!(
1586 Some(MENU_ITEM_RADIO_LABEL.into()),
1587 tree.state()
1588 .node_by_id(nid(MENU_ITEM_RADIO_ID))
1589 .unwrap()
1590 .label()
1591 );
1592 }
1593
1594 #[test]
1595 fn placeholder_should_be_exposed_on_empty_text_input() {
1596 const ROOT_ID: NodeId = NodeId(0);
1597 const TEXT_INPUT_ID: NodeId = NodeId(1);
1598 const TEXT_RUN_ID: NodeId = NodeId(2);
1599
1600 const PLACEHOLDER: &str = "John Doe";
1601
1602 let update = TreeUpdate {
1603 nodes: vec![
1604 (ROOT_ID, {
1605 let mut node = Node::new(Role::Window);
1606 node.set_children(vec![TEXT_INPUT_ID]);
1607 node
1608 }),
1609 (TEXT_INPUT_ID, {
1610 let mut node = Node::new(Role::MultilineTextInput);
1611 node.set_bounds(Rect {
1612 x0: 8.0,
1613 y0: 8.0,
1614 x1: 296.0,
1615 y1: 69.5,
1616 });
1617 node.push_child(TEXT_RUN_ID);
1618 node.set_placeholder(PLACEHOLDER);
1619 node.set_text_selection(TextSelection {
1620 anchor: TextPosition {
1621 node: TEXT_RUN_ID,
1622 character_index: 0,
1623 },
1624 focus: TextPosition {
1625 node: TEXT_RUN_ID,
1626 character_index: 0,
1627 },
1628 });
1629 node.add_action(Action::Focus);
1630 node
1631 }),
1632 (TEXT_RUN_ID, {
1633 let mut node = Node::new(Role::TextRun);
1634 node.set_bounds(Rect {
1635 x0: 12.0,
1636 y0: 10.0,
1637 x1: 12.0,
1638 y1: 24.0,
1639 });
1640 node.set_value("");
1641 node.set_character_lengths([]);
1642 node.set_character_positions([]);
1643 node.set_character_widths([]);
1644 node.set_text_direction(TextDirection::LeftToRight);
1645 node
1646 }),
1647 ],
1648 tree: Some(Tree::new(ROOT_ID)),
1649 tree_id: TreeId::ROOT,
1650 focus: TEXT_INPUT_ID,
1651 };
1652 let tree = crate::Tree::new(update, false);
1653 assert_eq!(
1654 Some(PLACEHOLDER),
1655 tree.state()
1656 .node_by_id(nid(TEXT_INPUT_ID))
1657 .unwrap()
1658 .placeholder()
1659 );
1660 }
1661
1662 #[test]
1663 fn placeholder_should_be_ignored_on_non_empty_text_input() {
1664 const ROOT_ID: NodeId = NodeId(0);
1665 const TEXT_INPUT_ID: NodeId = NodeId(1);
1666 const TEXT_RUN_ID: NodeId = NodeId(2);
1667
1668 const PLACEHOLDER: &str = "John Doe";
1669
1670 let update = TreeUpdate {
1671 nodes: vec![
1672 (ROOT_ID, {
1673 let mut node = Node::new(Role::Window);
1674 node.set_children(vec![TEXT_INPUT_ID]);
1675 node
1676 }),
1677 (TEXT_INPUT_ID, {
1678 let mut node = Node::new(Role::MultilineTextInput);
1679 node.set_bounds(Rect {
1680 x0: 8.0,
1681 y0: 8.0,
1682 x1: 296.0,
1683 y1: 69.5,
1684 });
1685 node.push_child(TEXT_RUN_ID);
1686 node.set_placeholder(PLACEHOLDER);
1687 node.set_text_selection(TextSelection {
1688 anchor: TextPosition {
1689 node: TEXT_RUN_ID,
1690 character_index: 1,
1691 },
1692 focus: TextPosition {
1693 node: TEXT_RUN_ID,
1694 character_index: 1,
1695 },
1696 });
1697 node.add_action(Action::Focus);
1698 node
1699 }),
1700 (TEXT_RUN_ID, {
1701 let mut node = Node::new(Role::TextRun);
1702 node.set_bounds(Rect {
1703 x0: 12.0,
1704 y0: 10.0,
1705 x1: 20.0,
1706 y1: 24.0,
1707 });
1708 node.set_value("A");
1709 node.set_character_lengths([1]);
1710 node.set_character_positions([0.0]);
1711 node.set_character_widths([8.0]);
1712 node.set_word_starts([0]);
1713 node.set_text_direction(TextDirection::LeftToRight);
1714 node
1715 }),
1716 ],
1717 tree: Some(Tree::new(ROOT_ID)),
1718 tree_id: TreeId::ROOT,
1719 focus: TEXT_INPUT_ID,
1720 };
1721 let tree = crate::Tree::new(update, false);
1722 assert_eq!(
1723 None,
1724 tree.state()
1725 .node_by_id(nid(TEXT_INPUT_ID))
1726 .unwrap()
1727 .placeholder()
1728 );
1729 }
1730
1731 #[test]
1732 fn hidden_flag_should_be_inherited() {
1733 const ROOT_ID: NodeId = NodeId(0);
1734 const CONTAINER_ID: NodeId = NodeId(1);
1735 const LEAF_ID: NodeId = NodeId(2);
1736
1737 let update = TreeUpdate {
1738 nodes: vec![
1739 (ROOT_ID, {
1740 let mut node = Node::new(Role::Window);
1741 node.set_children(vec![CONTAINER_ID]);
1742 node
1743 }),
1744 (CONTAINER_ID, {
1745 let mut node = Node::new(Role::GenericContainer);
1746 node.set_hidden();
1747 node.push_child(LEAF_ID);
1748 node
1749 }),
1750 (LEAF_ID, {
1751 let mut node = Node::new(Role::Button);
1752 node.set_label("OK");
1753 node
1754 }),
1755 ],
1756 tree: Some(Tree::new(ROOT_ID)),
1757 tree_id: TreeId::ROOT,
1758 focus: ROOT_ID,
1759 };
1760 let tree = crate::Tree::new(update, false);
1761 assert!(tree.state().node_by_id(nid(LEAF_ID)).unwrap().is_hidden());
1762 }
1763
1764 mod node_id {
1765 use super::NodeId as LocalNodeId;
1766 use crate::node::NodeId;
1767 use crate::tree::TreeIndex;
1768
1769 #[test]
1770 fn new_and_to_components_round_trip() {
1771 let node_id = LocalNodeId(42);
1772 let tree_index = TreeIndex(7);
1773 let id = NodeId::new(node_id, tree_index);
1774 let (extracted_node_id, extracted_tree_index) = id.to_components();
1775 assert_eq!(node_id, extracted_node_id);
1776 assert_eq!(tree_index, extracted_tree_index);
1777 }
1778
1779 #[test]
1780 fn with_same_tree_preserves_tree_index() {
1781 let original_node_id = LocalNodeId(100);
1782 let tree_index = TreeIndex(5);
1783 let id = NodeId::new(original_node_id, tree_index);
1784
1785 let new_node_id = LocalNodeId(200);
1786 let new_id = id.with_same_tree(new_node_id);
1787
1788 let (extracted_node_id, extracted_tree_index) = new_id.to_components();
1789 assert_eq!(new_node_id, extracted_node_id);
1790 assert_eq!(tree_index, extracted_tree_index);
1791 }
1792
1793 #[test]
1794 fn into_u128() {
1795 let node_id = LocalNodeId(12345);
1796 let tree_index = TreeIndex(67);
1797 let id = NodeId::new(node_id, tree_index);
1798 let (extracted_node_id, extracted_tree_index) = id.to_components();
1799 assert_eq!(node_id, extracted_node_id);
1800 assert_eq!(tree_index, extracted_tree_index);
1801 }
1802
1803 #[test]
1804 fn equality() {
1805 let id1 = NodeId::new(LocalNodeId(1), TreeIndex(2));
1806 let id2 = NodeId::new(LocalNodeId(1), TreeIndex(2));
1807 let id3 = NodeId::new(LocalNodeId(1), TreeIndex(3));
1808 let id4 = NodeId::new(LocalNodeId(2), TreeIndex(2));
1809
1810 assert_eq!(id1, id2);
1811 assert_ne!(id1, id3);
1812 assert_ne!(id1, id4);
1813 }
1814 }
1815
1816 #[test]
1817 fn is_focused_when_node_has_focus() {
1818 const ROOT_ID: NodeId = NodeId(0);
1819 const BUTTON_ID: NodeId = NodeId(1);
1820
1821 let update = TreeUpdate {
1822 nodes: vec![
1823 (ROOT_ID, {
1824 let mut node = Node::new(Role::Window);
1825 node.set_children(vec![BUTTON_ID]);
1826 node
1827 }),
1828 (BUTTON_ID, Node::new(Role::Button)),
1829 ],
1830 tree: Some(Tree::new(ROOT_ID)),
1831 tree_id: TreeId::ROOT,
1832 focus: BUTTON_ID,
1833 };
1834 let tree = crate::Tree::new(update, true);
1835 assert!(tree
1836 .state()
1837 .node_by_id(nid(BUTTON_ID))
1838 .unwrap()
1839 .is_focused());
1840 }
1841
1842 #[test]
1843 fn is_focused_when_node_does_not_have_focus() {
1844 const ROOT_ID: NodeId = NodeId(0);
1845 const BUTTON_ID: NodeId = NodeId(1);
1846
1847 let update = TreeUpdate {
1848 nodes: vec![
1849 (ROOT_ID, {
1850 let mut node = Node::new(Role::Window);
1851 node.set_children(vec![BUTTON_ID]);
1852 node
1853 }),
1854 (BUTTON_ID, Node::new(Role::Button)),
1855 ],
1856 tree: Some(Tree::new(ROOT_ID)),
1857 tree_id: TreeId::ROOT,
1858 focus: ROOT_ID,
1859 };
1860 let tree = crate::Tree::new(update, true);
1861 assert!(!tree
1862 .state()
1863 .node_by_id(nid(BUTTON_ID))
1864 .unwrap()
1865 .is_focused());
1866 }
1867
1868 #[test]
1869 fn is_focused_active_descendant_is_focused() {
1870 const ROOT_ID: NodeId = NodeId(0);
1871 const LISTBOX_ID: NodeId = NodeId(1);
1872 const ITEM_ID: NodeId = NodeId(2);
1873
1874 let update = TreeUpdate {
1875 nodes: vec![
1876 (ROOT_ID, {
1877 let mut node = Node::new(Role::Window);
1878 node.set_children(vec![LISTBOX_ID]);
1879 node
1880 }),
1881 (LISTBOX_ID, {
1882 let mut node = Node::new(Role::ListBox);
1883 node.set_children(vec![ITEM_ID]);
1884 node.set_active_descendant(ITEM_ID);
1885 node
1886 }),
1887 (ITEM_ID, Node::new(Role::ListBoxOption)),
1888 ],
1889 tree: Some(Tree::new(ROOT_ID)),
1890 tree_id: TreeId::ROOT,
1891 focus: LISTBOX_ID,
1892 };
1893 let tree = crate::Tree::new(update, true);
1894 assert!(tree.state().node_by_id(nid(ITEM_ID)).unwrap().is_focused());
1895 }
1896
1897 #[test]
1898 fn is_focused_node_with_active_descendant_is_not_focused() {
1899 const ROOT_ID: NodeId = NodeId(0);
1900 const LISTBOX_ID: NodeId = NodeId(1);
1901 const ITEM_ID: NodeId = NodeId(2);
1902
1903 let update = TreeUpdate {
1904 nodes: vec![
1905 (ROOT_ID, {
1906 let mut node = Node::new(Role::Window);
1907 node.set_children(vec![LISTBOX_ID]);
1908 node
1909 }),
1910 (LISTBOX_ID, {
1911 let mut node = Node::new(Role::ListBox);
1912 node.set_children(vec![ITEM_ID]);
1913 node.set_active_descendant(ITEM_ID);
1914 node
1915 }),
1916 (ITEM_ID, Node::new(Role::ListBoxOption)),
1917 ],
1918 tree: Some(Tree::new(ROOT_ID)),
1919 tree_id: TreeId::ROOT,
1920 focus: LISTBOX_ID,
1921 };
1922 let tree = crate::Tree::new(update, true);
1923 assert!(!tree
1924 .state()
1925 .node_by_id(nid(LISTBOX_ID))
1926 .unwrap()
1927 .is_focused());
1928 }
1929}