Skip to main content

accesskit_consumer/
node.rs

1// Copyright 2021 The AccessKit Authors. All rights reserved.
2// Licensed under the Apache License, Version 2.0 (found in
3// the LICENSE-APACHE file) or the MIT license (found in
4// the LICENSE-MIT file), at your option.
5
6// Derived from Chromium's accessibility abstraction.
7// Copyright 2021 The Chromium Authors. All rights reserved.
8// Use of this source code is governed by a BSD-style license that can be
9// found in the LICENSE.chromium file.
10
11use 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        // Don't check for absence of a parent node, in case a non-root node
102        // somehow gets detached from the tree.
103        self.id() == self.tree_state.root_id()
104    }
105
106    /// Returns true if this node is a graft node (has a tree_id property set).
107    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    /// Returns the single child of a graft node (the subtree root), if available.
143    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    /// Returns the transform defined directly on this node, or the identity
287    /// transform, without taking into account transforms on ancestors.
288    pub fn direct_transform(&self) -> Affine {
289        self.data()
290            .transform()
291            .map_or(Affine::IDENTITY, |value| *value)
292    }
293
294    /// Returns the combined affine transform of this node and its ancestors,
295    /// up to and including the root of this node's tree.
296    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    /// Returns the node's transformed bounding box relative to the tree's
324    /// container (e.g. window).
325    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    /// Returns the deepest filtered node, either this node or a descendant,
367    /// at the given point in this node's coordinate space.
368    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    // When probing for supported actions as the next several functions do,
587    // it's tempting to check the role. But it's better to not assume anything
588    // beyond what the provider has explicitly told us. Rationale:
589    // if the provider developer forgot to call `add_action` for an action,
590    // an AT (or even AccessKit itself) can fall back to simulating
591    // a mouse click. But if the provider doesn't handle an action request
592    // and we assume that it will based on the role, the attempted action
593    // does nothing. This stance is a departure from Chromium.
594
595    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        // It's selectable if it has the attribute, whether it's true or false.
601        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        // TODO: compute this if it is not provided (#9).
618        self.data().size_of_set()
619    }
620
621    pub fn position_in_set(&self) -> Option<usize> {
622        // TODO: compute this if it is not provided (#9).
623        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        // A control is "invocable" if it initiates an action when activated but
640        // does not maintain any state. A control that maintains state
641        // when activated would be considered a toggle or expand-collapse
642        // control - these controls are "clickable" but not "invocable".
643        // Similarly, if the action only gives the control keyboard focus,
644        // such as when clicking a text input, the control is not considered
645        // "invocable", as the "invoke" action would be a redundant synonym
646        // for the "set focus" action. The same logic applies to selection.
647        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        // The following mock UI probably isn't very localization-friendly,
1370        // but it's good for this test.
1371        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}