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(feature = "pyo3")]
12use pyo3::pyclass;
13#[cfg(feature = "schemars")]
14use schemars::{
15 gen::SchemaGenerator,
16 schema::{ArrayValidation, InstanceType, ObjectValidation, Schema, SchemaObject},
17 JsonSchema, Map as SchemaMap,
18};
19#[cfg(feature = "serde")]
20use serde::{
21 de::{Deserializer, IgnoredAny, MapAccess, SeqAccess, Visitor},
22 ser::{SerializeMap, SerializeSeq, Serializer},
23 Deserialize, Serialize,
24};
25use std::{collections::BTreeSet, ops::DerefMut, sync::Arc};
26#[cfg(feature = "serde")]
27use std::{fmt, mem::size_of_val};
28
29mod geometry;
30pub use geometry::{Affine, Point, Rect, Size, Vec2};
31
32/// The type of an accessibility node.
33///
34/// The majority of these roles come from the ARIA specification. Reference
35/// the latest draft for proper usage.
36///
37/// Like the AccessKit schema as a whole, this list is largely taken
38/// from Chromium. However, unlike Chromium's alphabetized list, this list
39/// is ordered roughly by expected usage frequency (with the notable exception
40/// of [`Role::Unknown`]). This is more efficient in serialization formats
41/// where integers use a variable-length encoding.
42#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
43#[cfg_attr(feature = "enumn", derive(enumn::N))]
44#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
45#[cfg_attr(feature = "schemars", derive(JsonSchema))]
46#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
47#[cfg_attr(
48 feature = "pyo3",
49 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
50)]
51#[repr(u8)]
52pub enum Role {
53 #[default]
54 Unknown,
55 InlineTextBox,
56 Cell,
57 StaticText,
58 Image,
59 Link,
60 Row,
61 ListItem,
62
63 /// Contains the bullet, number, or other marker for a list item.
64 ListMarker,
65
66 TreeItem,
67 ListBoxOption,
68 MenuItem,
69 MenuListOption,
70 Paragraph,
71
72 /// A generic container that should be ignored by assistive technologies
73 /// and filtered out of platform accessibility trees. Equivalent to the ARIA
74 /// `none` or `presentation` role, or to an HTML `div` with no role.
75 GenericContainer,
76
77 CheckBox,
78 RadioButton,
79 TextInput,
80 Button,
81 DefaultButton,
82 Pane,
83 RowHeader,
84 ColumnHeader,
85 Column,
86 RowGroup,
87 List,
88 Table,
89 TableHeaderContainer,
90 LayoutTableCell,
91 LayoutTableRow,
92 LayoutTable,
93 Switch,
94 ToggleButton,
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///
272/// In contrast to [`DefaultActionVerb`], these describe what happens to the
273/// object, e.g. "focus".
274#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
275#[cfg_attr(feature = "enumn", derive(enumn::N))]
276#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
277#[cfg_attr(feature = "schemars", derive(JsonSchema))]
278#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
279#[cfg_attr(
280 feature = "pyo3",
281 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
282)]
283#[repr(u8)]
284pub enum Action {
285 /// Do the default action for an object, typically this means "click".
286 Default,
287
288 Focus,
289 Blur,
290
291 Collapse,
292 Expand,
293
294 /// Requires [`ActionRequest::data`] to be set to [`ActionData::CustomAction`].
295 CustomAction,
296
297 /// Decrement a numeric value by one step.
298 Decrement,
299 /// Increment a numeric value by one step.
300 Increment,
301
302 HideTooltip,
303 ShowTooltip,
304
305 /// Delete any selected text in the control's text value and
306 /// insert the specified value in its place, like when typing or pasting.
307 /// Requires [`ActionRequest::data`] to be set to [`ActionData::Value`].
308 ReplaceSelectedText,
309
310 // Scrolls by approximately one screen in a specific direction.
311 // TBD: Do we need a doc comment on each of the values below?
312 // Or does this awkwardness suggest a refactor?
313 ScrollBackward,
314 ScrollDown,
315 ScrollForward,
316 ScrollLeft,
317 ScrollRight,
318 ScrollUp,
319
320 /// Scroll any scrollable containers to make the target object visible
321 /// on the screen. Optionally set [`ActionRequest::data`] to
322 /// [`ActionData::ScrollTargetRect`].
323 ScrollIntoView,
324
325 /// Scroll the given object to a specified point in the tree's container
326 /// (e.g. window). Requires [`ActionRequest::data`] to be set to
327 /// [`ActionData::ScrollToPoint`].
328 ScrollToPoint,
329
330 /// Requires [`ActionRequest::data`] to be set to [`ActionData::SetScrollOffset`].
331 SetScrollOffset,
332
333 /// Requires [`ActionRequest::data`] to be set to [`ActionData::SetTextSelection`].
334 SetTextSelection,
335
336 /// Don't focus this node, but set it as the sequential focus navigation
337 /// starting point, so that pressing Tab moves to the next element
338 /// following this one, for example.
339 SetSequentialFocusNavigationStartingPoint,
340
341 /// Replace the value of the control with the specified value and
342 /// reset the selection, if applicable. Requires [`ActionRequest::data`]
343 /// to be set to [`ActionData::Value`] or [`ActionData::NumericValue`].
344 SetValue,
345
346 ShowContextMenu,
347}
348
349impl Action {
350 fn mask(self) -> u32 {
351 1 << (self as u8)
352 }
353}
354
355#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
356#[repr(transparent)]
357struct Actions(u32);
358
359#[cfg(feature = "serde")]
360impl Serialize for Actions {
361 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
362 where
363 S: Serializer,
364 {
365 let mut seq = serializer.serialize_seq(None)?;
366 for i in 0..((size_of_val(&self.0) as u8) * 8) {
367 if let Some(action) = Action::n(i) {
368 if (self.0 & action.mask()) != 0 {
369 seq.serialize_element(&action)?;
370 }
371 }
372 }
373 seq.end()
374 }
375}
376
377#[cfg(feature = "serde")]
378struct ActionsVisitor;
379
380#[cfg(feature = "serde")]
381impl<'de> Visitor<'de> for ActionsVisitor {
382 type Value = Actions;
383
384 #[inline]
385 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
386 formatter.write_str("action set")
387 }
388
389 fn visit_seq<V>(self, mut seq: V) -> Result<Actions, V::Error>
390 where
391 V: SeqAccess<'de>,
392 {
393 let mut actions = Actions::default();
394 while let Some(action) = seq.next_element::<Action>()? {
395 actions.0 |= action.mask();
396 }
397 Ok(actions)
398 }
399}
400
401#[cfg(feature = "serde")]
402impl<'de> Deserialize<'de> for Actions {
403 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
404 where
405 D: Deserializer<'de>,
406 {
407 deserializer.deserialize_seq(ActionsVisitor)
408 }
409}
410
411#[cfg(feature = "schemars")]
412impl JsonSchema for Actions {
413 #[inline]
414 fn schema_name() -> String {
415 "Actions".into()
416 }
417
418 #[inline]
419 fn is_referenceable() -> bool {
420 false
421 }
422
423 fn json_schema(gen: &mut SchemaGenerator) -> Schema {
424 SchemaObject {
425 instance_type: Some(InstanceType::Array.into()),
426 array: Some(
427 ArrayValidation {
428 unique_items: Some(true),
429 items: Some(gen.subschema_for::<Action>().into()),
430 ..Default::default()
431 }
432 .into(),
433 ),
434 ..Default::default()
435 }
436 .into()
437 }
438}
439
440#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
441#[cfg_attr(feature = "enumn", derive(enumn::N))]
442#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
443#[cfg_attr(feature = "schemars", derive(JsonSchema))]
444#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
445#[cfg_attr(
446 feature = "pyo3",
447 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
448)]
449#[repr(u8)]
450pub enum Orientation {
451 /// E.g. most toolbars and separators.
452 Horizontal,
453 /// E.g. menu or combo box.
454 Vertical,
455}
456
457#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
458#[cfg_attr(feature = "enumn", derive(enumn::N))]
459#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
460#[cfg_attr(feature = "schemars", derive(JsonSchema))]
461#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
462#[cfg_attr(
463 feature = "pyo3",
464 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
465)]
466#[repr(u8)]
467pub enum TextDirection {
468 LeftToRight,
469 RightToLeft,
470 TopToBottom,
471 BottomToTop,
472}
473
474/// Indicates if a form control has invalid input or if a web DOM element has an
475/// [`aria-invalid`] attribute.
476///
477/// [`aria-invalid`]: https://www.w3.org/TR/wai-aria-1.1/#aria-invalid
478#[derive(Clone, Copy, Debug, PartialEq, Eq)]
479#[cfg_attr(feature = "enumn", derive(enumn::N))]
480#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
481#[cfg_attr(feature = "schemars", derive(JsonSchema))]
482#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
483#[cfg_attr(
484 feature = "pyo3",
485 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
486)]
487#[repr(u8)]
488pub enum Invalid {
489 True,
490 Grammar,
491 Spelling,
492}
493
494#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
495#[cfg_attr(feature = "enumn", derive(enumn::N))]
496#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
497#[cfg_attr(feature = "schemars", derive(JsonSchema))]
498#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
499#[cfg_attr(
500 feature = "pyo3",
501 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
502)]
503#[repr(u8)]
504pub enum Checked {
505 False,
506 True,
507 Mixed,
508}
509
510/// Describes the action that will be performed on a given node when
511/// executing the default action, which is a click.
512///
513/// In contrast to [`Action`], these describe what the user can do on the
514/// object, e.g. "press", not what happens to the object as a result.
515/// Only one verb can be used at a time to describe the default action.
516#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
517#[cfg_attr(feature = "enumn", derive(enumn::N))]
518#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
519#[cfg_attr(feature = "schemars", derive(JsonSchema))]
520#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
521#[cfg_attr(
522 feature = "pyo3",
523 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
524)]
525#[repr(u8)]
526pub enum DefaultActionVerb {
527 Click,
528 Focus,
529 Check,
530 Uncheck,
531 /// A click will be performed on one of the node's ancestors.
532 /// This happens when the node itself is not clickable, but one of its
533 /// ancestors has click handlers attached which are able to capture the click
534 /// as it bubbles up.
535 ClickAncestor,
536 Jump,
537 Open,
538 Press,
539 Select,
540 Unselect,
541}
542
543#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
544#[cfg_attr(feature = "enumn", derive(enumn::N))]
545#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
546#[cfg_attr(feature = "schemars", derive(JsonSchema))]
547#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
548#[cfg_attr(
549 feature = "pyo3",
550 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
551)]
552#[repr(u8)]
553pub enum SortDirection {
554 Unsorted,
555 Ascending,
556 Descending,
557 Other,
558}
559
560#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
561#[cfg_attr(feature = "enumn", derive(enumn::N))]
562#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
563#[cfg_attr(feature = "schemars", derive(JsonSchema))]
564#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
565#[cfg_attr(
566 feature = "pyo3",
567 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
568)]
569#[repr(u8)]
570pub enum AriaCurrent {
571 False,
572 True,
573 Page,
574 Step,
575 Location,
576 Date,
577 Time,
578}
579
580#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
581#[cfg_attr(feature = "enumn", derive(enumn::N))]
582#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
583#[cfg_attr(feature = "schemars", derive(JsonSchema))]
584#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
585#[cfg_attr(
586 feature = "pyo3",
587 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
588)]
589#[repr(u8)]
590pub enum AutoComplete {
591 Inline,
592 List,
593 Both,
594}
595
596#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
597#[cfg_attr(feature = "enumn", derive(enumn::N))]
598#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
599#[cfg_attr(feature = "schemars", derive(JsonSchema))]
600#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
601#[cfg_attr(
602 feature = "pyo3",
603 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
604)]
605#[repr(u8)]
606pub enum Live {
607 Off,
608 Polite,
609 Assertive,
610}
611
612#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
613#[cfg_attr(feature = "enumn", derive(enumn::N))]
614#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
615#[cfg_attr(feature = "schemars", derive(JsonSchema))]
616#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
617#[cfg_attr(
618 feature = "pyo3",
619 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
620)]
621#[repr(u8)]
622pub enum HasPopup {
623 True,
624 Menu,
625 Listbox,
626 Tree,
627 Grid,
628 Dialog,
629}
630
631#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
632#[cfg_attr(feature = "enumn", derive(enumn::N))]
633#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
634#[cfg_attr(feature = "schemars", derive(JsonSchema))]
635#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
636#[cfg_attr(
637 feature = "pyo3",
638 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
639)]
640#[repr(u8)]
641pub enum ListStyle {
642 Circle,
643 Disc,
644 Image,
645 Numeric,
646 Square,
647 /// Language specific ordering (alpha, roman, cjk-ideographic, etc...)
648 Other,
649}
650
651#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
652#[cfg_attr(feature = "enumn", derive(enumn::N))]
653#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
654#[cfg_attr(feature = "schemars", derive(JsonSchema))]
655#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
656#[cfg_attr(
657 feature = "pyo3",
658 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
659)]
660#[repr(u8)]
661pub enum TextAlign {
662 Left,
663 Right,
664 Center,
665 Justify,
666}
667
668#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
669#[cfg_attr(feature = "enumn", derive(enumn::N))]
670#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
671#[cfg_attr(feature = "schemars", derive(JsonSchema))]
672#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
673#[cfg_attr(
674 feature = "pyo3",
675 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
676)]
677#[repr(u8)]
678pub enum VerticalOffset {
679 Subscript,
680 Superscript,
681}
682
683#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
684#[cfg_attr(feature = "enumn", derive(enumn::N))]
685#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
686#[cfg_attr(feature = "schemars", derive(JsonSchema))]
687#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
688#[cfg_attr(
689 feature = "pyo3",
690 pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE")
691)]
692#[repr(u8)]
693pub enum TextDecoration {
694 Solid,
695 Dotted,
696 Dashed,
697 Double,
698 Wavy,
699}
700
701pub type NodeIdContent = u64;
702
703/// The stable identity of a [`Node`], unique within the node's tree.
704#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
705#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
706#[cfg_attr(feature = "schemars", derive(JsonSchema))]
707#[repr(transparent)]
708pub struct NodeId(pub NodeIdContent);
709
710impl From<NodeIdContent> for NodeId {
711 #[inline]
712 fn from(inner: NodeIdContent) -> Self {
713 Self(inner)
714 }
715}
716
717impl From<NodeId> for NodeIdContent {
718 #[inline]
719 fn from(outer: NodeId) -> Self {
720 outer.0
721 }
722}
723
724/// Defines a custom action for a UI element.
725///
726/// For example, a list UI can allow a user to reorder items in the list by dragging the
727/// items.
728#[derive(Clone, Debug, PartialEq, Eq)]
729#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
730#[cfg_attr(feature = "schemars", derive(JsonSchema))]
731#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
732#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
733pub struct CustomAction {
734 pub id: i32,
735 pub description: Box<str>,
736}
737
738#[derive(Clone, Copy, Debug, PartialEq, Eq)]
739#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
740#[cfg_attr(feature = "schemars", derive(JsonSchema))]
741#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
742#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
743pub struct TextPosition {
744 /// The node's role must be [`Role::InlineTextBox`].
745 pub node: NodeId,
746 /// The index of an item in [`Node::character_lengths`], or the length
747 /// of that slice if the position is at the end of the line.
748 pub character_index: usize,
749}
750
751#[derive(Clone, Copy, Debug, PartialEq, Eq)]
752#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
753#[cfg_attr(feature = "schemars", derive(JsonSchema))]
754#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
755#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
756pub struct TextSelection {
757 /// The position where the selection started, and which does not change
758 /// as the selection is expanded or contracted. If there is no selection
759 /// but only a caret, this must be equal to the value of [`TextSelection::focus`].
760 /// This is also known as a degenerate selection.
761 pub anchor: TextPosition,
762 /// The active end of the selection, which changes as the selection
763 /// is expanded or contracted, or the position of the caret if there is
764 /// no selection.
765 pub focus: TextPosition,
766}
767
768#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
769#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, enumn::N))]
770#[cfg_attr(feature = "schemars", derive(JsonSchema))]
771#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
772#[repr(u8)]
773enum Flag {
774 Hovered,
775 Hidden,
776 Linked,
777 Multiselectable,
778 Required,
779 Visited,
780 Busy,
781 LiveAtomic,
782 Modal,
783 TouchTransparent,
784 ReadOnly,
785 Disabled,
786 Bold,
787 Italic,
788 ClipsChildren,
789 IsLineBreakingObject,
790 IsPageBreakingObject,
791 IsSpellingError,
792 IsGrammarError,
793 IsSearchMatch,
794 IsSuggestion,
795}
796
797impl Flag {
798 fn mask(self) -> u32 {
799 1 << (self as u8)
800 }
801}
802
803// The following is based on the technique described here:
804// https://viruta.org/reducing-memory-consumption-in-librsvg-2.html
805
806#[derive(Clone, Debug, PartialEq)]
807enum PropertyValue {
808 None,
809 NodeIdVec(Vec<NodeId>),
810 NodeId(NodeId),
811 String(Box<str>),
812 F64(f64),
813 Usize(usize),
814 Color(u32),
815 TextDecoration(TextDecoration),
816 LengthSlice(Box<[u8]>),
817 CoordSlice(Box<[f32]>),
818 Bool(bool),
819 Invalid(Invalid),
820 Checked(Checked),
821 Live(Live),
822 DefaultActionVerb(DefaultActionVerb),
823 TextDirection(TextDirection),
824 Orientation(Orientation),
825 SortDirection(SortDirection),
826 AriaCurrent(AriaCurrent),
827 AutoComplete(AutoComplete),
828 HasPopup(HasPopup),
829 ListStyle(ListStyle),
830 TextAlign(TextAlign),
831 VerticalOffset(VerticalOffset),
832 Affine(Box<Affine>),
833 Rect(Rect),
834 TextSelection(Box<TextSelection>),
835 CustomActionVec(Vec<CustomAction>),
836}
837
838#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
839#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, enumn::N))]
840#[cfg_attr(feature = "schemars", derive(JsonSchema))]
841#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
842#[repr(u8)]
843enum PropertyId {
844 // NodeIdVec
845 Children,
846 Controls,
847 Details,
848 DescribedBy,
849 FlowTo,
850 LabelledBy,
851 RadioGroup,
852
853 // NodeId
854 ActiveDescendant,
855 ErrorMessage,
856 InPageLinkTarget,
857 MemberOf,
858 NextOnLine,
859 PreviousOnLine,
860 PopupFor,
861 TableHeader,
862 TableRowHeader,
863 TableColumnHeader,
864
865 // String
866 Name,
867 Description,
868 Value,
869 AccessKey,
870 ClassName,
871 FontFamily,
872 HtmlTag,
873 InnerHtml,
874 KeyboardShortcut,
875 Language,
876 Placeholder,
877 RoleDescription,
878 StateDescription,
879 Tooltip,
880 Url,
881
882 // f64
883 ScrollX,
884 ScrollXMin,
885 ScrollXMax,
886 ScrollY,
887 ScrollYMin,
888 ScrollYMax,
889 NumericValue,
890 MinNumericValue,
891 MaxNumericValue,
892 NumericValueStep,
893 NumericValueJump,
894 FontSize,
895 FontWeight,
896
897 // usize
898 TableRowCount,
899 TableColumnCount,
900 TableRowIndex,
901 TableColumnIndex,
902 TableCellColumnIndex,
903 TableCellColumnSpan,
904 TableCellRowIndex,
905 TableCellRowSpan,
906 HierarchicalLevel,
907 SizeOfSet,
908 PositionInSet,
909
910 // Color
911 ColorValue,
912 BackgroundColor,
913 ForegroundColor,
914
915 // TextDecoration
916 Overline,
917 Strikethrough,
918 Underline,
919
920 // LengthSlice
921 CharacterLengths,
922 WordLengths,
923
924 // CoordSlice
925 CharacterPositions,
926 CharacterWidths,
927
928 // bool
929 Expanded,
930 Selected,
931
932 // Unique enums
933 Invalid,
934 Checked,
935 Live,
936 DefaultActionVerb,
937 TextDirection,
938 Orientation,
939 SortDirection,
940 AriaCurrent,
941 AutoComplete,
942 HasPopup,
943 ListStyle,
944 TextAlign,
945 VerticalOffset,
946
947 // Other
948 Transform,
949 Bounds,
950 TextSelection,
951 CustomActions,
952
953 // This MUST be last.
954 Unset,
955}
956
957#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
958#[repr(transparent)]
959struct PropertyIndices([u8; PropertyId::Unset as usize]);
960
961impl Default for PropertyIndices {
962 fn default() -> Self {
963 Self([PropertyId::Unset as u8; PropertyId::Unset as usize])
964 }
965}
966
967#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
968struct NodeClass {
969 role: Role,
970 actions: Actions,
971 indices: PropertyIndices,
972}
973
974/// Allows nodes that have the same role, actions, and set of defined properties
975/// to share metadata. Each node has a class which is created by [`NodeBuilder`],
976/// and when [`NodeBuilder::build`] is called, the node's class is added
977/// to the provided instance of this struct if an identical class isn't
978/// in that set already. Once a class is added to a class set, it currently
979/// remains in that set for the life of that set, whether or not any nodes
980/// are still using the class.
981///
982/// It's not an error for different nodes in the same tree, or even subsequent
983/// versions of the same node, to be built from different class sets;
984/// it's merely suboptimal.
985///
986/// Note: This struct's `Default` implementation doesn't provide access to
987/// a shared set, as one might assume; it creates a new set. For a shared set,
988/// use [`NodeClassSet::lock_global`].
989#[derive(Clone, Default)]
990#[repr(transparent)]
991pub struct NodeClassSet(BTreeSet<Arc<NodeClass>>);
992
993impl NodeClassSet {
994 #[inline]
995 pub const fn new() -> Self {
996 Self(BTreeSet::new())
997 }
998
999 /// Accesses a shared class set guarded by a mutex.
1000 pub fn lock_global() -> impl DerefMut<Target = Self> {
1001 use std::sync::Mutex;
1002
1003 static INSTANCE: Mutex<NodeClassSet> = Mutex::new(NodeClassSet::new());
1004
1005 INSTANCE.lock().unwrap()
1006 }
1007}
1008
1009/// A single accessible object. A complete UI is represented as a tree of these.
1010///
1011/// For brevity, and to make more of the documentation usable in bindings
1012/// to other languages, documentation of getter methods is written as if
1013/// documenting fields in a struct, and such methods are referred to
1014/// as properties.
1015#[derive(Clone, Debug, PartialEq)]
1016pub struct Node {
1017 class: Arc<NodeClass>,
1018 flags: u32,
1019 props: Arc<[PropertyValue]>,
1020}
1021
1022/// Builds a [`Node`].
1023#[derive(Clone, Debug, Default, PartialEq)]
1024pub struct NodeBuilder {
1025 class: NodeClass,
1026 flags: u32,
1027 props: Vec<PropertyValue>,
1028}
1029
1030impl NodeClass {
1031 fn get_property<'a>(&self, props: &'a [PropertyValue], id: PropertyId) -> &'a PropertyValue {
1032 let index: u8 = self.indices.0[id as usize];
1033 if index == PropertyId::Unset as u8 {
1034 &PropertyValue::None
1035 } else {
1036 &props[index as usize]
1037 }
1038 }
1039}
1040
1041fn unexpected_property_type() -> ! {
1042 panic!();
1043}
1044
1045impl NodeBuilder {
1046 fn get_property_mut(&mut self, id: PropertyId, default: PropertyValue) -> &mut PropertyValue {
1047 let index = self.class.indices.0[id as usize] as usize;
1048 if index == PropertyId::Unset as usize {
1049 self.props.push(default);
1050 let index = self.props.len() - 1;
1051 self.class.indices.0[id as usize] = index as u8;
1052 &mut self.props[index]
1053 } else {
1054 if matches!(self.props[index], PropertyValue::None) {
1055 self.props[index] = default;
1056 }
1057 &mut self.props[index]
1058 }
1059 }
1060
1061 fn set_property(&mut self, id: PropertyId, value: PropertyValue) {
1062 let index = self.class.indices.0[id as usize];
1063 if index == PropertyId::Unset as u8 {
1064 self.props.push(value);
1065 self.class.indices.0[id as usize] = (self.props.len() - 1) as u8;
1066 } else {
1067 self.props[index as usize] = value;
1068 }
1069 }
1070
1071 fn clear_property(&mut self, id: PropertyId) {
1072 let index = self.class.indices.0[id as usize];
1073 if index != PropertyId::Unset as u8 {
1074 self.props[index as usize] = PropertyValue::None;
1075 }
1076 }
1077}
1078
1079macro_rules! flag_methods {
1080 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => {
1081 impl Node {
1082 $($(#[$doc])*
1083 #[inline]
1084 pub fn $getter(&self) -> bool {
1085 (self.flags & (Flag::$id).mask()) != 0
1086 })*
1087 }
1088 impl NodeBuilder {
1089 $($(#[$doc])*
1090 #[inline]
1091 pub fn $getter(&self) -> bool {
1092 (self.flags & (Flag::$id).mask()) != 0
1093 }
1094 #[inline]
1095 pub fn $setter(&mut self) {
1096 self.flags |= (Flag::$id).mask();
1097 }
1098 #[inline]
1099 pub fn $clearer(&mut self) {
1100 self.flags &= !((Flag::$id).mask());
1101 })*
1102 }
1103 }
1104}
1105
1106macro_rules! option_ref_type_getters {
1107 ($(($method:ident, $type:ty, $variant:ident)),+) => {
1108 impl NodeClass {
1109 $(fn $method<'a>(&self, props: &'a [PropertyValue], id: PropertyId) -> Option<&'a $type> {
1110 match self.get_property(props, id) {
1111 PropertyValue::None => None,
1112 PropertyValue::$variant(value) => Some(value),
1113 _ => unexpected_property_type(),
1114 }
1115 })*
1116 }
1117 }
1118}
1119
1120macro_rules! slice_type_getters {
1121 ($(($method:ident, $type:ty, $variant:ident)),+) => {
1122 impl NodeClass {
1123 $(fn $method<'a>(&self, props: &'a [PropertyValue], id: PropertyId) -> &'a [$type] {
1124 match self.get_property(props, id) {
1125 PropertyValue::None => &[],
1126 PropertyValue::$variant(value) => value,
1127 _ => unexpected_property_type(),
1128 }
1129 })*
1130 }
1131 }
1132}
1133
1134macro_rules! copy_type_getters {
1135 ($(($method:ident, $type:ty, $variant:ident)),+) => {
1136 impl NodeClass {
1137 $(fn $method(&self, props: &[PropertyValue], id: PropertyId) -> Option<$type> {
1138 match self.get_property(props, id) {
1139 PropertyValue::None => None,
1140 PropertyValue::$variant(value) => Some(*value),
1141 _ => unexpected_property_type(),
1142 }
1143 })*
1144 }
1145 }
1146}
1147
1148macro_rules! box_type_setters {
1149 ($(($method:ident, $type:ty, $variant:ident)),+) => {
1150 impl NodeBuilder {
1151 $(fn $method(&mut self, id: PropertyId, value: impl Into<Box<$type>>) {
1152 self.set_property(id, PropertyValue::$variant(value.into()));
1153 })*
1154 }
1155 }
1156}
1157
1158macro_rules! copy_type_setters {
1159 ($(($method:ident, $type:ty, $variant:ident)),+) => {
1160 impl NodeBuilder {
1161 $(fn $method(&mut self, id: PropertyId, value: $type) {
1162 self.set_property(id, PropertyValue::$variant(value));
1163 })*
1164 }
1165 }
1166}
1167
1168macro_rules! vec_type_methods {
1169 ($(($type:ty, $variant:ident, $getter:ident, $setter:ident, $pusher:ident)),+) => {
1170 $(slice_type_getters! {
1171 ($getter, $type, $variant)
1172 })*
1173 impl NodeBuilder {
1174 $(fn $setter(&mut self, id: PropertyId, value: impl Into<Vec<$type>>) {
1175 self.set_property(id, PropertyValue::$variant(value.into()));
1176 }
1177 fn $pusher(&mut self, id: PropertyId, item: $type) {
1178 match self.get_property_mut(id, PropertyValue::$variant(Vec::new())) {
1179 PropertyValue::$variant(v) => {
1180 v.push(item);
1181 }
1182 _ => unexpected_property_type(),
1183 }
1184 })*
1185 }
1186 }
1187}
1188
1189macro_rules! property_methods {
1190 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $type_getter:ident, $getter_result:ty, $setter:ident, $type_setter:ident, $setter_param:ty, $clearer:ident)),+) => {
1191 impl Node {
1192 $($(#[$doc])*
1193 #[inline]
1194 pub fn $getter(&self) -> $getter_result {
1195 self.class.$type_getter(&self.props, PropertyId::$id)
1196 })*
1197 }
1198 impl NodeBuilder {
1199 $($(#[$doc])*
1200 #[inline]
1201 pub fn $getter(&self) -> $getter_result {
1202 self.class.$type_getter(&self.props, PropertyId::$id)
1203 }
1204 #[inline]
1205 pub fn $setter(&mut self, value: $setter_param) {
1206 self.$type_setter(PropertyId::$id, value);
1207 }
1208 #[inline]
1209 pub fn $clearer(&mut self) {
1210 self.clear_property(PropertyId::$id);
1211 })*
1212 }
1213 }
1214}
1215
1216macro_rules! vec_property_methods {
1217 ($($(#[$doc:meta])* ($id:ident, $item_type:ty, $getter:ident, $type_getter:ident, $setter:ident, $type_setter:ident, $pusher:ident, $type_pusher:ident, $clearer:ident)),+) => {
1218 $(property_methods! {
1219 $(#[$doc])*
1220 ($id, $getter, $type_getter, &[$item_type], $setter, $type_setter, impl Into<Vec<$item_type>>, $clearer)
1221 }
1222 impl NodeBuilder {
1223 #[inline]
1224 pub fn $pusher(&mut self, item: $item_type) {
1225 self.$type_pusher(PropertyId::$id, item);
1226 }
1227 })*
1228 }
1229}
1230
1231macro_rules! node_id_vec_property_methods {
1232 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $pusher:ident, $clearer:ident)),+) => {
1233 $(vec_property_methods! {
1234 $(#[$doc])*
1235 ($id, NodeId, $getter, get_node_id_vec, $setter, set_node_id_vec, $pusher, push_to_node_id_vec, $clearer)
1236 })*
1237 }
1238}
1239
1240macro_rules! node_id_property_methods {
1241 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => {
1242 $(property_methods! {
1243 $(#[$doc])*
1244 ($id, $getter, get_node_id_property, Option<NodeId>, $setter, set_node_id_property, NodeId, $clearer)
1245 })*
1246 }
1247}
1248
1249macro_rules! string_property_methods {
1250 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => {
1251 $(property_methods! {
1252 $(#[$doc])*
1253 ($id, $getter, get_string_property, Option<&str>, $setter, set_string_property, impl Into<Box<str>>, $clearer)
1254 })*
1255 }
1256}
1257
1258macro_rules! f64_property_methods {
1259 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => {
1260 $(property_methods! {
1261 $(#[$doc])*
1262 ($id, $getter, get_f64_property, Option<f64>, $setter, set_f64_property, f64, $clearer)
1263 })*
1264 }
1265}
1266
1267macro_rules! usize_property_methods {
1268 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => {
1269 $(property_methods! {
1270 $(#[$doc])*
1271 ($id, $getter, get_usize_property, Option<usize>, $setter, set_usize_property, usize, $clearer)
1272 })*
1273 }
1274}
1275
1276macro_rules! color_property_methods {
1277 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => {
1278 $(property_methods! {
1279 $(#[$doc])*
1280 ($id, $getter, get_color_property, Option<u32>, $setter, set_color_property, u32, $clearer)
1281 })*
1282 }
1283}
1284
1285macro_rules! text_decoration_property_methods {
1286 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => {
1287 $(property_methods! {
1288 $(#[$doc])*
1289 ($id, $getter, get_text_decoration_property, Option<TextDecoration>, $setter, set_text_decoration_property, TextDecoration, $clearer)
1290 })*
1291 }
1292}
1293
1294macro_rules! length_slice_property_methods {
1295 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => {
1296 $(property_methods! {
1297 $(#[$doc])*
1298 ($id, $getter, get_length_slice_property, &[u8], $setter, set_length_slice_property, impl Into<Box<[u8]>>, $clearer)
1299 })*
1300 }
1301}
1302
1303macro_rules! coord_slice_property_methods {
1304 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => {
1305 $(property_methods! {
1306 $(#[$doc])*
1307 ($id, $getter, get_coord_slice_property, Option<&[f32]>, $setter, set_coord_slice_property, impl Into<Box<[f32]>>, $clearer)
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 }
1319}
1320
1321macro_rules! unique_enum_property_methods {
1322 ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => {
1323 impl Node {
1324 $($(#[$doc])*
1325 #[inline]
1326 pub fn $getter(&self) -> Option<$id> {
1327 match self.class.get_property(&self.props, PropertyId::$id) {
1328 PropertyValue::None => None,
1329 PropertyValue::$id(value) => Some(*value),
1330 _ => unexpected_property_type(),
1331 }
1332 })*
1333 }
1334 impl NodeBuilder {
1335 $($(#[$doc])*
1336 #[inline]
1337 pub fn $getter(&self) -> Option<$id> {
1338 match self.class.get_property(&self.props, PropertyId::$id) {
1339 PropertyValue::None => None,
1340 PropertyValue::$id(value) => Some(*value),
1341 _ => unexpected_property_type(),
1342 }
1343 }
1344 #[inline]
1345 pub fn $setter(&mut self, value: $id) {
1346 self.set_property(PropertyId::$id, PropertyValue::$id(value));
1347 }
1348 #[inline]
1349 pub fn $clearer(&mut self) {
1350 self.clear_property(PropertyId::$id);
1351 })*
1352 }
1353 }
1354}
1355
1356impl NodeBuilder {
1357 #[inline]
1358 pub fn new(role: Role) -> Self {
1359 Self {
1360 class: NodeClass {
1361 role,
1362 ..Default::default()
1363 },
1364 ..Default::default()
1365 }
1366 }
1367
1368 pub fn build(self, classes: &mut NodeClassSet) -> Node {
1369 let class = if let Some(class) = classes.0.get(&self.class) {
1370 Arc::clone(class)
1371 } else {
1372 let class = Arc::new(self.class);
1373 classes.0.insert(Arc::clone(&class));
1374 class
1375 };
1376 Node {
1377 class,
1378 flags: self.flags,
1379 props: self.props.into(),
1380 }
1381 }
1382}
1383
1384impl Node {
1385 #[inline]
1386 pub fn role(&self) -> Role {
1387 self.class.role
1388 }
1389}
1390
1391impl NodeBuilder {
1392 #[inline]
1393 pub fn role(&self) -> Role {
1394 self.class.role
1395 }
1396 #[inline]
1397 pub fn set_role(&mut self, value: Role) {
1398 self.class.role = value;
1399 }
1400}
1401
1402impl Node {
1403 #[inline]
1404 pub fn supports_action(&self, action: Action) -> bool {
1405 (self.class.actions.0 & action.mask()) != 0
1406 }
1407}
1408
1409impl NodeBuilder {
1410 #[inline]
1411 pub fn supports_action(&self, action: Action) -> bool {
1412 (self.class.actions.0 & action.mask()) != 0
1413 }
1414 #[inline]
1415 pub fn add_action(&mut self, action: Action) {
1416 self.class.actions.0 |= action.mask();
1417 }
1418 #[inline]
1419 pub fn remove_action(&mut self, action: Action) {
1420 self.class.actions.0 &= !(action.mask());
1421 }
1422 #[inline]
1423 pub fn clear_actions(&mut self) {
1424 self.class.actions.0 = 0;
1425 }
1426}
1427
1428flag_methods! {
1429 (Hovered, is_hovered, set_hovered, clear_hovered),
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 textbox 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 /// On radio buttons this should be set to a list of all of the buttons
1517 /// in the same group as this one, including this radio button itself.
1518 (RadioGroup, radio_group, set_radio_group, push_to_radio_group, clear_radio_group)
1519}
1520
1521node_id_property_methods! {
1522 (ActiveDescendant, active_descendant, set_active_descendant, clear_active_descendant),
1523 (ErrorMessage, error_message, set_error_message, clear_error_message),
1524 (InPageLinkTarget, in_page_link_target, set_in_page_link_target, clear_in_page_link_target),
1525 (MemberOf, member_of, set_member_of, clear_member_of),
1526 (NextOnLine, next_on_line, set_next_on_line, clear_next_on_line),
1527 (PreviousOnLine, previous_on_line, set_previous_on_line, clear_previous_on_line),
1528 (PopupFor, popup_for, set_popup_for, clear_popup_for),
1529 (TableHeader, table_header, set_table_header, clear_table_header),
1530 (TableRowHeader, table_row_header, set_table_row_header, clear_table_row_header),
1531 (TableColumnHeader, table_column_header, set_table_column_header, clear_table_column_header)
1532}
1533
1534string_property_methods! {
1535 (Name, name, set_name, clear_name),
1536 (Description, description, set_description, clear_description),
1537 (Value, value, set_value, clear_value),
1538 /// A single character, usually part of this node's name, that can be pressed,
1539 /// possibly along with a platform-specific modifier, to perform
1540 /// this node's default action. For menu items, the access key is only active
1541 /// while the menu is active, in contrast with [`keyboard_shortcut`];
1542 /// a single menu item may in fact have both properties.
1543 ///
1544 /// [`keyboard_shortcut`]: Node::keyboard_shortcut
1545 (AccessKey, access_key, set_access_key, clear_access_key),
1546 (ClassName, class_name, set_class_name, clear_class_name),
1547 /// Only present when different from parent.
1548 (FontFamily, font_family, set_font_family, clear_font_family),
1549 (HtmlTag, html_tag, set_html_tag, clear_html_tag),
1550 /// Inner HTML of an element. Only used for a top-level math element,
1551 /// to support third-party math accessibility products that parse MathML.
1552 (InnerHtml, inner_html, set_inner_html, clear_inner_html),
1553 /// A keystroke or sequence of keystrokes, complete with any required
1554 /// modifiers(s), that will perform this node's default action.
1555 /// The value of this property should be in a human-friendly format.
1556 (KeyboardShortcut, keyboard_shortcut, set_keyboard_shortcut, clear_keyboard_shortcut),
1557 /// Only present when different from parent.
1558 (Language, language, set_language, clear_language),
1559 /// If a text input has placeholder text, it should be exposed
1560 /// through this property rather than [`name`].
1561 ///
1562 /// [`name`]: Node::name
1563 (Placeholder, placeholder, set_placeholder, clear_placeholder),
1564 /// An optional string that may override an assistive technology's
1565 /// description of the node's role. Only provide this for custom control types.
1566 /// The value of this property should be in a human-friendly, localized format.
1567 (RoleDescription, role_description, set_role_description, clear_role_description),
1568 /// An optional string that may override an assistive technology's
1569 /// description of the node's state, replacing default strings such as
1570 /// "checked" or "selected". Note that most platform accessibility APIs
1571 /// and assistive technologies do not support this feature.
1572 (StateDescription, state_description, set_state_description, clear_state_description),
1573 /// If a node's only accessible name comes from a tooltip, it should be
1574 /// exposed through this property rather than [`name`].
1575 ///
1576 /// [`name`]: Node::name
1577 (Tooltip, tooltip, set_tooltip, clear_tooltip),
1578 (Url, url, set_url, clear_url)
1579}
1580
1581f64_property_methods! {
1582 (ScrollX, scroll_x, set_scroll_x, clear_scroll_x),
1583 (ScrollXMin, scroll_x_min, set_scroll_x_min, clear_scroll_x_min),
1584 (ScrollXMax, scroll_x_max, set_scroll_x_max, clear_scroll_x_max),
1585 (ScrollY, scroll_y, set_scroll_y, clear_scroll_y),
1586 (ScrollYMin, scroll_y_min, set_scroll_y_min, clear_scroll_y_min),
1587 (ScrollYMax, scroll_y_max, set_scroll_y_max, clear_scroll_y_max),
1588 (NumericValue, numeric_value, set_numeric_value, clear_numeric_value),
1589 (MinNumericValue, min_numeric_value, set_min_numeric_value, clear_min_numeric_value),
1590 (MaxNumericValue, max_numeric_value, set_max_numeric_value, clear_max_numeric_value),
1591 (NumericValueStep, numeric_value_step, set_numeric_value_step, clear_numeric_value_step),
1592 (NumericValueJump, numeric_value_jump, set_numeric_value_jump, clear_numeric_value_jump),
1593 /// Font size is in pixels.
1594 (FontSize, font_size, set_font_size, clear_font_size),
1595 /// Font weight can take on any arbitrary numeric value. Increments of 100 in
1596 /// range `[0, 900]` represent keywords such as light, normal, bold, etc.
1597 (FontWeight, font_weight, set_font_weight, clear_font_weight)
1598}
1599
1600usize_property_methods! {
1601 (TableRowCount, table_row_count, set_table_row_count, clear_table_row_count),
1602 (TableColumnCount, table_column_count, set_table_column_count, clear_table_column_count),
1603 (TableRowIndex, table_row_index, set_table_row_index, clear_table_row_index),
1604 (TableColumnIndex, table_column_index, set_table_column_index, clear_table_column_index),
1605 (TableCellColumnIndex, table_cell_column_index, set_table_cell_column_index, clear_table_cell_column_index),
1606 (TableCellColumnSpan, table_cell_column_span, set_table_cell_column_span, clear_table_cell_column_span),
1607 (TableCellRowIndex, table_cell_row_index, set_table_cell_row_index, clear_table_cell_row_index),
1608 (TableCellRowSpan, table_cell_row_span, set_table_cell_row_span, clear_table_cell_row_span),
1609 (HierarchicalLevel, hierarchical_level, set_hierarchical_level, clear_hierarchical_level),
1610 (SizeOfSet, size_of_set, set_size_of_set, clear_size_of_set),
1611 (PositionInSet, position_in_set, set_position_in_set, clear_position_in_set)
1612}
1613
1614color_property_methods! {
1615 /// For [`Role::ColorWell`], specifies the selected color in RGBA.
1616 (ColorValue, color_value, set_color_value, clear_color_value),
1617 /// Background color in RGBA.
1618 (BackgroundColor, background_color, set_background_color, clear_background_color),
1619 /// Foreground color in RGBA.
1620 (ForegroundColor, foreground_color, set_foreground_color, clear_foreground_color)
1621}
1622
1623text_decoration_property_methods! {
1624 (Overline, overline, set_overline, clear_overline),
1625 (Strikethrough, strikethrough, set_strikethrough, clear_strikethrough),
1626 (Underline, underline, set_underline, clear_underline)
1627}
1628
1629length_slice_property_methods! {
1630 /// For inline text. The length (non-inclusive) of each character
1631 /// in UTF-8 code units (bytes). The sum of these lengths must equal
1632 /// the length of [`value`], also in bytes.
1633 ///
1634 /// A character is defined as the smallest unit of text that
1635 /// can be selected. This isn't necessarily a single Unicode
1636 /// scalar value (code point). This is why AccessKit can't compute
1637 /// the lengths of the characters from the text itself; this information
1638 /// must be provided by the text editing implementation.
1639 ///
1640 /// If this node is the last text box in a line that ends with a hard
1641 /// line break, that line break should be included at the end of this
1642 /// node's value as either a CRLF or LF; in both cases, the line break
1643 /// should be counted as a single character for the sake of this slice.
1644 /// When the caret is at the end of such a line, the focus of the text
1645 /// selection should be on the line break, not after it.
1646 ///
1647 /// [`value`]: Node::value
1648 (CharacterLengths, character_lengths, set_character_lengths, clear_character_lengths),
1649
1650 /// For inline text. The length of each word in characters, as defined
1651 /// in [`character_lengths`]. The sum of these lengths must equal
1652 /// the length of [`character_lengths`].
1653 ///
1654 /// The end of each word is the beginning of the next word; there are no
1655 /// characters that are not considered part of a word. Trailing whitespace
1656 /// is typically considered part of the word that precedes it, while
1657 /// a line's leading whitespace is considered its own word. Whether
1658 /// punctuation is considered a separate word or part of the preceding
1659 /// word depends on the particular text editing implementation.
1660 /// Some editors may have their own definition of a word; for example,
1661 /// in an IDE, words may correspond to programming language tokens.
1662 ///
1663 /// Not all assistive technologies require information about word
1664 /// boundaries, and not all platform accessibility APIs even expose
1665 /// this information, but for assistive technologies that do use
1666 /// this information, users will get unpredictable results if the word
1667 /// boundaries exposed by the accessibility tree don't match
1668 /// the editor's behavior. This is why AccessKit does not determine
1669 /// word boundaries itself.
1670 ///
1671 /// [`character_lengths`]: Node::character_lengths
1672 (WordLengths, word_lengths, set_word_lengths, clear_word_lengths)
1673}
1674
1675coord_slice_property_methods! {
1676 /// For inline text. This is the position of each character within
1677 /// the node's bounding box, in the direction given by
1678 /// [`text_direction`], in the coordinate space of this node.
1679 ///
1680 /// When present, the length of this slice should be the same as the length
1681 /// of [`character_lengths`], including for lines that end
1682 /// with a hard line break. The position of such a line break should
1683 /// be the position where an end-of-paragraph marker would be rendered.
1684 ///
1685 /// This property is optional. Without it, AccessKit can't support some
1686 /// use cases, such as screen magnifiers that track the caret position
1687 /// or screen readers that display a highlight cursor. However,
1688 /// most text functionality still works without this information.
1689 ///
1690 /// [`text_direction`]: Node::text_direction
1691 /// [`character_lengths`]: Node::character_lengths
1692 (CharacterPositions, character_positions, set_character_positions, clear_character_positions),
1693
1694 /// For inline text. This is the advance width of each character,
1695 /// in the direction given by [`text_direction`], in the coordinate
1696 /// space of this node.
1697 ///
1698 /// When present, the length of this slice should be the same as the length
1699 /// of [`character_lengths`], including for lines that end
1700 /// with a hard line break. The width of such a line break should
1701 /// be non-zero if selecting the line break by itself results in
1702 /// a visible highlight (as in Microsoft Word), or zero if not
1703 /// (as in Windows Notepad).
1704 ///
1705 /// This property is optional. Without it, AccessKit can't support some
1706 /// use cases, such as screen magnifiers that track the caret position
1707 /// or screen readers that display a highlight cursor. However,
1708 /// most text functionality still works without this information.
1709 ///
1710 /// [`text_direction`]: Node::text_direction
1711 /// [`character_lengths`]: Node::character_lengths
1712 (CharacterWidths, character_widths, set_character_widths, clear_character_widths)
1713}
1714
1715bool_property_methods! {
1716 /// Whether this node is expanded, collapsed, or neither.
1717 ///
1718 /// Setting this to `false` means the node is collapsed; omitting it means this state
1719 /// isn't applicable.
1720 (Expanded, is_expanded, set_expanded, clear_expanded),
1721
1722 /// Indicates whether this node is selected or unselected.
1723 ///
1724 /// The absence of this flag (as opposed to a `false` setting)
1725 /// means that the concept of "selected" doesn't apply.
1726 /// When deciding whether to set the flag to false or omit it,
1727 /// consider whether it would be appropriate for a screen reader
1728 /// to announce "not selected". The ambiguity of this flag
1729 /// in platform accessibility APIs has made extraneous
1730 /// "not selected" announcements a common annoyance.
1731 (Selected, is_selected, set_selected, clear_selected)
1732}
1733
1734unique_enum_property_methods! {
1735 (Invalid, invalid, set_invalid, clear_invalid),
1736 (Checked, checked, set_checked, clear_checked),
1737 (Live, live, set_live, clear_live),
1738 (DefaultActionVerb, default_action_verb, set_default_action_verb, clear_default_action_verb),
1739 (TextDirection, text_direction, set_text_direction, clear_text_direction),
1740 (Orientation, orientation, set_orientation, clear_orientation),
1741 (SortDirection, sort_direction, set_sort_direction, clear_sort_direction),
1742 (AriaCurrent, aria_current, set_aria_current, clear_aria_current),
1743 (AutoComplete, auto_complete, set_auto_complete, clear_auto_complete),
1744 (HasPopup, has_popup, set_has_popup, clear_has_popup),
1745 /// The list style type. Only available on list items.
1746 (ListStyle, list_style, set_list_style, clear_list_style),
1747 (TextAlign, text_align, set_text_align, clear_text_align),
1748 (VerticalOffset, vertical_offset, set_vertical_offset, clear_vertical_offset)
1749}
1750
1751property_methods! {
1752 /// An affine transform to apply to any coordinates within this node
1753 /// and its descendants, including the [`bounds`] property of this node.
1754 /// The combined transforms of this node and its ancestors define
1755 /// the coordinate space of this node. /// This should be `None` if
1756 /// it would be set to the identity transform, which should be the case
1757 /// for most nodes.
1758 ///
1759 /// AccessKit expects the final transformed coordinates to be relative
1760 /// to the origin of the tree's container (e.g. window), in physical
1761 /// pixels, with the y coordinate being top-down.
1762 ///
1763 /// [`bounds`]: Node::bounds
1764 (Transform, transform, get_affine_property, Option<&Affine>, set_transform, set_affine_property, impl Into<Box<Affine>>, clear_transform),
1765
1766 /// The bounding box of this node, in the node's coordinate space.
1767 /// This property does not affect the coordinate space of either this node
1768 /// or its descendants; only the [`transform`] property affects that.
1769 /// This, along with the recommendation that most nodes should have
1770 /// a [`transform`] of `None`, implies that the `bounds` property
1771 /// of most nodes should be in the coordinate space of the nearest ancestor
1772 /// with a non-`None` [`transform`], or if there is no such ancestor,
1773 /// the tree's container (e.g. window).
1774 ///
1775 /// [`transform`]: Node::transform
1776 (Bounds, bounds, get_rect_property, Option<Rect>, set_bounds, set_rect_property, Rect, clear_bounds),
1777
1778 (TextSelection, text_selection, get_text_selection_property, Option<&TextSelection>, set_text_selection, set_text_selection_property, impl Into<Box<TextSelection>>, clear_text_selection)
1779}
1780
1781vec_property_methods! {
1782 (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)
1783}
1784
1785#[cfg(feature = "serde")]
1786#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
1787#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1788enum ClassFieldId {
1789 Role,
1790 Actions,
1791}
1792
1793#[cfg(feature = "serde")]
1794#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
1795#[serde(untagged)]
1796enum DeserializeKey {
1797 ClassField(ClassFieldId),
1798 Flag(Flag),
1799 Property(PropertyId),
1800 Unknown(String),
1801}
1802
1803#[cfg(feature = "serde")]
1804macro_rules! serialize_class_fields {
1805 ($self:ident, $map:ident, { $(($name:ident, $id:ident)),+ }) => {
1806 $($map.serialize_entry(&ClassFieldId::$id, &$self.class.$name)?;)*
1807 }
1808}
1809
1810#[cfg(feature = "serde")]
1811macro_rules! serialize_property {
1812 ($self:ident, $map:ident, $index:ident, $id:ident, { $($variant:ident),+ }) => {
1813 match &$self.props[$index as usize] {
1814 PropertyValue::None => (),
1815 $(PropertyValue::$variant(value) => {
1816 $map.serialize_entry(&$id, &Some(value))?;
1817 })*
1818 }
1819 }
1820}
1821
1822#[cfg(feature = "serde")]
1823macro_rules! deserialize_class_field {
1824 ($builder:ident, $map:ident, $key:ident, { $(($name:ident, $id:ident)),+ }) => {
1825 match $key {
1826 $(ClassFieldId::$id => {
1827 $builder.class.$name = $map.next_value()?;
1828 })*
1829 }
1830 }
1831}
1832
1833#[cfg(feature = "serde")]
1834macro_rules! deserialize_property {
1835 ($builder:ident, $map:ident, $key:ident, { $($type:ident { $($id:ident),+ }),+ }) => {
1836 match $key {
1837 $($(PropertyId::$id => {
1838 if let Some(value) = $map.next_value()? {
1839 $builder.set_property(PropertyId::$id, PropertyValue::$type(value));
1840 } else {
1841 $builder.clear_property(PropertyId::$id);
1842 }
1843 })*)*
1844 PropertyId::Unset => {
1845 let _ = $map.next_value::<IgnoredAny>()?;
1846 }
1847 }
1848 }
1849}
1850
1851#[cfg(feature = "serde")]
1852impl Serialize for Node {
1853 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1854 where
1855 S: Serializer,
1856 {
1857 let mut map = serializer.serialize_map(None)?;
1858 serialize_class_fields!(self, map, {
1859 (role, Role),
1860 (actions, Actions)
1861 });
1862 for i in 0..((size_of_val(&self.flags) as u8) * 8) {
1863 if let Some(flag) = Flag::n(i) {
1864 if (self.flags & flag.mask()) != 0 {
1865 map.serialize_entry(&flag, &true)?;
1866 }
1867 }
1868 }
1869 for (id, index) in self.class.indices.0.iter().copied().enumerate() {
1870 if index == PropertyId::Unset as u8 {
1871 continue;
1872 }
1873 let id = PropertyId::n(id as _).unwrap();
1874 serialize_property!(self, map, index, id, {
1875 NodeIdVec,
1876 NodeId,
1877 String,
1878 F64,
1879 Usize,
1880 Color,
1881 TextDecoration,
1882 LengthSlice,
1883 CoordSlice,
1884 Bool,
1885 Invalid,
1886 Checked,
1887 Live,
1888 DefaultActionVerb,
1889 TextDirection,
1890 Orientation,
1891 SortDirection,
1892 AriaCurrent,
1893 AutoComplete,
1894 HasPopup,
1895 ListStyle,
1896 TextAlign,
1897 VerticalOffset,
1898 Affine,
1899 Rect,
1900 TextSelection,
1901 CustomActionVec
1902 });
1903 }
1904 map.end()
1905 }
1906}
1907
1908#[cfg(feature = "serde")]
1909struct NodeVisitor;
1910
1911#[cfg(feature = "serde")]
1912impl<'de> Visitor<'de> for NodeVisitor {
1913 type Value = Node;
1914
1915 #[inline]
1916 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1917 formatter.write_str("struct Node")
1918 }
1919
1920 fn visit_map<V>(self, mut map: V) -> Result<Node, V::Error>
1921 where
1922 V: MapAccess<'de>,
1923 {
1924 let mut builder = NodeBuilder::default();
1925 while let Some(key) = map.next_key()? {
1926 match key {
1927 DeserializeKey::ClassField(id) => {
1928 deserialize_class_field!(builder, map, id, {
1929 (role, Role),
1930 (actions, Actions)
1931 });
1932 }
1933 DeserializeKey::Flag(flag) => {
1934 if map.next_value()? {
1935 builder.flags |= flag.mask();
1936 } else {
1937 builder.flags &= !(flag.mask());
1938 }
1939 }
1940 DeserializeKey::Property(id) => {
1941 deserialize_property!(builder, map, id, {
1942 NodeIdVec {
1943 Children,
1944 Controls,
1945 Details,
1946 DescribedBy,
1947 FlowTo,
1948 LabelledBy,
1949 RadioGroup
1950 },
1951 NodeId {
1952 ActiveDescendant,
1953 ErrorMessage,
1954 InPageLinkTarget,
1955 MemberOf,
1956 NextOnLine,
1957 PreviousOnLine,
1958 PopupFor,
1959 TableHeader,
1960 TableRowHeader,
1961 TableColumnHeader
1962 },
1963 String {
1964 Name,
1965 Description,
1966 Value,
1967 AccessKey,
1968 ClassName,
1969 FontFamily,
1970 HtmlTag,
1971 InnerHtml,
1972 KeyboardShortcut,
1973 Language,
1974 Placeholder,
1975 RoleDescription,
1976 StateDescription,
1977 Tooltip,
1978 Url
1979 },
1980 F64 {
1981 ScrollX,
1982 ScrollXMin,
1983 ScrollXMax,
1984 ScrollY,
1985 ScrollYMin,
1986 ScrollYMax,
1987 NumericValue,
1988 MinNumericValue,
1989 MaxNumericValue,
1990 NumericValueStep,
1991 NumericValueJump,
1992 FontSize,
1993 FontWeight
1994 },
1995 Usize {
1996 TableRowCount,
1997 TableColumnCount,
1998 TableRowIndex,
1999 TableColumnIndex,
2000 TableCellColumnIndex,
2001 TableCellColumnSpan,
2002 TableCellRowIndex,
2003 TableCellRowSpan,
2004 HierarchicalLevel,
2005 SizeOfSet,
2006 PositionInSet
2007 },
2008 Color {
2009 ColorValue,
2010 BackgroundColor,
2011 ForegroundColor
2012 },
2013 TextDecoration {
2014 Overline,
2015 Strikethrough,
2016 Underline
2017 },
2018 LengthSlice {
2019 CharacterLengths,
2020 WordLengths
2021 },
2022 CoordSlice {
2023 CharacterPositions,
2024 CharacterWidths
2025 },
2026 Bool {
2027 Expanded,
2028 Selected
2029 },
2030 Invalid { Invalid },
2031 Checked { Checked },
2032 Live { Live },
2033 DefaultActionVerb { DefaultActionVerb },
2034 TextDirection { TextDirection },
2035 Orientation { Orientation },
2036 SortDirection { SortDirection },
2037 AriaCurrent { AriaCurrent },
2038 AutoComplete { AutoComplete },
2039 HasPopup { HasPopup },
2040 ListStyle { ListStyle },
2041 TextAlign { TextAlign },
2042 VerticalOffset { VerticalOffset },
2043 Affine { Transform },
2044 Rect { Bounds },
2045 TextSelection { TextSelection },
2046 CustomActionVec { CustomActions }
2047 });
2048 }
2049 DeserializeKey::Unknown(_) => {
2050 let _ = map.next_value::<IgnoredAny>()?;
2051 }
2052 }
2053 }
2054
2055 Ok(builder.build(&mut NodeClassSet::lock_global()))
2056 }
2057}
2058
2059#[cfg(feature = "serde")]
2060impl<'de> Deserialize<'de> for Node {
2061 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2062 where
2063 D: Deserializer<'de>,
2064 {
2065 deserializer.deserialize_map(NodeVisitor)
2066 }
2067}
2068
2069#[cfg(feature = "schemars")]
2070macro_rules! add_schema_property {
2071 ($gen:ident, $properties:ident, $enum_value:expr, $type:ty) => {{
2072 let name = format!("{:?}", $enum_value);
2073 let name = name[..1].to_ascii_lowercase() + &name[1..];
2074 let subschema = $gen.subschema_for::<$type>();
2075 $properties.insert(name, subschema);
2076 }};
2077}
2078
2079#[cfg(feature = "schemars")]
2080macro_rules! add_flags_to_schema {
2081 ($gen:ident, $properties:ident, { $($variant:ident),+ }) => {
2082 $(add_schema_property!($gen, $properties, Flag::$variant, bool);)*
2083 }
2084}
2085
2086#[cfg(feature = "schemars")]
2087macro_rules! add_properties_to_schema {
2088 ($gen:ident, $properties:ident, { $($type:ty { $($id:ident),+ }),+ }) => {
2089 $($(add_schema_property!($gen, $properties, PropertyId::$id, Option<$type>);)*)*
2090 }
2091}
2092
2093#[cfg(feature = "schemars")]
2094impl JsonSchema for Node {
2095 #[inline]
2096 fn schema_name() -> String {
2097 "Node".into()
2098 }
2099
2100 fn json_schema(gen: &mut SchemaGenerator) -> Schema {
2101 let mut properties = SchemaMap::<String, Schema>::new();
2102 add_schema_property!(gen, properties, ClassFieldId::Role, Role);
2103 add_schema_property!(gen, properties, ClassFieldId::Actions, Actions);
2104 add_flags_to_schema!(gen, properties, {
2105 Hovered,
2106 Hidden,
2107 Linked,
2108 Multiselectable,
2109 Required,
2110 Visited,
2111 Busy,
2112 LiveAtomic,
2113 Modal,
2114 TouchTransparent,
2115 ReadOnly,
2116 Disabled,
2117 Bold,
2118 Italic,
2119 ClipsChildren,
2120 IsLineBreakingObject,
2121 IsPageBreakingObject,
2122 IsSpellingError,
2123 IsGrammarError,
2124 IsSearchMatch,
2125 IsSuggestion
2126 });
2127 add_properties_to_schema!(gen, properties, {
2128 Vec<NodeId> {
2129 Children,
2130 Controls,
2131 Details,
2132 DescribedBy,
2133 FlowTo,
2134 LabelledBy,
2135 RadioGroup
2136 },
2137 NodeId {
2138 ActiveDescendant,
2139 ErrorMessage,
2140 InPageLinkTarget,
2141 MemberOf,
2142 NextOnLine,
2143 PreviousOnLine,
2144 PopupFor,
2145 TableHeader,
2146 TableRowHeader,
2147 TableColumnHeader
2148 },
2149 Box<str> {
2150 Name,
2151 Description,
2152 Value,
2153 AccessKey,
2154 ClassName,
2155 FontFamily,
2156 HtmlTag,
2157 InnerHtml,
2158 KeyboardShortcut,
2159 Language,
2160 Placeholder,
2161 RoleDescription,
2162 StateDescription,
2163 Tooltip,
2164 Url
2165 },
2166 f64 {
2167 ScrollX,
2168 ScrollXMin,
2169 ScrollXMax,
2170 ScrollY,
2171 ScrollYMin,
2172 ScrollYMax,
2173 NumericValue,
2174 MinNumericValue,
2175 MaxNumericValue,
2176 NumericValueStep,
2177 NumericValueJump,
2178 FontSize,
2179 FontWeight
2180 },
2181 usize {
2182 TableRowCount,
2183 TableColumnCount,
2184 TableRowIndex,
2185 TableColumnIndex,
2186 TableCellColumnIndex,
2187 TableCellColumnSpan,
2188 TableCellRowIndex,
2189 TableCellRowSpan,
2190 HierarchicalLevel,
2191 SizeOfSet,
2192 PositionInSet
2193 },
2194 u32 {
2195 ColorValue,
2196 BackgroundColor,
2197 ForegroundColor
2198 },
2199 TextDecoration {
2200 Overline,
2201 Strikethrough,
2202 Underline
2203 },
2204 Box<[u8]> {
2205 CharacterLengths,
2206 WordLengths
2207 },
2208 Box<[f32]> {
2209 CharacterPositions,
2210 CharacterWidths
2211 },
2212 bool {
2213 Expanded,
2214 Selected
2215 },
2216 Invalid { Invalid },
2217 Checked { Checked },
2218 Live { Live },
2219 DefaultActionVerb { DefaultActionVerb },
2220 TextDirection { TextDirection },
2221 Orientation { Orientation },
2222 SortDirection { SortDirection },
2223 AriaCurrent { AriaCurrent },
2224 AutoComplete { AutoComplete },
2225 HasPopup { HasPopup },
2226 ListStyle { ListStyle },
2227 TextAlign { TextAlign },
2228 VerticalOffset { VerticalOffset },
2229 Affine { Transform },
2230 Rect { Bounds },
2231 TextSelection { TextSelection },
2232 Vec<CustomAction> { CustomActions }
2233 });
2234 SchemaObject {
2235 instance_type: Some(InstanceType::Object.into()),
2236 object: Some(
2237 ObjectValidation {
2238 properties,
2239 ..Default::default()
2240 }
2241 .into(),
2242 ),
2243 ..Default::default()
2244 }
2245 .into()
2246 }
2247}
2248
2249/// The data associated with an accessibility tree that's global to the
2250/// tree and not associated with any particular node.
2251#[derive(Clone, Debug, PartialEq, Eq)]
2252#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2253#[cfg_attr(feature = "schemars", derive(JsonSchema))]
2254#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
2255#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2256pub struct Tree {
2257 /// The identifier of the tree's root node.
2258 pub root: NodeId,
2259 /// The name of the application this tree belongs to.
2260 pub app_name: Option<String>,
2261 /// The name of the UI toolkit in use.
2262 pub toolkit_name: Option<String>,
2263 /// The version of the UI toolkit.
2264 pub toolkit_version: Option<String>,
2265}
2266
2267impl Tree {
2268 #[inline]
2269 pub fn new(root: NodeId) -> Tree {
2270 Tree {
2271 root,
2272 app_name: None,
2273 toolkit_name: None,
2274 toolkit_version: None,
2275 }
2276 }
2277}
2278
2279/// A serializable representation of an atomic change to a [`Tree`].
2280///
2281/// The sender and receiver must be in sync; the update is only meant
2282/// to bring the tree from a specific previous state into its next state.
2283/// Trying to apply it to the wrong tree should immediately panic.
2284///
2285/// Note that for performance, an update should only include nodes that are
2286/// new or changed. AccessKit platform adapters will avoid raising extraneous
2287/// events for nodes that have not changed since the previous update,
2288/// but there is still a cost in processing these nodes and replacing
2289/// the previous instances.
2290#[derive(Clone, Debug, PartialEq)]
2291#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2292#[cfg_attr(feature = "schemars", derive(JsonSchema))]
2293#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
2294#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2295pub struct TreeUpdate {
2296 /// Zero or more new or updated nodes. Order doesn't matter.
2297 ///
2298 /// Each node in this list will overwrite any existing node with the same ID.
2299 /// This means that when updating a node, fields that are unchanged
2300 /// from the previous version must still be set to the same values
2301 /// as before.
2302 ///
2303 /// It is an error for any node in this list to not be either the root
2304 /// or a child of another node. For nodes other than the root, the parent
2305 /// must be either an unchanged node already in the tree, or another node
2306 /// in this list.
2307 ///
2308 /// To add a child to the tree, the list must include both the child
2309 /// and an updated version of the parent with the child's ID added to
2310 /// [`Node::children`].
2311 ///
2312 /// To remove a child and all of its descendants, this list must include
2313 /// an updated version of the parent node with the child's ID removed
2314 /// from [`Node::children`]. Neither the child nor any of its descendants
2315 /// may be included in this list.
2316 pub nodes: Vec<(NodeId, Node)>,
2317
2318 /// Rarely updated information about the tree as a whole. This may be omitted
2319 /// if it has not changed since the previous update, but providing the same
2320 /// information again is also allowed. This is required when initializing
2321 /// a tree.
2322 pub tree: Option<Tree>,
2323
2324 /// The node within this tree that has keyboard focus when the native
2325 /// host (e.g. window) has focus. If no specific node within the tree
2326 /// has keyboard focus, this must be set to the root. The latest focus state
2327 /// must be provided with every tree update, even if the focus state
2328 /// didn't change in a given update.
2329 pub focus: NodeId,
2330}
2331
2332impl<T: FnOnce() -> TreeUpdate> From<T> for TreeUpdate {
2333 fn from(factory: T) -> Self {
2334 factory()
2335 }
2336}
2337
2338#[derive(Clone, Debug, PartialEq)]
2339#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2340#[cfg_attr(feature = "schemars", derive(JsonSchema))]
2341#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2342#[repr(C)]
2343pub enum ActionData {
2344 CustomAction(i32),
2345 Value(Box<str>),
2346 NumericValue(f64),
2347 /// Optional target rectangle for [`Action::ScrollIntoView`], in
2348 /// the coordinate space of the action's target node.
2349 ScrollTargetRect(Rect),
2350 /// Target for [`Action::ScrollToPoint`], in platform-native coordinates
2351 /// relative to the origin of the tree's container (e.g. window).
2352 ScrollToPoint(Point),
2353 /// Target for [`Action::SetScrollOffset`], in the coordinate space
2354 /// of the action's target node.
2355 SetScrollOffset(Point),
2356 SetTextSelection(TextSelection),
2357}
2358
2359#[derive(Clone, Debug, PartialEq)]
2360#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2361#[cfg_attr(feature = "schemars", derive(JsonSchema))]
2362#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
2363#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2364pub struct ActionRequest {
2365 pub action: Action,
2366 pub target: NodeId,
2367 pub data: Option<ActionData>,
2368}
2369
2370/// Handles requests from assistive technologies or other clients.
2371pub trait ActionHandler {
2372 /// Perform the requested action. If the requested action is not supported,
2373 /// this method must do nothing.
2374 ///
2375 /// The thread on which this method is called is platform-dependent.
2376 /// Refer to the platform adapter documentation for more details.
2377 ///
2378 /// This method may queue the request and handle it asynchronously.
2379 /// This behavior is preferred over blocking, e.g. when dispatching
2380 /// the request to another thread.
2381 fn do_action(&mut self, request: ActionRequest);
2382}
2383