1// Copyright 2021 The AccessKit Authors. All rights reserved.
2// Licensed under the Apache License, Version 2.0 (found in
3// the LICENSE-APACHE file) or the MIT license (found in
4// the LICENSE-MIT file), at your option.
5
6// Derived from Chromium's accessibility abstraction.
7// Copyright 2018 The Chromium Authors. All rights reserved.
8// Use of this source code is governed by a BSD-style license that can be
9// found in the LICENSE.chromium file.
10
11#![cfg_attr(not(any(feature = "pyo3", feature = "schemars")), no_std)]
12
13extern crate alloc;
14
15use alloc::{boxed::Box, string::String, vec::Vec};
16use core::fmt;
17#[cfg(feature = "pyo3")]
18use pyo3::pyclass;
19#[cfg(feature = "schemars")]
20use schemars::{
21 gen::SchemaGenerator,
22 schema::{InstanceType, ObjectValidation, Schema, SchemaObject},
23 JsonSchema, Map as SchemaMap,
24};
25#[cfg(feature = "serde")]
26use serde::{
27 de::{Deserializer, IgnoredAny, MapAccess, Visitor},
28 ser::{SerializeMap, Serializer},
29 Deserialize, Serialize,
30};
31
32mod geometry;
33pub use geometry::{Affine, Point, Rect, Size, Vec2};
34
35/// The type of an accessibility node.
36///
37/// The majority of these roles come from the ARIA specification. Reference
38/// the latest draft for proper usage.
39///
40/// Like the AccessKit schema as a whole, this list is largely taken
41/// from Chromium. However, unlike Chromium's alphabetized list, this list
42/// is ordered roughly by expected usage frequency (with the notable exception
43/// of [`Role::Unknown`]). This is more efficient in serialization formats
44/// where integers use a variable-length encoding.
45#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
46#[cfg_attr(feature = "enumn", derive(enumn::N))]
47#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
48#[cfg_attr(feature = "schemars", derive(JsonSchema))]
49#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
50#[cfg_attr(
51 feature = "pyo3",
52 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
53)]
54#[repr(u8)]
55pub enum Role {
56 #[default]
57 Unknown,
58 TextRun,
59 Cell,
60 Label,
61 Image,
62 Link,
63 Row,
64 ListItem,
65
66 /// Contains the bullet, number, or other marker for a list item.
67 ListMarker,
68
69 TreeItem,
70 ListBoxOption,
71 MenuItem,
72 MenuListOption,
73 Paragraph,
74
75 /// A generic container that should be ignored by assistive technologies
76 /// and filtered out of platform accessibility trees. Equivalent to the ARIA
77 /// `none` or `presentation` role, or to an HTML `div` with no role.
78 GenericContainer,
79
80 CheckBox,
81 RadioButton,
82 TextInput,
83 Button,
84 DefaultButton,
85 Pane,
86 RowHeader,
87 ColumnHeader,
88 RowGroup,
89 List,
90 Table,
91 LayoutTableCell,
92 LayoutTableRow,
93 LayoutTable,
94 Switch,
95 Menu,
96
97 MultilineTextInput,
98 SearchInput,
99 DateInput,
100 DateTimeInput,
101 WeekInput,
102 MonthInput,
103 TimeInput,
104 EmailInput,
105 NumberInput,
106 PasswordInput,
107 PhoneNumberInput,
108 UrlInput,
109
110 Abbr,
111 Alert,
112 AlertDialog,
113 Application,
114 Article,
115 Audio,
116 Banner,
117 Blockquote,
118 Canvas,
119 Caption,
120 Caret,
121 Code,
122 ColorWell,
123 ComboBox,
124 EditableComboBox,
125 Complementary,
126 Comment,
127 ContentDeletion,
128 ContentInsertion,
129 ContentInfo,
130 Definition,
131 DescriptionList,
132 DescriptionListDetail,
133 DescriptionListTerm,
134 Details,
135 Dialog,
136 Directory,
137 DisclosureTriangle,
138 Document,
139 EmbeddedObject,
140 Emphasis,
141 Feed,
142 FigureCaption,
143 Figure,
144 Footer,
145 FooterAsNonLandmark,
146 Form,
147 Grid,
148 Group,
149 Header,
150 HeaderAsNonLandmark,
151 Heading,
152 Iframe,
153 IframePresentational,
154 ImeCandidate,
155 Keyboard,
156 Legend,
157 LineBreak,
158 ListBox,
159 Log,
160 Main,
161 Mark,
162 Marquee,
163 Math,
164 MenuBar,
165 MenuItemCheckBox,
166 MenuItemRadio,
167 MenuListPopup,
168 Meter,
169 Navigation,
170 Note,
171 PluginObject,
172 Portal,
173 Pre,
174 ProgressIndicator,
175 RadioGroup,
176 Region,
177 RootWebArea,
178 Ruby,
179 RubyAnnotation,
180 ScrollBar,
181 ScrollView,
182 Search,
183 Section,
184 Slider,
185 SpinButton,
186 Splitter,
187 Status,
188 Strong,
189 Suggestion,
190 SvgRoot,
191 Tab,
192 TabList,
193 TabPanel,
194 Term,
195 Time,
196 Timer,
197 TitleBar,
198 Toolbar,
199 Tooltip,
200 Tree,
201 TreeGrid,
202 Video,
203 WebView,
204 Window,
205
206 PdfActionableHighlight,
207 PdfRoot,
208
209 // ARIA Graphics module roles:
210 // https://rawgit.com/w3c/graphics-aam/master/#mapping_role_table
211 GraphicsDocument,
212 GraphicsObject,
213 GraphicsSymbol,
214
215 // DPub Roles:
216 // https://www.w3.org/TR/dpub-aam-1.0/#mapping_role_table
217 DocAbstract,
218 DocAcknowledgements,
219 DocAfterword,
220 DocAppendix,
221 DocBackLink,
222 DocBiblioEntry,
223 DocBibliography,
224 DocBiblioRef,
225 DocChapter,
226 DocColophon,
227 DocConclusion,
228 DocCover,
229 DocCredit,
230 DocCredits,
231 DocDedication,
232 DocEndnote,
233 DocEndnotes,
234 DocEpigraph,
235 DocEpilogue,
236 DocErrata,
237 DocExample,
238 DocFootnote,
239 DocForeword,
240 DocGlossary,
241 DocGlossRef,
242 DocIndex,
243 DocIntroduction,
244 DocNoteRef,
245 DocNotice,
246 DocPageBreak,
247 DocPageFooter,
248 DocPageHeader,
249 DocPageList,
250 DocPart,
251 DocPreface,
252 DocPrologue,
253 DocPullquote,
254 DocQna,
255 DocSubtitle,
256 DocTip,
257 DocToc,
258
259 /// Behaves similar to an ARIA grid but is primarily used by Chromium's
260 /// `TableView` and its subclasses, so they can be exposed correctly
261 /// on certain platforms.
262 ListGrid,
263
264 /// This is just like a multi-line document, but signals that assistive
265 /// technologies should implement behavior specific to a VT-100-style
266 /// terminal.
267 Terminal,
268}
269
270/// An action to be taken on an accessibility node.
271#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
272#[cfg_attr(feature = "enumn", derive(enumn::N))]
273#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
274#[cfg_attr(feature = "schemars", derive(JsonSchema))]
275#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
276#[cfg_attr(
277 feature = "pyo3",
278 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
279)]
280#[repr(u8)]
281pub enum Action {
282 /// Do the equivalent of a single click or tap.
283 Click,
284
285 Focus,
286 Blur,
287
288 Collapse,
289 Expand,
290
291 /// Requires [`ActionRequest::data`] to be set to [`ActionData::CustomAction`].
292 CustomAction,
293
294 /// Decrement a numeric value by one step.
295 Decrement,
296 /// Increment a numeric value by one step.
297 Increment,
298
299 HideTooltip,
300 ShowTooltip,
301
302 /// Delete any selected text in the control's text value and
303 /// insert the specified value in its place, like when typing or pasting.
304 /// Requires [`ActionRequest::data`] to be set to [`ActionData::Value`].
305 ReplaceSelectedText,
306
307 // Scrolls by approximately one screen in a specific direction.
308 // TBD: Do we need a doc comment on each of the values below?
309 // Or does this awkwardness suggest a refactor?
310 ScrollBackward,
311 ScrollDown,
312 ScrollForward,
313 ScrollLeft,
314 ScrollRight,
315 ScrollUp,
316
317 /// Scroll any scrollable containers to make the target object visible
318 /// on the screen. Optionally set [`ActionRequest::data`] to
319 /// [`ActionData::ScrollTargetRect`].
320 ScrollIntoView,
321
322 /// Scroll the given object to a specified point in the tree's container
323 /// (e.g. window). Requires [`ActionRequest::data`] to be set to
324 /// [`ActionData::ScrollToPoint`].
325 ScrollToPoint,
326
327 /// Requires [`ActionRequest::data`] to be set to [`ActionData::SetScrollOffset`].
328 SetScrollOffset,
329
330 /// Requires [`ActionRequest::data`] to be set to [`ActionData::SetTextSelection`].
331 SetTextSelection,
332
333 /// Don't focus this node, but set it as the sequential focus navigation
334 /// starting point, so that pressing Tab moves to the next element
335 /// following this one, for example.
336 SetSequentialFocusNavigationStartingPoint,
337
338 /// Replace the value of the control with the specified value and
339 /// reset the selection, if applicable. Requires [`ActionRequest::data`]
340 /// to be set to [`ActionData::Value`] or [`ActionData::NumericValue`].
341 SetValue,
342
343 ShowContextMenu,
344}
345
346impl Action {
347 fn mask(self) -> u32 {
348 1 << (self as u8)
349 }
350
351 #[cfg(not(feature = "enumn"))]
352 fn n(value: u8) -> Option<Self> {
353 // Manually implement something similar to the enumn crate. We don't
354 // want to bring this crate by default though and we can't use a
355 // macro as it would break C bindings header file generation.
356 match value {
357 0 => Some(Action::Click),
358 1 => Some(Action::Focus),
359 2 => Some(Action::Blur),
360 3 => Some(Action::Collapse),
361 4 => Some(Action::Expand),
362 5 => Some(Action::CustomAction),
363 6 => Some(Action::Decrement),
364 7 => Some(Action::Increment),
365 8 => Some(Action::HideTooltip),
366 9 => Some(Action::ShowTooltip),
367 10 => Some(Action::ReplaceSelectedText),
368 11 => Some(Action::ScrollBackward),
369 12 => Some(Action::ScrollDown),
370 13 => Some(Action::ScrollForward),
371 14 => Some(Action::ScrollLeft),
372 15 => Some(Action::ScrollRight),
373 16 => Some(Action::ScrollUp),
374 17 => Some(Action::ScrollIntoView),
375 18 => Some(Action::ScrollToPoint),
376 19 => Some(Action::SetScrollOffset),
377 20 => Some(Action::SetTextSelection),
378 21 => Some(Action::SetSequentialFocusNavigationStartingPoint),
379 22 => Some(Action::SetValue),
380 23 => Some(Action::ShowContextMenu),
381 _ => None,
382 }
383 }
384}
385
386fn action_mask_to_action_vec(mask: u32) -> Vec<Action> {
387 let mut actions: Vec = Vec::new();
388 let mut i: u8 = 0;
389 while let Some(variant: Action) = Action::n(i) {
390 if mask & variant.mask() != 0 {
391 actions.push(variant);
392 }
393 i += 1;
394 }
395 actions
396}
397
398#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
399#[cfg_attr(feature = "enumn", derive(enumn::N))]
400#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
401#[cfg_attr(feature = "schemars", derive(JsonSchema))]
402#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
403#[cfg_attr(
404 feature = "pyo3",
405 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
406)]
407#[repr(u8)]
408pub enum Orientation {
409 /// E.g. most toolbars and separators.
410 Horizontal,
411 /// E.g. menu or combo box.
412 Vertical,
413}
414
415#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
416#[cfg_attr(feature = "enumn", derive(enumn::N))]
417#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
418#[cfg_attr(feature = "schemars", derive(JsonSchema))]
419#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
420#[cfg_attr(
421 feature = "pyo3",
422 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
423)]
424#[repr(u8)]
425pub enum TextDirection {
426 LeftToRight,
427 RightToLeft,
428 TopToBottom,
429 BottomToTop,
430}
431
432/// Indicates if a form control has invalid input or if a web DOM element has an
433/// [`aria-invalid`] attribute.
434///
435/// [`aria-invalid`]: https://www.w3.org/TR/wai-aria-1.1/#aria-invalid
436#[derive(Clone, Copy, Debug, PartialEq, Eq)]
437#[cfg_attr(feature = "enumn", derive(enumn::N))]
438#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
439#[cfg_attr(feature = "schemars", derive(JsonSchema))]
440#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
441#[cfg_attr(
442 feature = "pyo3",
443 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
444)]
445#[repr(u8)]
446pub enum Invalid {
447 True,
448 Grammar,
449 Spelling,
450}
451
452#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
453#[cfg_attr(feature = "enumn", derive(enumn::N))]
454#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
455#[cfg_attr(feature = "schemars", derive(JsonSchema))]
456#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
457#[cfg_attr(
458 feature = "pyo3",
459 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
460)]
461#[repr(u8)]
462pub enum Toggled {
463 False,
464 True,
465 Mixed,
466}
467
468#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
469#[cfg_attr(feature = "enumn", derive(enumn::N))]
470#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
471#[cfg_attr(feature = "schemars", derive(JsonSchema))]
472#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
473#[cfg_attr(
474 feature = "pyo3",
475 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
476)]
477#[repr(u8)]
478pub enum SortDirection {
479 Ascending,
480 Descending,
481 Other,
482}
483
484#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
485#[cfg_attr(feature = "enumn", derive(enumn::N))]
486#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
487#[cfg_attr(feature = "schemars", derive(JsonSchema))]
488#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
489#[cfg_attr(
490 feature = "pyo3",
491 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
492)]
493#[repr(u8)]
494pub enum AriaCurrent {
495 False,
496 True,
497 Page,
498 Step,
499 Location,
500 Date,
501 Time,
502}
503
504#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
505#[cfg_attr(feature = "enumn", derive(enumn::N))]
506#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
507#[cfg_attr(feature = "schemars", derive(JsonSchema))]
508#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
509#[cfg_attr(
510 feature = "pyo3",
511 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
512)]
513#[repr(u8)]
514pub enum AutoComplete {
515 Inline,
516 List,
517 Both,
518}
519
520#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
521#[cfg_attr(feature = "enumn", derive(enumn::N))]
522#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
523#[cfg_attr(feature = "schemars", derive(JsonSchema))]
524#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
525#[cfg_attr(
526 feature = "pyo3",
527 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
528)]
529#[repr(u8)]
530pub enum Live {
531 Off,
532 Polite,
533 Assertive,
534}
535
536#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
537#[cfg_attr(feature = "enumn", derive(enumn::N))]
538#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
539#[cfg_attr(feature = "schemars", derive(JsonSchema))]
540#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
541#[cfg_attr(
542 feature = "pyo3",
543 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
544)]
545#[repr(u8)]
546pub enum HasPopup {
547 True,
548 Menu,
549 Listbox,
550 Tree,
551 Grid,
552 Dialog,
553}
554
555#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
556#[cfg_attr(feature = "enumn", derive(enumn::N))]
557#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
558#[cfg_attr(feature = "schemars", derive(JsonSchema))]
559#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
560#[cfg_attr(
561 feature = "pyo3",
562 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
563)]
564#[repr(u8)]
565pub enum ListStyle {
566 Circle,
567 Disc,
568 Image,
569 Numeric,
570 Square,
571 /// Language specific ordering (alpha, roman, cjk-ideographic, etc...)
572 Other,
573}
574
575#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
576#[cfg_attr(feature = "enumn", derive(enumn::N))]
577#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
578#[cfg_attr(feature = "schemars", derive(JsonSchema))]
579#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
580#[cfg_attr(
581 feature = "pyo3",
582 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
583)]
584#[repr(u8)]
585pub enum TextAlign {
586 Left,
587 Right,
588 Center,
589 Justify,
590}
591
592#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
593#[cfg_attr(feature = "enumn", derive(enumn::N))]
594#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
595#[cfg_attr(feature = "schemars", derive(JsonSchema))]
596#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
597#[cfg_attr(
598 feature = "pyo3",
599 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
600)]
601#[repr(u8)]
602pub enum VerticalOffset {
603 Subscript,
604 Superscript,
605}
606
607#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
608#[cfg_attr(feature = "enumn", derive(enumn::N))]
609#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
610#[cfg_attr(feature = "schemars", derive(JsonSchema))]
611#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
612#[cfg_attr(
613 feature = "pyo3",
614 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
615)]
616#[repr(u8)]
617pub enum TextDecoration {
618 Solid,
619 Dotted,
620 Dashed,
621 Double,
622 Wavy,
623}
624
625pub type NodeIdContent = u64;
626
627/// The stable identity of a [`Node`], unique within the node's tree.
628#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
629#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
630#[cfg_attr(feature = "schemars", derive(JsonSchema))]
631#[repr(transparent)]
632pub struct NodeId(pub NodeIdContent);
633
634impl From<NodeIdContent> for NodeId {
635 #[inline]
636 fn from(inner: NodeIdContent) -> Self {
637 Self(inner)
638 }
639}
640
641impl From<NodeId> for NodeIdContent {
642 #[inline]
643 fn from(outer: NodeId) -> Self {
644 outer.0
645 }
646}
647
648/// Defines a custom action for a UI element.
649///
650/// For example, a list UI can allow a user to reorder items in the list by dragging the
651/// items.
652#[derive(Clone, Debug, PartialEq, Eq)]
653#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
654#[cfg_attr(feature = "schemars", derive(JsonSchema))]
655#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
656#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
657pub struct CustomAction {
658 pub id: i32,
659 pub description: Box<str>,
660}
661
662#[derive(Clone, Copy, Debug, PartialEq, Eq)]
663#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
664#[cfg_attr(feature = "schemars", derive(JsonSchema))]
665#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
666#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
667pub struct TextPosition {
668 /// The node's role must be [`Role::TextRun`].
669 pub node: NodeId,
670 /// The index of an item in [`Node::character_lengths`], or the length
671 /// of that slice if the position is at the end of the line.
672 pub character_index: usize,
673}
674
675#[derive(Clone, Copy, Debug, PartialEq, Eq)]
676#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
677#[cfg_attr(feature = "schemars", derive(JsonSchema))]
678#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
679#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
680pub struct TextSelection {
681 /// The position where the selection started, and which does not change
682 /// as the selection is expanded or contracted. If there is no selection
683 /// but only a caret, this must be equal to the value of [`TextSelection::focus`].
684 /// This is also known as a degenerate selection.
685 pub anchor: TextPosition,
686 /// The active end of the selection, which changes as the selection
687 /// is expanded or contracted, or the position of the caret if there is
688 /// no selection.
689 pub focus: TextPosition,
690}
691
692#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
693#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, enumn::N))]
694#[cfg_attr(feature = "schemars", derive(JsonSchema))]
695#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
696#[repr(u8)]
697enum Flag {
698 Hidden,
699 Linked,
700 Multiselectable,
701 Required,
702 Visited,
703 Busy,
704 LiveAtomic,
705 Modal,
706 TouchTransparent,
707 ReadOnly,
708 Disabled,
709 Bold,
710 Italic,
711 ClipsChildren,
712 IsLineBreakingObject,
713 IsPageBreakingObject,
714 IsSpellingError,
715 IsGrammarError,
716 IsSearchMatch,
717 IsSuggestion,
718}
719
720impl Flag {
721 fn mask(self) -> u32 {
722 1 << (self as u8)
723 }
724}
725
726// The following is based on the technique described here:
727// https://viruta.org/reducing-memory-consumption-in-librsvg-2.html
728
729#[derive(Clone, Debug, PartialEq)]
730enum PropertyValue {
731 None,
732 NodeIdVec(Vec<NodeId>),
733 NodeId(NodeId),
734 String(Box<str>),
735 F64(f64),
736 Usize(usize),
737 Color(u32),
738 TextDecoration(TextDecoration),
739 LengthSlice(Box<[u8]>),
740 CoordSlice(Box<[f32]>),
741 Bool(bool),
742 Invalid(Invalid),
743 Toggled(Toggled),
744 Live(Live),
745 TextDirection(TextDirection),
746 Orientation(Orientation),
747 SortDirection(SortDirection),
748 AriaCurrent(AriaCurrent),
749 AutoComplete(AutoComplete),
750 HasPopup(HasPopup),
751 ListStyle(ListStyle),
752 TextAlign(TextAlign),
753 VerticalOffset(VerticalOffset),
754 Affine(Box<Affine>),
755 Rect(Rect),
756 TextSelection(Box<TextSelection>),
757 CustomActionVec(Vec<CustomAction>),
758}
759
760#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
761#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, enumn::N))]
762#[cfg_attr(feature = "schemars", derive(JsonSchema))]
763#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
764#[repr(u8)]
765enum PropertyId {
766 // NodeIdVec
767 Children,
768 Controls,
769 Details,
770 DescribedBy,
771 FlowTo,
772 LabelledBy,
773 Owns,
774 RadioGroup,
775
776 // NodeId
777 ActiveDescendant,
778 ErrorMessage,
779 InPageLinkTarget,
780 MemberOf,
781 NextOnLine,
782 PreviousOnLine,
783 PopupFor,
784
785 // String
786 Label,
787 Description,
788 Value,
789 AccessKey,
790 AuthorId,
791 ClassName,
792 FontFamily,
793 HtmlTag,
794 InnerHtml,
795 KeyboardShortcut,
796 Language,
797 Placeholder,
798 RoleDescription,
799 StateDescription,
800 Tooltip,
801 Url,
802 RowIndexText,
803 ColumnIndexText,
804
805 // f64
806 ScrollX,
807 ScrollXMin,
808 ScrollXMax,
809 ScrollY,
810 ScrollYMin,
811 ScrollYMax,
812 NumericValue,
813 MinNumericValue,
814 MaxNumericValue,
815 NumericValueStep,
816 NumericValueJump,
817 FontSize,
818 FontWeight,
819
820 // usize
821 RowCount,
822 ColumnCount,
823 RowIndex,
824 ColumnIndex,
825 RowSpan,
826 ColumnSpan,
827 Level,
828 SizeOfSet,
829 PositionInSet,
830
831 // Color
832 ColorValue,
833 BackgroundColor,
834 ForegroundColor,
835
836 // TextDecoration
837 Overline,
838 Strikethrough,
839 Underline,
840
841 // LengthSlice
842 CharacterLengths,
843 WordLengths,
844
845 // CoordSlice
846 CharacterPositions,
847 CharacterWidths,
848
849 // bool
850 Expanded,
851 Selected,
852
853 // Unique enums
854 Invalid,
855 Toggled,
856 Live,
857 TextDirection,
858 Orientation,
859 SortDirection,
860 AriaCurrent,
861 AutoComplete,
862 HasPopup,
863 ListStyle,
864 TextAlign,
865 VerticalOffset,
866
867 // Other
868 Transform,
869 Bounds,
870 TextSelection,
871 CustomActions,
872
873 // This MUST be last.
874 Unset,
875}
876
877#[derive(Clone, Copy, Debug, PartialEq, Eq)]
878#[repr(transparent)]
879struct PropertyIndices([u8; PropertyId::Unset as usize]);
880
881impl Default for PropertyIndices {
882 fn default() -> Self {
883 Self([PropertyId::Unset as u8; PropertyId::Unset as usize])
884 }
885}
886
887#[derive(Clone, Debug, PartialEq)]
888struct FrozenProperties {
889 indices: PropertyIndices,
890 values: Box<[PropertyValue]>,
891}
892
893/// An accessibility node snapshot that can't be modified. This is not used by
894/// toolkits or applications, but only by code that retains an AccessKit tree
895/// in memory, such as the `accesskit_consumer` crate.
896#[derive(Clone, PartialEq)]
897pub struct FrozenNode {
898 role: Role,
899 actions: u32,
900 flags: u32,
901 properties: FrozenProperties,
902}
903
904#[derive(Clone, Debug, Default, PartialEq)]
905struct Properties {
906 indices: PropertyIndices,
907 values: Vec<PropertyValue>,
908}
909
910/// A single accessible object. A complete UI is represented as a tree of these.
911///
912/// For brevity, and to make more of the documentation usable in bindings
913/// to other languages, documentation of getter methods is written as if
914/// documenting fields in a struct, and such methods are referred to
915/// as properties.
916#[derive(Clone, Default, PartialEq)]
917#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
918#[cfg_attr(feature = "schemars", derive(JsonSchema))]
919#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
920#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
921pub struct Node {
922 role: Role,
923 actions: u32,
924 flags: u32,
925 properties: Properties,
926}
927
928impl PropertyIndices {
929 fn get<'a>(&self, values: &'a [PropertyValue], id: PropertyId) -> &'a PropertyValue {
930 let index: u8 = self.0[id as usize];
931 if index == PropertyId::Unset as u8 {
932 &PropertyValue::None
933 } else {
934 &values[index as usize]
935 }
936 }
937}
938
939fn unexpected_property_type() -> ! {
940 panic!();
941}
942
943impl Properties {
944 fn get_mut(&mut self, id: PropertyId, default: PropertyValue) -> &mut PropertyValue {
945 let index = self.indices.0[id as usize] as usize;
946 if index == PropertyId::Unset as usize {
947 self.values.push(default);
948 let index = self.values.len() - 1;
949 self.indices.0[id as usize] = index as u8;
950 &mut self.values[index]
951 } else {
952 if matches!(self.values[index], PropertyValue::None) {
953 self.values[index] = default;
954 }
955 &mut self.values[index]
956 }
957 }
958
959 fn set(&mut self, id: PropertyId, value: PropertyValue) {
960 let index = self.indices.0[id as usize];
961 if index == PropertyId::Unset as u8 {
962 self.values.push(value);
963 self.indices.0[id as usize] = (self.values.len() - 1) as u8;
964 } else {
965 self.values[index as usize] = value;
966 }
967 }
968
969 fn clear(&mut self, id: PropertyId) {
970 let index = self.indices.0[id as usize];
971 if index != PropertyId::Unset as u8 {
972 self.values[index as usize] = PropertyValue::None;
973 }
974 }
975}
976
977impl From<Properties> for FrozenProperties {
978 fn from(props: Properties) -> Self {
979 Self {
980 indices: props.indices,
981 values: props.values.into_boxed_slice(),
982 }
983 }
984}
985
986macro_rules! flag_methods {
987 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => {
988 impl FrozenNode {
989 $($(#[$doc])*
990 #[inline]
991 pub fn $getter(&self) -> bool {
992 (self.flags & (Flag::$id).mask()) != 0
993 })*
994 fn debug_flag_properties(&self, fmt: &mut fmt::DebugStruct) {
995 $(
996 if self.$getter() {
997 fmt.field(stringify!($getter), &true);
998 }
999 )*
1000 }
1001 }
1002 impl Node {
1003 $($(#[$doc])*
1004 #[inline]
1005 pub fn $getter(&self) -> bool {
1006 (self.flags & (Flag::$id).mask()) != 0
1007 }
1008 #[inline]
1009 pub fn $setter(&mut self) {
1010 self.flags |= (Flag::$id).mask();
1011 }
1012 #[inline]
1013 pub fn $clearer(&mut self) {
1014 self.flags &= !((Flag::$id).mask());
1015 })*
1016 fn debug_flag_properties(&self, fmt: &mut fmt::DebugStruct) {
1017 $(
1018 if self.$getter() {
1019 fmt.field(stringify!($getter), &true);
1020 }
1021 )*
1022 }
1023 }
1024 }
1025}
1026
1027macro_rules! option_ref_type_getters {
1028 ($(($method:ident, $type:ty, $variant:ident)),+) => {
1029 impl PropertyIndices {
1030 $(fn $method<'a>(&self, values: &'a [PropertyValue], id: PropertyId) -> Option<&'a $type> {
1031 match self.get(values, id) {
1032 PropertyValue::None => None,
1033 PropertyValue::$variant(value) => Some(value),
1034 _ => unexpected_property_type(),
1035 }
1036 })*
1037 }
1038 }
1039}
1040
1041macro_rules! slice_type_getters {
1042 ($(($method:ident, $type:ty, $variant:ident)),+) => {
1043 impl PropertyIndices {
1044 $(fn $method<'a>(&self, values: &'a [PropertyValue], id: PropertyId) -> &'a [$type] {
1045 match self.get(values, id) {
1046 PropertyValue::None => &[],
1047 PropertyValue::$variant(value) => value,
1048 _ => unexpected_property_type(),
1049 }
1050 })*
1051 }
1052 }
1053}
1054
1055macro_rules! copy_type_getters {
1056 ($(($method:ident, $type:ty, $variant:ident)),+) => {
1057 impl PropertyIndices {
1058 $(fn $method(&self, values: &[PropertyValue], id: PropertyId) -> Option<$type> {
1059 match self.get(values, id) {
1060 PropertyValue::None => None,
1061 PropertyValue::$variant(value) => Some(*value),
1062 _ => unexpected_property_type(),
1063 }
1064 })*
1065 }
1066 }
1067}
1068
1069macro_rules! box_type_setters {
1070 ($(($method:ident, $type:ty, $variant:ident)),+) => {
1071 impl Node {
1072 $(fn $method(&mut self, id: PropertyId, value: impl Into<Box<$type>>) {
1073 self.properties.set(id, PropertyValue::$variant(value.into()));
1074 })*
1075 }
1076 }
1077}
1078
1079macro_rules! copy_type_setters {
1080 ($(($method:ident, $type:ty, $variant:ident)),+) => {
1081 impl Node {
1082 $(fn $method(&mut self, id: PropertyId, value: $type) {
1083 self.properties.set(id, PropertyValue::$variant(value));
1084 })*
1085 }
1086 }
1087}
1088
1089macro_rules! vec_type_methods {
1090 ($(($type:ty, $variant:ident, $getter:ident, $setter:ident, $pusher:ident)),+) => {
1091 $(slice_type_getters! {
1092 ($getter, $type, $variant)
1093 })*
1094 impl Node {
1095 $(fn $setter(&mut self, id: PropertyId, value: impl Into<Vec<$type>>) {
1096 self.properties.set(id, PropertyValue::$variant(value.into()));
1097 }
1098 fn $pusher(&mut self, id: PropertyId, item: $type) {
1099 match self.properties.get_mut(id, PropertyValue::$variant(Vec::new())) {
1100 PropertyValue::$variant(v) => {
1101 v.push(item);
1102 }
1103 _ => unexpected_property_type(),
1104 }
1105 })*
1106 }
1107 }
1108}
1109
1110macro_rules! property_methods {
1111 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $type_getter:ident, $getter_result:ty, $setter:ident, $type_setter:ident, $setter_param:ty, $clearer:ident)),+) => {
1112 impl FrozenNode {
1113 $($(#[$doc])*
1114 #[inline]
1115 pub fn $getter(&self) -> $getter_result {
1116 self.properties.indices.$type_getter(&self.properties.values, PropertyId::$id)
1117 })*
1118 }
1119 impl Node {
1120 $($(#[$doc])*
1121 #[inline]
1122 pub fn $getter(&self) -> $getter_result {
1123 self.properties.indices.$type_getter(&self.properties.values, PropertyId::$id)
1124 }
1125 #[inline]
1126 pub fn $setter(&mut self, value: $setter_param) {
1127 self.$type_setter(PropertyId::$id, value);
1128 }
1129 #[inline]
1130 pub fn $clearer(&mut self) {
1131 self.properties.clear(PropertyId::$id);
1132 })*
1133 }
1134 }
1135}
1136
1137macro_rules! vec_property_methods {
1138 ($($(#[$doc:meta])* ($id:ident, $item_type:ty, $getter:ident, $type_getter:ident, $setter:ident, $type_setter:ident, $pusher:ident, $type_pusher:ident, $clearer:ident)),+) => {
1139 $(property_methods! {
1140 $(#[$doc])*
1141 ($id, $getter, $type_getter, &[$item_type], $setter, $type_setter, impl Into<Vec<$item_type>>, $clearer)
1142 }
1143 impl Node {
1144 #[inline]
1145 pub fn $pusher(&mut self, item: $item_type) {
1146 self.$type_pusher(PropertyId::$id, item);
1147 }
1148 })*
1149 }
1150}
1151
1152macro_rules! slice_properties_debug_method {
1153 ($name:ident, [$($getter:ident,)*]) => {
1154 fn $name(&self, fmt: &mut fmt::DebugStruct) {
1155 $(
1156 let value = self.$getter();
1157 if !value.is_empty() {
1158 fmt.field(stringify!($getter), &value);
1159 }
1160 )*
1161 }
1162 }
1163}
1164
1165macro_rules! node_id_vec_property_methods {
1166 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $pusher:ident, $clearer:ident)),+) => {
1167 $(vec_property_methods! {
1168 $(#[$doc])*
1169 ($id, NodeId, $getter, get_node_id_vec, $setter, set_node_id_vec, $pusher, push_to_node_id_vec, $clearer)
1170 })*
1171 impl FrozenNode {
1172 slice_properties_debug_method! { debug_node_id_vec_properties, [$($getter,)*] }
1173 }
1174 impl Node {
1175 slice_properties_debug_method! { debug_node_id_vec_properties, [$($getter,)*] }
1176 }
1177 }
1178}
1179
1180macro_rules! option_properties_debug_method {
1181 ($name:ident, [$($getter:ident,)*]) => {
1182 fn $name(&self, fmt: &mut fmt::DebugStruct) {
1183 $(
1184 if let Some(value) = self.$getter() {
1185 fmt.field(stringify!($getter), &value);
1186 }
1187 )*
1188 }
1189 }
1190}
1191
1192macro_rules! node_id_property_methods {
1193 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => {
1194 $(property_methods! {
1195 $(#[$doc])*
1196 ($id, $getter, get_node_id_property, Option<NodeId>, $setter, set_node_id_property, NodeId, $clearer)
1197 })*
1198 impl FrozenNode {
1199 option_properties_debug_method! { debug_node_id_properties, [$($getter,)*] }
1200 }
1201 impl Node {
1202 option_properties_debug_method! { debug_node_id_properties, [$($getter,)*] }
1203 }
1204 }
1205}
1206
1207macro_rules! string_property_methods {
1208 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => {
1209 $(property_methods! {
1210 $(#[$doc])*
1211 ($id, $getter, get_string_property, Option<&str>, $setter, set_string_property, impl Into<Box<str>>, $clearer)
1212 })*
1213 impl FrozenNode {
1214 option_properties_debug_method! { debug_string_properties, [$($getter,)*] }
1215 }
1216 impl Node {
1217 option_properties_debug_method! { debug_string_properties, [$($getter,)*] }
1218 }
1219 }
1220}
1221
1222macro_rules! f64_property_methods {
1223 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => {
1224 $(property_methods! {
1225 $(#[$doc])*
1226 ($id, $getter, get_f64_property, Option<f64>, $setter, set_f64_property, f64, $clearer)
1227 })*
1228 impl FrozenNode {
1229 option_properties_debug_method! { debug_f64_properties, [$($getter,)*] }
1230 }
1231 impl Node {
1232 option_properties_debug_method! { debug_f64_properties, [$($getter,)*] }
1233 }
1234 }
1235}
1236
1237macro_rules! usize_property_methods {
1238 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => {
1239 $(property_methods! {
1240 $(#[$doc])*
1241 ($id, $getter, get_usize_property, Option<usize>, $setter, set_usize_property, usize, $clearer)
1242 })*
1243 impl FrozenNode {
1244 option_properties_debug_method! { debug_usize_properties, [$($getter,)*] }
1245 }
1246 impl Node {
1247 option_properties_debug_method! { debug_usize_properties, [$($getter,)*] }
1248 }
1249 }
1250}
1251
1252macro_rules! color_property_methods {
1253 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => {
1254 $(property_methods! {
1255 $(#[$doc])*
1256 ($id, $getter, get_color_property, Option<u32>, $setter, set_color_property, u32, $clearer)
1257 })*
1258 impl FrozenNode {
1259 option_properties_debug_method! { debug_color_properties, [$($getter,)*] }
1260 }
1261 impl Node {
1262 option_properties_debug_method! { debug_color_properties, [$($getter,)*] }
1263 }
1264 }
1265}
1266
1267macro_rules! text_decoration_property_methods {
1268 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => {
1269 $(property_methods! {
1270 $(#[$doc])*
1271 ($id, $getter, get_text_decoration_property, Option<TextDecoration>, $setter, set_text_decoration_property, TextDecoration, $clearer)
1272 })*
1273 impl FrozenNode {
1274 option_properties_debug_method! { debug_text_decoration_properties, [$($getter,)*] }
1275 }
1276 impl Node {
1277 option_properties_debug_method! { debug_text_decoration_properties, [$($getter,)*] }
1278 }
1279 }
1280}
1281
1282macro_rules! length_slice_property_methods {
1283 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => {
1284 $(property_methods! {
1285 $(#[$doc])*
1286 ($id, $getter, get_length_slice_property, &[u8], $setter, set_length_slice_property, impl Into<Box<[u8]>>, $clearer)
1287 })*
1288 impl FrozenNode {
1289 slice_properties_debug_method! { debug_length_slice_properties, [$($getter,)*] }
1290 }
1291 impl Node {
1292 slice_properties_debug_method! { debug_length_slice_properties, [$($getter,)*] }
1293 }
1294 }
1295}
1296
1297macro_rules! coord_slice_property_methods {
1298 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => {
1299 $(property_methods! {
1300 $(#[$doc])*
1301 ($id, $getter, get_coord_slice_property, Option<&[f32]>, $setter, set_coord_slice_property, impl Into<Box<[f32]>>, $clearer)
1302 })*
1303 impl FrozenNode {
1304 option_properties_debug_method! { debug_coord_slice_properties, [$($getter,)*] }
1305 }
1306 impl Node {
1307 option_properties_debug_method! { debug_coord_slice_properties, [$($getter,)*] }
1308 }
1309 }
1310}
1311
1312macro_rules! bool_property_methods {
1313 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => {
1314 $(property_methods! {
1315 $(#[$doc])*
1316 ($id, $getter, get_bool_property, Option<bool>, $setter, set_bool_property, bool, $clearer)
1317 })*
1318 impl FrozenNode {
1319 option_properties_debug_method! { debug_bool_properties, [$($getter,)*] }
1320 }
1321 impl Node {
1322 option_properties_debug_method! { debug_bool_properties, [$($getter,)*] }
1323 }
1324 }
1325}
1326
1327macro_rules! unique_enum_property_methods {
1328 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => {
1329 impl FrozenNode {
1330 $($(#[$doc])*
1331 #[inline]
1332 pub fn $getter(&self) -> Option<$id> {
1333 match self.properties.indices.get(&self.properties.values, PropertyId::$id) {
1334 PropertyValue::None => None,
1335 PropertyValue::$id(value) => Some(*value),
1336 _ => unexpected_property_type(),
1337 }
1338 })*
1339 option_properties_debug_method! { debug_unique_enum_properties, [$($getter,)*] }
1340 }
1341 impl Node {
1342 $($(#[$doc])*
1343 #[inline]
1344 pub fn $getter(&self) -> Option<$id> {
1345 match self.properties.indices.get(&self.properties.values, PropertyId::$id) {
1346 PropertyValue::None => None,
1347 PropertyValue::$id(value) => Some(*value),
1348 _ => unexpected_property_type(),
1349 }
1350 }
1351 #[inline]
1352 pub fn $setter(&mut self, value: $id) {
1353 self.properties.set(PropertyId::$id, PropertyValue::$id(value));
1354 }
1355 #[inline]
1356 pub fn $clearer(&mut self) {
1357 self.properties.clear(PropertyId::$id);
1358 })*
1359 option_properties_debug_method! { debug_unique_enum_properties, [$($getter,)*] }
1360 }
1361 }
1362}
1363
1364impl Node {
1365 #[inline]
1366 pub fn new(role: Role) -> Self {
1367 Self {
1368 role,
1369 ..Default::default()
1370 }
1371 }
1372}
1373
1374impl From<Node> for FrozenNode {
1375 fn from(node: Node) -> Self {
1376 Self {
1377 role: node.role,
1378 actions: node.actions,
1379 flags: node.flags,
1380 properties: node.properties.into(),
1381 }
1382 }
1383}
1384
1385impl FrozenNode {
1386 #[inline]
1387 pub fn role(&self) -> Role {
1388 self.role
1389 }
1390}
1391
1392impl Node {
1393 #[inline]
1394 pub fn role(&self) -> Role {
1395 self.role
1396 }
1397 #[inline]
1398 pub fn set_role(&mut self, value: Role) {
1399 self.role = value;
1400 }
1401}
1402
1403impl FrozenNode {
1404 #[inline]
1405 pub fn supports_action(&self, action: Action) -> bool {
1406 (self.actions & action.mask()) != 0
1407 }
1408}
1409
1410impl Node {
1411 #[inline]
1412 pub fn supports_action(&self, action: Action) -> bool {
1413 (self.actions & action.mask()) != 0
1414 }
1415 #[inline]
1416 pub fn add_action(&mut self, action: Action) {
1417 self.actions |= action.mask();
1418 }
1419 #[inline]
1420 pub fn remove_action(&mut self, action: Action) {
1421 self.actions &= !(action.mask());
1422 }
1423 #[inline]
1424 pub fn clear_actions(&mut self) {
1425 self.actions = 0;
1426 }
1427}
1428
1429flag_methods! {
1430 /// Exclude this node and its descendants from the tree presented to
1431 /// assistive technologies, and from hit testing.
1432 (Hidden, is_hidden, set_hidden, clear_hidden),
1433 (Linked, is_linked, set_linked, clear_linked),
1434 (Multiselectable, is_multiselectable, set_multiselectable, clear_multiselectable),
1435 (Required, is_required, set_required, clear_required),
1436 (Visited, is_visited, set_visited, clear_visited),
1437 (Busy, is_busy, set_busy, clear_busy),
1438 (LiveAtomic, is_live_atomic, set_live_atomic, clear_live_atomic),
1439 /// If a dialog box is marked as explicitly modal.
1440 (Modal, is_modal, set_modal, clear_modal),
1441 /// This element allows touches to be passed through when a screen reader
1442 /// is in touch exploration mode, e.g. a virtual keyboard normally
1443 /// behaves this way.
1444 (TouchTransparent, is_touch_transparent, set_touch_transparent, clear_touch_transparent),
1445 /// Use for a text widget that allows focus/selection but not input.
1446 (ReadOnly, is_read_only, set_read_only, clear_read_only),
1447 /// Use for a control or group of controls that disallows input.
1448 (Disabled, is_disabled, set_disabled, clear_disabled),
1449 (Bold, is_bold, set_bold, clear_bold),
1450 (Italic, is_italic, set_italic, clear_italic),
1451 /// Indicates that this node clips its children, i.e. may have
1452 /// `overflow: hidden` or clip children by default.
1453 (ClipsChildren, clips_children, set_clips_children, clear_clips_children),
1454 /// Indicates whether this node causes a hard line-break
1455 /// (e.g. block level elements, or `<br>`).
1456 (IsLineBreakingObject, is_line_breaking_object, set_is_line_breaking_object, clear_is_line_breaking_object),
1457 /// Indicates whether this node causes a page break.
1458 (IsPageBreakingObject, is_page_breaking_object, set_is_page_breaking_object, clear_is_page_breaking_object),
1459 (IsSpellingError, is_spelling_error, set_is_spelling_error, clear_is_spelling_error),
1460 (IsGrammarError, is_grammar_error, set_is_grammar_error, clear_is_grammar_error),
1461 (IsSearchMatch, is_search_match, set_is_search_match, clear_is_search_match),
1462 (IsSuggestion, is_suggestion, set_is_suggestion, clear_is_suggestion)
1463}
1464
1465option_ref_type_getters! {
1466 (get_affine_property, Affine, Affine),
1467 (get_string_property, str, String),
1468 (get_coord_slice_property, [f32], CoordSlice),
1469 (get_text_selection_property, TextSelection, TextSelection)
1470}
1471
1472slice_type_getters! {
1473 (get_length_slice_property, u8, LengthSlice)
1474}
1475
1476copy_type_getters! {
1477 (get_rect_property, Rect, Rect),
1478 (get_node_id_property, NodeId, NodeId),
1479 (get_f64_property, f64, F64),
1480 (get_usize_property, usize, Usize),
1481 (get_color_property, u32, Color),
1482 (get_text_decoration_property, TextDecoration, TextDecoration),
1483 (get_bool_property, bool, Bool)
1484}
1485
1486box_type_setters! {
1487 (set_affine_property, Affine, Affine),
1488 (set_string_property, str, String),
1489 (set_length_slice_property, [u8], LengthSlice),
1490 (set_coord_slice_property, [f32], CoordSlice),
1491 (set_text_selection_property, TextSelection, TextSelection)
1492}
1493
1494copy_type_setters! {
1495 (set_rect_property, Rect, Rect),
1496 (set_node_id_property, NodeId, NodeId),
1497 (set_f64_property, f64, F64),
1498 (set_usize_property, usize, Usize),
1499 (set_color_property, u32, Color),
1500 (set_text_decoration_property, TextDecoration, TextDecoration),
1501 (set_bool_property, bool, Bool)
1502}
1503
1504vec_type_methods! {
1505 (NodeId, NodeIdVec, get_node_id_vec, set_node_id_vec, push_to_node_id_vec),
1506 (CustomAction, CustomActionVec, get_custom_action_vec, set_custom_action_vec, push_to_custom_action_vec)
1507}
1508
1509node_id_vec_property_methods! {
1510 (Children, children, set_children, push_child, clear_children),
1511 (Controls, controls, set_controls, push_controlled, clear_controls),
1512 (Details, details, set_details, push_detail, clear_details),
1513 (DescribedBy, described_by, set_described_by, push_described_by, clear_described_by),
1514 (FlowTo, flow_to, set_flow_to, push_flow_to, clear_flow_to),
1515 (LabelledBy, labelled_by, set_labelled_by, push_labelled_by, clear_labelled_by),
1516 /// As with the `aria-owns` property in ARIA, this property should be set
1517 /// only if the nodes referenced in the property are not descendants
1518 /// of the owning node in the AccessKit tree. In the common case, where the
1519 /// owned nodes are direct children or indirect descendants, this property
1520 /// is unnecessary.
1521 (Owns, owns, set_owns, push_owned, clear_owns),
1522 /// On radio buttons this should be set to a list of all of the buttons
1523 /// in the same group as this one, including this radio button itself.
1524 (RadioGroup, radio_group, set_radio_group, push_to_radio_group, clear_radio_group)
1525}
1526
1527node_id_property_methods! {
1528 (ActiveDescendant, active_descendant, set_active_descendant, clear_active_descendant),
1529 (ErrorMessage, error_message, set_error_message, clear_error_message),
1530 (InPageLinkTarget, in_page_link_target, set_in_page_link_target, clear_in_page_link_target),
1531 (MemberOf, member_of, set_member_of, clear_member_of),
1532 (NextOnLine, next_on_line, set_next_on_line, clear_next_on_line),
1533 (PreviousOnLine, previous_on_line, set_previous_on_line, clear_previous_on_line),
1534 (PopupFor, popup_for, set_popup_for, clear_popup_for)
1535}
1536
1537string_property_methods! {
1538 /// The label of a control that can have a label. If the label is specified
1539 /// via the [`Node::labelled_by`] relation, this doesn't need to be set.
1540 /// Note that the text content of a node with the [`Role::Label`] role
1541 /// should be provided via [`Node::value`], not this property.
1542 (Label, label, set_label, clear_label),
1543 (Description, description, set_description, clear_description),
1544 (Value, value, set_value, clear_value),
1545 /// A single character, usually part of this node's name, that can be pressed,
1546 /// possibly along with a platform-specific modifier, to perform
1547 /// this node's default action. For menu items, the access key is only active
1548 /// while the menu is active, in contrast with [`keyboard_shortcut`];
1549 /// a single menu item may in fact have both properties.
1550 ///
1551 /// [`keyboard_shortcut`]: Node::keyboard_shortcut
1552 (AccessKey, access_key, set_access_key, clear_access_key),
1553 /// A way for application authors to identify this node for automated
1554 /// testing purpose. The value must be unique among this node's siblings.
1555 (AuthorId, author_id, set_author_id, clear_author_id),
1556 (ClassName, class_name, set_class_name, clear_class_name),
1557 /// Only present when different from parent.
1558 (FontFamily, font_family, set_font_family, clear_font_family),
1559 (HtmlTag, html_tag, set_html_tag, clear_html_tag),
1560 /// Inner HTML of an element. Only used for a top-level math element,
1561 /// to support third-party math accessibility products that parse MathML.
1562 (InnerHtml, inner_html, set_inner_html, clear_inner_html),
1563 /// A keystroke or sequence of keystrokes, complete with any required
1564 /// modifiers(s), that will perform this node's default action.
1565 /// The value of this property should be in a human-friendly format.
1566 (KeyboardShortcut, keyboard_shortcut, set_keyboard_shortcut, clear_keyboard_shortcut),
1567 /// Only present when different from parent.
1568 (Language, language, set_language, clear_language),
1569 /// If a text input has placeholder text, it should be exposed
1570 /// through this property rather than [`label`].
1571 ///
1572 /// [`label`]: Node::label
1573 (Placeholder, placeholder, set_placeholder, clear_placeholder),
1574 /// An optional string that may override an assistive technology's
1575 /// description of the node's role. Only provide this for custom control types.
1576 /// The value of this property should be in a human-friendly, localized format.
1577 (RoleDescription, role_description, set_role_description, clear_role_description),
1578 /// An optional string that may override an assistive technology's
1579 /// description of the node's state, replacing default strings such as
1580 /// "checked" or "selected". Note that most platform accessibility APIs
1581 /// and assistive technologies do not support this feature.
1582 (StateDescription, state_description, set_state_description, clear_state_description),
1583 /// If a node's only accessible name comes from a tooltip, it should be
1584 /// exposed through this property rather than [`label`].
1585 ///
1586 /// [`label`]: Node::label
1587 (Tooltip, tooltip, set_tooltip, clear_tooltip),
1588 (Url, url, set_url, clear_url),
1589 (RowIndexText, row_index_text, set_row_index_text, clear_row_index_text),
1590 (ColumnIndexText, column_index_text, set_column_index_text, clear_column_index_text)
1591}
1592
1593f64_property_methods! {
1594 (ScrollX, scroll_x, set_scroll_x, clear_scroll_x),
1595 (ScrollXMin, scroll_x_min, set_scroll_x_min, clear_scroll_x_min),
1596 (ScrollXMax, scroll_x_max, set_scroll_x_max, clear_scroll_x_max),
1597 (ScrollY, scroll_y, set_scroll_y, clear_scroll_y),
1598 (ScrollYMin, scroll_y_min, set_scroll_y_min, clear_scroll_y_min),
1599 (ScrollYMax, scroll_y_max, set_scroll_y_max, clear_scroll_y_max),
1600 (NumericValue, numeric_value, set_numeric_value, clear_numeric_value),
1601 (MinNumericValue, min_numeric_value, set_min_numeric_value, clear_min_numeric_value),
1602 (MaxNumericValue, max_numeric_value, set_max_numeric_value, clear_max_numeric_value),
1603 (NumericValueStep, numeric_value_step, set_numeric_value_step, clear_numeric_value_step),
1604 (NumericValueJump, numeric_value_jump, set_numeric_value_jump, clear_numeric_value_jump),
1605 /// Font size is in pixels.
1606 (FontSize, font_size, set_font_size, clear_font_size),
1607 /// Font weight can take on any arbitrary numeric value. Increments of 100 in
1608 /// range `[0, 900]` represent keywords such as light, normal, bold, etc.
1609 (FontWeight, font_weight, set_font_weight, clear_font_weight)
1610}
1611
1612usize_property_methods! {
1613 (RowCount, row_count, set_row_count, clear_row_count),
1614 (ColumnCount, column_count, set_column_count, clear_column_count),
1615 (RowIndex, row_index, set_row_index, clear_row_index),
1616 (ColumnIndex, column_index, set_column_index, clear_column_index),
1617 (RowSpan, row_span, set_row_span, clear_row_span),
1618 (ColumnSpan, column_span, set_column_span, clear_column_span),
1619 (Level, level, set_level, clear_level),
1620 (SizeOfSet, size_of_set, set_size_of_set, clear_size_of_set),
1621 (PositionInSet, position_in_set, set_position_in_set, clear_position_in_set)
1622}
1623
1624color_property_methods! {
1625 /// For [`Role::ColorWell`], specifies the selected color in RGBA.
1626 (ColorValue, color_value, set_color_value, clear_color_value),
1627 /// Background color in RGBA.
1628 (BackgroundColor, background_color, set_background_color, clear_background_color),
1629 /// Foreground color in RGBA.
1630 (ForegroundColor, foreground_color, set_foreground_color, clear_foreground_color)
1631}
1632
1633text_decoration_property_methods! {
1634 (Overline, overline, set_overline, clear_overline),
1635 (Strikethrough, strikethrough, set_strikethrough, clear_strikethrough),
1636 (Underline, underline, set_underline, clear_underline)
1637}
1638
1639length_slice_property_methods! {
1640 /// For text runs, the length (non-inclusive) of each character
1641 /// in UTF-8 code units (bytes). The sum of these lengths must equal
1642 /// the length of [`value`], also in bytes.
1643 ///
1644 /// A character is defined as the smallest unit of text that
1645 /// can be selected. This isn't necessarily a single Unicode
1646 /// scalar value (code point). This is why AccessKit can't compute
1647 /// the lengths of the characters from the text itself; this information
1648 /// must be provided by the text editing implementation.
1649 ///
1650 /// If this node is the last text run in a line that ends with a hard
1651 /// line break, that line break should be included at the end of this
1652 /// node's value as either a CRLF or LF; in both cases, the line break
1653 /// should be counted as a single character for the sake of this slice.
1654 /// When the caret is at the end of such a line, the focus of the text
1655 /// selection should be on the line break, not after it.
1656 ///
1657 /// [`value`]: Node::value
1658 (CharacterLengths, character_lengths, set_character_lengths, clear_character_lengths),
1659
1660 /// For text runs, the length of each word in characters, as defined
1661 /// in [`character_lengths`]. The sum of these lengths must equal
1662 /// the length of [`character_lengths`].
1663 ///
1664 /// The end of each word is the beginning of the next word; there are no
1665 /// characters that are not considered part of a word. Trailing whitespace
1666 /// is typically considered part of the word that precedes it, while
1667 /// a line's leading whitespace is considered its own word. Whether
1668 /// punctuation is considered a separate word or part of the preceding
1669 /// word depends on the particular text editing implementation.
1670 /// Some editors may have their own definition of a word; for example,
1671 /// in an IDE, words may correspond to programming language tokens.
1672 ///
1673 /// Not all assistive technologies require information about word
1674 /// boundaries, and not all platform accessibility APIs even expose
1675 /// this information, but for assistive technologies that do use
1676 /// this information, users will get unpredictable results if the word
1677 /// boundaries exposed by the accessibility tree don't match
1678 /// the editor's behavior. This is why AccessKit does not determine
1679 /// word boundaries itself.
1680 ///
1681 /// [`character_lengths`]: Node::character_lengths
1682 (WordLengths, word_lengths, set_word_lengths, clear_word_lengths)
1683}
1684
1685coord_slice_property_methods! {
1686 /// For text runs, this is the position of each character within
1687 /// the node's bounding box, in the direction given by
1688 /// [`text_direction`], in the coordinate space of this node.
1689 ///
1690 /// When present, the length of this slice should be the same as the length
1691 /// of [`character_lengths`], including for lines that end
1692 /// with a hard line break. The position of such a line break should
1693 /// be the position where an end-of-paragraph marker would be rendered.
1694 ///
1695 /// This property is optional. Without it, AccessKit can't support some
1696 /// use cases, such as screen magnifiers that track the caret position
1697 /// or screen readers that display a highlight cursor. However,
1698 /// most text functionality still works without this information.
1699 ///
1700 /// [`text_direction`]: Node::text_direction
1701 /// [`character_lengths`]: Node::character_lengths
1702 (CharacterPositions, character_positions, set_character_positions, clear_character_positions),
1703
1704 /// For text runs, this is the advance width of each character,
1705 /// in the direction given by [`text_direction`], in the coordinate
1706 /// space of this node.
1707 ///
1708 /// When present, the length of this slice should be the same as the length
1709 /// of [`character_lengths`], including for lines that end
1710 /// with a hard line break. The width of such a line break should
1711 /// be non-zero if selecting the line break by itself results in
1712 /// a visible highlight (as in Microsoft Word), or zero if not
1713 /// (as in Windows Notepad).
1714 ///
1715 /// This property is optional. Without it, AccessKit can't support some
1716 /// use cases, such as screen magnifiers that track the caret position
1717 /// or screen readers that display a highlight cursor. However,
1718 /// most text functionality still works without this information.
1719 ///
1720 /// [`text_direction`]: Node::text_direction
1721 /// [`character_lengths`]: Node::character_lengths
1722 (CharacterWidths, character_widths, set_character_widths, clear_character_widths)
1723}
1724
1725bool_property_methods! {
1726 /// Whether this node is expanded, collapsed, or neither.
1727 ///
1728 /// Setting this to `false` means the node is collapsed; omitting it means this state
1729 /// isn't applicable.
1730 (Expanded, is_expanded, set_expanded, clear_expanded),
1731
1732 /// Indicates whether this node is selected or unselected.
1733 ///
1734 /// The absence of this flag (as opposed to a `false` setting)
1735 /// means that the concept of "selected" doesn't apply.
1736 /// When deciding whether to set the flag to false or omit it,
1737 /// consider whether it would be appropriate for a screen reader
1738 /// to announce "not selected". The ambiguity of this flag
1739 /// in platform accessibility APIs has made extraneous
1740 /// "not selected" announcements a common annoyance.
1741 (Selected, is_selected, set_selected, clear_selected)
1742}
1743
1744unique_enum_property_methods! {
1745 (Invalid, invalid, set_invalid, clear_invalid),
1746 (Toggled, toggled, set_toggled, clear_toggled),
1747 (Live, live, set_live, clear_live),
1748 (TextDirection, text_direction, set_text_direction, clear_text_direction),
1749 (Orientation, orientation, set_orientation, clear_orientation),
1750 (SortDirection, sort_direction, set_sort_direction, clear_sort_direction),
1751 (AriaCurrent, aria_current, set_aria_current, clear_aria_current),
1752 (AutoComplete, auto_complete, set_auto_complete, clear_auto_complete),
1753 (HasPopup, has_popup, set_has_popup, clear_has_popup),
1754 /// The list style type. Only available on list items.
1755 (ListStyle, list_style, set_list_style, clear_list_style),
1756 (TextAlign, text_align, set_text_align, clear_text_align),
1757 (VerticalOffset, vertical_offset, set_vertical_offset, clear_vertical_offset)
1758}
1759
1760property_methods! {
1761 /// An affine transform to apply to any coordinates within this node
1762 /// and its descendants, including the [`bounds`] property of this node.
1763 /// The combined transforms of this node and its ancestors define
1764 /// the coordinate space of this node. /// This should be `None` if
1765 /// it would be set to the identity transform, which should be the case
1766 /// for most nodes.
1767 ///
1768 /// AccessKit expects the final transformed coordinates to be relative
1769 /// to the origin of the tree's container (e.g. window), in physical
1770 /// pixels, with the y coordinate being top-down.
1771 ///
1772 /// [`bounds`]: Node::bounds
1773 (Transform, transform, get_affine_property, Option<&Affine>, set_transform, set_affine_property, impl Into<Box<Affine>>, clear_transform),
1774
1775 /// The bounding box of this node, in the node's coordinate space.
1776 /// This property does not affect the coordinate space of either this node
1777 /// or its descendants; only the [`transform`] property affects that.
1778 /// This, along with the recommendation that most nodes should have
1779 /// a [`transform`] of `None`, implies that the `bounds` property
1780 /// of most nodes should be in the coordinate space of the nearest ancestor
1781 /// with a non-`None` [`transform`], or if there is no such ancestor,
1782 /// the tree's container (e.g. window).
1783 ///
1784 /// [`transform`]: Node::transform
1785 (Bounds, bounds, get_rect_property, Option<Rect>, set_bounds, set_rect_property, Rect, clear_bounds),
1786
1787 (TextSelection, text_selection, get_text_selection_property, Option<&TextSelection>, set_text_selection, set_text_selection_property, impl Into<Box<TextSelection>>, clear_text_selection)
1788}
1789
1790impl FrozenNode {
1791 option_properties_debug_method! { debug_option_properties, [transform, bounds, text_selection,] }
1792}
1793
1794impl Node {
1795 option_properties_debug_method! { debug_option_properties, [transform, bounds, text_selection,] }
1796}
1797
1798vec_property_methods! {
1799 (CustomActions, CustomAction, custom_actions, get_custom_action_vec, set_custom_actions, set_custom_action_vec, push_custom_action, push_to_custom_action_vec, clear_custom_actions)
1800}
1801
1802impl fmt::Debug for FrozenNode {
1803 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1804 let mut fmt = f.debug_struct("FrozenNode");
1805
1806 fmt.field("role", &self.role());
1807
1808 let supported_actions = action_mask_to_action_vec(self.actions);
1809 if !supported_actions.is_empty() {
1810 fmt.field("actions", &supported_actions);
1811 }
1812
1813 self.debug_flag_properties(&mut fmt);
1814 self.debug_node_id_vec_properties(&mut fmt);
1815 self.debug_node_id_properties(&mut fmt);
1816 self.debug_string_properties(&mut fmt);
1817 self.debug_f64_properties(&mut fmt);
1818 self.debug_usize_properties(&mut fmt);
1819 self.debug_color_properties(&mut fmt);
1820 self.debug_text_decoration_properties(&mut fmt);
1821 self.debug_length_slice_properties(&mut fmt);
1822 self.debug_coord_slice_properties(&mut fmt);
1823 self.debug_bool_properties(&mut fmt);
1824 self.debug_unique_enum_properties(&mut fmt);
1825 self.debug_option_properties(&mut fmt);
1826
1827 let custom_actions = self.custom_actions();
1828 if !custom_actions.is_empty() {
1829 fmt.field("custom_actions", &custom_actions);
1830 }
1831
1832 fmt.finish()
1833 }
1834}
1835
1836impl fmt::Debug for Node {
1837 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1838 let mut fmt = f.debug_struct("Node");
1839
1840 fmt.field("role", &self.role());
1841
1842 let supported_actions = action_mask_to_action_vec(self.actions);
1843 if !supported_actions.is_empty() {
1844 fmt.field("actions", &supported_actions);
1845 }
1846
1847 self.debug_flag_properties(&mut fmt);
1848 self.debug_node_id_vec_properties(&mut fmt);
1849 self.debug_node_id_properties(&mut fmt);
1850 self.debug_string_properties(&mut fmt);
1851 self.debug_f64_properties(&mut fmt);
1852 self.debug_usize_properties(&mut fmt);
1853 self.debug_color_properties(&mut fmt);
1854 self.debug_text_decoration_properties(&mut fmt);
1855 self.debug_length_slice_properties(&mut fmt);
1856 self.debug_coord_slice_properties(&mut fmt);
1857 self.debug_bool_properties(&mut fmt);
1858 self.debug_unique_enum_properties(&mut fmt);
1859 self.debug_option_properties(&mut fmt);
1860
1861 let custom_actions = self.custom_actions();
1862 if !custom_actions.is_empty() {
1863 fmt.field("custom_actions", &custom_actions);
1864 }
1865
1866 fmt.finish()
1867 }
1868}
1869
1870#[cfg(feature = "serde")]
1871macro_rules! serialize_property {
1872 ($self:ident, $map:ident, $index:ident, $id:ident, { $($variant:ident),+ }) => {
1873 match &$self.values[$index as usize] {
1874 PropertyValue::None => (),
1875 $(PropertyValue::$variant(value) => {
1876 $map.serialize_entry(&$id, &value)?;
1877 })*
1878 }
1879 }
1880}
1881
1882#[cfg(feature = "serde")]
1883macro_rules! deserialize_property {
1884 ($props:ident, $map:ident, $key:ident, { $($type:ident { $($id:ident),+ }),+ }) => {
1885 match $key {
1886 $($(PropertyId::$id => {
1887 let value = $map.next_value()?;
1888 $props.set(PropertyId::$id, PropertyValue::$type(value));
1889 })*)*
1890 PropertyId::Unset => {
1891 let _ = $map.next_value::<IgnoredAny>()?;
1892 }
1893 }
1894 }
1895}
1896
1897#[cfg(feature = "serde")]
1898impl Serialize for Properties {
1899 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1900 where
1901 S: Serializer,
1902 {
1903 let mut len = 0;
1904 for value in &*self.values {
1905 if !matches!(*value, PropertyValue::None) {
1906 len += 1;
1907 }
1908 }
1909 let mut map = serializer.serialize_map(Some(len))?;
1910 for (id, index) in self.indices.0.iter().copied().enumerate() {
1911 if index == PropertyId::Unset as u8 {
1912 continue;
1913 }
1914 let id = PropertyId::n(id as _).unwrap();
1915 serialize_property!(self, map, index, id, {
1916 NodeIdVec,
1917 NodeId,
1918 String,
1919 F64,
1920 Usize,
1921 Color,
1922 TextDecoration,
1923 LengthSlice,
1924 CoordSlice,
1925 Bool,
1926 Invalid,
1927 Toggled,
1928 Live,
1929 TextDirection,
1930 Orientation,
1931 SortDirection,
1932 AriaCurrent,
1933 AutoComplete,
1934 HasPopup,
1935 ListStyle,
1936 TextAlign,
1937 VerticalOffset,
1938 Affine,
1939 Rect,
1940 TextSelection,
1941 CustomActionVec
1942 });
1943 }
1944 map.end()
1945 }
1946}
1947
1948#[cfg(feature = "serde")]
1949struct PropertiesVisitor;
1950
1951#[cfg(feature = "serde")]
1952impl<'de> Visitor<'de> for PropertiesVisitor {
1953 type Value = Properties;
1954
1955 #[inline]
1956 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1957 formatter.write_str("property map")
1958 }
1959
1960 fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
1961 where
1962 V: MapAccess<'de>,
1963 {
1964 let mut props = Properties::default();
1965 while let Some(id) = map.next_key()? {
1966 deserialize_property!(props, map, id, {
1967 NodeIdVec {
1968 Children,
1969 Controls,
1970 Details,
1971 DescribedBy,
1972 FlowTo,
1973 LabelledBy,
1974 Owns,
1975 RadioGroup
1976 },
1977 NodeId {
1978 ActiveDescendant,
1979 ErrorMessage,
1980 InPageLinkTarget,
1981 MemberOf,
1982 NextOnLine,
1983 PreviousOnLine,
1984 PopupFor
1985 },
1986 String {
1987 Label,
1988 Description,
1989 Value,
1990 AccessKey,
1991 AuthorId,
1992 ClassName,
1993 FontFamily,
1994 HtmlTag,
1995 InnerHtml,
1996 KeyboardShortcut,
1997 Language,
1998 Placeholder,
1999 RoleDescription,
2000 StateDescription,
2001 Tooltip,
2002 Url,
2003 RowIndexText,
2004 ColumnIndexText
2005 },
2006 F64 {
2007 ScrollX,
2008 ScrollXMin,
2009 ScrollXMax,
2010 ScrollY,
2011 ScrollYMin,
2012 ScrollYMax,
2013 NumericValue,
2014 MinNumericValue,
2015 MaxNumericValue,
2016 NumericValueStep,
2017 NumericValueJump,
2018 FontSize,
2019 FontWeight
2020 },
2021 Usize {
2022 RowCount,
2023 ColumnCount,
2024 RowIndex,
2025 ColumnIndex,
2026 RowSpan,
2027 ColumnSpan,
2028 Level,
2029 SizeOfSet,
2030 PositionInSet
2031 },
2032 Color {
2033 ColorValue,
2034 BackgroundColor,
2035 ForegroundColor
2036 },
2037 TextDecoration {
2038 Overline,
2039 Strikethrough,
2040 Underline
2041 },
2042 LengthSlice {
2043 CharacterLengths,
2044 WordLengths
2045 },
2046 CoordSlice {
2047 CharacterPositions,
2048 CharacterWidths
2049 },
2050 Bool {
2051 Expanded,
2052 Selected
2053 },
2054 Invalid { Invalid },
2055 Toggled { Toggled },
2056 Live { Live },
2057 TextDirection { TextDirection },
2058 Orientation { Orientation },
2059 SortDirection { SortDirection },
2060 AriaCurrent { AriaCurrent },
2061 AutoComplete { AutoComplete },
2062 HasPopup { HasPopup },
2063 ListStyle { ListStyle },
2064 TextAlign { TextAlign },
2065 VerticalOffset { VerticalOffset },
2066 Affine { Transform },
2067 Rect { Bounds },
2068 TextSelection { TextSelection },
2069 CustomActionVec { CustomActions }
2070 });
2071 }
2072
2073 Ok(props)
2074 }
2075}
2076
2077#[cfg(feature = "serde")]
2078impl<'de> Deserialize<'de> for Properties {
2079 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2080 where
2081 D: Deserializer<'de>,
2082 {
2083 deserializer.deserialize_map(PropertiesVisitor)
2084 }
2085}
2086
2087#[cfg(feature = "schemars")]
2088macro_rules! add_schema_property {
2089 ($gen:ident, $properties:ident, $enum_value:expr, $type:ty) => {{
2090 let name = format!("{:?}", $enum_value);
2091 let name = name[..1].to_ascii_lowercase() + &name[1..];
2092 let subschema = $gen.subschema_for::<$type>();
2093 $properties.insert(name, subschema);
2094 }};
2095}
2096
2097#[cfg(feature = "schemars")]
2098macro_rules! add_properties_to_schema {
2099 ($gen:ident, $properties:ident, { $($type:ty { $($id:ident),+ }),+ }) => {
2100 $($(add_schema_property!($gen, $properties, PropertyId::$id, $type);)*)*
2101 }
2102}
2103
2104#[cfg(feature = "schemars")]
2105impl JsonSchema for Properties {
2106 #[inline]
2107 fn schema_name() -> String {
2108 "Properties".into()
2109 }
2110
2111 fn json_schema(gen: &mut SchemaGenerator) -> Schema {
2112 let mut properties = SchemaMap::<String, Schema>::new();
2113 add_properties_to_schema!(gen, properties, {
2114 Vec<NodeId> {
2115 Children,
2116 Controls,
2117 Details,
2118 DescribedBy,
2119 FlowTo,
2120 LabelledBy,
2121 Owns,
2122 RadioGroup
2123 },
2124 NodeId {
2125 ActiveDescendant,
2126 ErrorMessage,
2127 InPageLinkTarget,
2128 MemberOf,
2129 NextOnLine,
2130 PreviousOnLine,
2131 PopupFor
2132 },
2133 Box<str> {
2134 Label,
2135 Description,
2136 Value,
2137 AccessKey,
2138 AuthorId,
2139 ClassName,
2140 FontFamily,
2141 HtmlTag,
2142 InnerHtml,
2143 KeyboardShortcut,
2144 Language,
2145 Placeholder,
2146 RoleDescription,
2147 StateDescription,
2148 Tooltip,
2149 Url,
2150 RowIndexText,
2151 ColumnIndexText
2152 },
2153 f64 {
2154 ScrollX,
2155 ScrollXMin,
2156 ScrollXMax,
2157 ScrollY,
2158 ScrollYMin,
2159 ScrollYMax,
2160 NumericValue,
2161 MinNumericValue,
2162 MaxNumericValue,
2163 NumericValueStep,
2164 NumericValueJump,
2165 FontSize,
2166 FontWeight
2167 },
2168 usize {
2169 RowCount,
2170 ColumnCount,
2171 RowIndex,
2172 ColumnIndex,
2173 RowSpan,
2174 ColumnSpan,
2175 Level,
2176 SizeOfSet,
2177 PositionInSet
2178 },
2179 u32 {
2180 ColorValue,
2181 BackgroundColor,
2182 ForegroundColor
2183 },
2184 TextDecoration {
2185 Overline,
2186 Strikethrough,
2187 Underline
2188 },
2189 Box<[u8]> {
2190 CharacterLengths,
2191 WordLengths
2192 },
2193 Box<[f32]> {
2194 CharacterPositions,
2195 CharacterWidths
2196 },
2197 bool {
2198 Expanded,
2199 Selected
2200 },
2201 Invalid { Invalid },
2202 Toggled { Toggled },
2203 Live { Live },
2204 TextDirection { TextDirection },
2205 Orientation { Orientation },
2206 SortDirection { SortDirection },
2207 AriaCurrent { AriaCurrent },
2208 AutoComplete { AutoComplete },
2209 HasPopup { HasPopup },
2210 ListStyle { ListStyle },
2211 TextAlign { TextAlign },
2212 VerticalOffset { VerticalOffset },
2213 Affine { Transform },
2214 Rect { Bounds },
2215 TextSelection { TextSelection },
2216 Vec<CustomAction> { CustomActions }
2217 });
2218 SchemaObject {
2219 instance_type: Some(InstanceType::Object.into()),
2220 object: Some(
2221 ObjectValidation {
2222 properties,
2223 ..Default::default()
2224 }
2225 .into(),
2226 ),
2227 ..Default::default()
2228 }
2229 .into()
2230 }
2231}
2232
2233/// The data associated with an accessibility tree that's global to the
2234/// tree and not associated with any particular node.
2235#[derive(Clone, Debug, PartialEq, Eq)]
2236#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2237#[cfg_attr(feature = "schemars", derive(JsonSchema))]
2238#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
2239#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2240pub struct Tree {
2241 /// The identifier of the tree's root node.
2242 pub root: NodeId,
2243 /// The name of the application this tree belongs to.
2244 pub app_name: Option<String>,
2245 /// The name of the UI toolkit in use.
2246 pub toolkit_name: Option<String>,
2247 /// The version of the UI toolkit.
2248 pub toolkit_version: Option<String>,
2249}
2250
2251impl Tree {
2252 #[inline]
2253 pub fn new(root: NodeId) -> Tree {
2254 Tree {
2255 root,
2256 app_name: None,
2257 toolkit_name: None,
2258 toolkit_version: None,
2259 }
2260 }
2261}
2262
2263/// A serializable representation of an atomic change to a [`Tree`].
2264///
2265/// The sender and receiver must be in sync; the update is only meant
2266/// to bring the tree from a specific previous state into its next state.
2267/// Trying to apply it to the wrong tree should immediately panic.
2268///
2269/// Note that for performance, an update should only include nodes that are
2270/// new or changed. AccessKit platform adapters will avoid raising extraneous
2271/// events for nodes that have not changed since the previous update,
2272/// but there is still a cost in processing these nodes and replacing
2273/// the previous instances.
2274#[derive(Clone, Debug, PartialEq)]
2275#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2276#[cfg_attr(feature = "schemars", derive(JsonSchema))]
2277#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
2278#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2279pub struct TreeUpdate {
2280 /// Zero or more new or updated nodes. Order doesn't matter.
2281 ///
2282 /// Each node in this list will overwrite any existing node with the same ID.
2283 /// This means that when updating a node, fields that are unchanged
2284 /// from the previous version must still be set to the same values
2285 /// as before.
2286 ///
2287 /// It is an error for any node in this list to not be either the root
2288 /// or a child of another node. For nodes other than the root, the parent
2289 /// must be either an unchanged node already in the tree, or another node
2290 /// in this list.
2291 ///
2292 /// To add a child to the tree, the list must include both the child
2293 /// and an updated version of the parent with the child's ID added to
2294 /// [`Node::children`].
2295 ///
2296 /// To remove a child and all of its descendants, this list must include
2297 /// an updated version of the parent node with the child's ID removed
2298 /// from [`Node::children`]. Neither the child nor any of its descendants
2299 /// may be included in this list.
2300 pub nodes: Vec<(NodeId, Node)>,
2301
2302 /// Rarely updated information about the tree as a whole. This may be omitted
2303 /// if it has not changed since the previous update, but providing the same
2304 /// information again is also allowed. This is required when initializing
2305 /// a tree.
2306 pub tree: Option<Tree>,
2307
2308 /// The node within this tree that has keyboard focus when the native
2309 /// host (e.g. window) has focus. If no specific node within the tree
2310 /// has keyboard focus, this must be set to the root. The latest focus state
2311 /// must be provided with every tree update, even if the focus state
2312 /// didn't change in a given update.
2313 pub focus: NodeId,
2314}
2315
2316#[derive(Clone, Debug, PartialEq)]
2317#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2318#[cfg_attr(feature = "schemars", derive(JsonSchema))]
2319#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2320#[repr(C)]
2321pub enum ActionData {
2322 CustomAction(i32),
2323 Value(Box<str>),
2324 NumericValue(f64),
2325 /// Optional target rectangle for [`Action::ScrollIntoView`], in
2326 /// the coordinate space of the action's target node.
2327 ScrollTargetRect(Rect),
2328 /// Target for [`Action::ScrollToPoint`], in platform-native coordinates
2329 /// relative to the origin of the tree's container (e.g. window).
2330 ScrollToPoint(Point),
2331 /// Target for [`Action::SetScrollOffset`], in the coordinate space
2332 /// of the action's target node.
2333 SetScrollOffset(Point),
2334 SetTextSelection(TextSelection),
2335}
2336
2337#[derive(Clone, Debug, PartialEq)]
2338#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2339#[cfg_attr(feature = "schemars", derive(JsonSchema))]
2340#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
2341#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2342pub struct ActionRequest {
2343 pub action: Action,
2344 pub target: NodeId,
2345 pub data: Option<ActionData>,
2346}
2347
2348/// Handles activation of the application's accessibility implementation.
2349pub trait ActivationHandler {
2350 /// Requests a [`TreeUpdate`] with a full tree. If the application
2351 /// can generate the tree synchronously within this method call,
2352 /// it should do so and return the [`TreeUpdate`]. Otherwise,
2353 /// it must send the update to the platform adapter asynchronously,
2354 /// no later than the next display refresh, even if a frame would not
2355 /// normally be rendered due to user input or other activity.
2356 /// The application should not return or send a placeholder [`TreeUpdate`];
2357 /// the platform adapter will provide one if necessary until the real
2358 /// tree is sent.
2359 ///
2360 /// The primary purpose of this method is to allow the application
2361 /// to lazily initialize its accessibility implementation. However,
2362 /// this method may be called consecutively without any call to
2363 /// [`DeactivationHandler::deactivate_accessibility`]; this typically happens
2364 /// if the platform adapter merely forwards tree updates to assistive
2365 /// technologies without maintaining any state. A call to this method
2366 /// must always generate a [`TreeUpdate`] with a full tree, even if
2367 /// the application normally sends incremental updates.
2368 ///
2369 /// The thread on which this method is called is platform-dependent.
2370 /// Refer to the platform adapter documentation for more details.
2371 fn request_initial_tree(&mut self) -> Option<TreeUpdate>;
2372}
2373
2374/// Handles requests from assistive technologies or other clients.
2375pub trait ActionHandler {
2376 /// Perform the requested action. If the requested action is not supported,
2377 /// this method must do nothing.
2378 ///
2379 /// The thread on which this method is called is platform-dependent.
2380 /// Refer to the platform adapter documentation for more details.
2381 ///
2382 /// This method may queue the request and handle it asynchronously.
2383 /// This behavior is preferred over blocking, e.g. when dispatching
2384 /// the request to another thread.
2385 fn do_action(&mut self, request: ActionRequest);
2386}
2387
2388/// Handles deactivation of the application's accessibility implementation.
2389pub trait DeactivationHandler {
2390 /// Deactivate the application's accessibility implementation and drop any
2391 /// associated data that can be reconstructed later. After this method
2392 /// is called, if an accessibility tree is needed again, the platform
2393 /// adapter will call [`ActivationHandler::request_initial_tree`] again.
2394 ///
2395 /// The thread on which this method is called is platform-dependent.
2396 /// Refer to the platform adapter documentation for more details.
2397 fn deactivate_accessibility(&mut self);
2398}
2399
2400#[cfg(test)]
2401mod tests {
2402 use super::*;
2403
2404 #[test]
2405 fn action_n() {
2406 assert_eq!(Action::n(0), Some(Action::Click));
2407 assert_eq!(Action::n(1), Some(Action::Focus));
2408 assert_eq!(Action::n(2), Some(Action::Blur));
2409 assert_eq!(Action::n(3), Some(Action::Collapse));
2410 assert_eq!(Action::n(4), Some(Action::Expand));
2411 assert_eq!(Action::n(5), Some(Action::CustomAction));
2412 assert_eq!(Action::n(6), Some(Action::Decrement));
2413 assert_eq!(Action::n(7), Some(Action::Increment));
2414 assert_eq!(Action::n(8), Some(Action::HideTooltip));
2415 assert_eq!(Action::n(9), Some(Action::ShowTooltip));
2416 assert_eq!(Action::n(10), Some(Action::ReplaceSelectedText));
2417 assert_eq!(Action::n(11), Some(Action::ScrollBackward));
2418 assert_eq!(Action::n(12), Some(Action::ScrollDown));
2419 assert_eq!(Action::n(13), Some(Action::ScrollForward));
2420 assert_eq!(Action::n(14), Some(Action::ScrollLeft));
2421 assert_eq!(Action::n(15), Some(Action::ScrollRight));
2422 assert_eq!(Action::n(16), Some(Action::ScrollUp));
2423 assert_eq!(Action::n(17), Some(Action::ScrollIntoView));
2424 assert_eq!(Action::n(18), Some(Action::ScrollToPoint));
2425 assert_eq!(Action::n(19), Some(Action::SetScrollOffset));
2426 assert_eq!(Action::n(20), Some(Action::SetTextSelection));
2427 assert_eq!(
2428 Action::n(21),
2429 Some(Action::SetSequentialFocusNavigationStartingPoint)
2430 );
2431 assert_eq!(Action::n(22), Some(Action::SetValue));
2432 assert_eq!(Action::n(23), Some(Action::ShowContextMenu));
2433 assert_eq!(Action::n(24), None);
2434 }
2435
2436 #[test]
2437 fn test_action_mask_to_action_vec() {
2438 assert_eq!(
2439 Vec::<Action>::new(),
2440 action_mask_to_action_vec(Node::new(Role::Unknown).actions)
2441 );
2442
2443 let mut node = Node::new(Role::Unknown);
2444 node.add_action(Action::Click);
2445 assert_eq!(
2446 &[Action::Click],
2447 action_mask_to_action_vec(node.actions).as_slice()
2448 );
2449
2450 let mut node = Node::new(Role::Unknown);
2451 node.add_action(Action::ShowContextMenu);
2452 assert_eq!(
2453 &[Action::ShowContextMenu],
2454 action_mask_to_action_vec(node.actions).as_slice()
2455 );
2456
2457 let mut node = Node::new(Role::Unknown);
2458 node.add_action(Action::Click);
2459 node.add_action(Action::ShowContextMenu);
2460 assert_eq!(
2461 &[Action::Click, Action::ShowContextMenu],
2462 action_mask_to_action_vec(node.actions).as_slice()
2463 );
2464
2465 let mut node = Node::new(Role::Unknown);
2466 node.add_action(Action::Focus);
2467 node.add_action(Action::Blur);
2468 node.add_action(Action::Collapse);
2469 assert_eq!(
2470 &[Action::Focus, Action::Blur, Action::Collapse],
2471 action_mask_to_action_vec(node.actions).as_slice()
2472 );
2473 }
2474}
2475