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)]
51#[derive(PartialEq, Clone)]
52pub enum AccessibilityAction {
53 Default,
54 Decrement,
55 Increment,
56 Expand,
57 ReplaceSelectedText(SharedString),
59 SetValue(SharedString),
60 SetSelectionOffsets(i32, i32),
63}
64
65bitflags! {
66 #[repr(transparent)]
68 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
69 pub struct SupportedAccessibilityAction: u32 {
70 const Default = 1;
71 const Decrement = 1 << 1;
72 const Increment = 1 << 2;
73 const Expand = 1 << 3;
74 const ReplaceSelectedText = 1 << 4;
75 const SetValue = 1 << 5;
76 const SetSelectionOffsets = 1 << 6;
77 }
78}
79
80pub fn accessible_descendents(root_item: &ItemRc) -> impl Iterator<Item = ItemRc> {
85 fn try_candidate_or_find_next_accessible_descendent(
86 candidate: ItemRc,
87 descendent_candidates: &mut Vec<ItemRc>,
88 ) -> Option<ItemRc> {
89 if candidate.is_accessible() {
90 return Some(candidate);
91 }
92
93 candidate.first_child().and_then(|child| {
94 if let Some(next) = child.next_sibling() {
95 descendent_candidates.push(next);
96 }
97 try_candidate_or_find_next_accessible_descendent(child, descendent_candidates)
98 })
99 }
100
101 let mut descendent_candidates = Vec::new();
104 if let Some(child) = root_item.first_child() {
105 descendent_candidates.push(child);
106 }
107
108 core::iter::from_fn(move || {
109 loop {
110 let candidate = descendent_candidates.pop()?;
111
112 if let Some(next_candidate) = candidate.next_sibling() {
113 descendent_candidates.push(next_candidate);
114 }
115
116 if let Some(descendent) = try_candidate_or_find_next_accessible_descendent(
117 candidate,
118 &mut descendent_candidates,
119 ) {
120 return Some(descendent);
121 }
122 }
123 })
124}
125
126pub fn nearest_accessible_item(mut item: ItemRc) -> ItemRc {
129 while !item.is_accessible() {
130 let Some(parent) = item.parent_item(ParentItemTraversalMode::StopAtPopups) else { break };
131 item = parent;
132 }
133 item
134}
135
136pub fn find_text_input(item: &ItemRc) -> Option<VRcMapped<ItemTreeVTable, TextInput>> {
138 find_text_input_with_rc(item).map(|(_, input)| input)
139}
140
141pub fn find_exposed_text_input(
147 item: &ItemRc,
148) -> Option<(ItemRc, VRcMapped<ItemTreeVTable, TextInput>)> {
149 let found = find_text_input_with_rc(item)?;
150 (nearest_accessible_item(found.0.clone()) == *item).then_some(found)
151}
152
153pub fn find_text_input_with_rc(
155 item: &ItemRc,
156) -> Option<(ItemRc, VRcMapped<ItemTreeVTable, TextInput>)> {
157 if let Some(input) = item.clone().downcast::<TextInput>() {
158 return Some((item.clone(), input));
159 }
160
161 let mut child = item.first_child();
162 while let Some(candidate) = child {
163 child = candidate.next_sibling();
164 if let Some(found) = find_text_input_with_rc(&candidate) {
165 return Some(found);
166 }
167 }
168 None
169}