densa_ui/access.rs
1//! Accessibility: what each widget is to assistive technology such as screen readers, and the
2//! requests it makes of them (press this, type that, scroll there).
3//!
4//! A window layer turns an [`AccessTree`] into the platform's accessibility tree and hands
5//! the requests it receives back with [`Ui::access_action`](crate::Ui::access_action).
6//! Nothing here depends on a platform API.
7
8use densa_core::Rect;
9
10/// What a widget is to assistive technology.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12#[non_exhaustive]
13pub enum Role {
14 /// A container with no meaning of its own (the default).
15 #[default]
16 Group,
17 /// The window: the tree's root.
18 Window,
19 /// Static text.
20 Label,
21 /// Something pressed to act.
22 Button,
23 /// Editable text.
24 TextInput,
25 /// A region whose content scrolls.
26 ScrollView,
27 /// A divider that resizes the panes either side of it.
28 Splitter,
29 /// A row of tabs.
30 TabList,
31 /// A tab, which shows its panel when chosen.
32 Tab,
33 /// The content a tab shows.
34 TabPanel,
35 /// Rows of cells under column headers.
36 Table,
37 /// A row of a table.
38 Row,
39 /// A column's title in a table.
40 ColumnHeader,
41 /// A cell of a table row.
42 Cell,
43 /// A picture of data, such as a chart: read by its label.
44 Image,
45 /// A row of menu titles.
46 MenuBar,
47 /// A list of commands that pops up.
48 Menu,
49 /// A command in a menu.
50 MenuItem,
51 /// A menu command that is on or off (see [`AccessNode::set_checked`]).
52 MenuItemCheckBox,
53}
54
55/// Something assistive technology can ask a widget to do.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57#[non_exhaustive]
58pub enum AccessAction {
59 /// The equivalent of a click.
60 Click,
61 /// Take keyboard focus.
62 Focus,
63 /// Replace the text (the request carries it).
64 SetValue,
65 /// Scroll the content up.
66 ScrollUp,
67 /// Scroll the content down.
68 ScrollDown,
69 /// Scroll the content left.
70 ScrollLeft,
71 /// Scroll the content right.
72 ScrollRight,
73 /// Scroll whatever contains the widget so that it is in view.
74 ScrollIntoView,
75}
76
77impl AccessAction {
78 const ALL: [Self; 8] = [
79 Self::Click,
80 Self::Focus,
81 Self::SetValue,
82 Self::ScrollUp,
83 Self::ScrollDown,
84 Self::ScrollLeft,
85 Self::ScrollRight,
86 Self::ScrollIntoView,
87 ];
88
89 fn bit(self) -> u16 {
90 1 << (self as u16)
91 }
92}
93
94/// A request from assistive technology, as a widget receives it in
95/// [`Widget::access_action`](crate::Widget::access_action).
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97#[non_exhaustive]
98pub struct AccessRequest<'a> {
99 /// What to do.
100 pub action: AccessAction,
101 /// The part of the widget it is for (see [`AccessNode::push_part`]), or the widget itself.
102 pub part: Option<u32>,
103 /// The text, for [`AccessAction::SetValue`].
104 pub value: Option<&'a str>,
105}
106
107/// A node of the accessibility tree: a widget, or a part of one (such as a dock's tab).
108///
109/// Bits a node does not support are not asked of it: [`AccessNode::add_action`] says which
110/// requests it handles.
111#[derive(Debug, Clone, PartialEq, Default)]
112pub struct AccessNode {
113 role: Role,
114 label: Option<String>,
115 value: Option<String>,
116 bounds: Rect,
117 actions: u16,
118 selected: Option<bool>,
119 checked: Option<bool>,
120 expanded: Option<bool>,
121 disabled: bool,
122 clips: bool,
123 placeholder: Option<String>,
124 focused_part: Option<u32>,
125 parts: Vec<(u32, AccessNode)>,
126 children: Vec<AccessId>,
127}
128
129impl AccessNode {
130 /// A node with `role`.
131 pub fn new(role: Role) -> Self {
132 Self {
133 role,
134 ..Self::default()
135 }
136 }
137
138 /// Sets what the node is.
139 pub fn set_role(&mut self, role: Role) {
140 self.role = role;
141 }
142
143 /// Sets its name, such as a button's text. Static text goes in
144 /// [`set_value`](Self::set_value).
145 pub fn set_label(&mut self, label: impl Into<String>) {
146 self.label = Some(label.into());
147 }
148
149 /// Sets its text content: a label's text, a text input's text.
150 pub fn set_value(&mut self, value: impl Into<String>) {
151 self.value = Some(value.into());
152 }
153
154 /// Sets where it is, in physical pixels from the window's top-left corner (a widget's
155 /// node starts with the widget's rectangle).
156 pub fn set_bounds(&mut self, bounds: Rect) {
157 self.bounds = bounds;
158 }
159
160 /// Says it handles `action`.
161 pub fn add_action(&mut self, action: AccessAction) {
162 self.actions |= action.bit();
163 }
164
165 /// Sets whether it is the chosen one of its kind, such as the shown tab.
166 pub fn set_selected(&mut self, selected: bool) {
167 self.selected = Some(selected);
168 }
169
170 /// Sets whether it is on, for something that is on or off (a check menu item).
171 pub fn set_checked(&mut self, checked: bool) {
172 self.checked = Some(checked);
173 }
174
175 /// Sets whether what it opens is open, for something that opens more (a menu title, an
176 /// item with a submenu).
177 pub fn set_expanded(&mut self, expanded: bool) {
178 self.expanded = Some(expanded);
179 }
180
181 /// Whether it is on, if it is something that is on or off.
182 pub fn checked(&self) -> Option<bool> {
183 self.checked
184 }
185
186 /// Whether what it opens is open, if it opens something.
187 pub fn expanded(&self) -> Option<bool> {
188 self.expanded
189 }
190
191 /// Sets whether it is unavailable.
192 pub fn set_disabled(&mut self, disabled: bool) {
193 self.disabled = disabled;
194 }
195
196 /// Says which of its parts has keyboard focus while the widget does (such as a dock's
197 /// chosen tab), so assistive technology reads that part rather than the whole widget.
198 pub fn set_focused_part(&mut self, part: u32) {
199 self.focused_part = Some(part);
200 }
201
202 /// The part with keyboard focus while the widget has it.
203 pub fn focused_part(&self) -> Option<u32> {
204 self.focused_part
205 }
206
207 /// Adds `node` as a part of this one, numbered `part`: a piece that is not a widget of its
208 /// own, such as a tab of a dock. Requests for it come to the widget with that number.
209 /// Parts come before the widget's children. Numbers must be unique within the widget, at
210 /// any depth, and at most [`MAX_PART`]; a part breaking either rule is left out. Keep a
211 /// part's number while it lasts (number by identity, not by position), or a screen reader
212 /// tracking it follows another one.
213 pub fn push_part(&mut self, part: u32, node: AccessNode) {
214 self.parts.push((part, node));
215 }
216
217 /// What the node is.
218 pub fn role(&self) -> Role {
219 self.role
220 }
221
222 /// Its name.
223 pub fn label(&self) -> Option<&str> {
224 self.label.as_deref()
225 }
226
227 /// Its text content.
228 pub fn value(&self) -> Option<&str> {
229 self.value.as_deref()
230 }
231
232 /// Where it is, in physical pixels.
233 pub fn bounds(&self) -> Rect {
234 self.bounds
235 }
236
237 /// Whether it handles `action`.
238 pub fn has_action(&self, action: AccessAction) -> bool {
239 self.actions & action.bit() != 0
240 }
241
242 /// The actions it handles.
243 pub fn actions(&self) -> impl Iterator<Item = AccessAction> + '_ {
244 AccessAction::ALL
245 .into_iter()
246 .filter(|a| self.has_action(*a))
247 }
248
249 /// Whether it is the chosen one of its kind, if that applies.
250 pub fn selected(&self) -> Option<bool> {
251 self.selected
252 }
253
254 /// Whether it is unavailable.
255 pub fn is_disabled(&self) -> bool {
256 self.disabled
257 }
258
259 /// Whether it cuts off its children where they leave its bounds (it scrolls or clips),
260 /// so children outside it are off screen.
261 pub fn clips_children(&self) -> bool {
262 self.clips
263 }
264
265 /// Sets whether it cuts off its children where they leave its bounds (a widget's node
266 /// starts with this set when its style clips or scrolls).
267 pub fn set_clips_children(&mut self, clips: bool) {
268 self.clips = clips;
269 }
270
271 /// Sets the hint shown while it is empty, such as a text input's placeholder.
272 pub fn set_placeholder(&mut self, placeholder: impl Into<String>) {
273 self.placeholder = Some(placeholder.into());
274 }
275
276 /// The hint shown while it is empty.
277 pub fn placeholder(&self) -> Option<&str> {
278 self.placeholder.as_deref()
279 }
280
281 /// Its children in the tree, in order (filled in by the [`Ui`](crate::Ui)).
282 pub fn children(&self) -> &[AccessId] {
283 &self.children
284 }
285
286 pub(crate) fn take_parts(&mut self) -> Vec<(u32, AccessNode)> {
287 std::mem::take(&mut self.parts)
288 }
289
290 pub(crate) fn push_child(&mut self, child: AccessId) {
291 self.children.push(child);
292 }
293}
294
295/// The highest part number a widget can use (see [`AccessNode::push_part`]).
296pub const MAX_PART: u32 = (1 << PART_BITS) - 2;
297
298/// Bits of an [`AccessId`] numbering a widget's parts; the rest number widgets.
299const PART_BITS: u32 = 24;
300
301/// Identifies a node of the accessibility tree: a widget, or a part of one. A widget's id is
302/// never given to another widget, even one in its slot after it is removed, so a request for
303/// a removed widget reaches nothing. Get one from [`Ui::access_id`](crate::Ui::access_id).
304#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
305pub struct AccessId(u64);
306
307impl AccessId {
308 /// The node of the widget numbered `serial`.
309 pub(crate) fn widget(serial: u64) -> Self {
310 Self(serial << PART_BITS)
311 }
312
313 /// The node of part `part` of this widget's node (see [`AccessNode::push_part`]); parts
314 /// past [`MAX_PART`] have no node, and get the widget's.
315 #[must_use]
316 pub fn part(self, part: u32) -> Self {
317 if part > MAX_PART {
318 return self;
319 }
320 // Offset by one: part 0 differs from the widget itself.
321 Self(self.0 & !Self::PART_MASK | u64::from(part + 1))
322 }
323
324 const PART_MASK: u64 = (1 << PART_BITS) - 1;
325
326 /// The id as a number, for platform APIs.
327 pub fn to_raw(self) -> u64 {
328 self.0
329 }
330
331 /// The id a [`to_raw`](Self::to_raw) number came from.
332 pub fn from_raw(raw: u64) -> Self {
333 Self(raw)
334 }
335
336 /// The widget's serial and the part (`None` for the widget itself).
337 pub(crate) fn split(self) -> (u64, Option<u32>) {
338 let part = (self.0 & Self::PART_MASK) as u32;
339 (self.0 >> PART_BITS, part.checked_sub(1))
340 }
341}
342
343/// The accessibility tree of a [`Ui`](crate::Ui): every shown widget (and the parts widgets
344/// add), parents before children.
345#[derive(Debug, Clone, PartialEq)]
346pub struct AccessTree {
347 pub(crate) nodes: Vec<(AccessId, AccessNode)>,
348 pub(crate) root: AccessId,
349 pub(crate) focus: AccessId,
350}
351
352impl AccessTree {
353 /// Every node and its id.
354 pub fn nodes(&self) -> &[(AccessId, AccessNode)] {
355 &self.nodes
356 }
357
358 /// The root's id: the [`Ui`](crate::Ui)'s root widget.
359 pub fn root(&self) -> AccessId {
360 self.root
361 }
362
363 /// The node with keyboard focus (the root if no widget has it).
364 pub fn focus(&self) -> AccessId {
365 self.focus
366 }
367
368 /// The node `id`.
369 pub fn node(&self, id: AccessId) -> Option<&AccessNode> {
370 self.nodes.iter().find(|(i, _)| *i == id).map(|(_, n)| n)
371 }
372}