Skip to main content

i_slint_core/
accessibility.rs

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    SharedString,
8    item_tree::ItemTreeVTable,
9    items::{ItemRc, TextInput},
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    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/// The argument of an accessible action.
44#[repr(u32)]
45#[derive(PartialEq, Clone)]
46pub enum AccessibilityAction {
47    Default,
48    Decrement,
49    Increment,
50    Expand,
51    /// This is currently unused
52    ReplaceSelectedText(SharedString),
53    SetValue(SharedString),
54}
55
56bitflags! {
57    /// Define a accessibility actions that supported by an item.
58    #[repr(transparent)]
59    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
60    pub struct SupportedAccessibilityAction: u32 {
61        const Default = 1;
62        const Decrement = 1 << 1;
63        const Increment = 1 << 2;
64        const Expand = 1 << 3;
65        const ReplaceSelectedText = 1 << 4;
66        const SetValue = 1 << 5;
67    }
68}
69
70/// Find accessible descendents of `root_item`.
71///
72/// This will recurse through all children of `root_item`, but will not recurse
73/// into nodes that are accessible.
74pub fn accessible_descendents(root_item: &ItemRc) -> impl Iterator<Item = ItemRc> {
75    fn try_candidate_or_find_next_accessible_descendent(
76        candidate: ItemRc,
77        descendent_candidates: &mut Vec<ItemRc>,
78    ) -> Option<ItemRc> {
79        if candidate.is_accessible() {
80            return Some(candidate);
81        }
82
83        candidate.first_child().and_then(|child| {
84            if let Some(next) = child.next_sibling() {
85                descendent_candidates.push(next);
86            }
87            try_candidate_or_find_next_accessible_descendent(child, descendent_candidates)
88        })
89    }
90
91    // Do not look on the root_item: That is either a component root or an
92    // accessible item already handled!
93    let mut descendent_candidates = Vec::new();
94    if let Some(child) = root_item.first_child() {
95        descendent_candidates.push(child);
96    }
97
98    core::iter::from_fn(move || {
99        loop {
100            let candidate = descendent_candidates.pop()?;
101
102            if let Some(next_candidate) = candidate.next_sibling() {
103                descendent_candidates.push(next_candidate);
104            }
105
106            if let Some(descendent) = try_candidate_or_find_next_accessible_descendent(
107                candidate,
108                &mut descendent_candidates,
109            ) {
110                return Some(descendent);
111            }
112        }
113    })
114}
115
116/// Find the first built-in `TextInput` in the descendents of `item`.
117pub fn find_text_input(item: &ItemRc) -> Option<VRcMapped<ItemTreeVTable, TextInput>> {
118    fn try_candidate_or_find_next_descendent(
119        candidate: ItemRc,
120        descendent_candidates: &mut Vec<ItemRc>,
121    ) -> Option<VRcMapped<ItemTreeVTable, TextInput>> {
122        if let Some(input) = candidate.downcast::<TextInput>() {
123            return Some(input);
124        }
125
126        candidate.first_child().and_then(|child| {
127            if let Some(next) = child.next_sibling() {
128                descendent_candidates.push(next);
129            }
130            try_candidate_or_find_next_descendent(child, descendent_candidates)
131        })
132    }
133
134    let mut descendent_candidates = vec![item.clone()];
135
136    loop {
137        let candidate = descendent_candidates.pop()?;
138
139        if let Some(next_candidate) = candidate.next_sibling() {
140            descendent_candidates.push(next_candidate);
141        }
142
143        if let Some(input) =
144            try_candidate_or_find_next_descendent(candidate, &mut descendent_candidates)
145        {
146            return Some(input);
147        }
148    }
149}