i_slint_core/
accessibility.rs1use crate::{
7 SharedString,
8 item_tree::{ItemTreeVTable, ParentItemTraversalMode},
9 items::{ItemRc, TextInput},
10};
11use alloc::vec::Vec;
12use bitflags::bitflags;
13use vtable::VRcMapped;
14
15#[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 Id,
28 ItemCount,
29 ItemIndex,
30 ItemSelectable,
31 ItemSelected,
32 Label,
33 LiveRegion,
34 Orientation,
35 PlaceholderText,
36 ReadOnly,
37 Value,
38 ValueMaximum,
39 ValueMinimum,
40 ValueStep,
41}
42
43#[repr(u32)]
50#[derive(PartialEq, Clone)]
51pub enum AccessibilityAction {
52 Default,
53 Decrement,
54 Increment,
55 Expand,
56 ReplaceSelectedText(SharedString),
58 SetValue(SharedString),
59 SetSelectionOffsets(i32, i32),
62}
63
64bitflags! {
65 #[repr(transparent)]
67 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
68 pub struct SupportedAccessibilityAction: u32 {
69 const Default = 1;
70 const Decrement = 1 << 1;
71 const Increment = 1 << 2;
72 const Expand = 1 << 3;
73 const ReplaceSelectedText = 1 << 4;
74 const SetValue = 1 << 5;
75 const SetSelectionOffsets = 1 << 6;
76 }
77}
78
79pub fn accessible_descendents(root_item: &ItemRc) -> impl Iterator<Item = ItemRc> {
84 fn try_candidate_or_find_next_accessible_descendent(
85 candidate: ItemRc,
86 descendent_candidates: &mut Vec<ItemRc>,
87 ) -> Option<ItemRc> {
88 if candidate.is_accessible() {
89 return Some(candidate);
90 }
91
92 candidate.first_child().and_then(|child| {
93 if let Some(next) = child.next_sibling() {
94 descendent_candidates.push(next);
95 }
96 try_candidate_or_find_next_accessible_descendent(child, descendent_candidates)
97 })
98 }
99
100 let mut descendent_candidates = Vec::new();
103 if let Some(child) = root_item.first_child() {
104 descendent_candidates.push(child);
105 }
106
107 core::iter::from_fn(move || {
108 loop {
109 let candidate = descendent_candidates.pop()?;
110
111 if let Some(next_candidate) = candidate.next_sibling() {
112 descendent_candidates.push(next_candidate);
113 }
114
115 if let Some(descendent) = try_candidate_or_find_next_accessible_descendent(
116 candidate,
117 &mut descendent_candidates,
118 ) {
119 return Some(descendent);
120 }
121 }
122 })
123}
124
125pub fn nearest_accessible_item(mut item: ItemRc) -> ItemRc {
128 while !item.is_accessible() {
129 let Some(parent) = item.parent_item(ParentItemTraversalMode::StopAtPopups) else { break };
130 item = parent;
131 }
132 item
133}
134
135pub fn find_text_input(item: &ItemRc) -> Option<VRcMapped<ItemTreeVTable, TextInput>> {
137 find_text_input_with_rc(item).map(|(_, input)| input)
138}
139
140pub fn find_exposed_text_input(
146 item: &ItemRc,
147) -> Option<(ItemRc, VRcMapped<ItemTreeVTable, TextInput>)> {
148 let found = find_text_input_with_rc(item)?;
149 (nearest_accessible_item(found.0.clone()) == *item).then_some(found)
150}
151
152pub fn find_text_input_with_rc(
154 item: &ItemRc,
155) -> Option<(ItemRc, VRcMapped<ItemTreeVTable, TextInput>)> {
156 if let Some(input) = item.clone().downcast::<TextInput>() {
157 return Some((item.clone(), input));
158 }
159
160 let mut child = item.first_child();
161 while let Some(candidate) = child {
162 child = candidate.next_sibling();
163 if let Some(found) = find_text_input_with_rc(&candidate) {
164 return Some(found);
165 }
166 }
167 None
168}