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