1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4// cSpell: ignore descendents
5
6use crate::{
7 item_tree::ItemTreeVTable,
8 items::{ItemRc, TextInput},
9 SharedString,
10};
11use alloc::{vec, vec::Vec};
12use bitflags::bitflags;
13use vtable::VRcMapped;
14
15/// The property names of the accessible-properties
16#[repr(u32)]
17#[derive(PartialEq, Eq, Copy, Clone, strum::Display)]
18#[strum(serialize_all = "kebab-case")]
19pub enum AccessibleStringProperty {
20 Checkable,
21 Checked,
22 DelegateFocus,
23 Description,
24 Enabled,
25 Expandable,
26 Expanded,
27 ItemCount,
28 ItemIndex,
29 ItemSelectable,
30 ItemSelected,
31 Label,
32 PlaceholderText,
33 ReadOnly,
34 Value,
35 ValueMaximum,
36 ValueMinimum,
37 ValueStep,
38}
39
40/// The argument of an accessible action.
41#[repr(u32)]
42#[derive(PartialEq, Clone)]
43pub enum AccessibilityAction {
44 Default,
45 Decrement,
46 Increment,
47 Expand,
48 /// This is currently unused
49 ReplaceSelectedText(SharedString),
50 SetValue(SharedString),
51}
52
53bitflags! {
54 /// Define a accessibility actions that supported by an item.
55 #[repr(transparent)]
56 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
57 pub struct SupportedAccessibilityAction: u32 {
58 const Default = 1;
59 const Decrement = 1 << 1;
60 const Increment = 1 << 2;
61 const Expand = 1 << 3;
62 const ReplaceSelectedText = 1 << 4;
63 const SetValue = 1 << 5;
64 }
65}
66
67/// Find accessible descendents of `root_item`.
68///
69/// This will recurse through all children of `root_item`, but will not recurse
70/// into nodes that are accessible.
71pub fn accessible_descendents(root_item: &ItemRc) -> impl Iterator<Item = ItemRc> {
72 fn try_candidate_or_find_next_accessible_descendent(
73 candidate: ItemRc,
74 descendent_candidates: &mut Vec<ItemRc>,
75 ) -> Option<ItemRc> {
76 if candidate.is_accessible() {
77 return Some(candidate);
78 }
79
80 candidate.first_child().and_then(|child| {
81 if let Some(next) = child.next_sibling() {
82 descendent_candidates.push(next);
83 }
84 try_candidate_or_find_next_accessible_descendent(child, descendent_candidates)
85 })
86 }
87
88 // Do not look on the root_item: That is either a component root or an
89 // accessible item already handled!
90 let mut descendent_candidates = Vec::new();
91 if let Some(child) = root_item.first_child() {
92 descendent_candidates.push(child);
93 }
94
95 core::iter::from_fn(move || loop {
96 let candidate = descendent_candidates.pop()?;
97
98 if let Some(next_candidate) = candidate.next_sibling() {
99 descendent_candidates.push(next_candidate);
100 }
101
102 if let Some(descendent) =
103 try_candidate_or_find_next_accessible_descendent(candidate, &mut descendent_candidates)
104 {
105 return Some(descendent);
106 }
107 })
108}
109
110/// Find the first built-in `TextInput` in the descendents of `item`.
111pub fn find_text_input(item: &ItemRc) -> Option<VRcMapped<ItemTreeVTable, TextInput>> {
112 fn try_candidate_or_find_next_descendent(
113 candidate: ItemRc,
114 descendent_candidates: &mut Vec<ItemRc>,
115 ) -> Option<VRcMapped<ItemTreeVTable, TextInput>> {
116 if let Some(input) = candidate.downcast::<TextInput>() {
117 return Some(input);
118 }
119
120 candidate.first_child().and_then(|child| {
121 if let Some(next) = child.next_sibling() {
122 descendent_candidates.push(next);
123 }
124 try_candidate_or_find_next_descendent(child, descendent_candidates)
125 })
126 }
127
128 let mut descendent_candidates = vec![item.clone()];
129
130 loop {
131 let candidate = descendent_candidates.pop()?;
132
133 if let Some(next_candidate) = candidate.next_sibling() {
134 descendent_candidates.push(next_candidate);
135 }
136
137 if let Some(input) =
138 try_candidate_or_find_next_descendent(candidate, &mut descendent_candidates)
139 {
140 return Some(input);
141 }
142 }
143}
144