1mod tree_builder;
15
16use accesskit::{Action, NodeId as AccessKitNodeId, Role, Toggled};
17
18use crate::Px;
19
20#[cfg(test)]
21pub(crate) use tree_builder::build_tree_update;
22pub(crate) use tree_builder::{AccessibilityIndex, dispatch_action};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29pub struct AccessibilityId(pub u64);
30
31impl AccessibilityId {
32 pub fn new(id: u64) -> Self {
34 Self(id)
35 }
36
37 pub fn to_accesskit_id(self) -> AccessKitNodeId {
39 AccessKitNodeId(self.0)
40 }
41
42 pub fn from_accesskit_id(id: AccessKitNodeId) -> Self {
44 Self(id.0)
45 }
46
47 pub fn from_component_node_id(node_id: indextree::NodeId) -> Self {
52 let index: usize = node_id.into();
54 Self(index as u64)
55 }
56}
57
58#[derive(Debug, Clone, Copy)]
60pub struct AccessibilityPadding {
61 pub left: Px,
63 pub top: Px,
65 pub right: Px,
67 pub bottom: Px,
69}
70
71impl AccessibilityPadding {
72 pub const fn zero() -> Self {
74 Self {
75 left: Px::ZERO,
76 top: Px::ZERO,
77 right: Px::ZERO,
78 bottom: Px::ZERO,
79 }
80 }
81}
82
83#[derive(Debug, Clone)]
88pub struct AccessibilityNode {
89 pub role: Option<Role>,
91 pub label: Option<String>,
93 pub description: Option<String>,
95 pub value: Option<String>,
97 pub numeric_value: Option<f64>,
99 pub min_numeric_value: Option<f64>,
101 pub max_numeric_value: Option<f64>,
103 pub orientation: Option<accesskit::Orientation>,
105 pub focusable: bool,
107 pub focused: bool,
109 pub toggled: Option<Toggled>,
111 pub invalid: Option<accesskit::Invalid>,
113 pub selected: Option<bool>,
115 pub expanded: Option<bool>,
117 pub has_popup: Option<accesskit::HasPopup>,
119 pub disabled: bool,
121 pub hidden: bool,
123 pub modal: bool,
125 pub actions: Vec<Action>,
127 pub key: Option<String>,
129 pub merge_descendants: bool,
132 pub bounds_padding: Option<AccessibilityPadding>,
135 pub state_description: Option<String>,
137 pub role_description: Option<String>,
139 pub tooltip: Option<String>,
142 pub live: Option<accesskit::Live>,
144 pub heading_level: Option<u32>,
147 pub scroll_x: Option<(f64, f64, f64)>,
149 pub scroll_y: Option<(f64, f64, f64)>,
151 pub numeric_value_step: Option<f64>,
153 pub numeric_value_jump: Option<f64>,
155 pub collection_info: Option<(usize, usize, bool)>,
157 pub collection_item_info: Option<(usize, usize, usize, usize, bool)>,
160 pub is_editable_text: bool,
162}
163
164impl AccessibilityNode {
165 pub fn new() -> Self {
167 Self::default()
168 }
169
170 pub fn with_role(mut self, role: Role) -> Self {
172 self.role = Some(role);
173 self
174 }
175
176 pub fn with_label(mut self, label: impl Into<String>) -> Self {
178 self.label = Some(label.into());
179 self
180 }
181
182 pub fn with_description(mut self, description: impl Into<String>) -> Self {
184 self.description = Some(description.into());
185 self
186 }
187
188 pub fn with_value(mut self, value: impl Into<String>) -> Self {
190 self.value = Some(value.into());
191 self
192 }
193
194 pub fn with_numeric_value(mut self, value: f64) -> Self {
196 self.numeric_value = Some(value);
197 self
198 }
199
200 pub fn with_numeric_range(mut self, min: f64, max: f64) -> Self {
202 self.min_numeric_value = Some(min);
203 self.max_numeric_value = Some(max);
204 self
205 }
206
207 pub fn focusable(mut self) -> Self {
209 self.focusable = true;
210 self
211 }
212
213 pub fn focused(mut self) -> Self {
215 self.focused = true;
216 self
217 }
218
219 pub fn with_toggled(mut self, toggled: Toggled) -> Self {
221 self.toggled = Some(toggled);
222 self
223 }
224
225 pub fn disabled(mut self) -> Self {
227 self.disabled = true;
228 self
229 }
230
231 pub fn hidden(mut self) -> Self {
233 self.hidden = true;
234 self
235 }
236
237 pub fn with_action(mut self, action: Action) -> Self {
239 self.actions.push(action);
240 self
241 }
242
243 pub fn with_actions(mut self, actions: impl IntoIterator<Item = Action>) -> Self {
245 self.actions.extend(actions);
246 self
247 }
248
249 pub fn with_key(mut self, key: impl Into<String>) -> Self {
251 self.key = Some(key.into());
252 self
253 }
254}
255
256impl Default for AccessibilityNode {
257 fn default() -> Self {
258 Self {
259 role: None,
260 label: None,
261 description: None,
262 value: None,
263 numeric_value: None,
264 min_numeric_value: None,
265 max_numeric_value: None,
266 orientation: None,
267 focusable: false,
268 focused: false,
269 toggled: None,
270 invalid: None,
271 selected: None,
272 expanded: None,
273 has_popup: None,
274 disabled: false,
275 hidden: false,
276 modal: false,
277 actions: Vec::new(),
278 key: None,
279 merge_descendants: true,
280 bounds_padding: None,
281 state_description: None,
282 role_description: None,
283 tooltip: None,
284 live: None,
285 heading_level: None,
286 scroll_x: None,
287 scroll_y: None,
288 numeric_value_step: None,
289 numeric_value_jump: None,
290 collection_info: None,
291 collection_item_info: None,
292 is_editable_text: false,
293 }
294 }
295}
296
297pub(crate) enum StoredSemantics {
298 Description(Box<SemanticDescription>),
299 Extended(Box<AccessibilityNode>),
300}
301
302pub(crate) struct SemanticDescription {
303 role: Option<Role>,
304 label: Option<String>,
305 description: Option<String>,
306 key: Option<String>,
307 merge_descendants: bool,
308 tooltip: Option<String>,
309}
310
311impl StoredSemantics {
312 pub(crate) fn write(storage: &mut Option<Self>, node: AccessibilityNode) {
313 match node {
314 AccessibilityNode {
315 role,
316 label,
317 description,
318 key,
319 merge_descendants,
320 tooltip,
321 value: None,
322 numeric_value: None,
323 min_numeric_value: None,
324 max_numeric_value: None,
325 orientation: None,
326 focusable: false,
327 focused: false,
328 toggled: None,
329 invalid: None,
330 selected: None,
331 expanded: None,
332 has_popup: None,
333 disabled: false,
334 hidden: false,
335 modal: false,
336 actions,
337 bounds_padding: None,
338 state_description: None,
339 role_description: None,
340 live: None,
341 heading_level: None,
342 scroll_x: None,
343 scroll_y: None,
344 numeric_value_step: None,
345 numeric_value_jump: None,
346 collection_info: None,
347 collection_item_info: None,
348 is_editable_text: false,
349 } if actions.is_empty() => {
350 let description = SemanticDescription {
351 role,
352 label,
353 description,
354 key,
355 merge_descendants,
356 tooltip,
357 };
358 match storage {
359 Some(Self::Description(previous)) => **previous = description,
360 _ => *storage = Some(Self::Description(Box::new(description))),
361 }
362 }
363 node => match storage {
364 Some(Self::Extended(previous)) => **previous = node,
365 _ => *storage = Some(Self::Extended(Box::new(node))),
366 },
367 }
368 }
369
370 pub(crate) fn descendant_context(&self) -> (bool, Option<&str>) {
371 match self {
372 Self::Description(node) => (node.merge_descendants, node.tooltip.as_deref()),
373 Self::Extended(node) => (node.merge_descendants, node.tooltip.as_deref()),
374 }
375 }
376
377 pub(crate) fn extended(&self) -> Option<&AccessibilityNode> {
378 match self {
379 Self::Description(_) => None,
380 Self::Extended(node) => Some(node),
381 }
382 }
383
384 pub(crate) fn snapshot(&self) -> AccessibilityNode {
385 match self {
386 Self::Description(node) => AccessibilityNode {
387 role: node.role,
388 label: node.label.clone(),
389 description: node.description.clone(),
390 key: node.key.clone(),
391 merge_descendants: node.merge_descendants,
392 tooltip: node.tooltip.clone(),
393 ..AccessibilityNode::default()
394 },
395 Self::Extended(node) => node.as_ref().clone(),
396 }
397 }
398}
399
400pub type AccessibilityActionHandler = Box<dyn Fn(Action) + Send + Sync>;