From 282e26fe2333ef2f78f417f3276baee034ee51dc Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sat, 7 Jan 2023 17:10:42 -0600 Subject: [PATCH 01/45] refactor!: Make `accesskit::Node` opaque so we can optimize it --- common/src/lib.rs | 1948 +++++++++++++++++---- consumer/src/iterators.rs | 10 +- consumer/src/lib.rs | 114 +- consumer/src/node.rs | 167 +- consumer/src/text.rs | 338 ++-- consumer/src/tree.rs | 139 +- platforms/macos/src/node.rs | 2 +- platforms/windows/examples/hello_world.rs | 40 +- platforms/windows/src/tests/simple.rs | 20 +- platforms/windows/src/tests/subclassed.rs | 22 +- platforms/winit/examples/simple.rs | 44 +- 11 files changed, 2007 insertions(+), 837 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 1a9da492b..59deb456b 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -388,26 +388,6 @@ pub enum DescriptionFrom { Title, } -/// Function that can be performed when a dragged object is released -/// on a drop target. -/// -/// Note: [`aria-dropeffect`] is deprecated in WAI-ARIA 1.1. -/// -/// [`aria-dropeffect`]: https://www.w3.org/TR/wai-aria-1.1/#aria-dropeffect -#[derive(EnumSetType, Debug)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -#[cfg_attr(feature = "serde", enumset(serialize_as_list))] -pub enum DropEffect { - Copy, - Execute, - Link, - Move, - Popup, -} - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] @@ -424,15 +404,15 @@ pub enum TextDirection { /// [`aria-invalid`] attribute. /// /// [`aria-invalid`]: https://www.w3.org/TR/wai-aria-1.1/#aria-invalid -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub enum InvalidState { - False, +pub enum Invalid { True, - Other(Box), + Grammar, + Spelling, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -652,6 +632,11 @@ pub struct TextSelection { } /// A single accessible object. A complete UI is represented as a tree of these. +/// +/// For brevity, and to make more of the documentation usable in bindings +/// to other languages, documentation of getter methods is written as if +/// documenting fields in a struct, and such methods are referred to +/// as properties. #[derive(Clone, Debug, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] @@ -659,567 +644,1842 @@ pub struct TextSelection { #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct Node { - pub role: Role, - /// An affine transform to apply to any coordinates within this node - /// and its descendants, including the [`bounds`] field of this node. - /// The combined transforms of this node and its ancestors define - /// the coordinate space of this node. This field should be `None` - /// if it would be set to the identity transform, which should be - /// the case for most nodes. - /// - /// AccessKit expects the final transformed coordinates to be relative - /// to the origin of the tree's container (e.g. window), in physical - /// pixels, with the y coordinate being top-down. - /// - /// [`bounds`]: Node::bounds + role: Role, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub transform: Option>, - /// The bounding box of this node, in the node's coordinate space. - /// This field does not affect the coordinate space of either this node - /// or its descendants; only the [`transform`] field affects that. - /// This, along with the recommendation that most nodes should have `None` - /// in their [`transform`] field, implies that the `bounds` field - /// of most nodes should be in the coordinate space of the nearest ancestor - /// with a non-`None` [`transform`] field, or if there is no such ancestor, - /// the tree's container (e.g. window). - /// - /// [`transform`]: Node::transform + transform: Option>, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub bounds: Option, + bounds: Option, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty"))] - pub children: Vec, + children: Vec, /// Unordered set of actions supported by this node. #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "EnumSet::is_empty"))] - pub actions: EnumSet, + actions: EnumSet, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub name: Option>, - /// What information was used to compute the object's name. + name: Option>, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub name_from: Option, + name_from: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub description: Option>, - /// What information was used to compute the object's description. + description: Option>, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub description_from: Option, + description_from: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub value: Option>, + value: Option>, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub autofill_available: bool, - /// Whether this node is expanded, collapsed, or neither. - /// - /// Setting this to `false` means the node is collapsed; omitting it means this state - /// isn't applicable. + autofill_available: bool, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub expanded: Option, + expanded: Option, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub default: bool, + default: bool, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub editable: bool, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub focusable: bool, + editable: bool, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub orientation: Option, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub hovered: bool, - /// Exclude this node and its descendants from the tree presented to - /// assistive technologies, and from hit testing. + orientation: Option, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub hidden: bool, + hovered: bool, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub linked: bool, + hidden: bool, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub multiline: bool, + linked: bool, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub multiselectable: bool, + multiline: bool, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub protected: bool, + multiselectable: bool, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub required: bool, + protected: bool, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub visited: bool, - + required: bool, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub busy: bool, + visited: bool, - /// The object functions as a text field which exposes its descendants. - /// - /// Use cases include the root of a content-editable region, an ARIA - /// textbox which isn't currently editable and which has interactive - /// descendants, and a `` element that has "design-mode" set to "on". #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub nonatomic_text_field_root: bool, + busy: bool, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub live_atomic: bool, + nonatomic_text_field_root: bool, - /// If a dialog box is marked as explicitly modal. #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub modal: bool, + live_atomic: bool, - /// Set on a canvas element if it has fallback content. #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub canvas_has_fallback: bool, + modal: bool, - /// Indicates this node is user-scrollable, e.g. `overflow: scroll|auto`, as - /// opposed to only programmatically scrollable, like `overflow: hidden`, or - /// not scrollable at all, e.g. `overflow: visible`. #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub scrollable: bool, + canvas_has_fallback: bool, - /// A hint to clients that the node is clickable. #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub clickable: bool, + scrollable: bool, - /// Indicates that this node clips its children, i.e. may have - /// `overflow: hidden` or clip children by default. #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub clips_children: bool, + clips_children: bool, - /// Indicates that this node is not selectable because the style has - /// `user-select: none`. Note that there may be other reasons why a node is - /// not selectable - for example, bullets in a list. However, this attribute - /// is only set on `user-select: none`. #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub not_user_selectable_style: bool, + not_user_selectable_style: bool, - /// Indicates whether this node is selected or unselected. - /// - /// The absence of this flag (as opposed to a `false` setting) - /// means that the concept of "selected" doesn't apply. - /// When deciding whether to set the flag to false or omit it, - /// consider whether it would be appropriate for a screen reader - /// to announce "not selected". The ambiguity of this flag - /// in platform accessibility APIs has made extraneous - /// "not selected" announcements a common annoyance. #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub selected: Option, - /// Indicates whether this node is selected due to selection follows focus. + selected: Option, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub selected_from_focus: bool, - - /// Indicates whether this node can be grabbed for drag-and-drop operation. - /// - /// Setting this flag to `false` rather than omitting it means that - /// this node is not currently grabbed but it can be. - /// - /// Note: [`aria-grabbed`] is deprecated in WAI-ARIA 1.1. - /// - /// [`aria-grabbed`]: https://www.w3.org/TR/wai-aria-1.1/#aria-grabbed - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub grabbed: Option, - /// Note: [`aria-dropeffect`] is deprecated in WAI-ARIA 1.1. - /// - /// [`aria-dropeffect`]: https://www.w3.org/TR/wai-aria-1.1/#aria-dropeffect - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "EnumSet::is_empty"))] - pub drop_effects: EnumSet, + selected_from_focus: bool, - /// Indicates whether this node causes a hard line-break - /// (e.g. block level elements, or `
`). #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub is_line_breaking_object: bool, - /// Indicates whether this node causes a page break. + is_line_breaking_object: bool, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub is_page_breaking_object: bool, + is_page_breaking_object: bool, - /// True if the node has any ARIA attributes set. #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub has_aria_attribute: bool, + has_aria_attribute: bool, - /// This element allows touches to be passed through when a screen reader - /// is in touch exploration mode, e.g. a virtual keyboard normally - /// behaves this way. #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub touch_pass_through: bool, + touch_pass_through: bool, - /// Ids of nodes that are children of this node logically, but are - /// not children of this node in the tree structure. As an example, - /// a table cell is a child of a row, and an 'indirect' child of a - /// column. #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty"))] - pub indirect_children: Vec, + indirect_children: Vec, - // Relationships between this node and other nodes. #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub active_descendant: Option, + active_descendant: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub error_message: Option, + error_message: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub in_page_link_target: Option, + in_page_link_target: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub member_of: Option, + member_of: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub next_on_line: Option, + next_on_line: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub previous_on_line: Option, + previous_on_line: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub popup_for: Option, + popup_for: Option, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty"))] - pub controls: Vec, + controls: Vec, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty"))] - pub details: Vec, + details: Vec, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty"))] - pub described_by: Vec, + described_by: Vec, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty"))] - pub flow_to: Vec, + flow_to: Vec, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty"))] - pub labelled_by: Vec, - /// On radio buttons this should be set to a list of all of the buttons - /// in the same group as this one, including this radio button itself. + labelled_by: Vec, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty"))] - pub radio_group: Vec, + radio_group: Vec, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub is_spelling_error: bool, + is_spelling_error: bool, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub is_grammar_error: bool, + is_grammar_error: bool, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub is_search_match: bool, + is_search_match: bool, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub is_suggestion: bool, + is_suggestion: bool, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub text_direction: Option, + text_direction: Option, - /// For inline text. The length (non-inclusive) of each character - /// in UTF-8 code units (bytes). The sum of these lengths must equal - /// the length of [`Node::value`], also in bytes. - /// - /// A character is defined as the smallest unit of text that - /// can be selected. This isn't necessarily a single Unicode - /// scalar value (code point). This is why AccessKit can't compute - /// the lengths of the characters from the text itself; this information - /// must be provided by the text editing implementation. - /// - /// If this node is the last text box in a line that ends with a hard - /// line break, that line break should be included at the end of this - /// node's value as either a CRLF or LF; in both cases, the line break - /// should be counted as a single character for the sake of this slice. - /// When the caret is at the end of such a line, the focus of the text - /// selection should be on the line break, not after it. #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty"))] - pub character_lengths: Box<[u8]>, - /// For inline text. This is the position of each character within - /// the node's bounding box, in the direction given by - /// [`Node::text_direction`], in the coordinate space of this node. - /// - /// When present, the length of this slice should be the same as the length - /// of [`Node::character_lengths`], including for lines that end - /// with a hard line break. The position of such a line break should - /// be the position where an end-of-paragraph marker would be rendered. - /// - /// This field is optional. Without it, AccessKit can't support some - /// use cases, such as screen magnifiers that track the caret position - /// or screen readers that display a highlight cursor. However, - /// most text functionality still works without this information. + character_lengths: Box<[u8]>, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub character_positions: Option>, - /// For inline text. This is the advance width of each character, - /// in the direction given by [`Node::text_direction`], in the coordinate - /// space of this node. - /// - /// When present, the length of this slice should be the same as the length - /// of [`Node::character_lengths`], including for lines that end - /// with a hard line break. The width of such a line break should - /// be non-zero if selecting the line break by itself results in - /// a visible highlight (as in Microsoft Word), or zero if not - /// (as in Windows Notepad). - /// - /// This field is optional. Without it, AccessKit can't support some - /// use cases, such as screen magnifiers that track the caret position - /// or screen readers that display a highlight cursor. However, - /// most text functionality still works without this information. + character_positions: Option>, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub character_widths: Option>, + character_widths: Option>, - /// For inline text. The length of each word in characters, as defined - /// in [`Node::character_lengths`]. The sum of these lengths must equal - /// the length of [`Node::character_lengths`]. - /// - /// The end of each word is the beginning of the next word; there are no - /// characters that are not considered part of a word. Trailing whitespace - /// is typically considered part of the word that precedes it, while - /// a line's leading whitespace is considered its own word. Whether - /// punctuation is considered a separate word or part of the preceding - /// word depends on the particular text editing implementation. - /// Some editors may have their own definition of a word; for example, - /// in an IDE, words may correspond to programming language tokens. - /// - /// Not all assistive technologies require information about word - /// boundaries, and not all platform accessibility APIs even expose - /// this information, but for assistive technologies that do use - /// this information, users will get unpredictable results if the word - /// boundaries exposed by the accessibility tree don't match - /// the editor's behavior. This is why AccessKit does not determine - /// word boundaries itself. #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty"))] - pub word_lengths: Box<[u8]>, + word_lengths: Box<[u8]>, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty"))] - pub custom_actions: Box<[CustomAction]>, + custom_actions: Vec, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub access_key: Option>, + access_key: Option>, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub invalid_state: Option, + invalid: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub auto_complete: Option>, + auto_complete: Option>, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub checked_state: Option, + checked_state: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub checked_state_description: Option>, + checked_state_description: Option>, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub class_name: Option>, + class_name: Option>, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub css_display: Option>, + css_display: Option>, - /// Only present when different from parent. #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub font_family: Option>, + font_family: Option>, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub html_tag: Option>, + html_tag: Option>, - /// Inner HTML of an element. Only used for a top-level math element, - /// to support third-party math accessibility products that parse MathML. #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub inner_html: Option>, + inner_html: Option>, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub input_type: Option>, + input_type: Option>, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub key_shortcuts: Option>, + key_shortcuts: Option>, - /// Only present when different from parent. #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub language: Option>, + language: Option>, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub live_relevant: Option>, + live_relevant: Option>, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub live: Option, + live: Option, - /// Only if not already exposed in [`Node::name`] ([`NameFrom::Placeholder`]). #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub placeholder: Option>, + placeholder: Option>, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub aria_role: Option>, + aria_role: Option>, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub role_description: Option>, + role_description: Option>, - /// Only if not already exposed in [`Node::name`] ([`NameFrom::Title`]). #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub tooltip: Option>, + tooltip: Option>, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub url: Option>, + url: Option>, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub default_action_verb: Option, + default_action_verb: Option, - // Scrollable container attributes. #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub scroll_x: Option, + scroll_x: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub scroll_x_min: Option, + scroll_x_min: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub scroll_x_max: Option, + scroll_x_max: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub scroll_y: Option, + scroll_y: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub scroll_y_min: Option, + scroll_y_min: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub scroll_y_max: Option, + scroll_y_max: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub text_selection: Option, + text_selection: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub aria_column_count: Option, + aria_column_count: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub aria_cell_column_index: Option, + aria_cell_column_index: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub aria_cell_column_span: Option, + aria_cell_column_span: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub aria_row_count: Option, + aria_row_count: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub aria_cell_row_index: Option, + aria_cell_row_index: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub aria_cell_row_span: Option, + aria_cell_row_span: Option, - // Table attributes. #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub table_row_count: Option, + table_row_count: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub table_column_count: Option, + table_column_count: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub table_header: Option, + table_header: Option, - // Table row attributes. #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub table_row_index: Option, + table_row_index: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub table_row_header: Option, + table_row_header: Option, - // Table column attributes. #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub table_column_index: Option, + table_column_index: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub table_column_header: Option, + table_column_header: Option, - // Table cell attributes. #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub table_cell_column_index: Option, + table_cell_column_index: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub table_cell_column_span: Option, + table_cell_column_span: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub table_cell_row_index: Option, + table_cell_row_index: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub table_cell_row_span: Option, + table_cell_row_span: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub sort_direction: Option, + sort_direction: Option, - /// Tree control attributes. #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub hierarchical_level: Option, + hierarchical_level: Option, - /// Use for a textbox that allows focus/selection but not input. #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub read_only: bool, - /// Use for a control or group of controls that disallows input. + read_only: bool, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub disabled: bool, + disabled: bool, - // Position or Number of items in current set of listitems or treeitems #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub set_size: Option, + size_of_set: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub pos_in_set: Option, + position_in_set: Option, - /// For [`Role::ColorWell`], specifies the selected color in RGBA. #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub color_value: Option, + color_value: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub aria_current: Option, + aria_current: Option, - /// Background color in RGBA. #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub background_color: Option, - /// Foreground color in RGBA. + background_color: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub foreground_color: Option, + foreground_color: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub has_popup: Option, + has_popup: Option, - /// The list style type. Only available on list items. #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub list_style: Option, + list_style: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub text_align: Option, + text_align: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub vertical_offset: Option, + vertical_offset: Option, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub bold: bool, + bold: bool, #[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - pub italic: bool, + italic: bool, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub overline: Option, + overline: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub strikethrough: Option, + strikethrough: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub underline: Option, + underline: Option, - // Focus traversal order. #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub previous_focus: Option, + previous_focus: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub next_focus: Option, + next_focus: Option, - // Numeric value attributes. #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub numeric_value: Option, + numeric_value: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub min_numeric_value: Option, + min_numeric_value: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub max_numeric_value: Option, + max_numeric_value: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub numeric_value_step: Option, + numeric_value_step: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub numeric_value_jump: Option, + numeric_value_jump: Option, - // Text attributes. - /// Font size is in pixels. #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub font_size: Option, - /// Font weight can take on any arbitrary numeric value. Increments of 100 in - /// range `[0, 900]` represent keywords such as light, normal, bold, etc. + font_size: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub font_weight: Option, - /// The text indent of the text, in mm. + font_weight: Option, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - pub text_indent: Option, + text_indent: Option, +} + +impl Node { + pub fn new(role: Role) -> Self { + Self { + role, + ..Default::default() + } + } + + pub fn role(&self) -> Role { + self.role + } + pub fn set_role(&mut self, value: Role) { + self.role = value; + } + + /// An affine transform to apply to any coordinates within this node + /// and its descendants, including the [`bounds`] property of this node. + /// The combined transforms of this node and its ancestors define + /// the coordinate space of this node. /// This should be `None` if + /// it would be set to the identity transform, which should be the case + /// for most nodes. + /// + /// AccessKit expects the final transformed coordinates to be relative + /// to the origin of the tree's container (e.g. window), in physical + /// pixels, with the y coordinate being top-down. + /// + /// [`bounds`]: NodeProvider::bounds + pub fn transform(&self) -> Option { + self.transform.as_ref().map(|v| **v) + } + pub fn set_transform(&mut self, value: Affine) { + self.transform = Some(Box::new(value)); + } + pub fn clear_transform(&mut self) { + self.transform = None; + } + + /// The bounding box of this node, in the node's coordinate space. + /// This property does not affect the coordinate space of either this node + /// or its descendants; only the [`transform`] property affects that. + /// This, along with the recommendation that most nodes should have + /// a [`transform`] of `None`, implies that the `bounds` property + /// of most nodes should be in the coordinate space of the nearest ancestor + /// with a non-`None` [`transform`], or if there is no such ancestor, + /// the tree's container (e.g. window). + /// + /// [`transform`]: NodeProvider::transform + pub fn bounds(&self) -> Option { + self.bounds + } + pub fn set_bounds(&mut self, value: Rect) { + self.bounds = Some(value); + } + pub fn clear_bounds(&mut self) { + self.bounds = None; + } + + pub fn children(&self) -> &[NodeId] { + &self.children + } + pub fn set_children(&mut self, value: impl Into>) { + self.children = value.into(); + } + pub fn push_child(&mut self, id: NodeId) { + self.children.push(id); + } + pub fn clear_children(&mut self) { + self.children.clear(); + } + + pub fn supports_action(&self, action: Action) -> bool { + self.actions.contains(action) + } + pub fn add_action(&mut self, action: Action) { + self.actions.insert(action); + } + pub fn remove_action(&mut self, action: Action) { + self.actions.remove(action); + } + pub fn clear_actions(&mut self) { + self.actions.clear(); + } + + pub fn name(&self) -> Option<&str> { + self.name.as_deref() + } + pub fn set_name(&mut self, value: impl Into>) { + self.name = Some(value.into()); + } + pub fn clear_name(&mut self) { + self.name = None; + } + + /// What information was used to compute the object's name. + pub fn name_from(&self) -> Option { + self.name_from + } + pub fn set_name_from(&mut self, value: NameFrom) { + self.name_from = Some(value); + } + pub fn clear_name_from(&mut self) { + self.name_from = None; + } + + pub fn description(&self) -> Option<&str> { + self.description.as_deref() + } + pub fn set_description(&mut self, value: impl Into>) { + self.description = Some(value.into()); + } + pub fn clear_description(&mut self) { + self.description = None; + } + + /// What information was used to compute the object's description. + pub fn description_from(&self) -> Option { + self.description_from + } + pub fn set_description_from(&mut self, value: DescriptionFrom) { + self.description_from = Some(value); + } + pub fn clear_description_from(&mut self) { + self.description_from = None; + } + + pub fn value(&self) -> Option<&str> { + self.value.as_deref() + } + pub fn set_value(&mut self, value: impl Into>) { + self.value = Some(value.into()); + } + pub fn clear_value(&mut self) { + self.value = None; + } + + pub fn is_autofill_available(&self) -> bool { + self.autofill_available + } + pub fn set_autofill_available(&mut self, value: bool) { + self.autofill_available = value; + } + + /// Whether this node is expanded, collapsed, or neither. + /// + /// Setting this to `false` means the node is collapsed; omitting it means this state + /// isn't applicable. + pub fn is_expanded(&self) -> Option { + self.expanded + } + pub fn set_expanded(&mut self, value: bool) { + self.expanded = Some(value); + } + pub fn clear_expanded(&mut self) { + self.expanded = None; + } + + pub fn is_default(&self) -> bool { + self.default + } + pub fn set_default(&mut self, value: bool) { + self.default = value; + } + + pub fn is_editable(&self) -> bool { + self.editable + } + pub fn set_editable(&mut self, value: bool) { + self.editable = value; + } + + pub fn orientation(&self) -> Option { + self.orientation + } + pub fn set_orientation(&mut self, value: Orientation) { + self.orientation = Some(value); + } + pub fn clear_orientation(&mut self) { + self.orientation = None; + } + + pub fn is_hovered(&self) -> bool { + self.hovered + } + pub fn set_hovered(&mut self, value: bool) { + self.hovered = value; + } + + /// Exclude this node and its descendants from the tree presented to + /// assistive technologies, and from hit testing. + pub fn is_hidden(&self) -> bool { + self.hidden + } + pub fn set_hidden(&mut self, value: bool) { + self.hidden = value; + } + + pub fn is_linked(&self) -> bool { + self.linked + } + pub fn set_linked(&mut self, value: bool) { + self.linked = value; + } + + pub fn is_multiline(&self) -> bool { + self.multiline + } + pub fn set_multiline(&mut self, value: bool) { + self.multiline = value; + } + + pub fn is_multiselectable(&self) -> bool { + self.multiselectable + } + pub fn set_multiselectable(&mut self, value: bool) { + self.multiselectable = value; + } + + pub fn is_protected(&self) -> bool { + self.protected + } + pub fn set_protected(&mut self, value: bool) { + self.protected = value; + } + + pub fn is_required(&self) -> bool { + self.required + } + pub fn set_required(&mut self, value: bool) { + self.required = value; + } + + pub fn is_visited(&self) -> bool { + self.visited + } + pub fn set_visited(&mut self, value: bool) { + self.visited = value; + } + + pub fn is_busy(&self) -> bool { + self.busy + } + pub fn set_busy(&mut self, value: bool) { + self.busy = value; + } + + /// The object functions as a text field which exposes its descendants. + /// + /// Use cases include the root of a content-editable region, an ARIA + /// textbox which isn't currently editable and which has interactive + /// descendants, and a `` element that has "design-mode" set to "on". + pub fn is_nonatomic_text_field_root(&self) -> bool { + self.nonatomic_text_field_root + } + pub fn set_nonatomic_text_field_root(&mut self, value: bool) { + self.nonatomic_text_field_root = value; + } + + pub fn is_live_atomic(&self) -> bool { + self.live_atomic + } + pub fn set_live_atomic(&mut self, value: bool) { + self.live_atomic = value; + } + + /// If a dialog box is marked as explicitly modal. + pub fn is_modal(&self) -> bool { + self.modal + } + pub fn set_modal(&mut self, value: bool) { + self.modal = value; + } + + /// Set on a canvas element if it has fallback content. + pub fn canvas_has_fallback(&self) -> bool { + self.canvas_has_fallback + } + pub fn set_canvas_has_fallback(&mut self, value: bool) { + self.canvas_has_fallback = value; + } + + /// Indicates this node is user-scrollable, e.g. `overflow: scroll|auto`, as + /// opposed to only programmatically scrollable, like `overflow: hidden`, or + /// not scrollable at all, e.g. `overflow: visible`. + pub fn is_scrollable(&self) -> bool { + self.scrollable + } + pub fn set_scrollable(&mut self, value: bool) { + self.scrollable = value; + } + + /// Indicates that this node clips its children, i.e. may have + /// `overflow: hidden` or clip children by default. + pub fn clips_children(&self) -> bool { + self.clips_children + } + pub fn set_clips_children(&mut self, value: bool) { + self.clips_children = value; + } + + /// Indicates that this node is not selectable because the style has + /// `user-select: none`. Note that there may be other reasons why a node is + /// not selectable - for example, bullets in a list. However, this attribute + /// is only set on `user-select: none`. + pub fn is_not_user_selectable_style(&self) -> bool { + self.not_user_selectable_style + } + pub fn set_not_user_selectable_style(&mut self, value: bool) { + self.not_user_selectable_style = value; + } + + /// Indicates whether this node is selected or unselected. + /// + /// The absence of this flag (as opposed to a `false` setting) + /// means that the concept of "selected" doesn't apply. + /// When deciding whether to set the flag to false or omit it, + /// consider whether it would be appropriate for a screen reader + /// to announce "not selected". The ambiguity of this flag + /// in platform accessibility APIs has made extraneous + /// "not selected" announcements a common annoyance. + pub fn is_selected(&self) -> Option { + self.selected + } + pub fn set_selected(&mut self, value: bool) { + self.selected = Some(value); + } + pub fn clear_selected(&mut self) { + self.selected = None; + } + + /// Indicates whether this node is selected due to selection follows focus. + pub fn is_selected_from_focus(&self) -> bool { + self.selected_from_focus + } + pub fn set_selected_from_focus(&mut self, value: bool) { + self.selected_from_focus = value; + } + + /// Indicates whether this node causes a hard line-break + /// (e.g. block level elements, or `
`). + pub fn is_line_breaking_object(&self) -> bool { + self.is_line_breaking_object + } + pub fn set_is_line_breaking_object(&mut self, value: bool) { + self.is_line_breaking_object = value; + } + + /// Indicates whether this node causes a page break. + pub fn is_page_breaking_object(&self) -> bool { + self.is_page_breaking_object + } + pub fn set_is_page_breaking_object(&mut self, value: bool) { + self.is_page_breaking_object = value; + } + + /// True if the node has any ARIA attributes set. + pub fn has_aria_attribute(&self) -> bool { + self.has_aria_attribute + } + pub fn set_has_aria_attribute(&mut self, value: bool) { + self.has_aria_attribute = value; + } + + /// This element allows touches to be passed through when a screen reader + /// is in touch exploration mode, e.g. a virtual keyboard normally + /// behaves this way. + pub fn is_touch_pass_through(&self) -> bool { + self.touch_pass_through + } + pub fn set_touch_pass_through(&mut self, value: bool) { + self.touch_pass_through = value; + } + + /// Ids of nodes that are children of this node logically, but are + /// not children of this node in the tree structure. As an example, + /// a table cell is a child of a row, and an 'indirect' child of a + /// column. + pub fn indirect_children(&self) -> &[NodeId] { + &self.indirect_children + } + pub fn set_indirect_children(&mut self, value: impl Into>) { + self.indirect_children = value.into(); + } + pub fn push_indirect_child(&mut self, id: NodeId) { + self.indirect_children.push(id); + } + pub fn clear_indirect_children(&mut self) { + self.indirect_children.clear(); + } + + // Relationships between this node and other nodes. + + pub fn active_descendant(&self) -> Option { + self.active_descendant + } + pub fn set_active_descendant(&mut self, value: NodeId) { + self.active_descendant = Some(value); + } + pub fn clear_active_descendant(&mut self) { + self.active_descendant = None; + } + + pub fn error_message(&self) -> Option { + self.error_message + } + pub fn set_error_message(&mut self, value: NodeId) { + self.error_message = Some(value); + } + pub fn clear_error_message(&mut self) { + self.error_message = None; + } + + pub fn in_page_link_target(&self) -> Option { + self.in_page_link_target + } + pub fn set_in_page_link_target(&mut self, value: NodeId) { + self.in_page_link_target = Some(value); + } + pub fn clear_in_page_link_target(&mut self) { + self.in_page_link_target = None; + } + + pub fn member_of(&self) -> Option { + self.member_of + } + pub fn set_member_of(&mut self, value: NodeId) { + self.member_of = Some(value); + } + pub fn clear_member_of(&mut self) { + self.member_of = None; + } + + pub fn next_on_line(&self) -> Option { + self.next_on_line + } + pub fn set_next_on_line(&mut self, value: NodeId) { + self.next_on_line = Some(value); + } + pub fn clear_next_on_line(&mut self) { + self.next_on_line = None; + } + + pub fn previous_on_line(&self) -> Option { + self.previous_on_line + } + pub fn set_previous_on_line(&mut self, value: NodeId) { + self.previous_on_line = Some(value); + } + pub fn clear_previous_on_line(&mut self) { + self.previous_on_line = None; + } + + pub fn popup_for(&self) -> Option { + self.popup_for + } + pub fn set_popup_for(&mut self, value: NodeId) { + self.popup_for = Some(value); + } + pub fn clear_popup_for(&mut self) { + self.popup_for = None; + } + + pub fn controls(&self) -> &[NodeId] { + &self.controls + } + pub fn set_controls(&mut self, value: impl Into>) { + self.controls = value.into(); + } + pub fn push_controls(&mut self, id: NodeId) { + self.controls.push(id); + } + pub fn clear_controls(&mut self) { + self.controls.clear(); + } + + pub fn details(&self) -> &[NodeId] { + &self.details + } + pub fn set_details(&mut self, value: impl Into>) { + self.details = value.into(); + } + pub fn push_details(&mut self, id: NodeId) { + self.details.push(id); + } + pub fn clear_details(&mut self) { + self.details.clear(); + } + + pub fn described_by(&self) -> &[NodeId] { + &self.described_by + } + pub fn set_described_by(&mut self, value: impl Into>) { + self.described_by = value.into(); + } + pub fn push_described_by(&mut self, id: NodeId) { + self.described_by.push(id); + } + pub fn clear_described_by(&mut self) { + self.described_by.clear(); + } + + pub fn flow_to(&self) -> &[NodeId] { + &self.flow_to + } + pub fn set_flow_to(&mut self, value: impl Into>) { + self.flow_to = value.into(); + } + pub fn push_flow_to(&mut self, id: NodeId) { + self.flow_to.push(id); + } + pub fn clear_flow_to(&mut self) { + self.flow_to.clear(); + } + + pub fn labelled_by(&self) -> &[NodeId] { + &self.labelled_by + } + pub fn set_labelled_by(&mut self, value: impl Into>) { + self.labelled_by = value.into(); + } + pub fn push_labelled_by(&mut self, id: NodeId) { + self.labelled_by.push(id); + } + pub fn clear_labelled_by(&mut self) { + self.labelled_by.clear(); + } + + /// On radio buttons this should be set to a list of all of the buttons + /// in the same group as this one, including this radio button itself. + pub fn radio_group(&self) -> &[NodeId] { + &self.radio_group + } + pub fn set_radio_group(&mut self, value: impl Into>) { + self.radio_group = value.into(); + } + pub fn push_to_radio_group(&mut self, id: NodeId) { + self.radio_group.push(id); + } + pub fn clear_radio_group(&mut self) { + self.radio_group.clear(); + } + + pub fn is_spelling_error(&self) -> bool { + self.is_spelling_error + } + pub fn set_is_spelling_error(&mut self, value: bool) { + self.is_spelling_error = value; + } + + pub fn is_grammar_error(&self) -> bool { + self.is_grammar_error + } + pub fn set_is_grammar_error(&mut self, value: bool) { + self.is_grammar_error = value; + } + + pub fn is_search_match(&self) -> bool { + self.is_search_match + } + pub fn set_is_search_match(&mut self, value: bool) { + self.is_search_match = value; + } + + pub fn is_suggestion(&self) -> bool { + self.is_suggestion + } + pub fn set_is_suggestion(&mut self, value: bool) { + self.is_suggestion = value; + } + + pub fn text_direction(&self) -> Option { + self.text_direction + } + pub fn set_text_direction(&mut self, value: TextDirection) { + self.text_direction = Some(value); + } + pub fn clear_text_direction(&mut self) { + self.text_direction = None; + } + + /// For inline text. The length (non-inclusive) of each character + /// in UTF-8 code units (bytes). The sum of these lengths must equal + /// the length of [`value`], also in bytes. + /// + /// A character is defined as the smallest unit of text that + /// can be selected. This isn't necessarily a single Unicode + /// scalar value (code point). This is why AccessKit can't compute + /// the lengths of the characters from the text itself; this information + /// must be provided by the text editing implementation. + /// + /// If this node is the last text box in a line that ends with a hard + /// line break, that line break should be included at the end of this + /// node's value as either a CRLF or LF; in both cases, the line break + /// should be counted as a single character for the sake of this slice. + /// When the caret is at the end of such a line, the focus of the text + /// selection should be on the line break, not after it. + /// + /// [`value`]: NodeProvider::value + pub fn character_lengths(&self) -> &[u8] { + &self.character_lengths + } + pub fn set_character_lengths(&mut self, value: impl Into>) { + self.character_lengths = value.into(); + } + + /// For inline text. This is the position of each character within + /// the node's bounding box, in the direction given by + /// [`text_direction`], in the coordinate space of this node. + /// + /// When present, the length of this slice should be the same as the length + /// of [`character_lengths`], including for lines that end + /// with a hard line break. The position of such a line break should + /// be the position where an end-of-paragraph marker would be rendered. + /// + /// This property is optional. Without it, AccessKit can't support some + /// use cases, such as screen magnifiers that track the caret position + /// or screen readers that display a highlight cursor. However, + /// most text functionality still works without this information. + /// + /// [`text_direction`]: NodeProvider::text_direction + /// [`character_lengths`]: NodeProvider::character_lengths + pub fn character_positions(&self) -> Option<&[f32]> { + self.character_positions.as_deref() + } + pub fn set_character_positions(&mut self, value: impl Into>) { + self.character_positions = Some(value.into()); + } + pub fn clear_character_positions(&mut self) { + self.character_positions = None; + } + + /// For inline text. This is the advance width of each character, + /// in the direction given by [`text_direction`], in the coordinate + /// space of this node. + /// + /// When present, the length of this slice should be the same as the length + /// of [`character_lengths`], including for lines that end + /// with a hard line break. The width of such a line break should + /// be non-zero if selecting the line break by itself results in + /// a visible highlight (as in Microsoft Word), or zero if not + /// (as in Windows Notepad). + /// + /// This property is optional. Without it, AccessKit can't support some + /// use cases, such as screen magnifiers that track the caret position + /// or screen readers that display a highlight cursor. However, + /// most text functionality still works without this information. + /// + /// [`text_direction`]: NodeProvider::text_direction + /// [`character_lengths`]: NodeProvider::character_lengths + pub fn character_widths(&self) -> Option<&[f32]> { + self.character_widths.as_deref() + } + pub fn set_character_widths(&mut self, value: impl Into>) { + self.character_widths = Some(value.into()); + } + pub fn clear_character_widths(&mut self) { + self.character_widths = None; + } + + /// For inline text. The length of each word in characters, as defined + /// in [`character_lengths`]. The sum of these lengths must equal + /// the length of [`character_lengths`]. + /// + /// The end of each word is the beginning of the next word; there are no + /// characters that are not considered part of a word. Trailing whitespace + /// is typically considered part of the word that precedes it, while + /// a line's leading whitespace is considered its own word. Whether + /// punctuation is considered a separate word or part of the preceding + /// word depends on the particular text editing implementation. + /// Some editors may have their own definition of a word; for example, + /// in an IDE, words may correspond to programming language tokens. + /// + /// Not all assistive technologies require information about word + /// boundaries, and not all platform accessibility APIs even expose + /// this information, but for assistive technologies that do use + /// this information, users will get unpredictable results if the word + /// boundaries exposed by the accessibility tree don't match + /// the editor's behavior. This is why AccessKit does not determine + /// word boundaries itself. + /// + /// [`character_lengths`]: NodeProvider::character_lengths + pub fn word_lengths(&self) -> &[u8] { + &self.word_lengths + } + pub fn set_word_lengths(&mut self, value: impl Into>) { + self.word_lengths = value.into(); + } + + pub fn custom_actions(&self) -> &[CustomAction] { + &self.custom_actions + } + pub fn set_custom_actions(&mut self, value: impl Into>) { + self.custom_actions = value.into(); + } + pub fn push_custom_action(&mut self, action: CustomAction) { + self.custom_actions.push(action); + } + pub fn clear_custom_actions(&mut self) { + self.custom_actions.clear(); + } + + pub fn access_key(&self) -> Option<&str> { + self.access_key.as_deref() + } + pub fn set_access_key(&mut self, value: impl Into>) { + self.access_key = Some(value.into()); + } + pub fn clear_access_key(&mut self) { + self.access_key = None; + } + + pub fn invalid(&self) -> Option { + self.invalid + } + pub fn set_invalid(&mut self, value: Invalid) { + self.invalid = Some(value); + } + pub fn clear_invalid(&mut self) { + self.invalid = None; + } + + pub fn auto_complete(&self) -> Option<&str> { + self.auto_complete.as_deref() + } + pub fn set_auto_complete(&mut self, value: impl Into>) { + self.auto_complete = Some(value.into()); + } + pub fn clear_auto_complete(&mut self) { + self.auto_complete = None; + } + + pub fn checked_state(&self) -> Option { + self.checked_state + } + pub fn set_checked_state(&mut self, value: CheckedState) { + self.checked_state = Some(value); + } + pub fn clear_checked_state(&mut self) { + self.checked_state = None; + } + + pub fn checked_state_description(&self) -> Option<&str> { + self.checked_state_description.as_deref() + } + pub fn set_checked_state_description(&mut self, value: impl Into>) { + self.checked_state_description = Some(value.into()); + } + pub fn clear_checked_state_description(&mut self) { + self.checked_state_description = None; + } + + pub fn class_name(&self) -> Option<&str> { + self.class_name.as_deref() + } + pub fn set_class_name(&mut self, value: impl Into>) { + self.class_name = Some(value.into()); + } + pub fn clear_class_name(&mut self) { + self.class_name = None; + } + + pub fn css_display(&self) -> Option<&str> { + self.css_display.as_deref() + } + pub fn set_css_display(&mut self, value: impl Into>) { + self.css_display = Some(value.into()); + } + pub fn clear_css_display(&mut self) { + self.css_display = None; + } + + /// Only present when different from parent. + pub fn font_family(&self) -> Option<&str> { + self.font_family.as_deref() + } + pub fn set_font_family(&mut self, value: impl Into>) { + self.font_family = Some(value.into()); + } + pub fn clear_font_family(&mut self) { + self.font_family = None; + } + + pub fn html_tag(&self) -> Option<&str> { + self.html_tag.as_deref() + } + pub fn set_html_tag(&mut self, value: impl Into>) { + self.html_tag = Some(value.into()); + } + pub fn clear_html_tag(&mut self) { + self.html_tag = None; + } + + /// Inner HTML of an element. Only used for a top-level math element, + /// to support third-party math accessibility products that parse MathML. + pub fn inner_html(&self) -> Option<&str> { + self.inner_html.as_deref() + } + pub fn set_inner_html(&mut self, value: impl Into>) { + self.inner_html = Some(value.into()); + } + pub fn clear_inner_html(&mut self) { + self.inner_html = None; + } + + pub fn input_type(&self) -> Option<&str> { + self.input_type.as_deref() + } + pub fn set_input_type(&mut self, value: impl Into>) { + self.input_type = Some(value.into()); + } + pub fn clear_input_type(&mut self) { + self.input_type = None; + } + + pub fn key_shortcuts(&self) -> Option<&str> { + self.key_shortcuts.as_deref() + } + pub fn set_key_shortcuts(&mut self, value: impl Into>) { + self.key_shortcuts = Some(value.into()); + } + pub fn clear_key_shortcuts(&mut self) { + self.key_shortcuts = None; + } + + /// Only present when different from parent. + pub fn language(&self) -> Option<&str> { + self.language.as_deref() + } + pub fn set_language(&mut self, value: impl Into>) { + self.language = Some(value.into()); + } + pub fn clear_language(&mut self) { + self.language = None; + } + + pub fn live_relevant(&self) -> Option<&str> { + self.live_relevant.as_deref() + } + pub fn set_live_relevant(&mut self, value: impl Into>) { + self.live_relevant = Some(value.into()); + } + pub fn clear_live_relevant(&mut self) { + self.live_relevant = None; + } + + pub fn live(&self) -> Option { + self.live + } + pub fn set_live(&mut self, value: Live) { + self.live = Some(value); + } + pub fn clear_live(&mut self) { + self.live = None; + } + + /// Only if not already exposed in [`name`] ([`NameFrom::Placeholder`]). + /// + /// [`name`]: NodeProvider::name + pub fn placeholder(&self) -> Option<&str> { + self.placeholder.as_deref() + } + pub fn set_placeholder(&mut self, value: impl Into>) { + self.placeholder = Some(value.into()); + } + pub fn clear_placeholder(&mut self) { + self.placeholder = None; + } + + pub fn aria_role(&self) -> Option<&str> { + self.aria_role.as_deref() + } + pub fn set_aria_role(&mut self, value: impl Into>) { + self.aria_role = Some(value.into()); + } + pub fn clear_aria_role(&mut self) { + self.aria_role = None; + } + + pub fn role_description(&self) -> Option<&str> { + self.role_description.as_deref() + } + pub fn set_role_description(&mut self, value: impl Into>) { + self.role_description = Some(value.into()); + } + pub fn clear_role_description(&mut self) { + self.role_description = None; + } + + /// Only if not already exposed in [`name`] ([`NameFrom::Title`]). + /// + /// [`name`]: NodeProvider::name + pub fn tooltip(&self) -> Option<&str> { + self.tooltip.as_deref() + } + pub fn set_tooltip(&mut self, value: impl Into>) { + self.tooltip = Some(value.into()); + } + pub fn clear_tooltip(&mut self) { + self.tooltip = None; + } + + pub fn url(&self) -> Option<&str> { + self.url.as_deref() + } + pub fn set_url(&mut self, value: impl Into>) { + self.url = Some(value.into()); + } + pub fn clear_url(&mut self) { + self.url = None; + } + + pub fn default_action_verb(&self) -> Option { + self.default_action_verb + } + pub fn set_default_action_verb(&mut self, value: DefaultActionVerb) { + self.default_action_verb = Some(value); + } + pub fn clear_default_action_verb(&mut self) { + self.default_action_verb = None; + } + + // Scrollable container attributes. + + pub fn scroll_x(&self) -> Option { + self.scroll_x + } + pub fn set_scroll_x(&mut self, value: f32) { + self.scroll_x = Some(value); + } + pub fn clear_scroll_x(&mut self) { + self.scroll_x = None; + } + + pub fn scroll_x_min(&self) -> Option { + self.scroll_x_min + } + pub fn set_scroll_x_min(&mut self, value: f32) { + self.scroll_x_min = Some(value); + } + pub fn clear_scroll_x_min(&mut self) { + self.scroll_x_min = None; + } + + pub fn scroll_x_max(&self) -> Option { + self.scroll_x_max + } + pub fn set_scroll_x_max(&mut self, value: f32) { + self.scroll_x_max = Some(value); + } + pub fn clear_scroll_x_max(&mut self) { + self.scroll_x_max = None; + } + + pub fn scroll_y(&self) -> Option { + self.scroll_y + } + pub fn set_scroll_y(&mut self, value: f32) { + self.scroll_y = Some(value); + } + pub fn clear_scroll_y(&mut self) { + self.scroll_y = None; + } + + pub fn scroll_y_min(&self) -> Option { + self.scroll_y_min + } + pub fn set_scroll_y_min(&mut self, value: f32) { + self.scroll_y_min = Some(value); + } + pub fn clear_scroll_y_min(&mut self) { + self.scroll_y_min = None; + } + + pub fn scroll_y_max(&self) -> Option { + self.scroll_y_max + } + pub fn set_scroll_y_max(&mut self, value: f32) { + self.scroll_y_max = Some(value); + } + pub fn clear_scroll_y_max(&mut self) { + self.scroll_y_max = None; + } + + pub fn text_selection(&self) -> Option { + self.text_selection + } + pub fn set_text_selection(&mut self, value: TextSelection) { + self.text_selection = Some(value); + } + pub fn clear_text_selection(&mut self) { + self.text_selection = None; + } + + pub fn aria_column_count(&self) -> Option { + self.aria_column_count + } + pub fn set_aria_column_count(&mut self, value: usize) { + self.aria_column_count = Some(value); + } + pub fn clear_aria_column_count(&mut self) { + self.aria_column_count = None; + } + + pub fn aria_cell_column_index(&self) -> Option { + self.aria_cell_column_index + } + pub fn set_aria_cell_column_index(&mut self, value: usize) { + self.aria_cell_column_index = Some(value); + } + pub fn clear_aria_cell_column_index(&mut self) { + self.aria_cell_column_index = None; + } + + pub fn aria_cell_column_span(&self) -> Option { + self.aria_cell_column_span + } + pub fn set_aria_cell_column_span(&mut self, value: usize) { + self.aria_cell_column_span = Some(value); + } + pub fn clear_aria_cell_column_span(&mut self) { + self.aria_cell_column_span = None; + } + + pub fn aria_row_count(&self) -> Option { + self.aria_row_count + } + pub fn set_aria_row_count(&mut self, value: usize) { + self.aria_row_count = Some(value); + } + pub fn clear_aria_row_count(&mut self) { + self.aria_row_count = None; + } + + pub fn aria_cell_row_index(&self) -> Option { + self.aria_cell_row_index + } + pub fn set_aria_cell_row_index(&mut self, value: usize) { + self.aria_cell_row_index = Some(value); + } + pub fn clear_aria_cell_row_index(&mut self) { + self.aria_cell_row_index = None; + } + + pub fn aria_cell_row_span(&self) -> Option { + self.aria_cell_row_span + } + pub fn set_aria_cell_row_span(&mut self, value: usize) { + self.aria_cell_row_span = Some(value); + } + pub fn clear_aria_cell_row_span(&mut self) { + self.aria_cell_row_span = None; + } + + // Table attributes. + + pub fn table_row_count(&self) -> Option { + self.table_row_count + } + pub fn set_table_row_count(&mut self, value: usize) { + self.table_row_count = Some(value); + } + pub fn clear_table_row_count(&mut self) { + self.table_row_count = None; + } + + pub fn table_column_count(&self) -> Option { + self.table_column_count + } + pub fn set_table_column_count(&mut self, value: usize) { + self.table_column_count = Some(value); + } + pub fn clear_table_column_count(&mut self) { + self.table_column_count = None; + } + + pub fn table_header(&self) -> Option { + self.table_header + } + pub fn set_table_header(&mut self, value: NodeId) { + self.table_header = Some(value); + } + pub fn clear_table_header(&mut self) { + self.table_header = None; + } + + // Table row attributes. + + pub fn table_row_index(&self) -> Option { + self.table_row_index + } + pub fn set_table_row_index(&mut self, value: usize) { + self.table_row_index = Some(value); + } + pub fn clear_table_row_index(&mut self) { + self.table_row_index = None; + } + + pub fn table_row_header(&self) -> Option { + self.table_row_header + } + pub fn set_table_row_header(&mut self, value: NodeId) { + self.table_row_header = Some(value); + } + pub fn clear_table_row_header(&mut self) { + self.table_row_header = None; + } + + // Table column attributes. + + pub fn table_column_index(&self) -> Option { + self.table_column_index + } + pub fn set_table_column_index(&mut self, value: usize) { + self.table_column_index = Some(value); + } + pub fn clear_table_column_index(&mut self) { + self.table_column_index = None; + } + + pub fn table_column_header(&self) -> Option { + self.table_column_header + } + pub fn set_table_column_header(&mut self, value: NodeId) { + self.table_column_header = Some(value); + } + pub fn clear_table_column_header(&mut self) { + self.table_column_header = None; + } + + // Table cell attributes. + + pub fn table_cell_column_index(&self) -> Option { + self.table_cell_column_index + } + pub fn set_table_cell_column_index(&mut self, value: usize) { + self.table_cell_column_index = Some(value); + } + pub fn clear_table_cell_column_index(&mut self) { + self.table_cell_column_index = None; + } + + pub fn table_cell_column_span(&self) -> Option { + self.table_cell_column_span + } + pub fn set_table_cell_column_span(&mut self, value: usize) { + self.table_cell_column_span = Some(value); + } + pub fn clear_table_cell_column_span(&mut self) { + self.table_cell_column_span = None; + } + + pub fn table_cell_row_index(&self) -> Option { + self.table_cell_row_index + } + pub fn set_table_cell_row_index(&mut self, value: usize) { + self.table_cell_row_index = Some(value); + } + pub fn clear_table_cell_row_index(&mut self) { + self.table_cell_row_index = None; + } + + pub fn table_cell_row_span(&self) -> Option { + self.table_cell_row_span + } + pub fn set_table_cell_row_span(&mut self, value: usize) { + self.table_cell_row_span = Some(value); + } + pub fn clear_table_cell_row_span(&mut self) { + self.table_cell_row_span = None; + } + + pub fn sort_direction(&self) -> Option { + self.sort_direction + } + pub fn set_sort_direction(&mut self, value: SortDirection) { + self.sort_direction = Some(value); + } + pub fn clear_sort_direction(&mut self) { + self.sort_direction = None; + } + + /// Tree control attributes. + pub fn hierarchical_level(&self) -> Option { + self.hierarchical_level + } + pub fn set_hierarchical_level(&mut self, value: usize) { + self.hierarchical_level = Some(value); + } + pub fn clear_hierarchical_level(&mut self) { + self.hierarchical_level = None; + } + + /// Use for a textbox that allows focus/selection but not input. + pub fn is_read_only(&self) -> bool { + self.read_only + } + pub fn set_read_only(&mut self, value: bool) { + self.read_only = value; + } + + /// Use for a control or group of controls that disallows input. + pub fn is_disabled(&self) -> bool { + self.disabled + } + pub fn set_disabled(&mut self, value: bool) { + self.disabled = value; + } + + // Position or Number of items in current set of listitems or treeitems + + pub fn size_of_set(&self) -> Option { + self.size_of_set + } + pub fn set_size_of_set(&mut self, value: usize) { + self.size_of_set = Some(value); + } + pub fn clear_size_of_set(&mut self) { + self.size_of_set = None; + } + + pub fn position_in_set(&self) -> Option { + self.position_in_set + } + pub fn set_position_in_set(&mut self, value: usize) { + self.position_in_set = Some(value); + } + pub fn clear_position_in_set(&mut self) { + self.position_in_set = None; + } + + /// For [`Role::ColorWell`], specifies the selected color in RGBA. + pub fn color_value(&self) -> Option { + self.color_value + } + pub fn set_color_value(&mut self, value: u32) { + self.color_value = Some(value); + } + pub fn clear_color_value(&mut self) { + self.color_value = None; + } + + pub fn aria_current(&self) -> Option { + self.aria_current + } + pub fn set_aria_current(&mut self, value: AriaCurrent) { + self.aria_current = Some(value); + } + pub fn clear_aria_current(&mut self) { + self.aria_current = None; + } + + /// Background color in RGBA. + pub fn background_color(&self) -> Option { + self.background_color + } + pub fn set_background_color(&mut self, value: u32) { + self.background_color = Some(value); + } + pub fn clear_background_color(&mut self) { + self.background_color = None; + } + + /// Foreground color in RGBA. + pub fn foreground_color(&self) -> Option { + self.foreground_color + } + pub fn set_foreground_color(&mut self, value: u32) { + self.foreground_color = Some(value); + } + pub fn clear_foreground_color(&mut self) { + self.foreground_color = None; + } + + pub fn has_popup(&self) -> Option { + self.has_popup + } + + /// The list style type. Only available on list items. + pub fn list_style(&self) -> Option { + self.list_style + } + pub fn set_list_style(&mut self, value: ListStyle) { + self.list_style = Some(value); + } + pub fn clear_list_style(&mut self) { + self.list_style = None; + } + + pub fn text_align(&self) -> Option { + self.text_align + } + pub fn set_text_align(&mut self, value: TextAlign) { + self.text_align = Some(value); + } + pub fn clear_text_align(&mut self) { + self.text_align = None; + } + + pub fn vertical_offset(&self) -> Option { + self.vertical_offset + } + pub fn set_vertical_offset(&mut self, value: VerticalOffset) { + self.vertical_offset = Some(value); + } + pub fn clear_vertical_offset(&mut self) { + self.vertical_offset = None; + } + + pub fn is_bold(&self) -> bool { + self.bold + } + pub fn set_bold(&mut self, value: bool) { + self.bold = value; + } + + pub fn is_italic(&self) -> bool { + self.italic + } + pub fn set_italic(&mut self, value: bool) { + self.italic = value; + } + + pub fn overline(&self) -> Option { + self.overline + } + pub fn set_overline(&mut self, value: TextDecoration) { + self.overline = Some(value); + } + pub fn clear_overline(&mut self) { + self.overline = None; + } + + pub fn strikethrough(&self) -> Option { + self.strikethrough + } + pub fn set_strikethrough(&mut self, value: TextDecoration) { + self.strikethrough = Some(value); + } + pub fn clear_strikethrough(&mut self) { + self.strikethrough = None; + } + + pub fn underline(&self) -> Option { + self.underline + } + pub fn set_underline(&mut self, value: TextDecoration) { + self.underline = Some(value); + } + pub fn clear_underline(&mut self) { + self.underline = None; + } + + // Focus traversal order. + + pub fn previous_focus(&self) -> Option { + self.previous_focus + } + pub fn set_previous_focus(&mut self, value: NodeId) { + self.previous_focus = Some(value); + } + pub fn clear_previous_focus(&mut self) { + self.previous_focus = None; + } + + pub fn next_focus(&self) -> Option { + self.next_focus + } + pub fn set_next_focus(&mut self, value: NodeId) { + self.next_focus = Some(value); + } + pub fn clear_next_focus(&mut self) { + self.next_focus = None; + } + + // Numeric value attributes. + + pub fn numeric_value(&self) -> Option { + self.numeric_value + } + pub fn set_numeric_value(&mut self, value: f64) { + self.numeric_value = Some(value); + } + pub fn clear_numeric_value(&mut self) { + self.numeric_value = None; + } + + pub fn min_numeric_value(&self) -> Option { + self.min_numeric_value + } + pub fn set_min_numeric_value(&mut self, value: f64) { + self.min_numeric_value = Some(value); + } + pub fn clear_min_numeric_value(&mut self) { + self.min_numeric_value = None; + } + + pub fn max_numeric_value(&self) -> Option { + self.max_numeric_value + } + pub fn set_max_numeric_value(&mut self, value: f64) { + self.max_numeric_value = Some(value); + } + pub fn clear_max_numeric_value(&mut self) { + self.max_numeric_value = None; + } + + pub fn numeric_value_step(&self) -> Option { + self.numeric_value_step + } + pub fn set_numeric_value_step(&mut self, value: f64) { + self.numeric_value_step = Some(value); + } + pub fn clear_numeric_value_step(&mut self) { + self.numeric_value_step = None; + } + + pub fn numeric_value_jump(&self) -> Option { + self.numeric_value_jump + } + pub fn set_numeric_value_jump(&mut self, value: f64) { + self.numeric_value_jump = Some(value); + } + pub fn clear_numeric_value_jump(&mut self) { + self.numeric_value_jump = None; + } + + /// Font size is in pixels. + pub fn font_size(&self) -> Option { + self.font_size + } + pub fn set_font_size(&mut self, value: f32) { + self.font_size = Some(value); + } + pub fn clear_font_size(&mut self) { + self.font_size = None; + } + + /// Font weight can take on any arbitrary numeric value. Increments of 100 in + /// range `[0, 900]` represent keywords such as light, normal, bold, etc. + pub fn font_weight(&self) -> Option { + self.font_weight + } + pub fn set_font_weight(&mut self, value: f32) { + self.font_weight = Some(value); + } + pub fn clear_font_weight(&mut self) { + self.font_weight = None; + } + + /// The text indent of the text, in mm. + pub fn text_indent(&self) -> Option { + self.text_indent + } + pub fn set_text_indent(&mut self, value: f32) { + self.text_indent = Some(value); + } + pub fn clear_text_indent(&mut self) { + self.text_indent = None; + } } /// The data associated with an accessibility tree that's global to the diff --git a/consumer/src/iterators.rs b/consumer/src/iterators.rs index c4b3d27e1..7d01b7bdd 100644 --- a/consumer/src/iterators.rs +++ b/consumer/src/iterators.rs @@ -29,7 +29,7 @@ impl<'a> FollowingSiblings<'a> { let parent_and_index = node.parent_and_index(); let (back_position, front_position, done) = if let Some((ref parent, index)) = parent_and_index { - let back_position = parent.data().children.len() - 1; + let back_position = parent.data().children().len() - 1; let front_position = index + 1; ( back_position, @@ -60,7 +60,7 @@ impl<'a> Iterator for FollowingSiblings<'a> { .parent .as_ref()? .data() - .children + .children() .get(self.front_position)?; self.front_position += 1; Some(*child) @@ -86,7 +86,7 @@ impl<'a> DoubleEndedIterator for FollowingSiblings<'a> { .parent .as_ref()? .data() - .children + .children() .get(self.back_position)?; self.back_position -= 1; Some(*child) @@ -138,7 +138,7 @@ impl<'a> Iterator for PrecedingSiblings<'a> { .parent .as_ref()? .data() - .children + .children() .get(self.front_position)?; if !self.done { self.front_position -= 1; @@ -166,7 +166,7 @@ impl<'a> DoubleEndedIterator for PrecedingSiblings<'a> { .parent .as_ref()? .data() - .children + .children() .get(self.back_position)?; self.back_position += 1; Some(*child) diff --git a/consumer/src/lib.rs b/consumer/src/lib.rs index 2881b534c..a45ba97f2 100644 --- a/consumer/src/lib.rs +++ b/consumer/src/lib.rs @@ -50,93 +50,87 @@ mod tests { } pub fn test_tree() -> crate::tree::Tree { - let root = Arc::new(Node { - role: Role::RootWebArea, - children: vec![ + let root = Arc::new({ + let mut node = Node::new(Role::RootWebArea); + node.set_children(vec![ PARAGRAPH_0_ID, PARAGRAPH_1_IGNORED_ID, PARAGRAPH_2_ID, PARAGRAPH_3_IGNORED_ID, - ], - ..Default::default() + ]); + node }); - let paragraph_0 = Arc::new(Node { - role: Role::Paragraph, - children: vec![STATIC_TEXT_0_0_IGNORED_ID], - ..Default::default() + let paragraph_0 = Arc::new({ + let mut node = Node::new(Role::Paragraph); + node.set_children(vec![STATIC_TEXT_0_0_IGNORED_ID]); + node }); - let static_text_0_0_ignored = Arc::new(Node { - role: Role::StaticText, - name: Some("static_text_0_0_ignored".into()), - ..Default::default() + let static_text_0_0_ignored = Arc::new({ + let mut node = Node::new(Role::StaticText); + node.set_name("static_text_0_0_ignored"); + node }); - let paragraph_1_ignored = Arc::new(Node { - role: Role::Paragraph, - transform: Some(Box::new(Affine::translate(Vec2::new(10.0, 40.0)))), - bounds: Some(Rect { + let paragraph_1_ignored = Arc::new({ + let mut node = Node::new(Role::Paragraph); + node.set_transform(Affine::translate(Vec2::new(10.0, 40.0))); + node.set_bounds(Rect { x0: 0.0, y0: 0.0, x1: 800.0, y1: 40.0, - }), - children: vec![STATIC_TEXT_1_0_ID], - ..Default::default() + }); + node.set_children(vec![STATIC_TEXT_1_0_ID]); + node }); - let static_text_1_0 = Arc::new(Node { - role: Role::StaticText, - bounds: Some(Rect { + let static_text_1_0 = Arc::new({ + let mut node = Node::new(Role::StaticText); + node.set_bounds(Rect { x0: 10.0, y0: 10.0, x1: 90.0, y1: 30.0, - }), - name: Some("static_text_1_0".into()), - ..Default::default() + }); + node.set_name("static_text_1_0"); + node }); - let paragraph_2 = Arc::new(Node { - role: Role::Paragraph, - children: vec![STATIC_TEXT_2_0_ID], - ..Default::default() + let paragraph_2 = Arc::new({ + let mut node = Node::new(Role::Paragraph); + node.set_children(vec![STATIC_TEXT_2_0_ID]); + node }); - let static_text_2_0 = Arc::new(Node { - role: Role::StaticText, - name: Some("static_text_2_0".into()), - ..Default::default() + let static_text_2_0 = Arc::new({ + let mut node = Node::new(Role::StaticText); + node.set_name("static_text_2_0"); + node }); - let paragraph_3_ignored = Arc::new(Node { - role: Role::Paragraph, - children: vec![ + let paragraph_3_ignored = Arc::new({ + let mut node = Node::new(Role::Paragraph); + node.set_children(vec![ EMPTY_CONTAINER_3_0_IGNORED_ID, LINK_3_1_IGNORED_ID, BUTTON_3_2_ID, EMPTY_CONTAINER_3_3_IGNORED_ID, - ], - ..Default::default() - }); - let empty_container_3_0_ignored = Arc::new(Node { - role: Role::GenericContainer, - ..Default::default() - }); - let link_3_1_ignored = Arc::new(Node { - role: Role::Link, - children: vec![STATIC_TEXT_3_1_0_ID], - linked: true, - ..Default::default() + ]); + node }); - let static_text_3_1_0 = Arc::new(Node { - role: Role::StaticText, - name: Some("static_text_3_1_0".into()), - ..Default::default() + let empty_container_3_0_ignored = Arc::new(Node::new(Role::GenericContainer)); + let link_3_1_ignored = Arc::new({ + let mut node = Node::new(Role::Link); + node.set_children(vec![STATIC_TEXT_3_1_0_ID]); + node.set_linked(true); + node }); - let button_3_2 = Arc::new(Node { - role: Role::Button, - name: Some("button_3_2".into()), - ..Default::default() + let static_text_3_1_0 = Arc::new({ + let mut node = Node::new(Role::StaticText); + node.set_name("static_text_3_1_0"); + node }); - let empty_container_3_3_ignored = Arc::new(Node { - role: Role::GenericContainer, - ..Default::default() + let button_3_2 = Arc::new({ + let mut node = Node::new(Role::Button); + node.set_name("button_3_2"); + node }); + let empty_container_3_3_ignored = Arc::new(Node::new(Role::GenericContainer)); let initial_update = TreeUpdate { nodes: vec![ (ROOT_ID, root), diff --git a/consumer/src/node.rs b/consumer/src/node.rs index c03e3e526..f879c3ddb 100644 --- a/consumer/src/node.rs +++ b/consumer/src/node.rs @@ -62,9 +62,7 @@ impl<'a> Node<'a> { impl NodeState { pub fn is_focusable(&self) -> bool { - // TBD: Is it ever safe to imply this on a node that doesn't explicitly - // specify it? - self.data().focusable + self.supports_action(Action::Focus) } } @@ -118,7 +116,7 @@ impl NodeState { + FusedIterator + '_ { let data = &self.data; - data.children.iter().copied() + data.children().iter().copied() } } @@ -249,10 +247,7 @@ impl NodeState { /// Returns the transform defined directly on this node, or the identity /// transform, without taking into account transforms on ancestors. pub fn direct_transform(&self) -> Affine { - self.data() - .transform - .as_ref() - .map_or(Affine::IDENTITY, |t| **t) + self.data().transform().unwrap_or(Affine::IDENTITY) } } @@ -281,7 +276,7 @@ impl<'a> Node<'a> { impl NodeState { pub fn raw_bounds(&self) -> Option { - self.data().bounds + self.data().bounds() } } @@ -352,22 +347,22 @@ impl NodeState { } pub fn role(&self) -> Role { - self.data().role + self.data().role() } pub fn is_hidden(&self) -> bool { - self.data().hidden + self.data().is_hidden() } pub fn is_disabled(&self) -> bool { - self.data().disabled + self.data().is_disabled() } pub fn is_read_only(&self) -> bool { let data = self.data(); - if data.read_only { + if data.is_read_only() { true - } else if !data.editable { + } else if !data.is_editable() { false } else { self.should_have_read_only_state_by_default() || !self.is_read_only_supported() @@ -379,35 +374,35 @@ impl NodeState { } pub fn checked_state(&self) -> Option { - self.data().checked_state + self.data().checked_state() } pub fn value(&self) -> Option<&str> { - self.data().value.as_deref() + self.data().value() } pub fn numeric_value(&self) -> Option { - self.data().numeric_value + self.data().numeric_value() } pub fn min_numeric_value(&self) -> Option { - self.data().min_numeric_value + self.data().min_numeric_value() } pub fn max_numeric_value(&self) -> Option { - self.data().max_numeric_value + self.data().max_numeric_value() } pub fn numeric_value_step(&self) -> Option { - self.data().numeric_value_step + self.data().numeric_value_step() } pub fn numeric_value_jump(&self) -> Option { - self.data().numeric_value_jump + self.data().numeric_value_jump() } pub fn is_text_field(&self) -> bool { - self.is_atomic_text_field() || self.data().nonatomic_text_field_root + self.is_atomic_text_field() || self.data().is_nonatomic_text_field_root() } pub fn is_atomic_text_field(&self) -> bool { @@ -421,22 +416,22 @@ impl NodeState { // treat them as non-atomic. match self.role() { Role::SearchBox | Role::TextField | Role::TextFieldWithComboBox => { - !self.data().nonatomic_text_field_root + !self.data().is_nonatomic_text_field_root() } _ => false, } } pub fn is_multiline(&self) -> bool { - self.data().multiline + self.data().is_multiline() } pub fn is_protected(&self) -> bool { - self.data().protected + self.data().is_protected() } pub fn default_action_verb(&self) -> Option { - self.data().default_action_verb + self.data().default_action_verb() } // When probing for supported actions as the next several functions do, @@ -467,7 +462,7 @@ impl NodeState { } pub fn supports_expand_collapse(&self) -> bool { - self.data().expanded.is_some() + self.data().is_expanded().is_some() } pub fn is_invocable(&self) -> bool { @@ -491,7 +486,7 @@ impl NodeState { // The future of the `Action` enum is undecided, so keep the following // function private for now. fn supports_action(&self, action: Action) -> bool { - self.data().actions.contains(action) + self.data().supports_action(action) } pub fn supports_increment(&self) -> bool { @@ -515,7 +510,7 @@ impl<'a> Node<'a> { pub fn labelled_by( &self, ) -> impl DoubleEndedIterator> + FusedIterator> + 'a { - let explicit = &self.state.data.labelled_by; + let explicit = &self.state.data.labelled_by(); if explicit.is_empty() && matches!(self.role(), Role::Button | Role::Link) { LabelledBy::FromDescendants(FilteredChildren::new(*self, &descendant_label_filter)) } else { @@ -527,7 +522,7 @@ impl<'a> Node<'a> { } pub fn name(&self) -> Option { - if let Some(name) = &self.data().name { + if let Some(name) = &self.data().name() { Some(name.to_string()) } else { let names = self @@ -596,18 +591,18 @@ impl NodeState { impl<'a> Node<'a> { pub fn live(&self) -> Live { self.data() - .live + .live() .unwrap_or_else(|| self.parent().map_or(Live::Off, |parent| parent.live())) } } impl NodeState { pub fn is_selected(&self) -> Option { - self.data().selected + self.data().is_selected() } - pub fn raw_text_selection(&self) -> Option<&TextSelection> { - self.data().text_selection.as_ref() + pub fn raw_text_selection(&self) -> Option { + self.data().text_selection() } } @@ -999,19 +994,13 @@ mod tests { nodes: vec![ ( NODE_ID_1, - Arc::new(Node { - role: Role::Window, - children: vec![NODE_ID_2], - ..Default::default() - }), - ), - ( - NODE_ID_2, - Arc::new(Node { - role: Role::Button, - ..Default::default() + Arc::new({ + let mut node = Node::new(Role::Window); + node.set_children(vec![NODE_ID_2]); + node }), ), + (NODE_ID_2, Arc::new(Node::new(Role::Button))), ], tree: Some(Tree::new(NODE_ID_1)), focus: None, @@ -1031,42 +1020,42 @@ mod tests { nodes: vec![ ( NODE_ID_1, - Arc::new(Node { - role: Role::Window, - children: vec![NODE_ID_2, NODE_ID_3, NODE_ID_4, NODE_ID_5], - ..Default::default() + Arc::new({ + let mut node = Node::new(Role::Window); + node.set_children(vec![NODE_ID_2, NODE_ID_3, NODE_ID_4, NODE_ID_5]); + node }), ), ( NODE_ID_2, - Arc::new(Node { - role: Role::CheckBox, - labelled_by: vec![NODE_ID_3, NODE_ID_5], - ..Default::default() + Arc::new({ + let mut node = Node::new(Role::CheckBox); + node.set_labelled_by(vec![NODE_ID_3, NODE_ID_5]); + node }), ), ( NODE_ID_3, - Arc::new(Node { - role: Role::StaticText, - name: Some(LABEL_1.into()), - ..Default::default() + Arc::new({ + let mut node = Node::new(Role::StaticText); + node.set_name(LABEL_1); + node }), ), ( NODE_ID_4, - Arc::new(Node { - role: Role::CheckBox, - labelled_by: vec![NODE_ID_5], - ..Default::default() + Arc::new({ + let mut node = Node::new(Role::TextField); + node.push_labelled_by(NODE_ID_5); + node }), ), ( NODE_ID_5, - Arc::new(Node { - role: Role::StaticText, - name: Some(LABEL_2.into()), - ..Default::default() + Arc::new({ + let mut node = Node::new(Role::StaticText); + node.set_name(LABEL_2); + node }), ), ], @@ -1093,50 +1082,50 @@ mod tests { nodes: vec![ ( NODE_ID_1, - Arc::new(Node { - role: Role::Window, - children: vec![NODE_ID_2, NODE_ID_4], - ..Default::default() + Arc::new({ + let mut node = Node::new(Role::Window); + node.set_children(vec![NODE_ID_2, NODE_ID_4]); + node }), ), ( NODE_ID_2, - Arc::new(Node { - role: Role::Button, - children: vec![NODE_ID_3], - ..Default::default() + Arc::new({ + let mut node = Node::new(Role::Button); + node.push_child(NODE_ID_3); + node }), ), ( NODE_ID_3, - Arc::new(Node { - role: Role::Image, - name: Some(BUTTON_LABEL.into()), - ..Default::default() + Arc::new({ + let mut node = Node::new(Role::Image); + node.set_name(BUTTON_LABEL); + node }), ), ( NODE_ID_4, - Arc::new(Node { - role: Role::Link, - children: vec![NODE_ID_5], - ..Default::default() + Arc::new({ + let mut node = Node::new(Role::Link); + node.push_child(NODE_ID_5); + node }), ), ( NODE_ID_5, - Arc::new(Node { - role: Role::GenericContainer, - children: vec![NODE_ID_6], - ..Default::default() + Arc::new({ + let mut node = Node::new(Role::GenericContainer); + node.push_child(NODE_ID_6); + node }), ), ( NODE_ID_6, - Arc::new(Node { - role: Role::StaticText, - name: Some(LINK_LABEL.into()), - ..Default::default() + Arc::new({ + let mut node = Node::new(Role::StaticText); + node.set_name(LINK_LABEL); + node }), ), ], diff --git a/consumer/src/text.rs b/consumer/src/text.rs index 808fa570b..d884955b0 100644 --- a/consumer/src/text.rs +++ b/consumer/src/text.rs @@ -22,7 +22,7 @@ impl<'a> InnerPosition<'a> { return None; } let character_index = weak.character_index; - if character_index > node.data().character_lengths.len() { + if character_index > node.data().character_lengths().len() { return None; } Some(Self { @@ -33,7 +33,7 @@ impl<'a> InnerPosition<'a> { fn is_word_start(&self) -> bool { let mut total_length = 0usize; - for length in self.node.data().word_lengths.iter() { + for length in self.node.data().word_lengths().iter() { if total_length == self.character_index { return true; } @@ -47,15 +47,15 @@ impl<'a> InnerPosition<'a> { } fn is_line_start(&self) -> bool { - self.is_box_start() && self.node.data().previous_on_line.is_none() + self.is_box_start() && self.node.data().previous_on_line().is_none() } fn is_box_end(&self) -> bool { - self.character_index == self.node.data().character_lengths.len() + self.character_index == self.node.data().character_lengths().len() } fn is_line_end(&self) -> bool { - self.is_box_end() && self.node.data().next_on_line.is_none() + self.is_box_end() && self.node.data().next_on_line().is_none() } fn is_paragraph_end(&self) -> bool { @@ -97,7 +97,7 @@ impl<'a> InnerPosition<'a> { if let Some(node) = self.node.preceding_inline_text_boxes(root_node).next() { return Self { node, - character_index: node.data().character_lengths.len(), + character_index: node.data().character_lengths().len(), }; } } @@ -114,7 +114,7 @@ impl<'a> InnerPosition<'a> { fn previous_word_start(&self) -> Self { let mut total_length_before = 0usize; - for length in self.node.data().word_lengths.iter() { + for length in self.node.data().word_lengths().iter() { let new_total_length = total_length_before + (*length as usize); if new_total_length >= self.character_index { break; @@ -129,7 +129,7 @@ impl<'a> InnerPosition<'a> { fn word_end(&self) -> Self { let mut total_length = 0usize; - for length in self.node.data().word_lengths.iter() { + for length in self.node.data().word_lengths().iter() { total_length += *length as usize; if total_length > self.character_index { break; @@ -143,7 +143,7 @@ impl<'a> InnerPosition<'a> { fn line_start(&self) -> Self { let mut node = self.node; - while let Some(id) = node.data().previous_on_line { + while let Some(id) = node.data().previous_on_line() { node = node.tree_state.node_by_id(id).unwrap(); } Self { @@ -154,12 +154,12 @@ impl<'a> InnerPosition<'a> { fn line_end(&self) -> Self { let mut node = self.node; - while let Some(id) = node.data().next_on_line { + while let Some(id) = node.data().next_on_line() { node = node.tree_state.node_by_id(id).unwrap(); } Self { node, - character_index: node.data().character_lengths.len(), + character_index: node.data().character_lengths().len(), } } @@ -234,7 +234,7 @@ impl<'a> Position<'a> { for node in self.root_node.inline_text_boxes() { let node_text = node.value().unwrap(); if node.id() == self.inner.node.id() { - let character_lengths = &node.data().character_lengths; + let character_lengths = node.data().character_lengths(); let slice_end = character_lengths[..self.inner.character_index] .iter() .copied() @@ -522,7 +522,7 @@ impl<'a> Range<'a> { pub fn text(&self) -> String { let mut result = String::new(); self.walk::<_, ()>(|node| { - let character_lengths = &node.data().character_lengths; + let character_lengths = node.data().character_lengths(); let start_index = if node.id() == self.start.node.id() { self.start.character_index } else { @@ -568,31 +568,31 @@ impl<'a> Range<'a> { pub fn bounding_boxes(&self) -> Vec { let mut result = Vec::new(); self.walk(|node| { - let mut rect = match &node.data().bounds { - Some(rect) => *rect, + let mut rect = match node.data().bounds() { + Some(rect) => rect, None => { return Some(Vec::new()); } }; - let positions = match &node.data().character_positions { + let positions = match node.data().character_positions() { Some(positions) => positions, None => { return Some(Vec::new()); } }; - let widths = match &node.data().character_widths { + let widths = match node.data().character_widths() { Some(widths) => widths, None => { return Some(Vec::new()); } }; - let direction = match node.data().text_direction { + let direction = match node.data().text_direction() { Some(direction) => direction, None => { return Some(Vec::new()); } }; - let character_lengths = &node.data().character_lengths; + let character_lengths = node.data().character_lengths(); let start_index = if node.id() == self.start.node.id() { self.start.character_index } else { @@ -762,21 +762,21 @@ fn text_node_filter(root_id: NodeId, node: &Node) -> FilterResult { fn character_index_at_point(node: &Node, point: Point) -> usize { // We know the node has a bounding rectangle because it was returned // by a hit test. - let rect = node.data().bounds.as_ref().unwrap(); - let character_lengths = &node.data().character_lengths; - let positions = match &node.data().character_positions { + let rect = node.data().bounds().unwrap(); + let character_lengths = node.data().character_lengths(); + let positions = match node.data().character_positions() { Some(positions) => positions, None => { return 0; } }; - let widths = match &node.data().character_widths { + let widths = match node.data().character_widths() { Some(widths) => widths, None => { return 0; } }; - let direction = match node.data().text_direction { + let direction = match node.data().text_direction() { Some(direction) => direction, None => { return 0; @@ -842,7 +842,7 @@ impl<'a> Node<'a> { let node = self.inline_text_boxes().next_back().unwrap(); InnerPosition { node, - character_index: node.data().character_lengths.len(), + character_index: node.data().character_lengths().len(), } } @@ -853,11 +853,11 @@ impl<'a> Node<'a> { } pub fn has_text_selection(&self) -> bool { - self.data().text_selection.is_some() + self.data().text_selection().is_some() } pub fn text_selection(&self) -> Option { - self.data().text_selection.map(|selection| { + self.data().text_selection().map(|selection| { let anchor = InnerPosition::upgrade(self.tree_state, selection.anchor).unwrap(); let focus = InnerPosition::upgrade(self.tree_state, selection.focus).unwrap(); Range::new(*self, anchor, focus) @@ -865,7 +865,7 @@ impl<'a> Node<'a> { } pub fn text_selection_focus(&self) -> Option { - self.data().text_selection.map(|selection| { + self.data().text_selection().map(|selection| { let focus = InnerPosition::upgrade(self.tree_state, selection.focus).unwrap(); Position { root_node: *self, @@ -908,7 +908,7 @@ impl<'a> Node<'a> { for node in self.inline_text_boxes().rev() { if let Some(rect) = node.bounding_box_in_coordinate_space(self) { - if let Some(direction) = node.data().text_direction { + if let Some(direction) = node.data().text_direction() { let is_past_end = match direction { TextDirection::LeftToRight => { point.y >= rect.y0 && point.y < rect.y1 && point.x >= rect.x1 @@ -931,7 +931,7 @@ impl<'a> Node<'a> { root_node: *self, inner: InnerPosition { node, - character_index: node.data().character_lengths.len(), + character_index: node.data().character_lengths().len(), }, }; } @@ -979,7 +979,7 @@ impl<'a> Node<'a> { let mut utf8_length = 0usize; let mut utf16_length = 0usize; for (character_index, utf8_char_length) in - node.data().character_lengths.iter().enumerate() + node.data().character_lengths().iter().enumerate() { let new_utf8_length = utf8_length + (*utf8_char_length as usize); let char_str = &node_text[utf8_length..new_utf8_length]; @@ -1031,230 +1031,204 @@ mod tests { // This is based on an actual tree produced by egui. fn main_multiline_tree(selection: Option) -> crate::Tree { use accesskit::kurbo::Affine; - use accesskit::{Node, Role, TextDirection, Tree, TreeUpdate}; + use accesskit::{Action, Node, Role, TextDirection, Tree, TreeUpdate}; let update = TreeUpdate { nodes: vec![ ( NODE_ID_1, - Arc::new(Node { - role: Role::Window, - transform: Some(Box::new(Affine::scale(1.5))), - children: vec![NODE_ID_2], - ..Default::default() + Arc::new({ + let mut node = Node::new(Role::Window); + node.set_transform(Affine::scale(1.5)); + node.set_children(vec![NODE_ID_2]); + node }), ), ( NODE_ID_2, - Arc::new(Node { - role: Role::TextField, - bounds: Some(Rect { + Arc::new({ + let mut node = Node::new(Role::TextField); + node.set_bounds(Rect { x0: 8.0, y0: 31.666664123535156, x1: 296.0, y1: 123.66666412353516, - }), - children: vec![ + }); + node.set_children(vec![ NODE_ID_3, NODE_ID_4, NODE_ID_5, NODE_ID_6, NODE_ID_7, NODE_ID_8, - ], - focusable: true, - text_selection: selection, - ..Default::default() + ]); + node.add_action(Action::Focus); + if let Some(selection) = selection { + node.set_text_selection(selection); + } + node }), ), ( NODE_ID_3, - Arc::new(Node { - role: Role::InlineTextBox, - bounds: Some(Rect { + Arc::new({ + let mut node = Node::new(Role::InlineTextBox); + node.set_bounds(Rect { x0: 12.0, y0: 33.666664123535156, x1: 290.9189147949219, y1: 48.33333206176758, - }), + }); // The non-breaking space in the following text // is in an arbitrary spot; its only purpose // is to test conversion between UTF-8 and UTF-16 // indices. - value: Some("This paragraph is\u{a0}long enough to wrap ".into()), - text_direction: Some(TextDirection::LeftToRight), - character_lengths: vec![ + node.set_value("This paragraph is\u{a0}long enough to wrap "); + node.set_text_direction(TextDirection::LeftToRight); + node.set_character_lengths([ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - ] - .into(), - character_positions: Some( - vec![ - 0.0, 7.3333335, 14.666667, 22.0, 29.333334, 36.666668, 44.0, - 51.333332, 58.666668, 66.0, 73.333336, 80.666664, 88.0, 95.333336, - 102.666664, 110.0, 117.333336, 124.666664, 132.0, 139.33333, - 146.66667, 154.0, 161.33333, 168.66667, 176.0, 183.33333, - 190.66667, 198.0, 205.33333, 212.66667, 220.0, 227.33333, - 234.66667, 242.0, 249.33333, 256.66666, 264.0, 271.33334, - ] - .into(), - ), - character_widths: Some( - vec![ - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, - 7.58557, 7.58557, 7.58557, - ] - .into(), - ), - word_lengths: vec![5, 10, 3, 5, 7, 3, 5].into(), - ..Default::default() + ]); + node.set_character_positions([ + 0.0, 7.3333335, 14.666667, 22.0, 29.333334, 36.666668, 44.0, 51.333332, + 58.666668, 66.0, 73.333336, 80.666664, 88.0, 95.333336, 102.666664, + 110.0, 117.333336, 124.666664, 132.0, 139.33333, 146.66667, 154.0, + 161.33333, 168.66667, 176.0, 183.33333, 190.66667, 198.0, 205.33333, + 212.66667, 220.0, 227.33333, 234.66667, 242.0, 249.33333, 256.66666, + 264.0, 271.33334, + ]); + node.set_character_widths([ + 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, + 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, + 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, + 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, + 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, + ]); + node.set_word_lengths([5, 10, 3, 5, 7, 3, 5]); + node }), ), ( NODE_ID_4, - Arc::new(Node { - role: Role::InlineTextBox, - bounds: Some(Rect { + Arc::new({ + let mut node = Node::new(Role::InlineTextBox); + node.set_bounds(Rect { x0: 12.0, y0: 48.33333206176758, x1: 129.5855712890625, y1: 63.0, - }), - value: Some("to another line.\n".into()), - text_direction: Some(TextDirection::LeftToRight), - character_lengths: vec![1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - .into(), - character_positions: Some( - vec![ - 0.0, 7.3333435, 14.666687, 22.0, 29.333344, 36.666687, 44.0, - 51.333344, 58.666687, 66.0, 73.33334, 80.66669, 88.0, 95.33334, - 102.66669, 110.0, 117.58557, - ] - .into(), - ), - character_widths: Some( - vec![ - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, - 7.58557, 7.58557, 0.0, - ] - .into(), - ), - word_lengths: vec![3, 8, 6].into(), - ..Default::default() + }); + node.set_value("to another line.\n"); + node.set_text_direction(TextDirection::LeftToRight); + node.set_character_lengths([ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + ]); + node.set_character_positions([ + 0.0, 7.3333435, 14.666687, 22.0, 29.333344, 36.666687, 44.0, 51.333344, + 58.666687, 66.0, 73.33334, 80.66669, 88.0, 95.33334, 102.66669, 110.0, + 117.58557, + ]); + node.set_character_widths([ + 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, + 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, + 0.0, + ]); + node.set_word_lengths([3, 8, 6]); + node }), ), ( NODE_ID_5, - Arc::new(Node { - role: Role::InlineTextBox, - bounds: Some(Rect { + Arc::new({ + let mut node = Node::new(Role::InlineTextBox); + node.set_bounds(Rect { x0: 12.0, y0: 63.0, x1: 144.25222778320313, y1: 77.66666412353516, - }), - value: Some("Another paragraph.\n".into()), - text_direction: Some(TextDirection::LeftToRight), - character_lengths: vec![ + }); + node.set_value("Another paragraph.\n"); + node.set_text_direction(TextDirection::LeftToRight); + node.set_character_lengths([ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - ] - .into(), - character_positions: Some( - vec![ - 0.0, 7.3333335, 14.666667, 22.0, 29.333334, 36.666668, 44.0, - 51.333332, 58.666668, 66.0, 73.333336, 80.666664, 88.0, 95.333336, - 102.666664, 110.0, 117.333336, 124.666664, 132.25223, - ] - .into(), - ), - character_widths: Some( - vec![ - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, - 7.58557, 7.58557, 7.58557, 7.58557, 0.0, - ] - .into(), - ), - word_lengths: vec![8, 11].into(), - ..Default::default() + ]); + node.set_character_positions([ + 0.0, 7.3333335, 14.666667, 22.0, 29.333334, 36.666668, 44.0, 51.333332, + 58.666668, 66.0, 73.333336, 80.666664, 88.0, 95.333336, 102.666664, + 110.0, 117.333336, 124.666664, 132.25223, + ]); + node.set_character_widths([ + 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, + 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, + 7.58557, 7.58557, 0.0, + ]); + node.set_word_lengths([8, 11]); + node }), ), ( NODE_ID_6, - Arc::new(Node { - role: Role::InlineTextBox, - bounds: Some(Rect { + Arc::new({ + let mut node = Node::new(Role::InlineTextBox); + node.set_bounds(Rect { x0: 12.0, y0: 77.66666412353516, x1: 12.0, y1: 92.33332824707031, - }), - value: Some("\n".into()), - text_direction: Some(TextDirection::LeftToRight), - character_lengths: vec![1].into(), - character_positions: Some(vec![0.0].into()), - character_widths: Some(vec![0.0].into()), - word_lengths: vec![1].into(), - ..Default::default() + }); + node.set_value("\n"); + node.set_text_direction(TextDirection::LeftToRight); + node.set_character_lengths([1]); + node.set_character_positions([0.0]); + node.set_character_widths([0.0]); + node.set_word_lengths([1]); + node }), ), ( NODE_ID_7, - Arc::new(Node { - role: Role::InlineTextBox, - bounds: Some(Rect { + Arc::new({ + let mut node = Node::new(Role::InlineTextBox); + node.set_bounds(Rect { x0: 12.0, y0: 92.33332824707031, x1: 158.9188995361328, y1: 107.0, - }), + }); // Use an arbitrary emoji that encodes to two // UTF-16 code units to fully test conversion between // UTF-8, UTF-16, and character indices. - value: Some("Last non-blank line\u{1f60a}\n".into()), - text_direction: Some(TextDirection::LeftToRight), - character_lengths: vec![ + node.set_value("Last non-blank line\u{1f60a}\n"); + node.set_text_direction(TextDirection::LeftToRight); + node.set_character_lengths([ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, - ] - .into(), - character_positions: Some( - vec![ - 0.0, 7.3333335, 14.666667, 22.0, 29.333334, 36.666668, 44.0, - 51.333332, 58.666668, 66.0, 73.333336, 80.666664, 88.0, 95.333336, - 102.666664, 110.0, 117.333336, 124.666664, 132.0, 139.33333, - 146.9189, - ] - .into(), - ), - character_widths: Some( - vec![ - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 0.0, - ] - .into(), - ), - word_lengths: vec![5, 4, 6, 6].into(), - ..Default::default() + ]); + node.set_character_positions([ + 0.0, 7.3333335, 14.666667, 22.0, 29.333334, 36.666668, 44.0, 51.333332, + 58.666668, 66.0, 73.333336, 80.666664, 88.0, 95.333336, 102.666664, + 110.0, 117.333336, 124.666664, 132.0, 139.33333, 146.9189, + ]); + node.set_character_widths([ + 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, + 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, + 7.58557, 7.58557, 7.58557, 7.58557, 0.0, + ]); + node.set_word_lengths([5, 4, 6, 6]); + node }), ), ( NODE_ID_8, - Arc::new(Node { - role: Role::InlineTextBox, - bounds: Some(Rect { + Arc::new({ + let mut node = Node::new(Role::InlineTextBox); + node.set_bounds(Rect { x0: 12.0, y0: 107.0, x1: 12.0, y1: 121.66666412353516, - }), - value: Some("".into()), - text_direction: Some(TextDirection::LeftToRight), - character_lengths: vec![].into(), - character_positions: Some(vec![].into()), - character_widths: Some(vec![].into()), - word_lengths: vec![0].into(), - ..Default::default() + }); + node.set_value(""); + node.set_text_direction(TextDirection::LeftToRight); + node.set_character_lengths([]); + node.set_character_positions([]); + node.set_character_widths([]); + node.set_word_lengths([0]); + node }), ), ], diff --git a/consumer/src/tree.rs b/consumer/src/tree.rs index 197ed116f..40fe1d919 100644 --- a/consumer/src/tree.rs +++ b/consumer/src/tree.rs @@ -101,7 +101,7 @@ impl State { orphans.remove(&node_id); let mut seen_child_ids = HashSet::new(); - for (child_index, child_id) in node_data.children.iter().enumerate() { + for (child_index, child_id) in node_data.children().iter().enumerate() { assert!(!seen_child_ids.contains(child_id)); orphans.remove(child_id); let parent_and_index = ParentAndIndex(node_id, child_index); @@ -127,7 +127,7 @@ impl State { if node_id == root { node_state.parent_and_index = None } - for child_id in node_state.data.children.iter() { + for child_id in node_state.data.children().iter() { if !seen_child_ids.contains(child_id) { orphans.insert(*child_id); } @@ -177,7 +177,7 @@ impl State { ) { to_remove.insert(id); let node = nodes.get(&id).unwrap(); - for child_id in node.data.children.iter() { + for child_id in node.data.children().iter() { traverse_orphan(nodes, to_remove, *child_id); } } @@ -213,7 +213,7 @@ impl State { let node = state.nodes.get(&id).unwrap(); nodes.push((id, Arc::clone(&node.data))); - for child_id in node.data.children.iter() { + for child_id in node.data.children().iter() { traverse(state, nodes, *child_id); } } @@ -438,13 +438,7 @@ mod tests { #[test] fn init_tree_with_root_node() { let update = TreeUpdate { - nodes: vec![( - NODE_ID_1, - Arc::new(Node { - role: Role::Window, - ..Node::default() - }), - )], + nodes: vec![(NODE_ID_1, Arc::new(Node::new(Role::Window)))], tree: Some(Tree::new(NODE_ID_1)), focus: None, }; @@ -460,26 +454,14 @@ mod tests { nodes: vec![ ( NODE_ID_1, - Arc::new(Node { - role: Role::Window, - children: vec![NODE_ID_2, NODE_ID_3], - ..Default::default() - }), - ), - ( - NODE_ID_2, - Arc::new(Node { - role: Role::Button, - ..Default::default() - }), - ), - ( - NODE_ID_3, - Arc::new(Node { - role: Role::Button, - ..Default::default() + Arc::new({ + let mut node = Node::new(Role::Window); + node.set_children(vec![NODE_ID_2, NODE_ID_3]); + node }), ), + (NODE_ID_2, Arc::new(Node::new(Role::Button))), + (NODE_ID_3, Arc::new(Node::new(Role::Button))), ], tree: Some(Tree::new(NODE_ID_1)), focus: None, @@ -499,10 +481,7 @@ mod tests { #[test] fn add_child_to_root_node() { - let root_node = Node { - role: Role::Window, - ..Default::default() - }; + let root_node = Node::new(Role::Window); let first_update = TreeUpdate { nodes: vec![(NODE_ID_1, Arc::new(root_node.clone()))], tree: Some(Tree::new(NODE_ID_1)), @@ -514,18 +493,13 @@ mod tests { nodes: vec![ ( NODE_ID_1, - Arc::new(Node { - children: vec![NODE_ID_2], - ..root_node - }), - ), - ( - NODE_ID_2, - Arc::new(Node { - role: Role::RootWebArea, - ..Default::default() + Arc::new({ + let mut node = root_node; + node.push_child(NODE_ID_2); + node }), ), + (NODE_ID_2, Arc::new(Node::new(Role::RootWebArea))), ], tree: None, focus: None, @@ -547,8 +521,8 @@ mod tests { } fn node_updated(&mut self, old_node: &crate::DetachedNode, new_node: &crate::Node) { if new_node.id() == NODE_ID_1 - && old_node.data().children == vec![] - && new_node.data().children == vec![NODE_ID_2] + && old_node.data().children().is_empty() + && new_node.data().children() == [NODE_ID_2] { self.got_updated_root_node = true; return; @@ -588,26 +562,18 @@ mod tests { #[test] fn remove_child_from_root_node() { - let root_node = Node { - role: Role::Window, - ..Default::default() - }; + let root_node = Node::new(Role::Window); let first_update = TreeUpdate { nodes: vec![ ( NODE_ID_1, - Arc::new(Node { - children: vec![NODE_ID_2], - ..root_node.clone() - }), - ), - ( - NODE_ID_2, - Arc::new(Node { - role: Role::RootWebArea, - ..Default::default() + Arc::new({ + let mut node = root_node.clone(); + node.push_child(NODE_ID_2); + node }), ), + (NODE_ID_2, Arc::new(Node::new(Role::RootWebArea))), ], tree: Some(Tree::new(NODE_ID_1)), focus: None, @@ -632,8 +598,8 @@ mod tests { } fn node_updated(&mut self, old_node: &crate::DetachedNode, new_node: &crate::Node) { if new_node.id() == NODE_ID_1 - && old_node.data().children == vec![NODE_ID_2] - && new_node.data().children == vec![] + && old_node.data().children() == [NODE_ID_2] + && new_node.data().children().is_empty() { self.got_updated_root_node = true; return; @@ -676,26 +642,14 @@ mod tests { nodes: vec![ ( NODE_ID_1, - Arc::new(Node { - role: Role::Window, - children: vec![NODE_ID_2, NODE_ID_3], - ..Default::default() - }), - ), - ( - NODE_ID_2, - Arc::new(Node { - role: Role::Button, - ..Default::default() - }), - ), - ( - NODE_ID_3, - Arc::new(Node { - role: Role::Button, - ..Default::default() + Arc::new({ + let mut node = Node::new(Role::Window); + node.set_children(vec![NODE_ID_2, NODE_ID_3]); + node }), ), + (NODE_ID_2, Arc::new(Node::new(Role::Button))), + (NODE_ID_3, Arc::new(Node::new(Role::Button))), ], tree: Some(Tree::new(NODE_ID_1)), focus: Some(NODE_ID_2), @@ -774,25 +728,23 @@ mod tests { #[test] fn update_node() { - let child_node = Node { - role: Role::Button, - ..Default::default() - }; + let child_node = Node::new(Role::Button); let first_update = TreeUpdate { nodes: vec![ ( NODE_ID_1, - Arc::new(Node { - role: Role::Window, - children: vec![NODE_ID_2], - ..Default::default() + Arc::new({ + let mut node = Node::new(Role::Window); + node.set_children(vec![NODE_ID_2]); + node }), ), ( NODE_ID_2, - Arc::new(Node { - name: Some("foo".into()), - ..child_node.clone() + Arc::new({ + let mut node = child_node.clone(); + node.set_name("foo"); + node }), ), ], @@ -807,9 +759,10 @@ mod tests { let second_update = TreeUpdate { nodes: vec![( NODE_ID_2, - Arc::new(Node { - name: Some("bar".into()), - ..child_node + Arc::new({ + let mut node = child_node; + node.set_name("bar"); + node }), )], tree: None, diff --git a/platforms/macos/src/node.rs b/platforms/macos/src/node.rs index 44af9cb0f..fa0b0dd8a 100644 --- a/platforms/macos/src/node.rs +++ b/platforms/macos/src/node.rs @@ -342,7 +342,7 @@ impl<'a> NodeWrapper<'a> { } } - pub(crate) fn raw_text_selection(&self) -> Option<&TextSelection> { + pub(crate) fn raw_text_selection(&self) -> Option { self.node_state().raw_text_selection() } } diff --git a/platforms/windows/examples/hello_world.rs b/platforms/windows/examples/hello_world.rs index 228f7b5e3..61df5f45d 100644 --- a/platforms/windows/examples/hello_world.rs +++ b/platforms/windows/examples/hello_world.rs @@ -70,21 +70,21 @@ fn make_button(id: NodeId, name: &str) -> Arc { _ => unreachable!(), }; - Arc::new(Node { - role: Role::Button, - bounds: Some(rect), - name: Some(name.into()), - focusable: true, - default_action_verb: Some(DefaultActionVerb::Click), - ..Default::default() + Arc::new({ + let mut node = Node::new(Role::Button); + node.set_bounds(rect); + node.set_name(name); + node.add_action(Action::Focus); + node.set_default_action_verb(DefaultActionVerb::Click); + node }) } fn get_initial_state() -> TreeUpdate { - let root = Arc::new(Node { - role: Role::Window, - children: vec![BUTTON_1_ID, BUTTON_2_ID], - ..Default::default() + let root = Arc::new({ + let mut node = Node::new(Role::Window); + node.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); + node }); let button_1 = make_button(BUTTON_1_ID, "Button 1"); let button_2 = make_button(BUTTON_2_ID, "Button 2"); @@ -152,16 +152,16 @@ impl WindowState { } else { "You pressed button 2" }; - let node = Arc::new(Node { - role: Role::StaticText, - name: Some(name.into()), - live: Some(Live::Polite), - ..Default::default() + let node = Arc::new({ + let mut node = Node::new(Role::StaticText); + node.set_name(name); + node.set_live(Live::Polite); + node }); - let root = Arc::new(Node { - role: Role::Window, - children: vec![BUTTON_1_ID, BUTTON_2_ID, PRESSED_TEXT_ID], - ..Node::default() + let root = Arc::new({ + let mut node = Node::new(Role::Window); + node.set_children(vec![BUTTON_1_ID, BUTTON_2_ID, PRESSED_TEXT_ID]); + node }); let update = TreeUpdate { nodes: vec![(PRESSED_TEXT_ID, node), (WINDOW_ID, root)], diff --git a/platforms/windows/src/tests/simple.rs b/platforms/windows/src/tests/simple.rs index 097fe797a..899312804 100644 --- a/platforms/windows/src/tests/simple.rs +++ b/platforms/windows/src/tests/simple.rs @@ -5,7 +5,7 @@ use std::{convert::TryInto, num::NonZeroU128, sync::Arc}; -use accesskit::{ActionHandler, ActionRequest, Node, NodeId, Role, Tree, TreeUpdate}; +use accesskit::{Action, ActionHandler, ActionRequest, Node, NodeId, Role, Tree, TreeUpdate}; use windows::{core::*, Win32::UI::Accessibility::*}; use super::*; @@ -17,19 +17,19 @@ const BUTTON_1_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(2) }); const BUTTON_2_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(3) }); fn make_button(name: &str) -> Arc { - Arc::new(Node { - role: Role::Button, - name: Some(name.into()), - focusable: true, - ..Default::default() + Arc::new({ + let mut node = Node::new(Role::Button); + node.set_name(name); + node.add_action(Action::Focus); + node }) } fn get_initial_state() -> TreeUpdate { - let root = Arc::new(Node { - role: Role::Window, - children: vec![BUTTON_1_ID, BUTTON_2_ID], - ..Default::default() + let root = Arc::new({ + let mut node = Node::new(Role::Window); + node.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); + node }); let button_1 = make_button("Button 1"); let button_2 = make_button("Button 2"); diff --git a/platforms/windows/src/tests/subclassed.rs b/platforms/windows/src/tests/subclassed.rs index 063562e40..f615b490c 100644 --- a/platforms/windows/src/tests/subclassed.rs +++ b/platforms/windows/src/tests/subclassed.rs @@ -5,7 +5,7 @@ use std::{num::NonZeroU128, sync::Arc}; -use accesskit::{ActionHandler, ActionRequest, Node, NodeId, Role, Tree, TreeUpdate}; +use accesskit::{Action, ActionHandler, ActionRequest, Node, NodeId, Role, Tree, TreeUpdate}; use windows::Win32::{Foundation::*, UI::Accessibility::*}; use winit::{ event_loop::EventLoopBuilder, @@ -22,20 +22,20 @@ const BUTTON_1_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(2) }); const BUTTON_2_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(3) }); fn make_button(name: &str) -> Arc { - Arc::new(Node { - role: Role::Button, - name: Some(name.into()), - focusable: true, - ..Default::default() + Arc::new({ + let mut node = Node::new(Role::Button); + node.set_name(name); + node.add_action(Action::Focus); + node }) } fn get_initial_state() -> TreeUpdate { - let root = Arc::new(Node { - role: Role::Window, - children: vec![BUTTON_1_ID, BUTTON_2_ID], - name: Some(WINDOW_TITLE.into()), - ..Default::default() + let root = Arc::new({ + let mut node = Node::new(Role::Window); + node.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); + node.set_name(WINDOW_TITLE); + node }); let button_1 = make_button("Button 1"); let button_2 = make_button("Button 2"); diff --git a/platforms/winit/examples/simple.rs b/platforms/winit/examples/simple.rs index d5070dd16..fe1b847d7 100644 --- a/platforms/winit/examples/simple.rs +++ b/platforms/winit/examples/simple.rs @@ -42,13 +42,13 @@ fn make_button(id: NodeId, name: &str) -> Arc { _ => unreachable!(), }; - Arc::new(Node { - role: Role::Button, - bounds: Some(rect), - name: Some(name.into()), - focusable: true, - default_action_verb: Some(DefaultActionVerb::Click), - ..Default::default() + Arc::new({ + let mut node = Node::new(Role::Button); + node.set_bounds(rect); + node.set_name(name); + node.add_action(Action::Focus); + node.set_default_action_verb(DefaultActionVerb::Click); + node }) } @@ -90,17 +90,17 @@ impl State { } else { "You pressed button 2" }; - let node = Arc::new(Node { - role: Role::StaticText, - name: Some(name.into()), - live: Some(Live::Polite), - ..Default::default() + let node = Arc::new({ + let mut node = Node::new(Role::StaticText); + node.set_name(name); + node.set_live(Live::Polite); + node }); - let root = Arc::new(Node { - role: Role::Window, - children: vec![BUTTON_1_ID, BUTTON_2_ID, PRESSED_TEXT_ID], - name: Some(WINDOW_TITLE.into()), - ..Default::default() + let root = Arc::new({ + let mut node = Node::new(Role::Window); + node.set_children(vec![BUTTON_1_ID, BUTTON_2_ID, PRESSED_TEXT_ID]); + node.set_name(WINDOW_TITLE); + node }); let update = TreeUpdate { nodes: vec![(PRESSED_TEXT_ID, node), (WINDOW_ID, root)], @@ -112,11 +112,11 @@ impl State { } fn initial_tree_update(state: &State) -> TreeUpdate { - let root = Arc::new(Node { - role: Role::Window, - children: vec![BUTTON_1_ID, BUTTON_2_ID], - name: Some(WINDOW_TITLE.into()), - ..Default::default() + let root = Arc::new({ + let mut node = Node::new(Role::Window); + node.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); + node.set_name(WINDOW_TITLE); + node }); let button_1 = make_button(BUTTON_1_ID, "Button 1"); let button_2 = make_button(BUTTON_2_ID, "Button 2"); From 83c17d1d6df5992e9317f82bdabe3f4d228e4ad7 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sat, 7 Jan 2023 23:11:34 -0600 Subject: [PATCH 02/45] Switch to table-based properties for anything larger than 1 byte; serialization is currently broken --- common/src/lib.rs | 1209 +++++++++++++++++++++++---------------------- 1 file changed, 614 insertions(+), 595 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 59deb456b..b5a38a883 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -631,398 +631,328 @@ pub struct TextSelection { pub focus: TextPosition, } +// The following is based on the technique described here: +// https://viruta.org/reducing-memory-consumption-in-librsvg-2.html + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(u8)] +enum PropertyId { + Transform, + Bounds, + Children, + Name, + Description, + Value, + IndirectChildren, + ActiveDescendant, + ErrorMessage, + InPageLinkTarget, + MemberOf, + NextOnLine, + PreviousOnLine, + PopupFor, + Controls, + Details, + DescribedBy, + FlowTo, + LabelledBy, + RadioGroup, + CharacterLengths, + CharacterPositions, + CharacterWidths, + WordLengths, + CustomActions, + AccessKey, + AutoComplete, + CheckedStateDescription, + ClassName, + CssDisplay, + FontFamily, + HtmlTag, + InnerHtml, + InputType, + KeyShortcuts, + Language, + LiveRelevant, + Placeholder, + AriaRole, + RoleDescription, + Tooltip, + Url, + ScrollX, + ScrollXMin, + ScrollXMax, + ScrollY, + ScrollYMin, + ScrollYMax, + TextSelection, + AriaColumnCount, + AriaCellColumnIndex, + AriaCellColumnSpan, + AriaRowCount, + AriaCellRowIndex, + AriaCellRowSpan, + TableRowCount, + TableColumnCount, + TableHeader, + TableRowIndex, + TableRowHeader, + TableColumnIndex, + TableColumnHeader, + TableCellColumnIndex, + TableCellColumnSpan, + TableCellRowIndex, + TableCellRowSpan, + HierarchicalLevel, + SizeOfSet, + PositionInSet, + ColorValue, + BackgroundColor, + ForegroundColor, + PreviousFocus, + NextFocus, + NumericValue, + MinNumericValue, + MaxNumericValue, + NumericValueStep, + NumericValueJump, + FontSize, + FontWeight, + TextIndent, + + // This should come last. + Unset, +} + +#[derive(Clone, Debug, PartialEq)] +enum Property { + None, + Affine(Affine), + Rect(Rect), + NodeIdVec(Vec), + NodeId(NodeId), + String(Box), + U8Slice(Box<[u8]>), + F32Slice(Box<[f32]>), + CustomActionVec(Vec), + F64(f64), + TextSelection(TextSelection), + Usize(usize), + U32(u32), +} + /// A single accessible object. A complete UI is represented as a tree of these. /// /// For brevity, and to make more of the documentation usable in bindings /// to other languages, documentation of getter methods is written as if /// documenting fields in a struct, and such methods are referred to /// as properties. -#[derive(Clone, Debug, Default, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] -#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +#[derive(Clone, Debug, PartialEq)] pub struct Node { role: Role, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - transform: Option>, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - bounds: Option, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty"))] - children: Vec, - - /// Unordered set of actions supported by this node. - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "EnumSet::is_empty"))] + indices: [u8; PropertyId::Unset as usize], + props: Vec, actions: EnumSet, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - name: Option>, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] name_from: Option, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - description: Option>, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] description_from: Option, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - value: Option>, - - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] autofill_available: bool, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] expanded: Option, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] default: bool, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] editable: bool, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] orientation: Option, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] hovered: bool, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] hidden: bool, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] linked: bool, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] multiline: bool, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] multiselectable: bool, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] protected: bool, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] required: bool, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] visited: bool, - - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] busy: bool, - - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] nonatomic_text_field_root: bool, - - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] live_atomic: bool, - - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] modal: bool, - - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] canvas_has_fallback: bool, - - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] scrollable: bool, - - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] clips_children: bool, - - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] not_user_selectable_style: bool, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] selected: Option, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] selected_from_focus: bool, - - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] is_line_breaking_object: bool, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] is_page_breaking_object: bool, - - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] has_aria_attribute: bool, - - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] touch_pass_through: bool, - - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty"))] - indirect_children: Vec, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - active_descendant: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - error_message: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - in_page_link_target: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - member_of: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - next_on_line: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - previous_on_line: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - popup_for: Option, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty"))] - controls: Vec, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty"))] - details: Vec, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty"))] - described_by: Vec, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty"))] - flow_to: Vec, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty"))] - labelled_by: Vec, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty"))] - radio_group: Vec, - - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] is_spelling_error: bool, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] is_grammar_error: bool, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] is_search_match: bool, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] is_suggestion: bool, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] text_direction: Option, - - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty"))] - character_lengths: Box<[u8]>, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - character_positions: Option>, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - character_widths: Option>, - - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty"))] - word_lengths: Box<[u8]>, - - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_empty"))] - custom_actions: Vec, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - access_key: Option>, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] invalid: Option, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - auto_complete: Option>, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] checked_state: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - checked_state_description: Option>, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - class_name: Option>, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - css_display: Option>, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - font_family: Option>, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - html_tag: Option>, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - inner_html: Option>, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - input_type: Option>, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - key_shortcuts: Option>, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - language: Option>, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - live_relevant: Option>, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] live: Option, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - placeholder: Option>, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - aria_role: Option>, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - role_description: Option>, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - tooltip: Option>, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - url: Option>, - - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] default_action_verb: Option, + sort_direction: Option, + read_only: bool, + disabled: bool, + aria_current: Option, + has_popup: Option, + list_style: Option, + text_align: Option, + vertical_offset: Option, + bold: bool, + italic: bool, + overline: Option, + strikethrough: Option, + underline: Option, +} - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - scroll_x: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - scroll_x_min: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - scroll_x_max: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - scroll_y: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - scroll_y_min: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - scroll_y_max: Option, +impl Node { + fn get_property(&self, id: PropertyId) -> &Property { + let index = self.indices[id as usize]; + if index == PropertyId::Unset as u8 { + &Property::None + } else { + &self.props[index as usize] + } + } - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - text_selection: Option, + fn get_property_mut(&mut self, id: PropertyId, default: Property) -> &mut Property { + let index = self.indices[id as usize] as usize; + if index == PropertyId::Unset as usize { + self.props.push(default); + let index = self.props.len() - 1; + self.indices[id as usize] = index as u8; + &mut self.props[index] + } else { + if matches!(self.props[index], Property::None) { + self.props[index] = default; + } + &mut self.props[index] + } + } - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - aria_column_count: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - aria_cell_column_index: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - aria_cell_column_span: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - aria_row_count: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - aria_cell_row_index: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - aria_cell_row_span: Option, + fn set_property(&mut self, id: PropertyId, value: Property) { + let index = self.indices[id as usize]; + if index == PropertyId::Unset as u8 { + self.props.push(value); + self.indices[id as usize] = (self.props.len() - 1) as u8; + } else { + self.props[index as usize] = value; + } + } - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - table_row_count: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - table_column_count: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - table_header: Option, + fn clear_property(&mut self, id: PropertyId) { + let index = self.indices[id as usize]; + if index != PropertyId::Unset as u8 { + self.props[index as usize] = Property::None; + } + } - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - table_row_index: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - table_row_header: Option, + fn get_node_id_vec(&self, id: PropertyId) -> &[NodeId] { + match self.get_property(id) { + Property::None => &[], + Property::NodeIdVec(value) => value, + _ => panic!(), + } + } - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - table_column_index: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - table_column_header: Option, + fn push_to_node_id_vec(&mut self, property_id: PropertyId, node_id: NodeId) { + match self.get_property_mut(property_id, Property::NodeIdVec(Vec::new())) { + Property::NodeIdVec(v) => { + v.push(node_id); + } + _ => panic!(), + } + } - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - table_cell_column_index: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - table_cell_column_span: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - table_cell_row_index: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - table_cell_row_span: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - sort_direction: Option, + fn set_node_id_vec(&mut self, id: PropertyId, value: impl Into>) { + self.set_property(id, Property::NodeIdVec(value.into())); + } - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - hierarchical_level: Option, + fn get_node_id(&self, id: PropertyId) -> Option { + match self.get_property(id) { + Property::None => None, + Property::NodeId(value) => Some(*value), + _ => panic!(), + } + } - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - read_only: bool, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - disabled: bool, + fn set_node_id(&mut self, id: PropertyId, value: NodeId) { + self.set_property(id, Property::NodeId(value)); + } - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - size_of_set: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - position_in_set: Option, + fn get_string(&self, id: PropertyId) -> Option<&str> { + match self.get_property(id) { + Property::None => None, + Property::String(value) => Some(value), + _ => panic!(), + } + } - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - color_value: Option, + fn set_string(&mut self, id: PropertyId, value: impl Into>) { + self.set_property(id, Property::String(value.into())); + } - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - aria_current: Option, + fn get_u8_slice(&self, id: PropertyId) -> &[u8] { + match self.get_property(id) { + Property::None => &[], + Property::U8Slice(value) => value, + _ => panic!(), + } + } - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - background_color: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - foreground_color: Option, + fn set_u8_slice(&mut self, id: PropertyId, value: impl Into>) { + self.set_property(id, Property::U8Slice(value.into())); + } - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - has_popup: Option, + fn get_optional_f32_slice(&self, id: PropertyId) -> Option<&[f32]> { + match self.get_property(id) { + Property::None => None, + Property::F32Slice(value) => Some(value), + _ => panic!(), + } + } - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - list_style: Option, + fn set_f32_slice(&mut self, id: PropertyId, value: impl Into>) { + self.set_property(id, Property::F32Slice(value.into())); + } - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - text_align: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - vertical_offset: Option, + fn get_f64(&self, id: PropertyId) -> Option { + match self.get_property(id) { + Property::None => None, + Property::F64(value) => Some(*value), + _ => panic!(), + } + } - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - bold: bool, - #[cfg_attr(feature = "serde", serde(default))] - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "is_false"))] - italic: bool, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - overline: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - strikethrough: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - underline: Option, + fn set_f64(&mut self, id: PropertyId, value: f64) { + self.set_property(id, Property::F64(value)); + } - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - previous_focus: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - next_focus: Option, + fn get_usize(&self, id: PropertyId) -> Option { + match self.get_property(id) { + Property::None => None, + Property::Usize(value) => Some(*value), + _ => panic!(), + } + } - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - numeric_value: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - min_numeric_value: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - max_numeric_value: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - numeric_value_step: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - numeric_value_jump: Option, + fn set_usize(&mut self, id: PropertyId, value: usize) { + self.set_property(id, Property::Usize(value)); + } - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - font_size: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - font_weight: Option, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] - text_indent: Option, -} + fn get_u32(&self, id: PropertyId) -> Option { + match self.get_property(id) { + Property::None => None, + Property::U32(value) => Some(*value), + _ => panic!(), + } + } + + fn set_u32(&mut self, id: PropertyId, value: u32) { + self.set_property(id, Property::U32(value)); + } -impl Node { pub fn new(role: Role) -> Self { Self { role, @@ -1050,13 +980,17 @@ impl Node { /// /// [`bounds`]: NodeProvider::bounds pub fn transform(&self) -> Option { - self.transform.as_ref().map(|v| **v) + match self.get_property(PropertyId::Transform) { + Property::None => None, + Property::Affine(value) => Some(*value), + _ => panic!(), + } } pub fn set_transform(&mut self, value: Affine) { - self.transform = Some(Box::new(value)); + self.set_property(PropertyId::Transform, Property::Affine(value)); } pub fn clear_transform(&mut self) { - self.transform = None; + self.clear_property(PropertyId::Transform); } /// The bounding box of this node, in the node's coordinate space. @@ -1070,26 +1004,30 @@ impl Node { /// /// [`transform`]: NodeProvider::transform pub fn bounds(&self) -> Option { - self.bounds + match self.get_property(PropertyId::Bounds) { + Property::None => None, + Property::Rect(value) => Some(*value), + _ => panic!(), + } } pub fn set_bounds(&mut self, value: Rect) { - self.bounds = Some(value); + self.set_property(PropertyId::Bounds, Property::Rect(value)); } pub fn clear_bounds(&mut self) { - self.bounds = None; + self.clear_property(PropertyId::Bounds); } pub fn children(&self) -> &[NodeId] { - &self.children + self.get_node_id_vec(PropertyId::Children) } pub fn set_children(&mut self, value: impl Into>) { - self.children = value.into(); + self.set_node_id_vec(PropertyId::Children, value); } pub fn push_child(&mut self, id: NodeId) { - self.children.push(id); + self.push_to_node_id_vec(PropertyId::Children, id); } pub fn clear_children(&mut self) { - self.children.clear(); + self.clear_property(PropertyId::Children); } pub fn supports_action(&self, action: Action) -> bool { @@ -1106,13 +1044,13 @@ impl Node { } pub fn name(&self) -> Option<&str> { - self.name.as_deref() + self.get_string(PropertyId::Name) } pub fn set_name(&mut self, value: impl Into>) { - self.name = Some(value.into()); + self.set_string(PropertyId::Name, value); } pub fn clear_name(&mut self) { - self.name = None; + self.clear_property(PropertyId::Name); } /// What information was used to compute the object's name. @@ -1127,13 +1065,13 @@ impl Node { } pub fn description(&self) -> Option<&str> { - self.description.as_deref() + self.get_string(PropertyId::Description) } pub fn set_description(&mut self, value: impl Into>) { - self.description = Some(value.into()); + self.set_string(PropertyId::Description, value); } pub fn clear_description(&mut self) { - self.description = None; + self.clear_property(PropertyId::Description); } /// What information was used to compute the object's description. @@ -1148,13 +1086,13 @@ impl Node { } pub fn value(&self) -> Option<&str> { - self.value.as_deref() + self.get_string(PropertyId::Value) } pub fn set_value(&mut self, value: impl Into>) { - self.value = Some(value.into()); + self.set_string(PropertyId::Value, value); } pub fn clear_value(&mut self) { - self.value = None; + self.clear_property(PropertyId::Value); } pub fn is_autofill_available(&self) -> bool { @@ -1399,168 +1337,168 @@ impl Node { /// a table cell is a child of a row, and an 'indirect' child of a /// column. pub fn indirect_children(&self) -> &[NodeId] { - &self.indirect_children + self.get_node_id_vec(PropertyId::IndirectChildren) } pub fn set_indirect_children(&mut self, value: impl Into>) { - self.indirect_children = value.into(); + self.set_node_id_vec(PropertyId::IndirectChildren, value); } pub fn push_indirect_child(&mut self, id: NodeId) { - self.indirect_children.push(id); + self.push_to_node_id_vec(PropertyId::IndirectChildren, id); } pub fn clear_indirect_children(&mut self) { - self.indirect_children.clear(); + self.clear_property(PropertyId::IndirectChildren); } // Relationships between this node and other nodes. pub fn active_descendant(&self) -> Option { - self.active_descendant + self.get_node_id(PropertyId::ActiveDescendant) } pub fn set_active_descendant(&mut self, value: NodeId) { - self.active_descendant = Some(value); + self.set_node_id(PropertyId::ActiveDescendant, value); } pub fn clear_active_descendant(&mut self) { - self.active_descendant = None; + self.clear_property(PropertyId::ActiveDescendant); } pub fn error_message(&self) -> Option { - self.error_message + self.get_node_id(PropertyId::ErrorMessage) } pub fn set_error_message(&mut self, value: NodeId) { - self.error_message = Some(value); + self.set_node_id(PropertyId::ErrorMessage, value); } pub fn clear_error_message(&mut self) { - self.error_message = None; + self.clear_property(PropertyId::ErrorMessage); } pub fn in_page_link_target(&self) -> Option { - self.in_page_link_target + self.get_node_id(PropertyId::InPageLinkTarget) } pub fn set_in_page_link_target(&mut self, value: NodeId) { - self.in_page_link_target = Some(value); + self.set_node_id(PropertyId::InPageLinkTarget, value); } pub fn clear_in_page_link_target(&mut self) { - self.in_page_link_target = None; + self.clear_property(PropertyId::InPageLinkTarget); } pub fn member_of(&self) -> Option { - self.member_of + self.get_node_id(PropertyId::MemberOf) } pub fn set_member_of(&mut self, value: NodeId) { - self.member_of = Some(value); + self.set_node_id(PropertyId::MemberOf, value); } pub fn clear_member_of(&mut self) { - self.member_of = None; + self.clear_property(PropertyId::MemberOf); } pub fn next_on_line(&self) -> Option { - self.next_on_line + self.get_node_id(PropertyId::NextOnLine) } pub fn set_next_on_line(&mut self, value: NodeId) { - self.next_on_line = Some(value); + self.set_node_id(PropertyId::NextOnLine, value); } pub fn clear_next_on_line(&mut self) { - self.next_on_line = None; + self.clear_property(PropertyId::NextOnLine); } pub fn previous_on_line(&self) -> Option { - self.previous_on_line + self.get_node_id(PropertyId::PreviousOnLine) } pub fn set_previous_on_line(&mut self, value: NodeId) { - self.previous_on_line = Some(value); + self.set_node_id(PropertyId::PreviousOnLine, value); } pub fn clear_previous_on_line(&mut self) { - self.previous_on_line = None; + self.clear_property(PropertyId::PreviousOnLine); } pub fn popup_for(&self) -> Option { - self.popup_for + self.get_node_id(PropertyId::PopupFor) } pub fn set_popup_for(&mut self, value: NodeId) { - self.popup_for = Some(value); + self.set_node_id(PropertyId::PopupFor, value); } pub fn clear_popup_for(&mut self) { - self.popup_for = None; + self.clear_property(PropertyId::PopupFor); } pub fn controls(&self) -> &[NodeId] { - &self.controls + self.get_node_id_vec(PropertyId::Controls) } pub fn set_controls(&mut self, value: impl Into>) { - self.controls = value.into(); + self.set_node_id_vec(PropertyId::Controls, value); } pub fn push_controls(&mut self, id: NodeId) { - self.controls.push(id); + self.push_to_node_id_vec(PropertyId::Controls, id); } pub fn clear_controls(&mut self) { - self.controls.clear(); + self.clear_property(PropertyId::Controls); } pub fn details(&self) -> &[NodeId] { - &self.details + self.get_node_id_vec(PropertyId::Details) } pub fn set_details(&mut self, value: impl Into>) { - self.details = value.into(); + self.set_node_id_vec(PropertyId::Details, value); } pub fn push_details(&mut self, id: NodeId) { - self.details.push(id); + self.push_to_node_id_vec(PropertyId::Details, id); } pub fn clear_details(&mut self) { - self.details.clear(); + self.clear_property(PropertyId::Details); } pub fn described_by(&self) -> &[NodeId] { - &self.described_by + self.get_node_id_vec(PropertyId::DescribedBy) } pub fn set_described_by(&mut self, value: impl Into>) { - self.described_by = value.into(); + self.set_node_id_vec(PropertyId::DescribedBy, value); } pub fn push_described_by(&mut self, id: NodeId) { - self.described_by.push(id); + self.push_to_node_id_vec(PropertyId::DescribedBy, id); } pub fn clear_described_by(&mut self) { - self.described_by.clear(); + self.clear_property(PropertyId::DescribedBy); } pub fn flow_to(&self) -> &[NodeId] { - &self.flow_to + self.get_node_id_vec(PropertyId::FlowTo) } pub fn set_flow_to(&mut self, value: impl Into>) { - self.flow_to = value.into(); + self.set_node_id_vec(PropertyId::FlowTo, value); } pub fn push_flow_to(&mut self, id: NodeId) { - self.flow_to.push(id); + self.push_to_node_id_vec(PropertyId::FlowTo, id); } pub fn clear_flow_to(&mut self) { - self.flow_to.clear(); + self.clear_property(PropertyId::FlowTo); } pub fn labelled_by(&self) -> &[NodeId] { - &self.labelled_by + self.get_node_id_vec(PropertyId::LabelledBy) } pub fn set_labelled_by(&mut self, value: impl Into>) { - self.labelled_by = value.into(); + self.set_node_id_vec(PropertyId::LabelledBy, value); } pub fn push_labelled_by(&mut self, id: NodeId) { - self.labelled_by.push(id); + self.push_to_node_id_vec(PropertyId::LabelledBy, id); } pub fn clear_labelled_by(&mut self) { - self.labelled_by.clear(); + self.clear_property(PropertyId::LabelledBy); } /// On radio buttons this should be set to a list of all of the buttons /// in the same group as this one, including this radio button itself. pub fn radio_group(&self) -> &[NodeId] { - &self.radio_group + self.get_node_id_vec(PropertyId::RadioGroup) } pub fn set_radio_group(&mut self, value: impl Into>) { - self.radio_group = value.into(); + self.set_node_id_vec(PropertyId::RadioGroup, value); } pub fn push_to_radio_group(&mut self, id: NodeId) { - self.radio_group.push(id); + self.push_to_node_id_vec(PropertyId::RadioGroup, id); } pub fn clear_radio_group(&mut self) { - self.radio_group.clear(); + self.clear_property(PropertyId::RadioGroup); } pub fn is_spelling_error(&self) -> bool { @@ -1620,10 +1558,10 @@ impl Node { /// /// [`value`]: NodeProvider::value pub fn character_lengths(&self) -> &[u8] { - &self.character_lengths + self.get_u8_slice(PropertyId::CharacterLengths) } pub fn set_character_lengths(&mut self, value: impl Into>) { - self.character_lengths = value.into(); + self.set_u8_slice(PropertyId::CharacterLengths, value); } /// For inline text. This is the position of each character within @@ -1643,13 +1581,13 @@ impl Node { /// [`text_direction`]: NodeProvider::text_direction /// [`character_lengths`]: NodeProvider::character_lengths pub fn character_positions(&self) -> Option<&[f32]> { - self.character_positions.as_deref() + self.get_optional_f32_slice(PropertyId::CharacterPositions) } pub fn set_character_positions(&mut self, value: impl Into>) { - self.character_positions = Some(value.into()); + self.set_f32_slice(PropertyId::CharacterPositions, value); } pub fn clear_character_positions(&mut self) { - self.character_positions = None; + self.clear_property(PropertyId::CharacterPositions); } /// For inline text. This is the advance width of each character, @@ -1671,13 +1609,13 @@ impl Node { /// [`text_direction`]: NodeProvider::text_direction /// [`character_lengths`]: NodeProvider::character_lengths pub fn character_widths(&self) -> Option<&[f32]> { - self.character_widths.as_deref() + self.get_optional_f32_slice(PropertyId::CharacterWidths) } pub fn set_character_widths(&mut self, value: impl Into>) { - self.character_widths = Some(value.into()); + self.set_f32_slice(PropertyId::CharacterWidths, value); } pub fn clear_character_widths(&mut self) { - self.character_widths = None; + self.clear_property(PropertyId::CharacterWidths); } /// For inline text. The length of each word in characters, as defined @@ -1703,33 +1641,48 @@ impl Node { /// /// [`character_lengths`]: NodeProvider::character_lengths pub fn word_lengths(&self) -> &[u8] { - &self.word_lengths + self.get_u8_slice(PropertyId::WordLengths) } pub fn set_word_lengths(&mut self, value: impl Into>) { - self.word_lengths = value.into(); + self.set_u8_slice(PropertyId::WordLengths, value); } pub fn custom_actions(&self) -> &[CustomAction] { - &self.custom_actions + match self.get_property(PropertyId::CustomActions) { + Property::None => &[], + Property::CustomActionVec(value) => value, + _ => panic!(), + } } pub fn set_custom_actions(&mut self, value: impl Into>) { - self.custom_actions = value.into(); + self.set_property( + PropertyId::CustomActions, + Property::CustomActionVec(value.into()), + ); } pub fn push_custom_action(&mut self, action: CustomAction) { - self.custom_actions.push(action); + match self.get_property_mut( + PropertyId::CustomActions, + Property::CustomActionVec(Vec::new()), + ) { + Property::CustomActionVec(v) => { + v.push(action); + } + _ => panic!(), + } } pub fn clear_custom_actions(&mut self) { - self.custom_actions.clear(); + self.clear_property(PropertyId::CustomActions); } pub fn access_key(&self) -> Option<&str> { - self.access_key.as_deref() + self.get_string(PropertyId::AccessKey) } pub fn set_access_key(&mut self, value: impl Into>) { - self.access_key = Some(value.into()); + self.set_string(PropertyId::AccessKey, value); } pub fn clear_access_key(&mut self) { - self.access_key = None; + self.clear_property(PropertyId::AccessKey); } pub fn invalid(&self) -> Option { @@ -1743,13 +1696,13 @@ impl Node { } pub fn auto_complete(&self) -> Option<&str> { - self.auto_complete.as_deref() + self.get_string(PropertyId::AutoComplete) } pub fn set_auto_complete(&mut self, value: impl Into>) { - self.auto_complete = Some(value.into()); + self.set_string(PropertyId::AutoComplete, value); } pub fn clear_auto_complete(&mut self) { - self.auto_complete = None; + self.clear_property(PropertyId::AutoComplete); } pub fn checked_state(&self) -> Option { @@ -1763,107 +1716,107 @@ impl Node { } pub fn checked_state_description(&self) -> Option<&str> { - self.checked_state_description.as_deref() + self.get_string(PropertyId::CheckedStateDescription) } pub fn set_checked_state_description(&mut self, value: impl Into>) { - self.checked_state_description = Some(value.into()); + self.set_string(PropertyId::CheckedStateDescription, value); } pub fn clear_checked_state_description(&mut self) { - self.checked_state_description = None; + self.clear_property(PropertyId::CheckedStateDescription); } pub fn class_name(&self) -> Option<&str> { - self.class_name.as_deref() + self.get_string(PropertyId::ClassName) } pub fn set_class_name(&mut self, value: impl Into>) { - self.class_name = Some(value.into()); + self.set_string(PropertyId::ClassName, value); } pub fn clear_class_name(&mut self) { - self.class_name = None; + self.clear_property(PropertyId::ClassName); } pub fn css_display(&self) -> Option<&str> { - self.css_display.as_deref() + self.get_string(PropertyId::CssDisplay) } pub fn set_css_display(&mut self, value: impl Into>) { - self.css_display = Some(value.into()); + self.set_string(PropertyId::CssDisplay, value); } pub fn clear_css_display(&mut self) { - self.css_display = None; + self.clear_property(PropertyId::CssDisplay); } /// Only present when different from parent. pub fn font_family(&self) -> Option<&str> { - self.font_family.as_deref() + self.get_string(PropertyId::FontFamily) } pub fn set_font_family(&mut self, value: impl Into>) { - self.font_family = Some(value.into()); + self.set_string(PropertyId::FontFamily, value); } pub fn clear_font_family(&mut self) { - self.font_family = None; + self.clear_property(PropertyId::FontFamily); } pub fn html_tag(&self) -> Option<&str> { - self.html_tag.as_deref() + self.get_string(PropertyId::HtmlTag) } pub fn set_html_tag(&mut self, value: impl Into>) { - self.html_tag = Some(value.into()); + self.set_string(PropertyId::HtmlTag, value); } pub fn clear_html_tag(&mut self) { - self.html_tag = None; + self.clear_property(PropertyId::HtmlTag); } /// Inner HTML of an element. Only used for a top-level math element, /// to support third-party math accessibility products that parse MathML. pub fn inner_html(&self) -> Option<&str> { - self.inner_html.as_deref() + self.get_string(PropertyId::InnerHtml) } pub fn set_inner_html(&mut self, value: impl Into>) { - self.inner_html = Some(value.into()); + self.set_string(PropertyId::InnerHtml, value); } pub fn clear_inner_html(&mut self) { - self.inner_html = None; + self.clear_property(PropertyId::InnerHtml); } pub fn input_type(&self) -> Option<&str> { - self.input_type.as_deref() + self.get_string(PropertyId::InputType) } pub fn set_input_type(&mut self, value: impl Into>) { - self.input_type = Some(value.into()); + self.set_string(PropertyId::InputType, value); } pub fn clear_input_type(&mut self) { - self.input_type = None; + self.clear_property(PropertyId::InputType); } pub fn key_shortcuts(&self) -> Option<&str> { - self.key_shortcuts.as_deref() + self.get_string(PropertyId::KeyShortcuts) } pub fn set_key_shortcuts(&mut self, value: impl Into>) { - self.key_shortcuts = Some(value.into()); + self.set_string(PropertyId::KeyShortcuts, value); } pub fn clear_key_shortcuts(&mut self) { - self.key_shortcuts = None; + self.clear_property(PropertyId::KeyShortcuts); } /// Only present when different from parent. pub fn language(&self) -> Option<&str> { - self.language.as_deref() + self.get_string(PropertyId::Language) } pub fn set_language(&mut self, value: impl Into>) { - self.language = Some(value.into()); + self.set_string(PropertyId::Language, value); } pub fn clear_language(&mut self) { - self.language = None; + self.clear_property(PropertyId::Language); } pub fn live_relevant(&self) -> Option<&str> { - self.live_relevant.as_deref() + self.get_string(PropertyId::LiveRelevant) } pub fn set_live_relevant(&mut self, value: impl Into>) { - self.live_relevant = Some(value.into()); + self.set_string(PropertyId::LiveRelevant, value); } pub fn clear_live_relevant(&mut self) { - self.live_relevant = None; + self.clear_property(PropertyId::LiveRelevant); } pub fn live(&self) -> Option { @@ -1880,56 +1833,56 @@ impl Node { /// /// [`name`]: NodeProvider::name pub fn placeholder(&self) -> Option<&str> { - self.placeholder.as_deref() + self.get_string(PropertyId::Placeholder) } pub fn set_placeholder(&mut self, value: impl Into>) { - self.placeholder = Some(value.into()); + self.set_string(PropertyId::Placeholder, value); } pub fn clear_placeholder(&mut self) { - self.placeholder = None; + self.clear_property(PropertyId::Placeholder); } pub fn aria_role(&self) -> Option<&str> { - self.aria_role.as_deref() + self.get_string(PropertyId::AriaRole) } pub fn set_aria_role(&mut self, value: impl Into>) { - self.aria_role = Some(value.into()); + self.set_string(PropertyId::AriaRole, value); } pub fn clear_aria_role(&mut self) { - self.aria_role = None; + self.clear_property(PropertyId::AriaRole); } pub fn role_description(&self) -> Option<&str> { - self.role_description.as_deref() + self.get_string(PropertyId::RoleDescription) } pub fn set_role_description(&mut self, value: impl Into>) { - self.role_description = Some(value.into()); + self.set_string(PropertyId::RoleDescription, value); } pub fn clear_role_description(&mut self) { - self.role_description = None; + self.clear_property(PropertyId::RoleDescription); } /// Only if not already exposed in [`name`] ([`NameFrom::Title`]). /// /// [`name`]: NodeProvider::name pub fn tooltip(&self) -> Option<&str> { - self.tooltip.as_deref() + self.get_string(PropertyId::Tooltip) } pub fn set_tooltip(&mut self, value: impl Into>) { - self.tooltip = Some(value.into()); + self.set_string(PropertyId::Tooltip, value); } pub fn clear_tooltip(&mut self) { - self.tooltip = None; + self.clear_property(PropertyId::Tooltip); } pub fn url(&self) -> Option<&str> { - self.url.as_deref() + self.get_string(PropertyId::Url) } pub fn set_url(&mut self, value: impl Into>) { - self.url = Some(value.into()); + self.set_string(PropertyId::Url, value); } pub fn clear_url(&mut self) { - self.url = None; + self.clear_property(PropertyId::Url); } pub fn default_action_verb(&self) -> Option { @@ -1944,252 +1897,256 @@ impl Node { // Scrollable container attributes. - pub fn scroll_x(&self) -> Option { - self.scroll_x + pub fn scroll_x(&self) -> Option { + self.get_f64(PropertyId::ScrollX) } - pub fn set_scroll_x(&mut self, value: f32) { - self.scroll_x = Some(value); + pub fn set_scroll_x(&mut self, value: f64) { + self.set_f64(PropertyId::ScrollX, value); } pub fn clear_scroll_x(&mut self) { - self.scroll_x = None; + self.clear_property(PropertyId::ScrollX); } - pub fn scroll_x_min(&self) -> Option { - self.scroll_x_min + pub fn scroll_x_min(&self) -> Option { + self.get_f64(PropertyId::ScrollXMin) } - pub fn set_scroll_x_min(&mut self, value: f32) { - self.scroll_x_min = Some(value); + pub fn set_scroll_x_min(&mut self, value: f64) { + self.set_f64(PropertyId::ScrollXMin, value); } pub fn clear_scroll_x_min(&mut self) { - self.scroll_x_min = None; + self.clear_property(PropertyId::ScrollXMin); } - pub fn scroll_x_max(&self) -> Option { - self.scroll_x_max + pub fn scroll_x_max(&self) -> Option { + self.get_f64(PropertyId::ScrollXMax) } - pub fn set_scroll_x_max(&mut self, value: f32) { - self.scroll_x_max = Some(value); + pub fn set_scroll_x_max(&mut self, value: f64) { + self.set_f64(PropertyId::ScrollXMax, value); } pub fn clear_scroll_x_max(&mut self) { - self.scroll_x_max = None; + self.clear_property(PropertyId::ScrollXMax); } - pub fn scroll_y(&self) -> Option { - self.scroll_y + pub fn scroll_y(&self) -> Option { + self.get_f64(PropertyId::ScrollY) } - pub fn set_scroll_y(&mut self, value: f32) { - self.scroll_y = Some(value); + pub fn set_scroll_y(&mut self, value: f64) { + self.set_f64(PropertyId::ScrollY, value); } pub fn clear_scroll_y(&mut self) { - self.scroll_y = None; + self.clear_property(PropertyId::ScrollY); } - pub fn scroll_y_min(&self) -> Option { - self.scroll_y_min + pub fn scroll_y_min(&self) -> Option { + self.get_f64(PropertyId::ScrollYMin) } - pub fn set_scroll_y_min(&mut self, value: f32) { - self.scroll_y_min = Some(value); + pub fn set_scroll_y_min(&mut self, value: f64) { + self.set_f64(PropertyId::ScrollYMin, value); } pub fn clear_scroll_y_min(&mut self) { - self.scroll_y_min = None; + self.clear_property(PropertyId::ScrollYMin); } - pub fn scroll_y_max(&self) -> Option { - self.scroll_y_max + pub fn scroll_y_max(&self) -> Option { + self.get_f64(PropertyId::ScrollYMax) } - pub fn set_scroll_y_max(&mut self, value: f32) { - self.scroll_y_max = Some(value); + pub fn set_scroll_y_max(&mut self, value: f64) { + self.set_f64(PropertyId::ScrollYMax, value); } pub fn clear_scroll_y_max(&mut self) { - self.scroll_y_max = None; + self.clear_property(PropertyId::ScrollYMax); } pub fn text_selection(&self) -> Option { - self.text_selection + match self.get_property(PropertyId::TextSelection) { + Property::None => None, + Property::TextSelection(value) => Some(*value), + _ => panic!(), + } } pub fn set_text_selection(&mut self, value: TextSelection) { - self.text_selection = Some(value); + self.set_property(PropertyId::TextSelection, Property::TextSelection(value)); } pub fn clear_text_selection(&mut self) { - self.text_selection = None; + self.clear_property(PropertyId::TextSelection); } pub fn aria_column_count(&self) -> Option { - self.aria_column_count + self.get_usize(PropertyId::AriaColumnCount) } pub fn set_aria_column_count(&mut self, value: usize) { - self.aria_column_count = Some(value); + self.set_usize(PropertyId::AriaColumnCount, value); } pub fn clear_aria_column_count(&mut self) { - self.aria_column_count = None; + self.clear_property(PropertyId::AriaColumnCount); } pub fn aria_cell_column_index(&self) -> Option { - self.aria_cell_column_index + self.get_usize(PropertyId::AriaCellColumnIndex) } pub fn set_aria_cell_column_index(&mut self, value: usize) { - self.aria_cell_column_index = Some(value); + self.set_usize(PropertyId::AriaCellColumnIndex, value); } pub fn clear_aria_cell_column_index(&mut self) { - self.aria_cell_column_index = None; + self.clear_property(PropertyId::AriaCellColumnIndex); } pub fn aria_cell_column_span(&self) -> Option { - self.aria_cell_column_span + self.get_usize(PropertyId::AriaCellColumnSpan) } pub fn set_aria_cell_column_span(&mut self, value: usize) { - self.aria_cell_column_span = Some(value); + self.set_usize(PropertyId::AriaCellColumnSpan, value); } pub fn clear_aria_cell_column_span(&mut self) { - self.aria_cell_column_span = None; + self.clear_property(PropertyId::AriaCellColumnSpan); } pub fn aria_row_count(&self) -> Option { - self.aria_row_count + self.get_usize(PropertyId::AriaRowCount) } pub fn set_aria_row_count(&mut self, value: usize) { - self.aria_row_count = Some(value); + self.set_usize(PropertyId::AriaRowCount, value); } pub fn clear_aria_row_count(&mut self) { - self.aria_row_count = None; + self.clear_property(PropertyId::AriaRowCount); } pub fn aria_cell_row_index(&self) -> Option { - self.aria_cell_row_index + self.get_usize(PropertyId::AriaCellRowIndex) } pub fn set_aria_cell_row_index(&mut self, value: usize) { - self.aria_cell_row_index = Some(value); + self.set_usize(PropertyId::AriaCellRowIndex, value); } pub fn clear_aria_cell_row_index(&mut self) { - self.aria_cell_row_index = None; + self.clear_property(PropertyId::AriaCellRowIndex); } pub fn aria_cell_row_span(&self) -> Option { - self.aria_cell_row_span + self.get_usize(PropertyId::AriaCellRowSpan) } pub fn set_aria_cell_row_span(&mut self, value: usize) { - self.aria_cell_row_span = Some(value); + self.set_usize(PropertyId::AriaCellRowSpan, value); } pub fn clear_aria_cell_row_span(&mut self) { - self.aria_cell_row_span = None; + self.clear_property(PropertyId::AriaCellRowSpan); } // Table attributes. pub fn table_row_count(&self) -> Option { - self.table_row_count + self.get_usize(PropertyId::TableRowCount) } pub fn set_table_row_count(&mut self, value: usize) { - self.table_row_count = Some(value); + self.set_usize(PropertyId::TableRowCount, value); } pub fn clear_table_row_count(&mut self) { - self.table_row_count = None; + self.clear_property(PropertyId::TableRowCount); } pub fn table_column_count(&self) -> Option { - self.table_column_count + self.get_usize(PropertyId::TableColumnCount) } pub fn set_table_column_count(&mut self, value: usize) { - self.table_column_count = Some(value); + self.set_usize(PropertyId::TableColumnCount, value); } pub fn clear_table_column_count(&mut self) { - self.table_column_count = None; + self.clear_property(PropertyId::TableColumnCount); } pub fn table_header(&self) -> Option { - self.table_header + self.get_node_id(PropertyId::TableHeader) } pub fn set_table_header(&mut self, value: NodeId) { - self.table_header = Some(value); + self.set_node_id(PropertyId::TableHeader, value); } pub fn clear_table_header(&mut self) { - self.table_header = None; + self.clear_property(PropertyId::TableHeader); } // Table row attributes. pub fn table_row_index(&self) -> Option { - self.table_row_index + self.get_usize(PropertyId::TableRowIndex) } pub fn set_table_row_index(&mut self, value: usize) { - self.table_row_index = Some(value); + self.set_usize(PropertyId::TableRowIndex, value); } pub fn clear_table_row_index(&mut self) { - self.table_row_index = None; + self.clear_property(PropertyId::TableRowIndex); } pub fn table_row_header(&self) -> Option { - self.table_row_header + self.get_node_id(PropertyId::TableRowHeader) } pub fn set_table_row_header(&mut self, value: NodeId) { - self.table_row_header = Some(value); + self.set_node_id(PropertyId::TableRowHeader, value); } pub fn clear_table_row_header(&mut self) { - self.table_row_header = None; + self.clear_property(PropertyId::TableRowHeader); } // Table column attributes. pub fn table_column_index(&self) -> Option { - self.table_column_index + self.get_usize(PropertyId::TableColumnIndex) } pub fn set_table_column_index(&mut self, value: usize) { - self.table_column_index = Some(value); + self.set_usize(PropertyId::TableColumnIndex, value); } pub fn clear_table_column_index(&mut self) { - self.table_column_index = None; + self.clear_property(PropertyId::TableColumnIndex); } pub fn table_column_header(&self) -> Option { - self.table_column_header + self.get_node_id(PropertyId::TableColumnHeader) } pub fn set_table_column_header(&mut self, value: NodeId) { - self.table_column_header = Some(value); + self.set_node_id(PropertyId::TableColumnHeader, value); } pub fn clear_table_column_header(&mut self) { - self.table_column_header = None; + self.clear_property(PropertyId::TableColumnHeader); } // Table cell attributes. pub fn table_cell_column_index(&self) -> Option { - self.table_cell_column_index + self.get_usize(PropertyId::TableCellColumnIndex) } pub fn set_table_cell_column_index(&mut self, value: usize) { - self.table_cell_column_index = Some(value); + self.set_usize(PropertyId::TableCellColumnIndex, value); } pub fn clear_table_cell_column_index(&mut self) { - self.table_cell_column_index = None; + self.clear_property(PropertyId::TableCellColumnIndex); } pub fn table_cell_column_span(&self) -> Option { - self.table_cell_column_span + self.get_usize(PropertyId::TableCellColumnSpan) } pub fn set_table_cell_column_span(&mut self, value: usize) { - self.table_cell_column_span = Some(value); + self.set_usize(PropertyId::TableCellColumnSpan, value); } pub fn clear_table_cell_column_span(&mut self) { - self.table_cell_column_span = None; + self.clear_property(PropertyId::TableCellColumnSpan); } pub fn table_cell_row_index(&self) -> Option { - self.table_cell_row_index + self.get_usize(PropertyId::TableCellRowIndex) } pub fn set_table_cell_row_index(&mut self, value: usize) { - self.table_cell_row_index = Some(value); + self.set_usize(PropertyId::TableCellRowIndex, value); } pub fn clear_table_cell_row_index(&mut self) { - self.table_cell_row_index = None; + self.clear_property(PropertyId::TableCellRowIndex); } pub fn table_cell_row_span(&self) -> Option { - self.table_cell_row_span + self.get_usize(PropertyId::TableCellRowSpan) } pub fn set_table_cell_row_span(&mut self, value: usize) { - self.table_cell_row_span = Some(value); + self.set_usize(PropertyId::TableCellRowSpan, value); } pub fn clear_table_cell_row_span(&mut self) { - self.table_cell_row_span = None; + self.clear_property(PropertyId::TableCellRowSpan); } pub fn sort_direction(&self) -> Option { @@ -2204,13 +2161,13 @@ impl Node { /// Tree control attributes. pub fn hierarchical_level(&self) -> Option { - self.hierarchical_level + self.get_usize(PropertyId::HierarchicalLevel) } pub fn set_hierarchical_level(&mut self, value: usize) { - self.hierarchical_level = Some(value); + self.set_usize(PropertyId::HierarchicalLevel, value); } pub fn clear_hierarchical_level(&mut self) { - self.hierarchical_level = None; + self.clear_property(PropertyId::HierarchicalLevel); } /// Use for a textbox that allows focus/selection but not input. @@ -2232,34 +2189,34 @@ impl Node { // Position or Number of items in current set of listitems or treeitems pub fn size_of_set(&self) -> Option { - self.size_of_set + self.get_usize(PropertyId::SizeOfSet) } pub fn set_size_of_set(&mut self, value: usize) { - self.size_of_set = Some(value); + self.set_usize(PropertyId::SizeOfSet, value); } pub fn clear_size_of_set(&mut self) { - self.size_of_set = None; + self.clear_property(PropertyId::SizeOfSet); } pub fn position_in_set(&self) -> Option { - self.position_in_set + self.get_usize(PropertyId::PositionInSet) } pub fn set_position_in_set(&mut self, value: usize) { - self.position_in_set = Some(value); + self.set_usize(PropertyId::PositionInSet, value); } pub fn clear_position_in_set(&mut self) { - self.position_in_set = None; + self.clear_property(PropertyId::PositionInSet); } /// For [`Role::ColorWell`], specifies the selected color in RGBA. pub fn color_value(&self) -> Option { - self.color_value + self.get_u32(PropertyId::ColorValue) } pub fn set_color_value(&mut self, value: u32) { - self.color_value = Some(value); + self.set_u32(PropertyId::ColorValue, value); } pub fn clear_color_value(&mut self) { - self.color_value = None; + self.clear_property(PropertyId::ColorValue); } pub fn aria_current(&self) -> Option { @@ -2274,24 +2231,24 @@ impl Node { /// Background color in RGBA. pub fn background_color(&self) -> Option { - self.background_color + self.get_u32(PropertyId::BackgroundColor) } pub fn set_background_color(&mut self, value: u32) { - self.background_color = Some(value); + self.set_u32(PropertyId::BackgroundColor, value); } pub fn clear_background_color(&mut self) { - self.background_color = None; + self.clear_property(PropertyId::BackgroundColor); } /// Foreground color in RGBA. pub fn foreground_color(&self) -> Option { - self.foreground_color + self.get_u32(PropertyId::ForegroundColor) } pub fn set_foreground_color(&mut self, value: u32) { - self.foreground_color = Some(value); + self.set_u32(PropertyId::ForegroundColor, value); } pub fn clear_foreground_color(&mut self) { - self.foreground_color = None; + self.clear_property(PropertyId::ForegroundColor); } pub fn has_popup(&self) -> Option { @@ -2376,109 +2333,171 @@ impl Node { // Focus traversal order. pub fn previous_focus(&self) -> Option { - self.previous_focus + self.get_node_id(PropertyId::PreviousFocus) } pub fn set_previous_focus(&mut self, value: NodeId) { - self.previous_focus = Some(value); + self.set_node_id(PropertyId::PreviousFocus, value); } pub fn clear_previous_focus(&mut self) { - self.previous_focus = None; + self.clear_property(PropertyId::PreviousFocus); } pub fn next_focus(&self) -> Option { - self.next_focus + self.get_node_id(PropertyId::NextFocus) } pub fn set_next_focus(&mut self, value: NodeId) { - self.next_focus = Some(value); + self.set_node_id(PropertyId::NextFocus, value); } pub fn clear_next_focus(&mut self) { - self.next_focus = None; + self.clear_property(PropertyId::NextFocus); } // Numeric value attributes. pub fn numeric_value(&self) -> Option { - self.numeric_value + self.get_f64(PropertyId::NumericValue) } pub fn set_numeric_value(&mut self, value: f64) { - self.numeric_value = Some(value); + self.set_f64(PropertyId::NumericValue, value); } pub fn clear_numeric_value(&mut self) { - self.numeric_value = None; + self.clear_property(PropertyId::NumericValue); } pub fn min_numeric_value(&self) -> Option { - self.min_numeric_value + self.get_f64(PropertyId::MinNumericValue) } pub fn set_min_numeric_value(&mut self, value: f64) { - self.min_numeric_value = Some(value); + self.set_f64(PropertyId::MinNumericValue, value); } pub fn clear_min_numeric_value(&mut self) { - self.min_numeric_value = None; + self.clear_property(PropertyId::MinNumericValue); } pub fn max_numeric_value(&self) -> Option { - self.max_numeric_value + self.get_f64(PropertyId::MaxNumericValue) } pub fn set_max_numeric_value(&mut self, value: f64) { - self.max_numeric_value = Some(value); + self.set_f64(PropertyId::MaxNumericValue, value); } pub fn clear_max_numeric_value(&mut self) { - self.max_numeric_value = None; + self.clear_property(PropertyId::MaxNumericValue); } pub fn numeric_value_step(&self) -> Option { - self.numeric_value_step + self.get_f64(PropertyId::NumericValueStep) } pub fn set_numeric_value_step(&mut self, value: f64) { - self.numeric_value_step = Some(value); + self.set_f64(PropertyId::NumericValueStep, value); } pub fn clear_numeric_value_step(&mut self) { - self.numeric_value_step = None; + self.clear_property(PropertyId::NumericValueStep); } pub fn numeric_value_jump(&self) -> Option { - self.numeric_value_jump + self.get_f64(PropertyId::NumericValueJump) } pub fn set_numeric_value_jump(&mut self, value: f64) { - self.numeric_value_jump = Some(value); + self.set_f64(PropertyId::NumericValueJump, value); } pub fn clear_numeric_value_jump(&mut self) { - self.numeric_value_jump = None; + self.clear_property(PropertyId::NumericValueJump); } /// Font size is in pixels. - pub fn font_size(&self) -> Option { - self.font_size + pub fn font_size(&self) -> Option { + self.get_f64(PropertyId::FontSize) } - pub fn set_font_size(&mut self, value: f32) { - self.font_size = Some(value); + pub fn set_font_size(&mut self, value: f64) { + self.set_f64(PropertyId::FontSize, value); } pub fn clear_font_size(&mut self) { - self.font_size = None; + self.clear_property(PropertyId::FontSize); } /// Font weight can take on any arbitrary numeric value. Increments of 100 in /// range `[0, 900]` represent keywords such as light, normal, bold, etc. - pub fn font_weight(&self) -> Option { - self.font_weight + pub fn font_weight(&self) -> Option { + self.get_f64(PropertyId::FontWeight) } - pub fn set_font_weight(&mut self, value: f32) { - self.font_weight = Some(value); + pub fn set_font_weight(&mut self, value: f64) { + self.set_f64(PropertyId::FontWeight, value); } pub fn clear_font_weight(&mut self) { - self.font_weight = None; + self.clear_property(PropertyId::FontWeight); } /// The text indent of the text, in mm. - pub fn text_indent(&self) -> Option { - self.text_indent + pub fn text_indent(&self) -> Option { + self.get_f64(PropertyId::TextIndent) } - pub fn set_text_indent(&mut self, value: f32) { - self.text_indent = Some(value); + pub fn set_text_indent(&mut self, value: f64) { + self.set_f64(PropertyId::TextIndent, value); } pub fn clear_text_indent(&mut self) { - self.text_indent = None; + self.clear_property(PropertyId::TextIndent); + } +} + +impl Default for Node { + fn default() -> Self { + Self { + role: Role::Unknown, + indices: [PropertyId::Unset as u8; PropertyId::Unset as usize], + props: Vec::new(), + actions: EnumSet::new(), + name_from: None, + description_from: None, + autofill_available: false, + expanded: None, + default: false, + editable: false, + orientation: None, + hovered: false, + hidden: false, + linked: false, + multiline: false, + multiselectable: false, + protected: false, + required: false, + visited: false, + busy: false, + nonatomic_text_field_root: false, + live_atomic: false, + modal: false, + canvas_has_fallback: false, + scrollable: false, + clips_children: false, + not_user_selectable_style: false, + selected: None, + selected_from_focus: false, + is_line_breaking_object: false, + is_page_breaking_object: false, + has_aria_attribute: false, + touch_pass_through: false, + is_spelling_error: false, + is_grammar_error: false, + is_search_match: false, + is_suggestion: false, + text_direction: None, + invalid: None, + checked_state: None, + live: None, + default_action_verb: None, + sort_direction: None, + read_only: false, + disabled: false, + aria_current: None, + has_popup: None, + list_style: None, + text_align: None, + vertical_offset: None, + bold: false, + italic: false, + overline: None, + strikethrough: None, + underline: None, + } } } From 200a86e1d57407e8f5f41fd88fd7612e2dbe4e47 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 8 Jan 2023 07:06:59 -0600 Subject: [PATCH 03/45] Fix irregularity in property method names --- common/src/lib.rs | 16 ++++++++-------- consumer/src/node.rs | 8 ++++---- consumer/src/tree.rs | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index b5a38a883..602343563 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1023,7 +1023,7 @@ impl Node { pub fn set_children(&mut self, value: impl Into>) { self.set_node_id_vec(PropertyId::Children, value); } - pub fn push_child(&mut self, id: NodeId) { + pub fn push_to_children(&mut self, id: NodeId) { self.push_to_node_id_vec(PropertyId::Children, id); } pub fn clear_children(&mut self) { @@ -1342,7 +1342,7 @@ impl Node { pub fn set_indirect_children(&mut self, value: impl Into>) { self.set_node_id_vec(PropertyId::IndirectChildren, value); } - pub fn push_indirect_child(&mut self, id: NodeId) { + pub fn push_to_indirect_children(&mut self, id: NodeId) { self.push_to_node_id_vec(PropertyId::IndirectChildren, id); } pub fn clear_indirect_children(&mut self) { @@ -1427,7 +1427,7 @@ impl Node { pub fn set_controls(&mut self, value: impl Into>) { self.set_node_id_vec(PropertyId::Controls, value); } - pub fn push_controls(&mut self, id: NodeId) { + pub fn push_to_controls(&mut self, id: NodeId) { self.push_to_node_id_vec(PropertyId::Controls, id); } pub fn clear_controls(&mut self) { @@ -1440,7 +1440,7 @@ impl Node { pub fn set_details(&mut self, value: impl Into>) { self.set_node_id_vec(PropertyId::Details, value); } - pub fn push_details(&mut self, id: NodeId) { + pub fn push_to_details(&mut self, id: NodeId) { self.push_to_node_id_vec(PropertyId::Details, id); } pub fn clear_details(&mut self) { @@ -1453,7 +1453,7 @@ impl Node { pub fn set_described_by(&mut self, value: impl Into>) { self.set_node_id_vec(PropertyId::DescribedBy, value); } - pub fn push_described_by(&mut self, id: NodeId) { + pub fn push_to_described_by(&mut self, id: NodeId) { self.push_to_node_id_vec(PropertyId::DescribedBy, id); } pub fn clear_described_by(&mut self) { @@ -1466,7 +1466,7 @@ impl Node { pub fn set_flow_to(&mut self, value: impl Into>) { self.set_node_id_vec(PropertyId::FlowTo, value); } - pub fn push_flow_to(&mut self, id: NodeId) { + pub fn push_to_flow_to(&mut self, id: NodeId) { self.push_to_node_id_vec(PropertyId::FlowTo, id); } pub fn clear_flow_to(&mut self) { @@ -1479,7 +1479,7 @@ impl Node { pub fn set_labelled_by(&mut self, value: impl Into>) { self.set_node_id_vec(PropertyId::LabelledBy, value); } - pub fn push_labelled_by(&mut self, id: NodeId) { + pub fn push_to_labelled_by(&mut self, id: NodeId) { self.push_to_node_id_vec(PropertyId::LabelledBy, id); } pub fn clear_labelled_by(&mut self) { @@ -1660,7 +1660,7 @@ impl Node { Property::CustomActionVec(value.into()), ); } - pub fn push_custom_action(&mut self, action: CustomAction) { + pub fn push_to_custom_actions(&mut self, action: CustomAction) { match self.get_property_mut( PropertyId::CustomActions, Property::CustomActionVec(Vec::new()), diff --git a/consumer/src/node.rs b/consumer/src/node.rs index f879c3ddb..631a3a126 100644 --- a/consumer/src/node.rs +++ b/consumer/src/node.rs @@ -1046,7 +1046,7 @@ mod tests { NODE_ID_4, Arc::new({ let mut node = Node::new(Role::TextField); - node.push_labelled_by(NODE_ID_5); + node.push_to_labelled_by(NODE_ID_5); node }), ), @@ -1092,7 +1092,7 @@ mod tests { NODE_ID_2, Arc::new({ let mut node = Node::new(Role::Button); - node.push_child(NODE_ID_3); + node.push_to_children(NODE_ID_3); node }), ), @@ -1108,7 +1108,7 @@ mod tests { NODE_ID_4, Arc::new({ let mut node = Node::new(Role::Link); - node.push_child(NODE_ID_5); + node.push_to_children(NODE_ID_5); node }), ), @@ -1116,7 +1116,7 @@ mod tests { NODE_ID_5, Arc::new({ let mut node = Node::new(Role::GenericContainer); - node.push_child(NODE_ID_6); + node.push_to_children(NODE_ID_6); node }), ), diff --git a/consumer/src/tree.rs b/consumer/src/tree.rs index 40fe1d919..f8d465583 100644 --- a/consumer/src/tree.rs +++ b/consumer/src/tree.rs @@ -495,7 +495,7 @@ mod tests { NODE_ID_1, Arc::new({ let mut node = root_node; - node.push_child(NODE_ID_2); + node.push_to_children(NODE_ID_2); node }), ), @@ -569,7 +569,7 @@ mod tests { NODE_ID_1, Arc::new({ let mut node = root_node.clone(); - node.push_child(NODE_ID_2); + node.push_to_children(NODE_ID_2); node }), ), From 727ea591a7d467c7171adf0c524f5d575cec9216 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 8 Jan 2023 07:11:48 -0600 Subject: [PATCH 04/45] Fix irregularity in internal property type naming --- common/src/lib.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 602343563..a3ad322d7 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -732,8 +732,8 @@ enum Property { NodeIdVec(Vec), NodeId(NodeId), String(Box), - U8Slice(Box<[u8]>), - F32Slice(Box<[f32]>), + LengthSlice(Box<[u8]>), + CoordSlice(Box<[f32]>), CustomActionVec(Vec), F64(f64), TextSelection(TextSelection), @@ -893,28 +893,28 @@ impl Node { self.set_property(id, Property::String(value.into())); } - fn get_u8_slice(&self, id: PropertyId) -> &[u8] { + fn get_length_slice(&self, id: PropertyId) -> &[u8] { match self.get_property(id) { Property::None => &[], - Property::U8Slice(value) => value, + Property::LengthSlice(value) => value, _ => panic!(), } } - fn set_u8_slice(&mut self, id: PropertyId, value: impl Into>) { - self.set_property(id, Property::U8Slice(value.into())); + fn set_length_slice(&mut self, id: PropertyId, value: impl Into>) { + self.set_property(id, Property::LengthSlice(value.into())); } - fn get_optional_f32_slice(&self, id: PropertyId) -> Option<&[f32]> { + fn get_coord_slice(&self, id: PropertyId) -> Option<&[f32]> { match self.get_property(id) { Property::None => None, - Property::F32Slice(value) => Some(value), + Property::CoordSlice(value) => Some(value), _ => panic!(), } } - fn set_f32_slice(&mut self, id: PropertyId, value: impl Into>) { - self.set_property(id, Property::F32Slice(value.into())); + fn set_coord_slice(&mut self, id: PropertyId, value: impl Into>) { + self.set_property(id, Property::CoordSlice(value.into())); } fn get_f64(&self, id: PropertyId) -> Option { @@ -1558,10 +1558,10 @@ impl Node { /// /// [`value`]: NodeProvider::value pub fn character_lengths(&self) -> &[u8] { - self.get_u8_slice(PropertyId::CharacterLengths) + self.get_length_slice(PropertyId::CharacterLengths) } pub fn set_character_lengths(&mut self, value: impl Into>) { - self.set_u8_slice(PropertyId::CharacterLengths, value); + self.set_length_slice(PropertyId::CharacterLengths, value); } /// For inline text. This is the position of each character within @@ -1581,10 +1581,10 @@ impl Node { /// [`text_direction`]: NodeProvider::text_direction /// [`character_lengths`]: NodeProvider::character_lengths pub fn character_positions(&self) -> Option<&[f32]> { - self.get_optional_f32_slice(PropertyId::CharacterPositions) + self.get_coord_slice(PropertyId::CharacterPositions) } pub fn set_character_positions(&mut self, value: impl Into>) { - self.set_f32_slice(PropertyId::CharacterPositions, value); + self.set_coord_slice(PropertyId::CharacterPositions, value); } pub fn clear_character_positions(&mut self) { self.clear_property(PropertyId::CharacterPositions); @@ -1609,10 +1609,10 @@ impl Node { /// [`text_direction`]: NodeProvider::text_direction /// [`character_lengths`]: NodeProvider::character_lengths pub fn character_widths(&self) -> Option<&[f32]> { - self.get_optional_f32_slice(PropertyId::CharacterWidths) + self.get_coord_slice(PropertyId::CharacterWidths) } pub fn set_character_widths(&mut self, value: impl Into>) { - self.set_f32_slice(PropertyId::CharacterWidths, value); + self.set_coord_slice(PropertyId::CharacterWidths, value); } pub fn clear_character_widths(&mut self) { self.clear_property(PropertyId::CharacterWidths); @@ -1641,10 +1641,10 @@ impl Node { /// /// [`character_lengths`]: NodeProvider::character_lengths pub fn word_lengths(&self) -> &[u8] { - self.get_u8_slice(PropertyId::WordLengths) + self.get_length_slice(PropertyId::WordLengths) } pub fn set_word_lengths(&mut self, value: impl Into>) { - self.set_u8_slice(PropertyId::WordLengths, value); + self.set_length_slice(PropertyId::WordLengths, value); } pub fn custom_actions(&self) -> &[CustomAction] { From 85bcbadf0977edfb047ebb75d2b9ecf80c366dbd Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 8 Jan 2023 10:54:27 -0600 Subject: [PATCH 05/45] Use macros to define the methods for the dynamic properties --- Cargo.lock | 1 + common/Cargo.toml | 1 + common/src/lib.rs | 1826 ++++++++++++++++----------------------------- 3 files changed, 629 insertions(+), 1199 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d6e7cc6cf..87a791a3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,7 @@ version = "0.8.1" dependencies = [ "enumset", "kurbo", + "paste", "schemars", "serde", ] diff --git a/common/Cargo.toml b/common/Cargo.toml index c8eaddd30..4a2876feb 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -16,6 +16,7 @@ features = ["schemars", "serde"] [dependencies] enumset = "1.0.8" kurbo = "0.8.3" +paste = "1.0" schemars_lib = { package = "schemars", version = "0.8.7", features = ["enumset"], optional = true } serde_lib = { package = "serde", version = "1.0", features = ["derive", "rc"], optional = true } diff --git a/common/src/lib.rs b/common/src/lib.rs index a3ad322d7..e027260fa 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -11,6 +11,7 @@ use enumset::{EnumSet, EnumSetType}; pub use kurbo; use kurbo::{Affine, Point, Rect}; +use paste::paste; #[cfg(feature = "schemars")] use schemars_lib as schemars; #[cfg(feature = "schemars")] @@ -634,16 +635,37 @@ pub struct TextSelection { // The following is based on the technique described here: // https://viruta.org/reducing-memory-consumption-in-librsvg-2.html +#[derive(Clone, Debug, PartialEq)] +enum Property { + None, + NodeIdVec(Vec), + NodeId(NodeId), + String(Box), + F64(f64), + Usize(usize), + Color(u32), + LengthSlice(Box<[u8]>), + CoordSlice(Box<[f32]>), + Affine(Affine), + Rect(Rect), + TextSelection(TextSelection), + CustomActionVec(Vec), +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(u8)] enum PropertyId { - Transform, - Bounds, + // NodeIdVec Children, - Name, - Description, - Value, IndirectChildren, + Controls, + Details, + DescribedBy, + FlowTo, + LabelledBy, + RadioGroup, + + // NodeId ActiveDescendant, ErrorMessage, InPageLinkTarget, @@ -651,17 +673,16 @@ enum PropertyId { NextOnLine, PreviousOnLine, PopupFor, - Controls, - Details, - DescribedBy, - FlowTo, - LabelledBy, - RadioGroup, - CharacterLengths, - CharacterPositions, - CharacterWidths, - WordLengths, - CustomActions, + TableHeader, + TableRowHeader, + TableColumnHeader, + NextFocus, + PreviousFocus, + + // String + Name, + Description, + Value, AccessKey, AutoComplete, CheckedStateDescription, @@ -679,13 +700,24 @@ enum PropertyId { RoleDescription, Tooltip, Url, + + // f64 ScrollX, ScrollXMin, ScrollXMax, ScrollY, ScrollYMin, ScrollYMax, - TextSelection, + NumericValue, + MinNumericValue, + MaxNumericValue, + NumericValueStep, + NumericValueJump, + FontSize, + FontWeight, + TextIndent, + + // usize AriaColumnCount, AriaCellColumnIndex, AriaCellColumnSpan, @@ -694,11 +726,8 @@ enum PropertyId { AriaCellRowSpan, TableRowCount, TableColumnCount, - TableHeader, TableRowIndex, - TableRowHeader, TableColumnIndex, - TableColumnHeader, TableCellColumnIndex, TableCellColumnSpan, TableCellRowIndex, @@ -706,39 +735,141 @@ enum PropertyId { HierarchicalLevel, SizeOfSet, PositionInSet, + + // Color ColorValue, BackgroundColor, ForegroundColor, - PreviousFocus, - NextFocus, - NumericValue, - MinNumericValue, - MaxNumericValue, - NumericValueStep, - NumericValueJump, - FontSize, - FontWeight, - TextIndent, + + // LengthSlice + CharacterLengths, + WordLengths, + + // CoordSlice + CharacterPositions, + CharacterWidths, + + // Other + Transform, + Bounds, + TextSelection, + CustomActions, // This should come last. Unset, } -#[derive(Clone, Debug, PartialEq)] -enum Property { - None, - Affine(Affine), - Rect(Rect), - NodeIdVec(Vec), - NodeId(NodeId), - String(Box), - LengthSlice(Box<[u8]>), - CoordSlice(Box<[f32]>), - CustomActionVec(Vec), - F64(f64), - TextSelection(TextSelection), - Usize(usize), - U32(u32), +macro_rules! property_methods { + ($($(#[$doc:meta])* ($base_name:ident, $id:ident, $type_method_base:ident, $getter_result:ty, $setter_param:ty))+) => { + paste! { + impl Node { + $($(#[$doc])* + pub fn $base_name(&self) -> $getter_result { + self.[< get_ $type_method_base >](PropertyId::$id) + } + pub fn [< set_ $base_name >](&mut self, value: $setter_param) { + self.[< set_ $type_method_base >](PropertyId::$id, value); + } + pub fn [< clear_ $base_name >](&mut self) { + self.clear_property(PropertyId::$id); + })* + } + } + } +} + +macro_rules! vec_property_methods { + ($($(#[$doc:meta])* ($base_name:ident, $id:ident, $type_method_base:ident, $item_type:ty))+) => { + paste! { + impl Node { + $($(#[$doc])* + pub fn $base_name(&self) -> &[$item_type] { + self.[< get_ $type_method_base >](PropertyId::$id) + } + pub fn [< set_ $base_name >](&mut self, value: impl Into>) { + self.[< set_ $type_method_base >](PropertyId::$id, value); + } + pub fn [< push_to_ $base_name >](&mut self, item: $item_type) { + self.[< push_to_ $type_method_base >](PropertyId::$id, item); + } + pub fn [< clear_ $base_name >](&mut self) { + self.clear_property(PropertyId::$id); + })* + } + } + } +} + +macro_rules! node_id_vec_property_methods { + ($($(#[$doc:meta])* ($base_name:ident, $id:ident))+) => { + vec_property_methods! { + $($(#[$doc])* + ($base_name, $id, node_id_vec, NodeId))* + } + } +} + +macro_rules! node_id_property_methods { + ($($(#[$doc:meta])* ($base_name:ident, $id:ident))+) => { + property_methods! { + $($(#[$doc])* + ($base_name, $id, node_id, Option, NodeId))* + } + } +} + +macro_rules! string_property_methods { + ($($(#[$doc:meta])* ($base_name:ident, $id:ident))+) => { + property_methods! { + $($(#[$doc])* + ($base_name, $id, string, Option<&str>, impl Into>))* + } + } +} + +macro_rules! f64_property_methods { + ($($(#[$doc:meta])* ($base_name:ident, $id:ident))+) => { + property_methods! { + $($(#[$doc])* + ($base_name, $id, f64, Option, f64))* + } + } +} + +macro_rules! usize_property_methods { + ($($(#[$doc:meta])* ($base_name:ident, $id:ident))+) => { + property_methods! { + $($(#[$doc])* + ($base_name, $id, usize, Option, usize))* + } + } +} + +macro_rules! color_property_methods { + ($($(#[$doc:meta])* ($base_name:ident, $id:ident))+) => { + property_methods! { + $($(#[$doc])* + ($base_name, $id, color, Option, u32))* + } + } +} + +macro_rules! length_slice_property_methods { + ($($(#[$doc:meta])* ($base_name:ident, $id:ident))+) => { + property_methods! { + $($(#[$doc])* + ($base_name, $id, length_slice, &[u8], impl Into>))* + } + } +} + +macro_rules! coord_slice_property_methods { + ($($(#[$doc:meta])* ($base_name:ident, $id:ident))+) => { + property_methods! { + $($(#[$doc])* + ($base_name, $id, coord_slice, Option<&[f32]>, impl Into>))* + } + } } /// A single accessible object. A complete UI is represented as a tree of these. @@ -750,16 +881,12 @@ enum Property { #[derive(Clone, Debug, PartialEq)] pub struct Node { role: Role, + actions: EnumSet, indices: [u8; PropertyId::Unset as usize], props: Vec, - actions: EnumSet, - name_from: Option, - description_from: Option, autofill_available: bool, - expanded: Option, default: bool, editable: bool, - orientation: Option, hovered: bool, hidden: bool, linked: bool, @@ -776,31 +903,35 @@ pub struct Node { scrollable: bool, clips_children: bool, not_user_selectable_style: bool, - selected: Option, selected_from_focus: bool, - is_line_breaking_object: bool, - is_page_breaking_object: bool, has_aria_attribute: bool, touch_pass_through: bool, + read_only: bool, + disabled: bool, + bold: bool, + italic: bool, + is_line_breaking_object: bool, + is_page_breaking_object: bool, is_spelling_error: bool, is_grammar_error: bool, is_search_match: bool, is_suggestion: bool, - text_direction: Option, + expanded: Option, + selected: Option, + name_from: Option, + description_from: Option, invalid: Option, checked_state: Option, live: Option, default_action_verb: Option, + text_direction: Option, + orientation: Option, sort_direction: Option, - read_only: bool, - disabled: bool, aria_current: Option, has_popup: Option, list_style: Option, text_align: Option, vertical_offset: Option, - bold: bool, - italic: bool, overline: Option, strikethrough: Option, underline: Option, @@ -848,6 +979,30 @@ impl Node { } } + fn get_affine(&self, id: PropertyId) -> Option { + match self.get_property(id) { + Property::None => None, + Property::Affine(value) => Some(*value), + _ => panic!(), + } + } + + fn set_affine(&mut self, id: PropertyId, value: Affine) { + self.set_property(id, Property::Affine(value)); + } + + fn get_rect(&self, id: PropertyId) -> Option { + match self.get_property(id) { + Property::None => None, + Property::Rect(value) => Some(*value), + _ => panic!(), + } + } + + fn set_rect(&mut self, id: PropertyId, value: Rect) { + self.set_property(id, Property::Rect(value)); + } + fn get_node_id_vec(&self, id: PropertyId) -> &[NodeId] { match self.get_property(id) { Property::None => &[], @@ -893,64 +1048,64 @@ impl Node { self.set_property(id, Property::String(value.into())); } - fn get_length_slice(&self, id: PropertyId) -> &[u8] { + fn get_f64(&self, id: PropertyId) -> Option { match self.get_property(id) { - Property::None => &[], - Property::LengthSlice(value) => value, + Property::None => None, + Property::F64(value) => Some(*value), _ => panic!(), } } - fn set_length_slice(&mut self, id: PropertyId, value: impl Into>) { - self.set_property(id, Property::LengthSlice(value.into())); + fn set_f64(&mut self, id: PropertyId, value: f64) { + self.set_property(id, Property::F64(value)); } - fn get_coord_slice(&self, id: PropertyId) -> Option<&[f32]> { + fn get_usize(&self, id: PropertyId) -> Option { match self.get_property(id) { Property::None => None, - Property::CoordSlice(value) => Some(value), + Property::Usize(value) => Some(*value), _ => panic!(), } } - fn set_coord_slice(&mut self, id: PropertyId, value: impl Into>) { - self.set_property(id, Property::CoordSlice(value.into())); + fn set_usize(&mut self, id: PropertyId, value: usize) { + self.set_property(id, Property::Usize(value)); } - fn get_f64(&self, id: PropertyId) -> Option { + fn get_color(&self, id: PropertyId) -> Option { match self.get_property(id) { Property::None => None, - Property::F64(value) => Some(*value), + Property::Color(value) => Some(*value), _ => panic!(), } } - fn set_f64(&mut self, id: PropertyId, value: f64) { - self.set_property(id, Property::F64(value)); + fn set_color(&mut self, id: PropertyId, value: u32) { + self.set_property(id, Property::Color(value)); } - fn get_usize(&self, id: PropertyId) -> Option { + fn get_length_slice(&self, id: PropertyId) -> &[u8] { match self.get_property(id) { - Property::None => None, - Property::Usize(value) => Some(*value), + Property::None => &[], + Property::LengthSlice(value) => value, _ => panic!(), } } - fn set_usize(&mut self, id: PropertyId, value: usize) { - self.set_property(id, Property::Usize(value)); + fn set_length_slice(&mut self, id: PropertyId, value: impl Into>) { + self.set_property(id, Property::LengthSlice(value.into())); } - fn get_u32(&self, id: PropertyId) -> Option { + fn get_coord_slice(&self, id: PropertyId) -> Option<&[f32]> { match self.get_property(id) { Property::None => None, - Property::U32(value) => Some(*value), + Property::CoordSlice(value) => Some(value), _ => panic!(), } } - fn set_u32(&mut self, id: PropertyId, value: u32) { - self.set_property(id, Property::U32(value)); + fn set_coord_slice(&mut self, id: PropertyId, value: impl Into>) { + self.set_property(id, Property::CoordSlice(value.into())); } pub fn new(role: Role) -> Self { @@ -967,132 +1122,17 @@ impl Node { self.role = value; } - /// An affine transform to apply to any coordinates within this node - /// and its descendants, including the [`bounds`] property of this node. - /// The combined transforms of this node and its ancestors define - /// the coordinate space of this node. /// This should be `None` if - /// it would be set to the identity transform, which should be the case - /// for most nodes. - /// - /// AccessKit expects the final transformed coordinates to be relative - /// to the origin of the tree's container (e.g. window), in physical - /// pixels, with the y coordinate being top-down. - /// - /// [`bounds`]: NodeProvider::bounds - pub fn transform(&self) -> Option { - match self.get_property(PropertyId::Transform) { - Property::None => None, - Property::Affine(value) => Some(*value), - _ => panic!(), - } + pub fn supports_action(&self, action: Action) -> bool { + self.actions.contains(action) } - pub fn set_transform(&mut self, value: Affine) { - self.set_property(PropertyId::Transform, Property::Affine(value)); + pub fn add_action(&mut self, action: Action) { + self.actions.insert(action); + } + pub fn remove_action(&mut self, action: Action) { + self.actions.remove(action); } - pub fn clear_transform(&mut self) { - self.clear_property(PropertyId::Transform); - } - - /// The bounding box of this node, in the node's coordinate space. - /// This property does not affect the coordinate space of either this node - /// or its descendants; only the [`transform`] property affects that. - /// This, along with the recommendation that most nodes should have - /// a [`transform`] of `None`, implies that the `bounds` property - /// of most nodes should be in the coordinate space of the nearest ancestor - /// with a non-`None` [`transform`], or if there is no such ancestor, - /// the tree's container (e.g. window). - /// - /// [`transform`]: NodeProvider::transform - pub fn bounds(&self) -> Option { - match self.get_property(PropertyId::Bounds) { - Property::None => None, - Property::Rect(value) => Some(*value), - _ => panic!(), - } - } - pub fn set_bounds(&mut self, value: Rect) { - self.set_property(PropertyId::Bounds, Property::Rect(value)); - } - pub fn clear_bounds(&mut self) { - self.clear_property(PropertyId::Bounds); - } - - pub fn children(&self) -> &[NodeId] { - self.get_node_id_vec(PropertyId::Children) - } - pub fn set_children(&mut self, value: impl Into>) { - self.set_node_id_vec(PropertyId::Children, value); - } - pub fn push_to_children(&mut self, id: NodeId) { - self.push_to_node_id_vec(PropertyId::Children, id); - } - pub fn clear_children(&mut self) { - self.clear_property(PropertyId::Children); - } - - pub fn supports_action(&self, action: Action) -> bool { - self.actions.contains(action) - } - pub fn add_action(&mut self, action: Action) { - self.actions.insert(action); - } - pub fn remove_action(&mut self, action: Action) { - self.actions.remove(action); - } - pub fn clear_actions(&mut self) { - self.actions.clear(); - } - - pub fn name(&self) -> Option<&str> { - self.get_string(PropertyId::Name) - } - pub fn set_name(&mut self, value: impl Into>) { - self.set_string(PropertyId::Name, value); - } - pub fn clear_name(&mut self) { - self.clear_property(PropertyId::Name); - } - - /// What information was used to compute the object's name. - pub fn name_from(&self) -> Option { - self.name_from - } - pub fn set_name_from(&mut self, value: NameFrom) { - self.name_from = Some(value); - } - pub fn clear_name_from(&mut self) { - self.name_from = None; - } - - pub fn description(&self) -> Option<&str> { - self.get_string(PropertyId::Description) - } - pub fn set_description(&mut self, value: impl Into>) { - self.set_string(PropertyId::Description, value); - } - pub fn clear_description(&mut self) { - self.clear_property(PropertyId::Description); - } - - /// What information was used to compute the object's description. - pub fn description_from(&self) -> Option { - self.description_from - } - pub fn set_description_from(&mut self, value: DescriptionFrom) { - self.description_from = Some(value); - } - pub fn clear_description_from(&mut self) { - self.description_from = None; - } - - pub fn value(&self) -> Option<&str> { - self.get_string(PropertyId::Value) - } - pub fn set_value(&mut self, value: impl Into>) { - self.set_string(PropertyId::Value, value); - } - pub fn clear_value(&mut self) { - self.clear_property(PropertyId::Value); + pub fn clear_actions(&mut self) { + self.actions.clear(); } pub fn is_autofill_available(&self) -> bool { @@ -1102,20 +1142,6 @@ impl Node { self.autofill_available = value; } - /// Whether this node is expanded, collapsed, or neither. - /// - /// Setting this to `false` means the node is collapsed; omitting it means this state - /// isn't applicable. - pub fn is_expanded(&self) -> Option { - self.expanded - } - pub fn set_expanded(&mut self, value: bool) { - self.expanded = Some(value); - } - pub fn clear_expanded(&mut self) { - self.expanded = None; - } - pub fn is_default(&self) -> bool { self.default } @@ -1130,16 +1156,6 @@ impl Node { self.editable = value; } - pub fn orientation(&self) -> Option { - self.orientation - } - pub fn set_orientation(&mut self, value: Orientation) { - self.orientation = Some(value); - } - pub fn clear_orientation(&mut self) { - self.orientation = None; - } - pub fn is_hovered(&self) -> bool { self.hovered } @@ -1270,25 +1286,6 @@ impl Node { self.not_user_selectable_style = value; } - /// Indicates whether this node is selected or unselected. - /// - /// The absence of this flag (as opposed to a `false` setting) - /// means that the concept of "selected" doesn't apply. - /// When deciding whether to set the flag to false or omit it, - /// consider whether it would be appropriate for a screen reader - /// to announce "not selected". The ambiguity of this flag - /// in platform accessibility APIs has made extraneous - /// "not selected" announcements a common annoyance. - pub fn is_selected(&self) -> Option { - self.selected - } - pub fn set_selected(&mut self, value: bool) { - self.selected = Some(value); - } - pub fn clear_selected(&mut self) { - self.selected = None; - } - /// Indicates whether this node is selected due to selection follows focus. pub fn is_selected_from_focus(&self) -> bool { self.selected_from_focus @@ -1297,23 +1294,6 @@ impl Node { self.selected_from_focus = value; } - /// Indicates whether this node causes a hard line-break - /// (e.g. block level elements, or `
`). - pub fn is_line_breaking_object(&self) -> bool { - self.is_line_breaking_object - } - pub fn set_is_line_breaking_object(&mut self, value: bool) { - self.is_line_breaking_object = value; - } - - /// Indicates whether this node causes a page break. - pub fn is_page_breaking_object(&self) -> bool { - self.is_page_breaking_object - } - pub fn set_is_page_breaking_object(&mut self, value: bool) { - self.is_page_breaking_object = value; - } - /// True if the node has any ARIA attributes set. pub fn has_aria_attribute(&self) -> bool { self.has_aria_attribute @@ -1332,205 +1312,178 @@ impl Node { self.touch_pass_through = value; } - /// Ids of nodes that are children of this node logically, but are - /// not children of this node in the tree structure. As an example, - /// a table cell is a child of a row, and an 'indirect' child of a - /// column. - pub fn indirect_children(&self) -> &[NodeId] { - self.get_node_id_vec(PropertyId::IndirectChildren) + /// Use for a textbox that allows focus/selection but not input. + pub fn is_read_only(&self) -> bool { + self.read_only } - pub fn set_indirect_children(&mut self, value: impl Into>) { - self.set_node_id_vec(PropertyId::IndirectChildren, value); + pub fn set_read_only(&mut self, value: bool) { + self.read_only = value; } - pub fn push_to_indirect_children(&mut self, id: NodeId) { - self.push_to_node_id_vec(PropertyId::IndirectChildren, id); + + /// Use for a control or group of controls that disallows input. + pub fn is_disabled(&self) -> bool { + self.disabled } - pub fn clear_indirect_children(&mut self) { - self.clear_property(PropertyId::IndirectChildren); + pub fn set_disabled(&mut self, value: bool) { + self.disabled = value; } - // Relationships between this node and other nodes. - - pub fn active_descendant(&self) -> Option { - self.get_node_id(PropertyId::ActiveDescendant) - } - pub fn set_active_descendant(&mut self, value: NodeId) { - self.set_node_id(PropertyId::ActiveDescendant, value); + pub fn is_bold(&self) -> bool { + self.bold } - pub fn clear_active_descendant(&mut self) { - self.clear_property(PropertyId::ActiveDescendant); + pub fn set_bold(&mut self, value: bool) { + self.bold = value; } - pub fn error_message(&self) -> Option { - self.get_node_id(PropertyId::ErrorMessage) - } - pub fn set_error_message(&mut self, value: NodeId) { - self.set_node_id(PropertyId::ErrorMessage, value); + pub fn is_italic(&self) -> bool { + self.italic } - pub fn clear_error_message(&mut self) { - self.clear_property(PropertyId::ErrorMessage); + pub fn set_italic(&mut self, value: bool) { + self.italic = value; } - pub fn in_page_link_target(&self) -> Option { - self.get_node_id(PropertyId::InPageLinkTarget) - } - pub fn set_in_page_link_target(&mut self, value: NodeId) { - self.set_node_id(PropertyId::InPageLinkTarget, value); + /// Indicates whether this node causes a hard line-break + /// (e.g. block level elements, or `
`). + pub fn is_line_breaking_object(&self) -> bool { + self.is_line_breaking_object } - pub fn clear_in_page_link_target(&mut self) { - self.clear_property(PropertyId::InPageLinkTarget); + pub fn set_is_line_breaking_object(&mut self, value: bool) { + self.is_line_breaking_object = value; } - pub fn member_of(&self) -> Option { - self.get_node_id(PropertyId::MemberOf) - } - pub fn set_member_of(&mut self, value: NodeId) { - self.set_node_id(PropertyId::MemberOf, value); + /// Indicates whether this node causes a page break. + pub fn is_page_breaking_object(&self) -> bool { + self.is_page_breaking_object } - pub fn clear_member_of(&mut self) { - self.clear_property(PropertyId::MemberOf); + pub fn set_is_page_breaking_object(&mut self, value: bool) { + self.is_page_breaking_object = value; } - pub fn next_on_line(&self) -> Option { - self.get_node_id(PropertyId::NextOnLine) + pub fn is_spelling_error(&self) -> bool { + self.is_spelling_error + } + pub fn set_is_spelling_error(&mut self, value: bool) { + self.is_spelling_error = value; } - pub fn set_next_on_line(&mut self, value: NodeId) { - self.set_node_id(PropertyId::NextOnLine, value); + + pub fn is_grammar_error(&self) -> bool { + self.is_grammar_error } - pub fn clear_next_on_line(&mut self) { - self.clear_property(PropertyId::NextOnLine); + pub fn set_is_grammar_error(&mut self, value: bool) { + self.is_grammar_error = value; } - pub fn previous_on_line(&self) -> Option { - self.get_node_id(PropertyId::PreviousOnLine) + pub fn is_search_match(&self) -> bool { + self.is_search_match } - pub fn set_previous_on_line(&mut self, value: NodeId) { - self.set_node_id(PropertyId::PreviousOnLine, value); + pub fn set_is_search_match(&mut self, value: bool) { + self.is_search_match = value; + } + + pub fn is_suggestion(&self) -> bool { + self.is_suggestion } - pub fn clear_previous_on_line(&mut self) { - self.clear_property(PropertyId::PreviousOnLine); + pub fn set_is_suggestion(&mut self, value: bool) { + self.is_suggestion = value; } - pub fn popup_for(&self) -> Option { - self.get_node_id(PropertyId::PopupFor) + /// Whether this node is expanded, collapsed, or neither. + /// + /// Setting this to `false` means the node is collapsed; omitting it means this state + /// isn't applicable. + pub fn is_expanded(&self) -> Option { + self.expanded } - pub fn set_popup_for(&mut self, value: NodeId) { - self.set_node_id(PropertyId::PopupFor, value); + pub fn set_expanded(&mut self, value: bool) { + self.expanded = Some(value); } - pub fn clear_popup_for(&mut self) { - self.clear_property(PropertyId::PopupFor); + pub fn clear_expanded(&mut self) { + self.expanded = None; } - pub fn controls(&self) -> &[NodeId] { - self.get_node_id_vec(PropertyId::Controls) - } - pub fn set_controls(&mut self, value: impl Into>) { - self.set_node_id_vec(PropertyId::Controls, value); + /// Indicates whether this node is selected or unselected. + /// + /// The absence of this flag (as opposed to a `false` setting) + /// means that the concept of "selected" doesn't apply. + /// When deciding whether to set the flag to false or omit it, + /// consider whether it would be appropriate for a screen reader + /// to announce "not selected". The ambiguity of this flag + /// in platform accessibility APIs has made extraneous + /// "not selected" announcements a common annoyance. + pub fn is_selected(&self) -> Option { + self.selected } - pub fn push_to_controls(&mut self, id: NodeId) { - self.push_to_node_id_vec(PropertyId::Controls, id); + pub fn set_selected(&mut self, value: bool) { + self.selected = Some(value); } - pub fn clear_controls(&mut self) { - self.clear_property(PropertyId::Controls); + pub fn clear_selected(&mut self) { + self.selected = None; } - pub fn details(&self) -> &[NodeId] { - self.get_node_id_vec(PropertyId::Details) - } - pub fn set_details(&mut self, value: impl Into>) { - self.set_node_id_vec(PropertyId::Details, value); + /// What information was used to compute the object's name. + pub fn name_from(&self) -> Option { + self.name_from } - pub fn push_to_details(&mut self, id: NodeId) { - self.push_to_node_id_vec(PropertyId::Details, id); + pub fn set_name_from(&mut self, value: NameFrom) { + self.name_from = Some(value); } - pub fn clear_details(&mut self) { - self.clear_property(PropertyId::Details); + pub fn clear_name_from(&mut self) { + self.name_from = None; } - pub fn described_by(&self) -> &[NodeId] { - self.get_node_id_vec(PropertyId::DescribedBy) - } - pub fn set_described_by(&mut self, value: impl Into>) { - self.set_node_id_vec(PropertyId::DescribedBy, value); + /// What information was used to compute the object's description. + pub fn description_from(&self) -> Option { + self.description_from } - pub fn push_to_described_by(&mut self, id: NodeId) { - self.push_to_node_id_vec(PropertyId::DescribedBy, id); + pub fn set_description_from(&mut self, value: DescriptionFrom) { + self.description_from = Some(value); } - pub fn clear_described_by(&mut self) { - self.clear_property(PropertyId::DescribedBy); + pub fn clear_description_from(&mut self) { + self.description_from = None; } - pub fn flow_to(&self) -> &[NodeId] { - self.get_node_id_vec(PropertyId::FlowTo) - } - pub fn set_flow_to(&mut self, value: impl Into>) { - self.set_node_id_vec(PropertyId::FlowTo, value); + pub fn invalid(&self) -> Option { + self.invalid } - pub fn push_to_flow_to(&mut self, id: NodeId) { - self.push_to_node_id_vec(PropertyId::FlowTo, id); + pub fn set_invalid(&mut self, value: Invalid) { + self.invalid = Some(value); } - pub fn clear_flow_to(&mut self) { - self.clear_property(PropertyId::FlowTo); + pub fn clear_invalid(&mut self) { + self.invalid = None; } - pub fn labelled_by(&self) -> &[NodeId] { - self.get_node_id_vec(PropertyId::LabelledBy) - } - pub fn set_labelled_by(&mut self, value: impl Into>) { - self.set_node_id_vec(PropertyId::LabelledBy, value); + pub fn checked_state(&self) -> Option { + self.checked_state } - pub fn push_to_labelled_by(&mut self, id: NodeId) { - self.push_to_node_id_vec(PropertyId::LabelledBy, id); + pub fn set_checked_state(&mut self, value: CheckedState) { + self.checked_state = Some(value); } - pub fn clear_labelled_by(&mut self) { - self.clear_property(PropertyId::LabelledBy); + pub fn clear_checked_state(&mut self) { + self.checked_state = None; } - /// On radio buttons this should be set to a list of all of the buttons - /// in the same group as this one, including this radio button itself. - pub fn radio_group(&self) -> &[NodeId] { - self.get_node_id_vec(PropertyId::RadioGroup) - } - pub fn set_radio_group(&mut self, value: impl Into>) { - self.set_node_id_vec(PropertyId::RadioGroup, value); + pub fn live(&self) -> Option { + self.live } - pub fn push_to_radio_group(&mut self, id: NodeId) { - self.push_to_node_id_vec(PropertyId::RadioGroup, id); + pub fn set_live(&mut self, value: Live) { + self.live = Some(value); } - pub fn clear_radio_group(&mut self) { - self.clear_property(PropertyId::RadioGroup); + pub fn clear_live(&mut self) { + self.live = None; } - pub fn is_spelling_error(&self) -> bool { - self.is_spelling_error + pub fn default_action_verb(&self) -> Option { + self.default_action_verb } - pub fn set_is_spelling_error(&mut self, value: bool) { - self.is_spelling_error = value; + pub fn set_default_action_verb(&mut self, value: DefaultActionVerb) { + self.default_action_verb = Some(value); + } + pub fn clear_default_action_verb(&mut self) { + self.default_action_verb = None; } - pub fn is_grammar_error(&self) -> bool { - self.is_grammar_error - } - pub fn set_is_grammar_error(&mut self, value: bool) { - self.is_grammar_error = value; - } - - pub fn is_search_match(&self) -> bool { - self.is_search_match - } - pub fn set_is_search_match(&mut self, value: bool) { - self.is_search_match = value; - } - - pub fn is_suggestion(&self) -> bool { - self.is_suggestion - } - pub fn set_is_suggestion(&mut self, value: bool) { - self.is_suggestion = value; - } - - pub fn text_direction(&self) -> Option { - self.text_direction + pub fn text_direction(&self) -> Option { + self.text_direction } pub fn set_text_direction(&mut self, value: TextDirection) { self.text_direction = Some(value); @@ -1539,614 +1492,14 @@ impl Node { self.text_direction = None; } - /// For inline text. The length (non-inclusive) of each character - /// in UTF-8 code units (bytes). The sum of these lengths must equal - /// the length of [`value`], also in bytes. - /// - /// A character is defined as the smallest unit of text that - /// can be selected. This isn't necessarily a single Unicode - /// scalar value (code point). This is why AccessKit can't compute - /// the lengths of the characters from the text itself; this information - /// must be provided by the text editing implementation. - /// - /// If this node is the last text box in a line that ends with a hard - /// line break, that line break should be included at the end of this - /// node's value as either a CRLF or LF; in both cases, the line break - /// should be counted as a single character for the sake of this slice. - /// When the caret is at the end of such a line, the focus of the text - /// selection should be on the line break, not after it. - /// - /// [`value`]: NodeProvider::value - pub fn character_lengths(&self) -> &[u8] { - self.get_length_slice(PropertyId::CharacterLengths) - } - pub fn set_character_lengths(&mut self, value: impl Into>) { - self.set_length_slice(PropertyId::CharacterLengths, value); - } - - /// For inline text. This is the position of each character within - /// the node's bounding box, in the direction given by - /// [`text_direction`], in the coordinate space of this node. - /// - /// When present, the length of this slice should be the same as the length - /// of [`character_lengths`], including for lines that end - /// with a hard line break. The position of such a line break should - /// be the position where an end-of-paragraph marker would be rendered. - /// - /// This property is optional. Without it, AccessKit can't support some - /// use cases, such as screen magnifiers that track the caret position - /// or screen readers that display a highlight cursor. However, - /// most text functionality still works without this information. - /// - /// [`text_direction`]: NodeProvider::text_direction - /// [`character_lengths`]: NodeProvider::character_lengths - pub fn character_positions(&self) -> Option<&[f32]> { - self.get_coord_slice(PropertyId::CharacterPositions) - } - pub fn set_character_positions(&mut self, value: impl Into>) { - self.set_coord_slice(PropertyId::CharacterPositions, value); - } - pub fn clear_character_positions(&mut self) { - self.clear_property(PropertyId::CharacterPositions); - } - - /// For inline text. This is the advance width of each character, - /// in the direction given by [`text_direction`], in the coordinate - /// space of this node. - /// - /// When present, the length of this slice should be the same as the length - /// of [`character_lengths`], including for lines that end - /// with a hard line break. The width of such a line break should - /// be non-zero if selecting the line break by itself results in - /// a visible highlight (as in Microsoft Word), or zero if not - /// (as in Windows Notepad). - /// - /// This property is optional. Without it, AccessKit can't support some - /// use cases, such as screen magnifiers that track the caret position - /// or screen readers that display a highlight cursor. However, - /// most text functionality still works without this information. - /// - /// [`text_direction`]: NodeProvider::text_direction - /// [`character_lengths`]: NodeProvider::character_lengths - pub fn character_widths(&self) -> Option<&[f32]> { - self.get_coord_slice(PropertyId::CharacterWidths) - } - pub fn set_character_widths(&mut self, value: impl Into>) { - self.set_coord_slice(PropertyId::CharacterWidths, value); - } - pub fn clear_character_widths(&mut self) { - self.clear_property(PropertyId::CharacterWidths); - } - - /// For inline text. The length of each word in characters, as defined - /// in [`character_lengths`]. The sum of these lengths must equal - /// the length of [`character_lengths`]. - /// - /// The end of each word is the beginning of the next word; there are no - /// characters that are not considered part of a word. Trailing whitespace - /// is typically considered part of the word that precedes it, while - /// a line's leading whitespace is considered its own word. Whether - /// punctuation is considered a separate word or part of the preceding - /// word depends on the particular text editing implementation. - /// Some editors may have their own definition of a word; for example, - /// in an IDE, words may correspond to programming language tokens. - /// - /// Not all assistive technologies require information about word - /// boundaries, and not all platform accessibility APIs even expose - /// this information, but for assistive technologies that do use - /// this information, users will get unpredictable results if the word - /// boundaries exposed by the accessibility tree don't match - /// the editor's behavior. This is why AccessKit does not determine - /// word boundaries itself. - /// - /// [`character_lengths`]: NodeProvider::character_lengths - pub fn word_lengths(&self) -> &[u8] { - self.get_length_slice(PropertyId::WordLengths) - } - pub fn set_word_lengths(&mut self, value: impl Into>) { - self.set_length_slice(PropertyId::WordLengths, value); - } - - pub fn custom_actions(&self) -> &[CustomAction] { - match self.get_property(PropertyId::CustomActions) { - Property::None => &[], - Property::CustomActionVec(value) => value, - _ => panic!(), - } - } - pub fn set_custom_actions(&mut self, value: impl Into>) { - self.set_property( - PropertyId::CustomActions, - Property::CustomActionVec(value.into()), - ); - } - pub fn push_to_custom_actions(&mut self, action: CustomAction) { - match self.get_property_mut( - PropertyId::CustomActions, - Property::CustomActionVec(Vec::new()), - ) { - Property::CustomActionVec(v) => { - v.push(action); - } - _ => panic!(), - } - } - pub fn clear_custom_actions(&mut self) { - self.clear_property(PropertyId::CustomActions); - } - - pub fn access_key(&self) -> Option<&str> { - self.get_string(PropertyId::AccessKey) - } - pub fn set_access_key(&mut self, value: impl Into>) { - self.set_string(PropertyId::AccessKey, value); - } - pub fn clear_access_key(&mut self) { - self.clear_property(PropertyId::AccessKey); - } - - pub fn invalid(&self) -> Option { - self.invalid - } - pub fn set_invalid(&mut self, value: Invalid) { - self.invalid = Some(value); - } - pub fn clear_invalid(&mut self) { - self.invalid = None; - } - - pub fn auto_complete(&self) -> Option<&str> { - self.get_string(PropertyId::AutoComplete) - } - pub fn set_auto_complete(&mut self, value: impl Into>) { - self.set_string(PropertyId::AutoComplete, value); - } - pub fn clear_auto_complete(&mut self) { - self.clear_property(PropertyId::AutoComplete); - } - - pub fn checked_state(&self) -> Option { - self.checked_state - } - pub fn set_checked_state(&mut self, value: CheckedState) { - self.checked_state = Some(value); - } - pub fn clear_checked_state(&mut self) { - self.checked_state = None; - } - - pub fn checked_state_description(&self) -> Option<&str> { - self.get_string(PropertyId::CheckedStateDescription) - } - pub fn set_checked_state_description(&mut self, value: impl Into>) { - self.set_string(PropertyId::CheckedStateDescription, value); - } - pub fn clear_checked_state_description(&mut self) { - self.clear_property(PropertyId::CheckedStateDescription); - } - - pub fn class_name(&self) -> Option<&str> { - self.get_string(PropertyId::ClassName) - } - pub fn set_class_name(&mut self, value: impl Into>) { - self.set_string(PropertyId::ClassName, value); - } - pub fn clear_class_name(&mut self) { - self.clear_property(PropertyId::ClassName); - } - - pub fn css_display(&self) -> Option<&str> { - self.get_string(PropertyId::CssDisplay) - } - pub fn set_css_display(&mut self, value: impl Into>) { - self.set_string(PropertyId::CssDisplay, value); - } - pub fn clear_css_display(&mut self) { - self.clear_property(PropertyId::CssDisplay); - } - - /// Only present when different from parent. - pub fn font_family(&self) -> Option<&str> { - self.get_string(PropertyId::FontFamily) - } - pub fn set_font_family(&mut self, value: impl Into>) { - self.set_string(PropertyId::FontFamily, value); - } - pub fn clear_font_family(&mut self) { - self.clear_property(PropertyId::FontFamily); - } - - pub fn html_tag(&self) -> Option<&str> { - self.get_string(PropertyId::HtmlTag) - } - pub fn set_html_tag(&mut self, value: impl Into>) { - self.set_string(PropertyId::HtmlTag, value); - } - pub fn clear_html_tag(&mut self) { - self.clear_property(PropertyId::HtmlTag); - } - - /// Inner HTML of an element. Only used for a top-level math element, - /// to support third-party math accessibility products that parse MathML. - pub fn inner_html(&self) -> Option<&str> { - self.get_string(PropertyId::InnerHtml) - } - pub fn set_inner_html(&mut self, value: impl Into>) { - self.set_string(PropertyId::InnerHtml, value); - } - pub fn clear_inner_html(&mut self) { - self.clear_property(PropertyId::InnerHtml); - } - - pub fn input_type(&self) -> Option<&str> { - self.get_string(PropertyId::InputType) - } - pub fn set_input_type(&mut self, value: impl Into>) { - self.set_string(PropertyId::InputType, value); - } - pub fn clear_input_type(&mut self) { - self.clear_property(PropertyId::InputType); - } - - pub fn key_shortcuts(&self) -> Option<&str> { - self.get_string(PropertyId::KeyShortcuts) - } - pub fn set_key_shortcuts(&mut self, value: impl Into>) { - self.set_string(PropertyId::KeyShortcuts, value); - } - pub fn clear_key_shortcuts(&mut self) { - self.clear_property(PropertyId::KeyShortcuts); - } - - /// Only present when different from parent. - pub fn language(&self) -> Option<&str> { - self.get_string(PropertyId::Language) - } - pub fn set_language(&mut self, value: impl Into>) { - self.set_string(PropertyId::Language, value); - } - pub fn clear_language(&mut self) { - self.clear_property(PropertyId::Language); - } - - pub fn live_relevant(&self) -> Option<&str> { - self.get_string(PropertyId::LiveRelevant) - } - pub fn set_live_relevant(&mut self, value: impl Into>) { - self.set_string(PropertyId::LiveRelevant, value); - } - pub fn clear_live_relevant(&mut self) { - self.clear_property(PropertyId::LiveRelevant); - } - - pub fn live(&self) -> Option { - self.live - } - pub fn set_live(&mut self, value: Live) { - self.live = Some(value); - } - pub fn clear_live(&mut self) { - self.live = None; - } - - /// Only if not already exposed in [`name`] ([`NameFrom::Placeholder`]). - /// - /// [`name`]: NodeProvider::name - pub fn placeholder(&self) -> Option<&str> { - self.get_string(PropertyId::Placeholder) - } - pub fn set_placeholder(&mut self, value: impl Into>) { - self.set_string(PropertyId::Placeholder, value); - } - pub fn clear_placeholder(&mut self) { - self.clear_property(PropertyId::Placeholder); - } - - pub fn aria_role(&self) -> Option<&str> { - self.get_string(PropertyId::AriaRole) - } - pub fn set_aria_role(&mut self, value: impl Into>) { - self.set_string(PropertyId::AriaRole, value); - } - pub fn clear_aria_role(&mut self) { - self.clear_property(PropertyId::AriaRole); - } - - pub fn role_description(&self) -> Option<&str> { - self.get_string(PropertyId::RoleDescription) - } - pub fn set_role_description(&mut self, value: impl Into>) { - self.set_string(PropertyId::RoleDescription, value); - } - pub fn clear_role_description(&mut self) { - self.clear_property(PropertyId::RoleDescription); - } - - /// Only if not already exposed in [`name`] ([`NameFrom::Title`]). - /// - /// [`name`]: NodeProvider::name - pub fn tooltip(&self) -> Option<&str> { - self.get_string(PropertyId::Tooltip) - } - pub fn set_tooltip(&mut self, value: impl Into>) { - self.set_string(PropertyId::Tooltip, value); - } - pub fn clear_tooltip(&mut self) { - self.clear_property(PropertyId::Tooltip); - } - - pub fn url(&self) -> Option<&str> { - self.get_string(PropertyId::Url) - } - pub fn set_url(&mut self, value: impl Into>) { - self.set_string(PropertyId::Url, value); - } - pub fn clear_url(&mut self) { - self.clear_property(PropertyId::Url); - } - - pub fn default_action_verb(&self) -> Option { - self.default_action_verb - } - pub fn set_default_action_verb(&mut self, value: DefaultActionVerb) { - self.default_action_verb = Some(value); - } - pub fn clear_default_action_verb(&mut self) { - self.default_action_verb = None; - } - - // Scrollable container attributes. - - pub fn scroll_x(&self) -> Option { - self.get_f64(PropertyId::ScrollX) - } - pub fn set_scroll_x(&mut self, value: f64) { - self.set_f64(PropertyId::ScrollX, value); - } - pub fn clear_scroll_x(&mut self) { - self.clear_property(PropertyId::ScrollX); - } - - pub fn scroll_x_min(&self) -> Option { - self.get_f64(PropertyId::ScrollXMin) - } - pub fn set_scroll_x_min(&mut self, value: f64) { - self.set_f64(PropertyId::ScrollXMin, value); - } - pub fn clear_scroll_x_min(&mut self) { - self.clear_property(PropertyId::ScrollXMin); - } - - pub fn scroll_x_max(&self) -> Option { - self.get_f64(PropertyId::ScrollXMax) - } - pub fn set_scroll_x_max(&mut self, value: f64) { - self.set_f64(PropertyId::ScrollXMax, value); - } - pub fn clear_scroll_x_max(&mut self) { - self.clear_property(PropertyId::ScrollXMax); - } - - pub fn scroll_y(&self) -> Option { - self.get_f64(PropertyId::ScrollY) - } - pub fn set_scroll_y(&mut self, value: f64) { - self.set_f64(PropertyId::ScrollY, value); - } - pub fn clear_scroll_y(&mut self) { - self.clear_property(PropertyId::ScrollY); - } - - pub fn scroll_y_min(&self) -> Option { - self.get_f64(PropertyId::ScrollYMin) - } - pub fn set_scroll_y_min(&mut self, value: f64) { - self.set_f64(PropertyId::ScrollYMin, value); - } - pub fn clear_scroll_y_min(&mut self) { - self.clear_property(PropertyId::ScrollYMin); - } - - pub fn scroll_y_max(&self) -> Option { - self.get_f64(PropertyId::ScrollYMax) - } - pub fn set_scroll_y_max(&mut self, value: f64) { - self.set_f64(PropertyId::ScrollYMax, value); - } - pub fn clear_scroll_y_max(&mut self) { - self.clear_property(PropertyId::ScrollYMax); - } - - pub fn text_selection(&self) -> Option { - match self.get_property(PropertyId::TextSelection) { - Property::None => None, - Property::TextSelection(value) => Some(*value), - _ => panic!(), - } - } - pub fn set_text_selection(&mut self, value: TextSelection) { - self.set_property(PropertyId::TextSelection, Property::TextSelection(value)); - } - pub fn clear_text_selection(&mut self) { - self.clear_property(PropertyId::TextSelection); - } - - pub fn aria_column_count(&self) -> Option { - self.get_usize(PropertyId::AriaColumnCount) - } - pub fn set_aria_column_count(&mut self, value: usize) { - self.set_usize(PropertyId::AriaColumnCount, value); - } - pub fn clear_aria_column_count(&mut self) { - self.clear_property(PropertyId::AriaColumnCount); - } - - pub fn aria_cell_column_index(&self) -> Option { - self.get_usize(PropertyId::AriaCellColumnIndex) - } - pub fn set_aria_cell_column_index(&mut self, value: usize) { - self.set_usize(PropertyId::AriaCellColumnIndex, value); - } - pub fn clear_aria_cell_column_index(&mut self) { - self.clear_property(PropertyId::AriaCellColumnIndex); - } - - pub fn aria_cell_column_span(&self) -> Option { - self.get_usize(PropertyId::AriaCellColumnSpan) - } - pub fn set_aria_cell_column_span(&mut self, value: usize) { - self.set_usize(PropertyId::AriaCellColumnSpan, value); - } - pub fn clear_aria_cell_column_span(&mut self) { - self.clear_property(PropertyId::AriaCellColumnSpan); - } - - pub fn aria_row_count(&self) -> Option { - self.get_usize(PropertyId::AriaRowCount) - } - pub fn set_aria_row_count(&mut self, value: usize) { - self.set_usize(PropertyId::AriaRowCount, value); - } - pub fn clear_aria_row_count(&mut self) { - self.clear_property(PropertyId::AriaRowCount); - } - - pub fn aria_cell_row_index(&self) -> Option { - self.get_usize(PropertyId::AriaCellRowIndex) - } - pub fn set_aria_cell_row_index(&mut self, value: usize) { - self.set_usize(PropertyId::AriaCellRowIndex, value); - } - pub fn clear_aria_cell_row_index(&mut self) { - self.clear_property(PropertyId::AriaCellRowIndex); - } - - pub fn aria_cell_row_span(&self) -> Option { - self.get_usize(PropertyId::AriaCellRowSpan) - } - pub fn set_aria_cell_row_span(&mut self, value: usize) { - self.set_usize(PropertyId::AriaCellRowSpan, value); - } - pub fn clear_aria_cell_row_span(&mut self) { - self.clear_property(PropertyId::AriaCellRowSpan); - } - - // Table attributes. - - pub fn table_row_count(&self) -> Option { - self.get_usize(PropertyId::TableRowCount) - } - pub fn set_table_row_count(&mut self, value: usize) { - self.set_usize(PropertyId::TableRowCount, value); - } - pub fn clear_table_row_count(&mut self) { - self.clear_property(PropertyId::TableRowCount); - } - - pub fn table_column_count(&self) -> Option { - self.get_usize(PropertyId::TableColumnCount) - } - pub fn set_table_column_count(&mut self, value: usize) { - self.set_usize(PropertyId::TableColumnCount, value); - } - pub fn clear_table_column_count(&mut self) { - self.clear_property(PropertyId::TableColumnCount); - } - - pub fn table_header(&self) -> Option { - self.get_node_id(PropertyId::TableHeader) - } - pub fn set_table_header(&mut self, value: NodeId) { - self.set_node_id(PropertyId::TableHeader, value); - } - pub fn clear_table_header(&mut self) { - self.clear_property(PropertyId::TableHeader); - } - - // Table row attributes. - - pub fn table_row_index(&self) -> Option { - self.get_usize(PropertyId::TableRowIndex) - } - pub fn set_table_row_index(&mut self, value: usize) { - self.set_usize(PropertyId::TableRowIndex, value); - } - pub fn clear_table_row_index(&mut self) { - self.clear_property(PropertyId::TableRowIndex); - } - - pub fn table_row_header(&self) -> Option { - self.get_node_id(PropertyId::TableRowHeader) - } - pub fn set_table_row_header(&mut self, value: NodeId) { - self.set_node_id(PropertyId::TableRowHeader, value); - } - pub fn clear_table_row_header(&mut self) { - self.clear_property(PropertyId::TableRowHeader); - } - - // Table column attributes. - - pub fn table_column_index(&self) -> Option { - self.get_usize(PropertyId::TableColumnIndex) - } - pub fn set_table_column_index(&mut self, value: usize) { - self.set_usize(PropertyId::TableColumnIndex, value); - } - pub fn clear_table_column_index(&mut self) { - self.clear_property(PropertyId::TableColumnIndex); - } - - pub fn table_column_header(&self) -> Option { - self.get_node_id(PropertyId::TableColumnHeader) - } - pub fn set_table_column_header(&mut self, value: NodeId) { - self.set_node_id(PropertyId::TableColumnHeader, value); - } - pub fn clear_table_column_header(&mut self) { - self.clear_property(PropertyId::TableColumnHeader); - } - - // Table cell attributes. - - pub fn table_cell_column_index(&self) -> Option { - self.get_usize(PropertyId::TableCellColumnIndex) - } - pub fn set_table_cell_column_index(&mut self, value: usize) { - self.set_usize(PropertyId::TableCellColumnIndex, value); - } - pub fn clear_table_cell_column_index(&mut self) { - self.clear_property(PropertyId::TableCellColumnIndex); - } - - pub fn table_cell_column_span(&self) -> Option { - self.get_usize(PropertyId::TableCellColumnSpan) - } - pub fn set_table_cell_column_span(&mut self, value: usize) { - self.set_usize(PropertyId::TableCellColumnSpan, value); - } - pub fn clear_table_cell_column_span(&mut self) { - self.clear_property(PropertyId::TableCellColumnSpan); - } - - pub fn table_cell_row_index(&self) -> Option { - self.get_usize(PropertyId::TableCellRowIndex) - } - pub fn set_table_cell_row_index(&mut self, value: usize) { - self.set_usize(PropertyId::TableCellRowIndex, value); - } - pub fn clear_table_cell_row_index(&mut self) { - self.clear_property(PropertyId::TableCellRowIndex); - } - - pub fn table_cell_row_span(&self) -> Option { - self.get_usize(PropertyId::TableCellRowSpan) + pub fn orientation(&self) -> Option { + self.orientation } - pub fn set_table_cell_row_span(&mut self, value: usize) { - self.set_usize(PropertyId::TableCellRowSpan, value); + pub fn set_orientation(&mut self, value: Orientation) { + self.orientation = Some(value); } - pub fn clear_table_cell_row_span(&mut self) { - self.clear_property(PropertyId::TableCellRowSpan); + pub fn clear_orientation(&mut self) { + self.orientation = None; } pub fn sort_direction(&self) -> Option { @@ -2159,66 +1512,6 @@ impl Node { self.sort_direction = None; } - /// Tree control attributes. - pub fn hierarchical_level(&self) -> Option { - self.get_usize(PropertyId::HierarchicalLevel) - } - pub fn set_hierarchical_level(&mut self, value: usize) { - self.set_usize(PropertyId::HierarchicalLevel, value); - } - pub fn clear_hierarchical_level(&mut self) { - self.clear_property(PropertyId::HierarchicalLevel); - } - - /// Use for a textbox that allows focus/selection but not input. - pub fn is_read_only(&self) -> bool { - self.read_only - } - pub fn set_read_only(&mut self, value: bool) { - self.read_only = value; - } - - /// Use for a control or group of controls that disallows input. - pub fn is_disabled(&self) -> bool { - self.disabled - } - pub fn set_disabled(&mut self, value: bool) { - self.disabled = value; - } - - // Position or Number of items in current set of listitems or treeitems - - pub fn size_of_set(&self) -> Option { - self.get_usize(PropertyId::SizeOfSet) - } - pub fn set_size_of_set(&mut self, value: usize) { - self.set_usize(PropertyId::SizeOfSet, value); - } - pub fn clear_size_of_set(&mut self) { - self.clear_property(PropertyId::SizeOfSet); - } - - pub fn position_in_set(&self) -> Option { - self.get_usize(PropertyId::PositionInSet) - } - pub fn set_position_in_set(&mut self, value: usize) { - self.set_usize(PropertyId::PositionInSet, value); - } - pub fn clear_position_in_set(&mut self) { - self.clear_property(PropertyId::PositionInSet); - } - - /// For [`Role::ColorWell`], specifies the selected color in RGBA. - pub fn color_value(&self) -> Option { - self.get_u32(PropertyId::ColorValue) - } - pub fn set_color_value(&mut self, value: u32) { - self.set_u32(PropertyId::ColorValue, value); - } - pub fn clear_color_value(&mut self) { - self.clear_property(PropertyId::ColorValue); - } - pub fn aria_current(&self) -> Option { self.aria_current } @@ -2229,31 +1522,15 @@ impl Node { self.aria_current = None; } - /// Background color in RGBA. - pub fn background_color(&self) -> Option { - self.get_u32(PropertyId::BackgroundColor) - } - pub fn set_background_color(&mut self, value: u32) { - self.set_u32(PropertyId::BackgroundColor, value); - } - pub fn clear_background_color(&mut self) { - self.clear_property(PropertyId::BackgroundColor); - } - - /// Foreground color in RGBA. - pub fn foreground_color(&self) -> Option { - self.get_u32(PropertyId::ForegroundColor) - } - pub fn set_foreground_color(&mut self, value: u32) { - self.set_u32(PropertyId::ForegroundColor, value); - } - pub fn clear_foreground_color(&mut self) { - self.clear_property(PropertyId::ForegroundColor); - } - pub fn has_popup(&self) -> Option { self.has_popup } + pub fn set_has_popup(&mut self, value: HasPopup) { + self.has_popup = Some(value); + } + pub fn clear_has_popup(&mut self) { + self.has_popup = None; + } /// The list style type. Only available on list items. pub fn list_style(&self) -> Option { @@ -2286,20 +1563,6 @@ impl Node { self.vertical_offset = None; } - pub fn is_bold(&self) -> bool { - self.bold - } - pub fn set_bold(&mut self, value: bool) { - self.bold = value; - } - - pub fn is_italic(&self) -> bool { - self.italic - } - pub fn set_italic(&mut self, value: bool) { - self.italic = value; - } - pub fn overline(&self) -> Option { self.overline } @@ -2329,113 +1592,278 @@ impl Node { pub fn clear_underline(&mut self) { self.underline = None; } +} - // Focus traversal order. +node_id_vec_property_methods! { + (children, Children) + /// Ids of nodes that are children of this node logically, but are + /// not children of this node in the tree structure. As an example, + /// a table cell is a child of a row, and an 'indirect' child of a + /// column. + (indirect_children, IndirectChildren) + (controls, Controls) + (details, Details) + (described_by, DescribedBy) + (flow_to, FlowTo) + (labelled_by, LabelledBy) + /// On radio buttons this should be set to a list of all of the buttons + /// in the same group as this one, including this radio button itself. + (radio_group, RadioGroup) +} - pub fn previous_focus(&self) -> Option { - self.get_node_id(PropertyId::PreviousFocus) - } - pub fn set_previous_focus(&mut self, value: NodeId) { - self.set_node_id(PropertyId::PreviousFocus, value); - } - pub fn clear_previous_focus(&mut self) { - self.clear_property(PropertyId::PreviousFocus); - } +node_id_property_methods! { + (active_descendant, ActiveDescendant) + (error_message, ErrorMessage) + (in_page_link_target, InPageLinkTarget) + (member_of, MemberOf) + (next_on_line, NextOnLine) + (previous_on_line, PreviousOnLine) + (popup_for, PopupFor) + (table_header, TableHeader) + (table_row_header, TableRowHeader) + (table_column_header, TableColumnHeader) + (next_focus, NextFocus) + (previous_focus, PreviousFocus) +} - pub fn next_focus(&self) -> Option { - self.get_node_id(PropertyId::NextFocus) - } - pub fn set_next_focus(&mut self, value: NodeId) { - self.set_node_id(PropertyId::NextFocus, value); - } - pub fn clear_next_focus(&mut self) { - self.clear_property(PropertyId::NextFocus); - } +string_property_methods! { + (name, Name) + (description, Description) + (value, Value) + (access_key, AccessKey) + (auto_complete, AutoComplete) + (checked_state_description, CheckedStateDescription) + (class_name, ClassName) + (css_display, CssDisplay) + /// Only present when different from parent. + (font_family, FontFamily) + (html_tag, HtmlTag) + /// Inner HTML of an element. Only used for a top-level math element, + /// to support third-party math accessibility products that parse MathML. + (inner_html, InnerHtml) + (input_type, InputType) + (key_shortcuts, KeyShortcuts) + /// Only present when different from parent. + (language, Language) + (live_relevant, LiveRelevant) + /// Only if not already exposed in [`name`] ([`NameFrom::Placeholder`]). + /// + /// [`name`]: NodeProvider::name + (placeholder, Placeholder) + (aria_role, AriaRole) + (role_description, RoleDescription) + /// Only if not already exposed in [`name`] ([`NameFrom::Title`]). + /// + /// [`name`]: NodeProvider::name + (tooltip, Tooltip) + (url, Url) +} - // Numeric value attributes. +f64_property_methods! { + (scroll_x, ScrollX) + (scroll_x_min, ScrollXMin) + (scroll_x_max, ScrollXMax) + (scroll_y, ScrollY) + (scroll_y_min, ScrollYMin) + (scroll_y_max, ScrollYMax) + (numeric_value, NumericValue) + (min_numeric_value, MinNumericValue) + (max_numeric_value, MaxNumericValue) + (numeric_value_step, NumericValueStep) + (numeric_value_jump, NumericValueJump) + /// Font size is in pixels. + (font_size, FontSize) + /// Font weight can take on any arbitrary numeric value. Increments of 100 in + /// range `[0, 900]` represent keywords such as light, normal, bold, etc. + (font_weight, FontWeight) + /// The indentation of the text, in mm. + (text_indent, TextIndent) +} - pub fn numeric_value(&self) -> Option { - self.get_f64(PropertyId::NumericValue) - } - pub fn set_numeric_value(&mut self, value: f64) { - self.set_f64(PropertyId::NumericValue, value); - } - pub fn clear_numeric_value(&mut self) { - self.clear_property(PropertyId::NumericValue); - } +usize_property_methods! { + (aria_column_count, AriaColumnCount) + (aria_cell_column_index, AriaCellColumnIndex) + (aria_cell_column_span, AriaCellColumnSpan) + (aria_row_count, AriaRowCount) + (aria_cell_row_index, AriaCellRowIndex) + (aria_cell_row_span, AriaCellRowSpan) + (table_row_count, TableRowCount) + (table_column_count, TableColumnCount) + (table_row_index, TableRowIndex) + (table_column_index, TableColumnIndex) + (table_cell_column_index, TableCellColumnIndex) + (table_cell_column_span, TableCellColumnSpan) + (table_cell_row_index, TableCellRowIndex) + (table_cell_row_span, TableCellRowSpan) + (hierarchical_level, HierarchicalLevel) + (size_of_set, SizeOfSet) + (position_in_set, PositionInSet) +} - pub fn min_numeric_value(&self) -> Option { - self.get_f64(PropertyId::MinNumericValue) - } - pub fn set_min_numeric_value(&mut self, value: f64) { - self.set_f64(PropertyId::MinNumericValue, value); - } - pub fn clear_min_numeric_value(&mut self) { - self.clear_property(PropertyId::MinNumericValue); - } +color_property_methods! { + /// For [`Role::ColorWell`], specifies the selected color in RGBA. + (color_value, ColorValue) + /// Background color in RGBA. + (background_color, BackgroundColor) + /// Foreground color in RGBA. + (foreground_color, ForegroundColor) +} - pub fn max_numeric_value(&self) -> Option { - self.get_f64(PropertyId::MaxNumericValue) - } - pub fn set_max_numeric_value(&mut self, value: f64) { - self.set_f64(PropertyId::MaxNumericValue, value); - } - pub fn clear_max_numeric_value(&mut self) { - self.clear_property(PropertyId::MaxNumericValue); - } +length_slice_property_methods! { + /// For inline text. The length (non-inclusive) of each character + /// in UTF-8 code units (bytes). The sum of these lengths must equal + /// the length of [`value`], also in bytes. + /// + /// A character is defined as the smallest unit of text that + /// can be selected. This isn't necessarily a single Unicode + /// scalar value (code point). This is why AccessKit can't compute + /// the lengths of the characters from the text itself; this information + /// must be provided by the text editing implementation. + /// + /// If this node is the last text box in a line that ends with a hard + /// line break, that line break should be included at the end of this + /// node's value as either a CRLF or LF; in both cases, the line break + /// should be counted as a single character for the sake of this slice. + /// When the caret is at the end of such a line, the focus of the text + /// selection should be on the line break, not after it. + /// + /// [`value`]: NodeProvider::value + (character_lengths, CharacterLengths) - pub fn numeric_value_step(&self) -> Option { - self.get_f64(PropertyId::NumericValueStep) - } - pub fn set_numeric_value_step(&mut self, value: f64) { - self.set_f64(PropertyId::NumericValueStep, value); - } - pub fn clear_numeric_value_step(&mut self) { - self.clear_property(PropertyId::NumericValueStep); - } + /// For inline text. The length of each word in characters, as defined + /// in [`character_lengths`]. The sum of these lengths must equal + /// the length of [`character_lengths`]. + /// + /// The end of each word is the beginning of the next word; there are no + /// characters that are not considered part of a word. Trailing whitespace + /// is typically considered part of the word that precedes it, while + /// a line's leading whitespace is considered its own word. Whether + /// punctuation is considered a separate word or part of the preceding + /// word depends on the particular text editing implementation. + /// Some editors may have their own definition of a word; for example, + /// in an IDE, words may correspond to programming language tokens. + /// + /// Not all assistive technologies require information about word + /// boundaries, and not all platform accessibility APIs even expose + /// this information, but for assistive technologies that do use + /// this information, users will get unpredictable results if the word + /// boundaries exposed by the accessibility tree don't match + /// the editor's behavior. This is why AccessKit does not determine + /// word boundaries itself. + /// + /// [`character_lengths`]: NodeProvider::character_lengths + (word_lengths, WordLengths) +} - pub fn numeric_value_jump(&self) -> Option { - self.get_f64(PropertyId::NumericValueJump) - } - pub fn set_numeric_value_jump(&mut self, value: f64) { - self.set_f64(PropertyId::NumericValueJump, value); - } - pub fn clear_numeric_value_jump(&mut self) { - self.clear_property(PropertyId::NumericValueJump); - } +coord_slice_property_methods! { + /// For inline text. This is the position of each character within + /// the node's bounding box, in the direction given by + /// [`text_direction`], in the coordinate space of this node. + /// + /// When present, the length of this slice should be the same as the length + /// of [`character_lengths`], including for lines that end + /// with a hard line break. The position of such a line break should + /// be the position where an end-of-paragraph marker would be rendered. + /// + /// This property is optional. Without it, AccessKit can't support some + /// use cases, such as screen magnifiers that track the caret position + /// or screen readers that display a highlight cursor. However, + /// most text functionality still works without this information. + /// + /// [`text_direction`]: NodeProvider::text_direction + /// [`character_lengths`]: NodeProvider::character_lengths + (character_positions, CharacterPositions) - /// Font size is in pixels. - pub fn font_size(&self) -> Option { - self.get_f64(PropertyId::FontSize) - } - pub fn set_font_size(&mut self, value: f64) { - self.set_f64(PropertyId::FontSize, value); - } - pub fn clear_font_size(&mut self) { - self.clear_property(PropertyId::FontSize); - } + /// For inline text. This is the advance width of each character, + /// in the direction given by [`text_direction`], in the coordinate + /// space of this node. + /// + /// When present, the length of this slice should be the same as the length + /// of [`character_lengths`], including for lines that end + /// with a hard line break. The width of such a line break should + /// be non-zero if selecting the line break by itself results in + /// a visible highlight (as in Microsoft Word), or zero if not + /// (as in Windows Notepad). + /// + /// This property is optional. Without it, AccessKit can't support some + /// use cases, such as screen magnifiers that track the caret position + /// or screen readers that display a highlight cursor. However, + /// most text functionality still works without this information. + /// + /// [`text_direction`]: NodeProvider::text_direction + /// [`character_lengths`]: NodeProvider::character_lengths + (character_widths, CharacterWidths) +} - /// Font weight can take on any arbitrary numeric value. Increments of 100 in - /// range `[0, 900]` represent keywords such as light, normal, bold, etc. - pub fn font_weight(&self) -> Option { - self.get_f64(PropertyId::FontWeight) +property_methods! { + /// An affine transform to apply to any coordinates within this node + /// and its descendants, including the [`bounds`] property of this node. + /// The combined transforms of this node and its ancestors define + /// the coordinate space of this node. /// This should be `None` if + /// it would be set to the identity transform, which should be the case + /// for most nodes. + /// + /// AccessKit expects the final transformed coordinates to be relative + /// to the origin of the tree's container (e.g. window), in physical + /// pixels, with the y coordinate being top-down. + /// + /// [`bounds`]: NodeProvider::bounds + (transform, Transform, affine, Option, Affine) + + /// The bounding box of this node, in the node's coordinate space. + /// This property does not affect the coordinate space of either this node + /// or its descendants; only the [`transform`] property affects that. + /// This, along with the recommendation that most nodes should have + /// a [`transform`] of `None`, implies that the `bounds` property + /// of most nodes should be in the coordinate space of the nearest ancestor + /// with a non-`None` [`transform`], or if there is no such ancestor, + /// the tree's container (e.g. window). + /// + /// [`transform`]: NodeProvider::transform + (bounds, Bounds, rect, Option, Rect) +} + +impl Node { + pub fn text_selection(&self) -> Option { + match self.get_property(PropertyId::TextSelection) { + Property::None => None, + Property::TextSelection(value) => Some(*value), + _ => panic!(), + } } - pub fn set_font_weight(&mut self, value: f64) { - self.set_f64(PropertyId::FontWeight, value); + pub fn set_text_selection(&mut self, value: TextSelection) { + self.set_property(PropertyId::TextSelection, Property::TextSelection(value)); } - pub fn clear_font_weight(&mut self) { - self.clear_property(PropertyId::FontWeight); + pub fn clear_text_selection(&mut self) { + self.clear_property(PropertyId::TextSelection); } - /// The text indent of the text, in mm. - pub fn text_indent(&self) -> Option { - self.get_f64(PropertyId::TextIndent) + pub fn custom_actions(&self) -> &[CustomAction] { + match self.get_property(PropertyId::CustomActions) { + Property::None => &[], + Property::CustomActionVec(value) => value, + _ => panic!(), + } + } + pub fn set_custom_actions(&mut self, value: impl Into>) { + self.set_property( + PropertyId::CustomActions, + Property::CustomActionVec(value.into()), + ); } - pub fn set_text_indent(&mut self, value: f64) { - self.set_f64(PropertyId::TextIndent, value); + pub fn push_to_custom_actions(&mut self, action: CustomAction) { + match self.get_property_mut( + PropertyId::CustomActions, + Property::CustomActionVec(Vec::new()), + ) { + Property::CustomActionVec(v) => { + v.push(action); + } + _ => panic!(), + } } - pub fn clear_text_indent(&mut self) { - self.clear_property(PropertyId::TextIndent); + pub fn clear_custom_actions(&mut self) { + self.clear_property(PropertyId::CustomActions); } } From e7743e231518145ca337deb5d19fac99d91617cb Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 8 Jan 2023 10:56:41 -0600 Subject: [PATCH 06/45] Fix references to NodeProvider in doc comments, left over from an abandoned experiment --- common/src/lib.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index e027260fa..6a3ba8c52 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1648,13 +1648,13 @@ string_property_methods! { (live_relevant, LiveRelevant) /// Only if not already exposed in [`name`] ([`NameFrom::Placeholder`]). /// - /// [`name`]: NodeProvider::name + /// [`name`]: Node::name (placeholder, Placeholder) (aria_role, AriaRole) (role_description, RoleDescription) /// Only if not already exposed in [`name`] ([`NameFrom::Title`]). /// - /// [`name`]: NodeProvider::name + /// [`name`]: Node::name (tooltip, Tooltip) (url, Url) } @@ -1727,7 +1727,7 @@ length_slice_property_methods! { /// When the caret is at the end of such a line, the focus of the text /// selection should be on the line break, not after it. /// - /// [`value`]: NodeProvider::value + /// [`value`]: Node::value (character_lengths, CharacterLengths) /// For inline text. The length of each word in characters, as defined @@ -1751,7 +1751,7 @@ length_slice_property_methods! { /// the editor's behavior. This is why AccessKit does not determine /// word boundaries itself. /// - /// [`character_lengths`]: NodeProvider::character_lengths + /// [`character_lengths`]: Node::character_lengths (word_lengths, WordLengths) } @@ -1770,8 +1770,8 @@ coord_slice_property_methods! { /// or screen readers that display a highlight cursor. However, /// most text functionality still works without this information. /// - /// [`text_direction`]: NodeProvider::text_direction - /// [`character_lengths`]: NodeProvider::character_lengths + /// [`text_direction`]: Node::text_direction + /// [`character_lengths`]: Node::character_lengths (character_positions, CharacterPositions) /// For inline text. This is the advance width of each character, @@ -1790,8 +1790,8 @@ coord_slice_property_methods! { /// or screen readers that display a highlight cursor. However, /// most text functionality still works without this information. /// - /// [`text_direction`]: NodeProvider::text_direction - /// [`character_lengths`]: NodeProvider::character_lengths + /// [`text_direction`]: Node::text_direction + /// [`character_lengths`]: Node::character_lengths (character_widths, CharacterWidths) } @@ -1807,7 +1807,7 @@ property_methods! { /// to the origin of the tree's container (e.g. window), in physical /// pixels, with the y coordinate being top-down. /// - /// [`bounds`]: NodeProvider::bounds + /// [`bounds`]: Node::bounds (transform, Transform, affine, Option, Affine) /// The bounding box of this node, in the node's coordinate space. @@ -1819,7 +1819,7 @@ property_methods! { /// with a non-`None` [`transform`], or if there is no such ancestor, /// the tree's container (e.g. window). /// - /// [`transform`]: NodeProvider::transform + /// [`transform`]: Node::transform (bounds, Bounds, rect, Option, Rect) } From 407e979771179e465caec53ed07833fc1abedfb1 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 8 Jan 2023 11:19:47 -0600 Subject: [PATCH 07/45] Refactor to allow Node to derive Default again --- common/src/lib.rs | 90 ++++++++++------------------------------------- 1 file changed, 19 insertions(+), 71 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 6a3ba8c52..a709870b4 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -755,10 +755,20 @@ enum PropertyId { TextSelection, CustomActions, - // This should come last. + // This MUST be last. Unset, } +#[derive(Clone, Copy, Debug, PartialEq)] +#[repr(transparent)] +struct PropertyIndices([u8; PropertyId::Unset as usize]); + +impl Default for PropertyIndices { + fn default() -> Self { + Self([PropertyId::Unset as u8; PropertyId::Unset as usize]) + } +} + macro_rules! property_methods { ($($(#[$doc:meta])* ($base_name:ident, $id:ident, $type_method_base:ident, $getter_result:ty, $setter_param:ty))+) => { paste! { @@ -878,11 +888,11 @@ macro_rules! coord_slice_property_methods { /// to other languages, documentation of getter methods is written as if /// documenting fields in a struct, and such methods are referred to /// as properties. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, Default, PartialEq)] pub struct Node { role: Role, actions: EnumSet, - indices: [u8; PropertyId::Unset as usize], + indices: PropertyIndices, props: Vec, autofill_available: bool, default: bool, @@ -939,7 +949,7 @@ pub struct Node { impl Node { fn get_property(&self, id: PropertyId) -> &Property { - let index = self.indices[id as usize]; + let index = self.indices.0[id as usize]; if index == PropertyId::Unset as u8 { &Property::None } else { @@ -948,11 +958,11 @@ impl Node { } fn get_property_mut(&mut self, id: PropertyId, default: Property) -> &mut Property { - let index = self.indices[id as usize] as usize; + let index = self.indices.0[id as usize] as usize; if index == PropertyId::Unset as usize { self.props.push(default); let index = self.props.len() - 1; - self.indices[id as usize] = index as u8; + self.indices.0[id as usize] = index as u8; &mut self.props[index] } else { if matches!(self.props[index], Property::None) { @@ -963,17 +973,17 @@ impl Node { } fn set_property(&mut self, id: PropertyId, value: Property) { - let index = self.indices[id as usize]; + let index = self.indices.0[id as usize]; if index == PropertyId::Unset as u8 { self.props.push(value); - self.indices[id as usize] = (self.props.len() - 1) as u8; + self.indices.0[id as usize] = (self.props.len() - 1) as u8; } else { self.props[index as usize] = value; } } fn clear_property(&mut self, id: PropertyId) { - let index = self.indices[id as usize]; + let index = self.indices.0[id as usize]; if index != PropertyId::Unset as u8 { self.props[index as usize] = Property::None; } @@ -1867,68 +1877,6 @@ impl Node { } } -impl Default for Node { - fn default() -> Self { - Self { - role: Role::Unknown, - indices: [PropertyId::Unset as u8; PropertyId::Unset as usize], - props: Vec::new(), - actions: EnumSet::new(), - name_from: None, - description_from: None, - autofill_available: false, - expanded: None, - default: false, - editable: false, - orientation: None, - hovered: false, - hidden: false, - linked: false, - multiline: false, - multiselectable: false, - protected: false, - required: false, - visited: false, - busy: false, - nonatomic_text_field_root: false, - live_atomic: false, - modal: false, - canvas_has_fallback: false, - scrollable: false, - clips_children: false, - not_user_selectable_style: false, - selected: None, - selected_from_focus: false, - is_line_breaking_object: false, - is_page_breaking_object: false, - has_aria_attribute: false, - touch_pass_through: false, - is_spelling_error: false, - is_grammar_error: false, - is_search_match: false, - is_suggestion: false, - text_direction: None, - invalid: None, - checked_state: None, - live: None, - default_action_verb: None, - sort_direction: None, - read_only: false, - disabled: false, - aria_current: None, - has_popup: None, - list_style: None, - text_align: None, - vertical_offset: None, - bold: false, - italic: false, - overline: None, - strikethrough: None, - underline: None, - } - } -} - /// The data associated with an accessibility tree that's global to the /// tree and not associated with any particular node. #[derive(Clone, Debug, PartialEq, Eq)] From 54a31632c4858bd2c826389d19627e512c3d38da Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 8 Jan 2023 11:32:15 -0600 Subject: [PATCH 08/45] Use a macro for the various optional enum fields --- common/src/lib.rs | 207 +++++++++------------------------------------- 1 file changed, 38 insertions(+), 169 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index a709870b4..488dfddc3 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -769,6 +769,25 @@ impl Default for PropertyIndices { } } +macro_rules! optional_enum_methods { + ($($(#[$doc:meta])* ($base_name:ident, $type:ty))+) => { + paste! { + impl Node { + $($(#[$doc])* + pub fn $base_name(&self) -> Option<$type> { + self.$base_name + } + pub fn [< set_ $base_name >](&mut self, value: $type) { + self.$base_name = Some(value); + } + pub fn [< clear_ $base_name >](&mut self) { + self.$base_name = None; + })* + } + } + } +} + macro_rules! property_methods { ($($(#[$doc:meta])* ($base_name:ident, $id:ident, $type_method_base:ident, $getter_result:ty, $setter_param:ty))+) => { paste! { @@ -1429,179 +1448,29 @@ impl Node { pub fn clear_selected(&mut self) { self.selected = None; } +} +optional_enum_methods! { /// What information was used to compute the object's name. - pub fn name_from(&self) -> Option { - self.name_from - } - pub fn set_name_from(&mut self, value: NameFrom) { - self.name_from = Some(value); - } - pub fn clear_name_from(&mut self) { - self.name_from = None; - } - + (name_from, NameFrom) /// What information was used to compute the object's description. - pub fn description_from(&self) -> Option { - self.description_from - } - pub fn set_description_from(&mut self, value: DescriptionFrom) { - self.description_from = Some(value); - } - pub fn clear_description_from(&mut self) { - self.description_from = None; - } - - pub fn invalid(&self) -> Option { - self.invalid - } - pub fn set_invalid(&mut self, value: Invalid) { - self.invalid = Some(value); - } - pub fn clear_invalid(&mut self) { - self.invalid = None; - } - - pub fn checked_state(&self) -> Option { - self.checked_state - } - pub fn set_checked_state(&mut self, value: CheckedState) { - self.checked_state = Some(value); - } - pub fn clear_checked_state(&mut self) { - self.checked_state = None; - } - - pub fn live(&self) -> Option { - self.live - } - pub fn set_live(&mut self, value: Live) { - self.live = Some(value); - } - pub fn clear_live(&mut self) { - self.live = None; - } - - pub fn default_action_verb(&self) -> Option { - self.default_action_verb - } - pub fn set_default_action_verb(&mut self, value: DefaultActionVerb) { - self.default_action_verb = Some(value); - } - pub fn clear_default_action_verb(&mut self) { - self.default_action_verb = None; - } - - pub fn text_direction(&self) -> Option { - self.text_direction - } - pub fn set_text_direction(&mut self, value: TextDirection) { - self.text_direction = Some(value); - } - pub fn clear_text_direction(&mut self) { - self.text_direction = None; - } - - pub fn orientation(&self) -> Option { - self.orientation - } - pub fn set_orientation(&mut self, value: Orientation) { - self.orientation = Some(value); - } - pub fn clear_orientation(&mut self) { - self.orientation = None; - } - - pub fn sort_direction(&self) -> Option { - self.sort_direction - } - pub fn set_sort_direction(&mut self, value: SortDirection) { - self.sort_direction = Some(value); - } - pub fn clear_sort_direction(&mut self) { - self.sort_direction = None; - } - - pub fn aria_current(&self) -> Option { - self.aria_current - } - pub fn set_aria_current(&mut self, value: AriaCurrent) { - self.aria_current = Some(value); - } - pub fn clear_aria_current(&mut self) { - self.aria_current = None; - } - - pub fn has_popup(&self) -> Option { - self.has_popup - } - pub fn set_has_popup(&mut self, value: HasPopup) { - self.has_popup = Some(value); - } - pub fn clear_has_popup(&mut self) { - self.has_popup = None; - } - + (description_from, DescriptionFrom) + (invalid, Invalid) + (checked_state, CheckedState) + (live, Live) + (default_action_verb, DefaultActionVerb) + (text_direction, TextDirection) + (orientation, Orientation) + (sort_direction, SortDirection) + (aria_current, AriaCurrent) + (has_popup, HasPopup) /// The list style type. Only available on list items. - pub fn list_style(&self) -> Option { - self.list_style - } - pub fn set_list_style(&mut self, value: ListStyle) { - self.list_style = Some(value); - } - pub fn clear_list_style(&mut self) { - self.list_style = None; - } - - pub fn text_align(&self) -> Option { - self.text_align - } - pub fn set_text_align(&mut self, value: TextAlign) { - self.text_align = Some(value); - } - pub fn clear_text_align(&mut self) { - self.text_align = None; - } - - pub fn vertical_offset(&self) -> Option { - self.vertical_offset - } - pub fn set_vertical_offset(&mut self, value: VerticalOffset) { - self.vertical_offset = Some(value); - } - pub fn clear_vertical_offset(&mut self) { - self.vertical_offset = None; - } - - pub fn overline(&self) -> Option { - self.overline - } - pub fn set_overline(&mut self, value: TextDecoration) { - self.overline = Some(value); - } - pub fn clear_overline(&mut self) { - self.overline = None; - } - - pub fn strikethrough(&self) -> Option { - self.strikethrough - } - pub fn set_strikethrough(&mut self, value: TextDecoration) { - self.strikethrough = Some(value); - } - pub fn clear_strikethrough(&mut self) { - self.strikethrough = None; - } - - pub fn underline(&self) -> Option { - self.underline - } - pub fn set_underline(&mut self, value: TextDecoration) { - self.underline = Some(value); - } - pub fn clear_underline(&mut self) { - self.underline = None; - } + (list_style, ListStyle) + (text_align, TextAlign) + (vertical_offset, VerticalOffset) + (overline, TextDecoration) + (strikethrough, TextDecoration) + (underline, TextDecoration) } node_id_vec_property_methods! { From cdd2aa207382500046c685a0f37e55dc87dcd3ce Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 8 Jan 2023 12:07:33 -0600 Subject: [PATCH 09/45] Use macros for all the flag getter/setter methods --- common/src/lib.rs | 357 ++++++++++++++-------------------------------- 1 file changed, 104 insertions(+), 253 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 488dfddc3..7e93b1bb4 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -769,6 +769,57 @@ impl Default for PropertyIndices { } } +macro_rules! bool_methods { + ($($(#[$doc:meta])* ($base_name:ident))+) => { + paste! { + impl Node { + $($(#[$doc])* + pub fn [< is_ $base_name >](&self) -> bool { + self.$base_name + } + pub fn [< set_ $base_name >](&mut self, value: bool) { + self.$base_name = value; + })* + } + } + } +} + +macro_rules! irregular_bool_methods { + ($($(#[$doc:meta])* ($base_name:ident))+) => { + paste! { + impl Node { + $($(#[$doc])* + pub fn $base_name(&self) -> bool { + self.$base_name + } + pub fn [< set_ $base_name >](&mut self, value: bool) { + self.$base_name = value; + })* + } + } + } +} + +macro_rules! optional_bool_methods { + ($($(#[$doc:meta])* ($base_name:ident))+) => { + paste! { + impl Node { + $($(#[$doc])* + pub fn [< is_ $base_name >](&self) -> Option { + self.$base_name + } + pub fn [< set_ $base_name >](&mut self, value: bool) { + self.$base_name = Some(value); + } + pub fn [< clear_ $base_name >](&mut self) { + self.$base_name = None; + })* + } + } + } +} + macro_rules! optional_enum_methods { ($($(#[$doc:meta])* ($base_name:ident, $type:ty))+) => { paste! { @@ -925,25 +976,25 @@ pub struct Node { required: bool, visited: bool, busy: bool, - nonatomic_text_field_root: bool, live_atomic: bool, modal: bool, - canvas_has_fallback: bool, scrollable: bool, - clips_children: bool, not_user_selectable_style: bool, selected_from_focus: bool, - has_aria_attribute: bool, touch_pass_through: bool, read_only: bool, disabled: bool, bold: bool, italic: bool, + canvas_has_fallback: bool, + clips_children: bool, + has_aria_attribute: bool, is_line_breaking_object: bool, is_page_breaking_object: bool, is_spelling_error: bool, is_grammar_error: bool, is_search_match: bool, + is_nonatomic_text_field_root: bool, is_suggestion: bool, expanded: Option, selected: Option, @@ -1163,272 +1214,80 @@ impl Node { pub fn clear_actions(&mut self) { self.actions.clear(); } +} - pub fn is_autofill_available(&self) -> bool { - self.autofill_available - } - pub fn set_autofill_available(&mut self, value: bool) { - self.autofill_available = value; - } - - pub fn is_default(&self) -> bool { - self.default - } - pub fn set_default(&mut self, value: bool) { - self.default = value; - } - - pub fn is_editable(&self) -> bool { - self.editable - } - pub fn set_editable(&mut self, value: bool) { - self.editable = value; - } - - pub fn is_hovered(&self) -> bool { - self.hovered - } - pub fn set_hovered(&mut self, value: bool) { - self.hovered = value; - } - +bool_methods! { + (autofill_available) + (default) + (editable) + (hovered) /// Exclude this node and its descendants from the tree presented to /// assistive technologies, and from hit testing. - pub fn is_hidden(&self) -> bool { - self.hidden - } - pub fn set_hidden(&mut self, value: bool) { - self.hidden = value; - } - - pub fn is_linked(&self) -> bool { - self.linked - } - pub fn set_linked(&mut self, value: bool) { - self.linked = value; - } - - pub fn is_multiline(&self) -> bool { - self.multiline - } - pub fn set_multiline(&mut self, value: bool) { - self.multiline = value; - } - - pub fn is_multiselectable(&self) -> bool { - self.multiselectable - } - pub fn set_multiselectable(&mut self, value: bool) { - self.multiselectable = value; - } - - pub fn is_protected(&self) -> bool { - self.protected - } - pub fn set_protected(&mut self, value: bool) { - self.protected = value; - } - - pub fn is_required(&self) -> bool { - self.required - } - pub fn set_required(&mut self, value: bool) { - self.required = value; - } - - pub fn is_visited(&self) -> bool { - self.visited - } - pub fn set_visited(&mut self, value: bool) { - self.visited = value; - } - - pub fn is_busy(&self) -> bool { - self.busy - } - pub fn set_busy(&mut self, value: bool) { - self.busy = value; - } - - /// The object functions as a text field which exposes its descendants. - /// - /// Use cases include the root of a content-editable region, an ARIA - /// textbox which isn't currently editable and which has interactive - /// descendants, and a `` element that has "design-mode" set to "on". - pub fn is_nonatomic_text_field_root(&self) -> bool { - self.nonatomic_text_field_root - } - pub fn set_nonatomic_text_field_root(&mut self, value: bool) { - self.nonatomic_text_field_root = value; - } - - pub fn is_live_atomic(&self) -> bool { - self.live_atomic - } - pub fn set_live_atomic(&mut self, value: bool) { - self.live_atomic = value; - } - + (hidden) + (linked) + (multiline) + (multiselectable) + (protected) + (required) + (visited) + (busy) + (live_atomic) /// If a dialog box is marked as explicitly modal. - pub fn is_modal(&self) -> bool { - self.modal - } - pub fn set_modal(&mut self, value: bool) { - self.modal = value; - } - - /// Set on a canvas element if it has fallback content. - pub fn canvas_has_fallback(&self) -> bool { - self.canvas_has_fallback - } - pub fn set_canvas_has_fallback(&mut self, value: bool) { - self.canvas_has_fallback = value; - } - + (modal) /// Indicates this node is user-scrollable, e.g. `overflow: scroll|auto`, as /// opposed to only programmatically scrollable, like `overflow: hidden`, or /// not scrollable at all, e.g. `overflow: visible`. - pub fn is_scrollable(&self) -> bool { - self.scrollable - } - pub fn set_scrollable(&mut self, value: bool) { - self.scrollable = value; - } - - /// Indicates that this node clips its children, i.e. may have - /// `overflow: hidden` or clip children by default. - pub fn clips_children(&self) -> bool { - self.clips_children - } - pub fn set_clips_children(&mut self, value: bool) { - self.clips_children = value; - } - + (scrollable) /// Indicates that this node is not selectable because the style has /// `user-select: none`. Note that there may be other reasons why a node is /// not selectable - for example, bullets in a list. However, this attribute /// is only set on `user-select: none`. - pub fn is_not_user_selectable_style(&self) -> bool { - self.not_user_selectable_style - } - pub fn set_not_user_selectable_style(&mut self, value: bool) { - self.not_user_selectable_style = value; - } - + (not_user_selectable_style) /// Indicates whether this node is selected due to selection follows focus. - pub fn is_selected_from_focus(&self) -> bool { - self.selected_from_focus - } - pub fn set_selected_from_focus(&mut self, value: bool) { - self.selected_from_focus = value; - } - - /// True if the node has any ARIA attributes set. - pub fn has_aria_attribute(&self) -> bool { - self.has_aria_attribute - } - pub fn set_has_aria_attribute(&mut self, value: bool) { - self.has_aria_attribute = value; - } - + (selected_from_focus) /// This element allows touches to be passed through when a screen reader /// is in touch exploration mode, e.g. a virtual keyboard normally /// behaves this way. - pub fn is_touch_pass_through(&self) -> bool { - self.touch_pass_through - } - pub fn set_touch_pass_through(&mut self, value: bool) { - self.touch_pass_through = value; - } - + (touch_pass_through) /// Use for a textbox that allows focus/selection but not input. - pub fn is_read_only(&self) -> bool { - self.read_only - } - pub fn set_read_only(&mut self, value: bool) { - self.read_only = value; - } - + (read_only) /// Use for a control or group of controls that disallows input. - pub fn is_disabled(&self) -> bool { - self.disabled - } - pub fn set_disabled(&mut self, value: bool) { - self.disabled = value; - } - - pub fn is_bold(&self) -> bool { - self.bold - } - pub fn set_bold(&mut self, value: bool) { - self.bold = value; - } - - pub fn is_italic(&self) -> bool { - self.italic - } - pub fn set_italic(&mut self, value: bool) { - self.italic = value; - } + (disabled) + (bold) + (italic) +} +irregular_bool_methods! { + /// Set on a canvas element if it has fallback content. + (canvas_has_fallback) + /// Indicates that this node clips its children, i.e. may have + /// `overflow: hidden` or clip children by default. + (clips_children) + /// True if the node has any ARIA attributes set. + (has_aria_attribute) /// Indicates whether this node causes a hard line-break /// (e.g. block level elements, or `
`). - pub fn is_line_breaking_object(&self) -> bool { - self.is_line_breaking_object - } - pub fn set_is_line_breaking_object(&mut self, value: bool) { - self.is_line_breaking_object = value; - } - + (is_line_breaking_object) /// Indicates whether this node causes a page break. - pub fn is_page_breaking_object(&self) -> bool { - self.is_page_breaking_object - } - pub fn set_is_page_breaking_object(&mut self, value: bool) { - self.is_page_breaking_object = value; - } - - pub fn is_spelling_error(&self) -> bool { - self.is_spelling_error - } - pub fn set_is_spelling_error(&mut self, value: bool) { - self.is_spelling_error = value; - } - - pub fn is_grammar_error(&self) -> bool { - self.is_grammar_error - } - pub fn set_is_grammar_error(&mut self, value: bool) { - self.is_grammar_error = value; - } - - pub fn is_search_match(&self) -> bool { - self.is_search_match - } - pub fn set_is_search_match(&mut self, value: bool) { - self.is_search_match = value; - } - - pub fn is_suggestion(&self) -> bool { - self.is_suggestion - } - pub fn set_is_suggestion(&mut self, value: bool) { - self.is_suggestion = value; - } + (is_page_breaking_object) + (is_spelling_error) + (is_grammar_error) + (is_search_match) + (is_suggestion) + /// The object functions as a text field which exposes its descendants. + /// + /// Use cases include the root of a content-editable region, an ARIA + /// textbox which isn't currently editable and which has interactive + /// descendants, and a `` element that has "design-mode" set to "on". + (is_nonatomic_text_field_root) +} +optional_bool_methods! { /// Whether this node is expanded, collapsed, or neither. /// /// Setting this to `false` means the node is collapsed; omitting it means this state /// isn't applicable. - pub fn is_expanded(&self) -> Option { - self.expanded - } - pub fn set_expanded(&mut self, value: bool) { - self.expanded = Some(value); - } - pub fn clear_expanded(&mut self) { - self.expanded = None; - } + (expanded) /// Indicates whether this node is selected or unselected. /// @@ -1439,15 +1298,7 @@ impl Node { /// to announce "not selected". The ambiguity of this flag /// in platform accessibility APIs has made extraneous /// "not selected" announcements a common annoyance. - pub fn is_selected(&self) -> Option { - self.selected - } - pub fn set_selected(&mut self, value: bool) { - self.selected = Some(value); - } - pub fn clear_selected(&mut self) { - self.selected = None; - } + (selected) } optional_enum_methods! { From 6a638d81db22577f314fcde54e3762f9f77836c3 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 8 Jan 2023 12:30:50 -0600 Subject: [PATCH 10/45] Remove old workaround for feature/dependency name collision --- common/Cargo.toml | 8 ++++---- common/src/lib.rs | 33 ++------------------------------- 2 files changed, 6 insertions(+), 35 deletions(-) diff --git a/common/Cargo.toml b/common/Cargo.toml index 4a2876feb..292aa291c 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -17,9 +17,9 @@ features = ["schemars", "serde"] enumset = "1.0.8" kurbo = "0.8.3" paste = "1.0" -schemars_lib = { package = "schemars", version = "0.8.7", features = ["enumset"], optional = true } -serde_lib = { package = "serde", version = "1.0", features = ["derive", "rc"], optional = true } +schemars = { version = "0.8.7", features = ["enumset"], optional = true } +serde = { version = "1.0", features = ["derive", "rc"], optional = true } [features] -schemars = ["serde", "schemars_lib", "kurbo/schemars"] -serde = ["serde_lib", "enumset/serde", "kurbo/serde"] +schemars = ["serde", "dep:schemars", "kurbo/schemars"] +serde = ["dep:serde", "enumset/serde", "kurbo/serde"] diff --git a/common/src/lib.rs b/common/src/lib.rs index 7e93b1bb4..3408d270a 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -13,13 +13,9 @@ pub use kurbo; use kurbo::{Affine, Point, Rect}; use paste::paste; #[cfg(feature = "schemars")] -use schemars_lib as schemars; -#[cfg(feature = "schemars")] -use schemars_lib::JsonSchema; -#[cfg(feature = "serde")] -use serde_lib as serde; +use schemars::JsonSchema; #[cfg(feature = "serde")] -use serde_lib::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use std::{ num::{NonZeroU128, NonZeroU64}, sync::Arc, @@ -38,7 +34,6 @@ use std::{ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub enum Role { Unknown, @@ -258,7 +253,6 @@ impl Default for Role { #[derive(EnumSetType, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[cfg_attr(feature = "serde", enumset(serialize_as_list))] pub enum Action { @@ -338,7 +332,6 @@ pub enum Action { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub enum Orientation { /// E.g. most toolbars and separators. @@ -350,7 +343,6 @@ pub enum Orientation { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub enum NameFrom { /// E.g. [`aria-label`]. @@ -374,7 +366,6 @@ pub enum NameFrom { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub enum DescriptionFrom { AriaDescription, @@ -392,7 +383,6 @@ pub enum DescriptionFrom { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub enum TextDirection { LeftToRight, @@ -408,7 +398,6 @@ pub enum TextDirection { #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub enum Invalid { True, @@ -419,7 +408,6 @@ pub enum Invalid { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub enum CheckedState { False, @@ -436,7 +424,6 @@ pub enum CheckedState { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub enum DefaultActionVerb { Click, @@ -457,7 +444,6 @@ pub enum DefaultActionVerb { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub enum SortDirection { Unsorted, @@ -469,7 +455,6 @@ pub enum SortDirection { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub enum AriaCurrent { False, @@ -484,7 +469,6 @@ pub enum AriaCurrent { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub enum Live { Off, @@ -495,7 +479,6 @@ pub enum Live { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub enum HasPopup { True, @@ -509,7 +492,6 @@ pub enum HasPopup { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub enum ListStyle { Circle, @@ -524,7 +506,6 @@ pub enum ListStyle { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub enum TextAlign { Left, @@ -536,7 +517,6 @@ pub enum TextAlign { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub enum VerticalOffset { Subscript, @@ -546,7 +526,6 @@ pub enum VerticalOffset { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub enum TextDecoration { Solid, @@ -564,7 +543,6 @@ pub type NodeIdContent = NonZeroU128; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] pub struct NodeId(pub NodeIdContent); impl From for NodeId { @@ -580,7 +558,6 @@ impl From for NodeId { #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct CustomAction { @@ -603,7 +580,6 @@ fn is_empty(slice: &[T]) -> bool { #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct TextPosition { @@ -617,7 +593,6 @@ pub struct TextPosition { #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct TextSelection { @@ -1602,7 +1577,6 @@ impl Node { #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct Tree { @@ -1638,7 +1612,6 @@ impl Tree { #[derive(Clone, Debug, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct TreeUpdate { @@ -1695,7 +1668,6 @@ impl TreeUpdate> From for TreeUpdate { #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub enum ActionData { CustomAction(i32), @@ -1716,7 +1688,6 @@ pub enum ActionData { #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] -#[cfg_attr(feature = "serde", serde(crate = "serde"))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct ActionRequest { From 7b11644d7e1515ad5365230d0b711a04b226c6f0 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 8 Jan 2023 12:55:58 -0600 Subject: [PATCH 11/45] Use EnumSet to compactly represent flags --- common/src/lib.rs | 167 ++++++++++++++++++++++++-------------------- consumer/src/lib.rs | 2 +- 2 files changed, 92 insertions(+), 77 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 3408d270a..3f5df2ad9 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -607,6 +607,46 @@ pub struct TextSelection { pub focus: TextPosition, } +#[derive(EnumSetType, Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "schemars", derive(JsonSchema))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +#[cfg_attr(feature = "serde", enumset(serialize_as_list))] +enum Flag { + AutofillAvailable, + Default, + Editable, + Hovered, + Hidden, + Linked, + Multiline, + Multiselectable, + Protected, + Required, + Visited, + Busy, + LiveAtomic, + Modal, + Scrollable, + NotUserSelectableStyle, + SelectedFromFocus, + TouchPassThrough, + ReadOnly, + Disabled, + Bold, + Italic, + CanvasHasFallback, + ClipsChildren, + HasAriaAttribute, + IsLineBreakingObject, + IsPageBreakingObject, + IsSpellingError, + IsGrammarError, + IsSearchMatch, + IsSuggestion, + IsNonatomicTextFieldRoot, +} + // The following is based on the technique described here: // https://viruta.org/reducing-memory-consumption-in-librsvg-2.html @@ -744,32 +784,38 @@ impl Default for PropertyIndices { } } -macro_rules! bool_methods { - ($($(#[$doc:meta])* ($base_name:ident))+) => { +macro_rules! flag_methods { + ($($(#[$doc:meta])* ($base_name:ident, $id:ident))+) => { paste! { impl Node { $($(#[$doc])* pub fn [< is_ $base_name >](&self) -> bool { - self.$base_name + self.flags.contains(Flag::$id) } - pub fn [< set_ $base_name >](&mut self, value: bool) { - self.$base_name = value; + pub fn [< set_ $base_name >](&mut self) { + self.flags.insert(Flag::$id); + } + pub fn [< clear_ $base_name >](&mut self) { + self.flags.remove(Flag::$id); })* } } } } -macro_rules! irregular_bool_methods { - ($($(#[$doc:meta])* ($base_name:ident))+) => { +macro_rules! irregular_flag_methods { + ($($(#[$doc:meta])* ($base_name:ident, $id:ident))+) => { paste! { impl Node { $($(#[$doc])* pub fn $base_name(&self) -> bool { - self.$base_name + self.flags.contains(Flag::$id) } - pub fn [< set_ $base_name >](&mut self, value: bool) { - self.$base_name = value; + pub fn [< set_ $base_name >](&mut self) { + self.flags.insert(Flag::$id); + } + pub fn [< clear_ $base_name >](&mut self) { + self.flags.remove(Flag::$id); })* } } @@ -939,38 +985,7 @@ pub struct Node { actions: EnumSet, indices: PropertyIndices, props: Vec, - autofill_available: bool, - default: bool, - editable: bool, - hovered: bool, - hidden: bool, - linked: bool, - multiline: bool, - multiselectable: bool, - protected: bool, - required: bool, - visited: bool, - busy: bool, - live_atomic: bool, - modal: bool, - scrollable: bool, - not_user_selectable_style: bool, - selected_from_focus: bool, - touch_pass_through: bool, - read_only: bool, - disabled: bool, - bold: bool, - italic: bool, - canvas_has_fallback: bool, - clips_children: bool, - has_aria_attribute: bool, - is_line_breaking_object: bool, - is_page_breaking_object: bool, - is_spelling_error: bool, - is_grammar_error: bool, - is_search_match: bool, - is_nonatomic_text_field_root: bool, - is_suggestion: bool, + flags: EnumSet, expanded: Option, selected: Option, name_from: Option, @@ -1191,70 +1206,70 @@ impl Node { } } -bool_methods! { - (autofill_available) - (default) - (editable) - (hovered) +flag_methods! { + (autofill_available, AutofillAvailable) + (default, Default) + (editable, Editable) + (hovered, Hovered) /// Exclude this node and its descendants from the tree presented to /// assistive technologies, and from hit testing. - (hidden) - (linked) - (multiline) - (multiselectable) - (protected) - (required) - (visited) - (busy) - (live_atomic) + (hidden, Hidden) + (linked, Linked) + (multiline, Multiline) + (multiselectable, Multiselectable) + (protected, Protected) + (required, Required) + (visited, Visited) + (busy, Busy) + (live_atomic, LiveAtomic) /// If a dialog box is marked as explicitly modal. - (modal) + (modal, Modal) /// Indicates this node is user-scrollable, e.g. `overflow: scroll|auto`, as /// opposed to only programmatically scrollable, like `overflow: hidden`, or /// not scrollable at all, e.g. `overflow: visible`. - (scrollable) + (scrollable, Scrollable) /// Indicates that this node is not selectable because the style has /// `user-select: none`. Note that there may be other reasons why a node is /// not selectable - for example, bullets in a list. However, this attribute /// is only set on `user-select: none`. - (not_user_selectable_style) + (not_user_selectable_style, NotUserSelectableStyle) /// Indicates whether this node is selected due to selection follows focus. - (selected_from_focus) + (selected_from_focus, SelectedFromFocus) /// This element allows touches to be passed through when a screen reader /// is in touch exploration mode, e.g. a virtual keyboard normally /// behaves this way. - (touch_pass_through) + (touch_pass_through, TouchPassThrough) /// Use for a textbox that allows focus/selection but not input. - (read_only) + (read_only, ReadOnly) /// Use for a control or group of controls that disallows input. - (disabled) - (bold) - (italic) + (disabled, Disabled) + (bold, Bold) + (italic, Italic) } -irregular_bool_methods! { +irregular_flag_methods! { /// Set on a canvas element if it has fallback content. - (canvas_has_fallback) + (canvas_has_fallback, CanvasHasFallback) /// Indicates that this node clips its children, i.e. may have /// `overflow: hidden` or clip children by default. - (clips_children) + (clips_children, ClipsChildren) /// True if the node has any ARIA attributes set. - (has_aria_attribute) + (has_aria_attribute, HasAriaAttribute) /// Indicates whether this node causes a hard line-break /// (e.g. block level elements, or `
`). - (is_line_breaking_object) + (is_line_breaking_object, IsLineBreakingObject) /// Indicates whether this node causes a page break. - (is_page_breaking_object) - (is_spelling_error) - (is_grammar_error) - (is_search_match) - (is_suggestion) + (is_page_breaking_object, IsPageBreakingObject) + (is_spelling_error, IsSpellingError) + (is_grammar_error, IsGrammarError) + (is_search_match, IsSearchMatch) + (is_suggestion, IsSuggestion) /// The object functions as a text field which exposes its descendants. /// /// Use cases include the root of a content-editable region, an ARIA /// textbox which isn't currently editable and which has interactive /// descendants, and a `` element that has "design-mode" set to "on". - (is_nonatomic_text_field_root) + (is_nonatomic_text_field_root, IsNonatomicTextFieldRoot) } optional_bool_methods! { diff --git a/consumer/src/lib.rs b/consumer/src/lib.rs index a45ba97f2..ee2628acd 100644 --- a/consumer/src/lib.rs +++ b/consumer/src/lib.rs @@ -117,7 +117,7 @@ mod tests { let link_3_1_ignored = Arc::new({ let mut node = Node::new(Role::Link); node.set_children(vec![STATIC_TEXT_3_1_0_ID]); - node.set_linked(true); + node.set_linked(); node }); let static_text_3_1_0 = Arc::new({ From c0171069f33f9ba08daea388cc79145e9d58f0e0 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 8 Jan 2023 12:58:36 -0600 Subject: [PATCH 12/45] Drop has_aria_attribute; pretty sure that was just an internal detail of Chromium's caching implementation --- common/src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 3f5df2ad9..f3d4d7b55 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -637,7 +637,6 @@ enum Flag { Italic, CanvasHasFallback, ClipsChildren, - HasAriaAttribute, IsLineBreakingObject, IsPageBreakingObject, IsSpellingError, @@ -1253,8 +1252,6 @@ irregular_flag_methods! { /// Indicates that this node clips its children, i.e. may have /// `overflow: hidden` or clip children by default. (clips_children, ClipsChildren) - /// True if the node has any ARIA attributes set. - (has_aria_attribute, HasAriaAttribute) /// Indicates whether this node causes a hard line-break /// (e.g. block level elements, or `
`). (is_line_breaking_object, IsLineBreakingObject) From aadb0fd39f8080a2340e37a0e9614c964444b7cc Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 8 Jan 2023 13:02:09 -0600 Subject: [PATCH 13/45] Likewise, not_user_selectable_style is very Chromium-specific. Now we have 2 spare bits in the flags field. --- common/src/lib.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index f3d4d7b55..700b5075c 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -628,7 +628,6 @@ enum Flag { LiveAtomic, Modal, Scrollable, - NotUserSelectableStyle, SelectedFromFocus, TouchPassThrough, ReadOnly, @@ -1227,11 +1226,6 @@ flag_methods! { /// opposed to only programmatically scrollable, like `overflow: hidden`, or /// not scrollable at all, e.g. `overflow: visible`. (scrollable, Scrollable) - /// Indicates that this node is not selectable because the style has - /// `user-select: none`. Note that there may be other reasons why a node is - /// not selectable - for example, bullets in a list. However, this attribute - /// is only set on `user-select: none`. - (not_user_selectable_style, NotUserSelectableStyle) /// Indicates whether this node is selected due to selection follows focus. (selected_from_focus, SelectedFromFocus) /// This element allows touches to be passed through when a screen reader From 3204e387634dd9c90bb22c128bb721efeae73254 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 8 Jan 2023 14:44:27 -0600 Subject: [PATCH 14/45] Remove some more Chromium-internal properties that we don't need. Now at 128 bytes! --- common/src/lib.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 700b5075c..a9f2da74d 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -731,12 +731,6 @@ enum PropertyId { TextIndent, // usize - AriaColumnCount, - AriaCellColumnIndex, - AriaCellColumnSpan, - AriaRowCount, - AriaCellRowIndex, - AriaCellRowSpan, TableRowCount, TableColumnCount, TableRowIndex, @@ -1392,12 +1386,6 @@ f64_property_methods! { } usize_property_methods! { - (aria_column_count, AriaColumnCount) - (aria_cell_column_index, AriaCellColumnIndex) - (aria_cell_column_span, AriaCellColumnSpan) - (aria_row_count, AriaRowCount) - (aria_cell_row_index, AriaCellRowIndex) - (aria_cell_row_span, AriaCellRowSpan) (table_row_count, TableRowCount) (table_column_count, TableColumnCount) (table_row_index, TableRowIndex) From 1358b30d9d8e09a15651b562a9ac074e0a9056f6 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 8 Jan 2023 18:59:43 -0600 Subject: [PATCH 15/45] Implement Serialize; stub Deserialize --- Cargo.lock | 22 +++++++ common/Cargo.toml | 4 +- common/src/lib.rs | 160 ++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 171 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 87a791a3e..e52b0d4d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,8 @@ version = "0.8.1" dependencies = [ "enumset", "kurbo", + "num-derive", + "num-traits", "paste", "schemars", "serde", @@ -1092,6 +1094,26 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "num_enum" version = "0.5.7" diff --git a/common/Cargo.toml b/common/Cargo.toml index 292aa291c..5fa7cc7b6 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -16,10 +16,12 @@ features = ["schemars", "serde"] [dependencies] enumset = "1.0.8" kurbo = "0.8.3" +num-derive = { version = "0.3", optional = true } +num-traits = { version = "0.2", optional = true } paste = "1.0" schemars = { version = "0.8.7", features = ["enumset"], optional = true } serde = { version = "1.0", features = ["derive", "rc"], optional = true } [features] schemars = ["serde", "dep:schemars", "kurbo/schemars"] -serde = ["dep:serde", "enumset/serde", "kurbo/serde"] +serde = ["dep:serde", "enumset/serde", "kurbo/serde", "num-derive", "num-traits"] diff --git a/common/src/lib.rs b/common/src/lib.rs index a9f2da74d..a830141e0 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -8,14 +8,24 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE.chromium file. +#[cfg(feature = "serde")] +#[macro_use] +extern crate num_derive; + use enumset::{EnumSet, EnumSetType}; pub use kurbo; use kurbo::{Affine, Point, Rect}; +#[cfg(feature = "serde")] +use num_traits::FromPrimitive; use paste::paste; #[cfg(feature = "schemars")] use schemars::JsonSchema; #[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; +use serde::{ + de::Deserializer, + ser::{SerializeMap, Serializer}, + Deserialize, Serialize, +}; use std::{ num::{NonZeroU128, NonZeroU64}, sync::Arc, @@ -565,18 +575,6 @@ pub struct CustomAction { pub description: Box, } -// Helper for skipping false values in serialization. -#[cfg(feature = "serde")] -fn is_false(b: &bool) -> bool { - !b -} - -// Helper for skipping empty slices in serialization. -#[cfg(feature = "serde")] -fn is_empty(slice: &[T]) -> bool { - slice.is_empty() -} - #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] @@ -611,7 +609,6 @@ pub struct TextSelection { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -#[cfg_attr(feature = "serde", enumset(serialize_as_list))] enum Flag { AutofillAvailable, Default, @@ -666,6 +663,9 @@ enum Property { } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, FromPrimitive))] +#[cfg_attr(feature = "schemars", derive(JsonSchema))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[repr(u8)] enum PropertyId { // NodeIdVec @@ -1566,6 +1566,138 @@ impl Node { } } +#[cfg(feature = "serde")] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)] +enum OtherField { + Role, + Actions, + Expanded, + Selected, + NameFrom, + DescriptionFrom, + Invalid, + CheckedState, + Live, + DefaultActionVerb, + TextDirection, + Orientation, + SortDirection, + AriaCurrent, + HasPopup, + ListStyle, + TextAlign, + VerticalOffset, + Overline, + Strikethrough, + Underline, +} + +#[cfg(feature = "serde")] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[serde(untagged)] +enum Field { + Flag(Flag), + Property(PropertyId), + Other(OtherField), +} + +#[cfg(feature = "serde")] +macro_rules! serialize_simple_fields { + ($self:ident, $map:ident, { $(($name:ident, $id:ident)),+ }) => { + $($map.serialize_entry(&OtherField::$id, &$self.$name)?;)* + } +} + +#[cfg(feature = "serde")] +macro_rules! serialize_optional_fields { + ($self:ident, $map:ident, { $(($name:ident, $id:ident)),+ }) => { + $(if let Some(value) = $self.$name { + $map.serialize_entry(&OtherField::$id, &value)?; + })* + } +} + +#[cfg(feature = "serde")] +macro_rules! serialize_property { + ($self:ident, $map:ident, $index:ident, $id:ident, { $($variant:ident),+ }) => { + match &$self.props[$index as usize] { + Property::None => (), + $(Property::$variant(value) => { + $map.serialize_entry(&$id, value)?; + })* + } + } +} + +#[cfg(feature = "serde")] +impl Serialize for Node { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut map = serializer.serialize_map(None)?; + serialize_simple_fields!(self, map, { + (role, Role), + (actions, Actions) + }); + for flag in self.flags { + map.serialize_entry(&flag, &true)?; + } + serialize_optional_fields!(self, map, { + (expanded, Expanded), + (selected, Selected), + (name_from, NameFrom), + (description_from, DescriptionFrom), + (invalid, Invalid), + (checked_state, CheckedState), + (live, Live), + (default_action_verb, DefaultActionVerb), + (text_direction, TextDirection), + (orientation, Orientation), + (sort_direction, SortDirection), + (aria_current, AriaCurrent), + (has_popup, HasPopup), + (list_style, ListStyle), + (text_align, TextAlign), + (vertical_offset, VerticalOffset), + (overline, Overline), + (strikethrough, Strikethrough), + (underline, Underline) + }); + for (id, index) in self.indices.0.iter().copied().enumerate() { + if index == PropertyId::Unset as u8 { + continue; + } + let id = PropertyId::from_usize(id).unwrap(); + serialize_property!(self, map, index, id, { + NodeIdVec, + NodeId, + String, + F64, + Usize, + Color, + LengthSlice, + CoordSlice, + Affine, + Rect, + TextSelection, + CustomActionVec + }); + } + map.end() + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Node { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + todo!() + } +} + /// The data associated with an accessibility tree that's global to the /// tree and not associated with any particular node. #[derive(Clone, Debug, PartialEq, Eq)] From 516966f80601a1f4fede5aea0bc1dc0dda82a680 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 8 Jan 2023 20:51:45 -0600 Subject: [PATCH 16/45] Implement deserialization --- common/src/lib.rs | 204 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 196 insertions(+), 8 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index a830141e0..ebf48f0ef 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -22,10 +22,12 @@ use paste::paste; use schemars::JsonSchema; #[cfg(feature = "serde")] use serde::{ - de::Deserializer, + de::{Deserializer, IgnoredAny, MapAccess, Visitor}, ser::{SerializeMap, Serializer}, Deserialize, Serialize, }; +#[cfg(feature = "serde")] +use std::fmt; use std::{ num::{NonZeroU128, NonZeroU64}, sync::Arc, @@ -1568,7 +1570,7 @@ impl Node { #[cfg(feature = "serde")] #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)] -enum OtherField { +enum FieldId { Role, Actions, Expanded, @@ -1593,18 +1595,19 @@ enum OtherField { } #[cfg(feature = "serde")] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[serde(untagged)] -enum Field { +enum DeserializeKey { + Field(FieldId), Flag(Flag), Property(PropertyId), - Other(OtherField), + Unknown(String), } #[cfg(feature = "serde")] macro_rules! serialize_simple_fields { ($self:ident, $map:ident, { $(($name:ident, $id:ident)),+ }) => { - $($map.serialize_entry(&OtherField::$id, &$self.$name)?;)* + $($map.serialize_entry(&FieldId::$id, &$self.$name)?;)* } } @@ -1612,7 +1615,7 @@ macro_rules! serialize_simple_fields { macro_rules! serialize_optional_fields { ($self:ident, $map:ident, { $(($name:ident, $id:ident)),+ }) => { $(if let Some(value) = $self.$name { - $map.serialize_entry(&OtherField::$id, &value)?; + $map.serialize_entry(&FieldId::$id, &value)?; })* } } @@ -1629,6 +1632,35 @@ macro_rules! serialize_property { } } +#[cfg(feature = "serde")] +macro_rules! deserialize_field { + ($node:ident, $map:ident, $key:ident, { $(($name:ident, $id:ident)),+ }) => { + match $key { + $(FieldId::$id => { + $node.$name = $map.next_value()?; + })* + } + } +} + +#[cfg(feature = "serde")] +macro_rules! deserialize_property { + ($node:ident, $map:ident, $key:ident, { $($type:ident { $($id:ident),+ }),+ }) => { + match $key { + $($(PropertyId::$id => { + if let Some(value) = $map.next_value()? { + $node.set_property(PropertyId::$id, Property::$type(value)); + } else { + $node.clear_property(PropertyId::$id); + } + })*)* + PropertyId::Unset => { + let _ = $map.next_value::()?; + } + } + } +} + #[cfg(feature = "serde")] impl Serialize for Node { fn serialize(&self, serializer: S) -> Result @@ -1688,13 +1720,169 @@ impl Serialize for Node { } } +#[cfg(feature = "serde")] +struct NodeVisitor; + +#[cfg(feature = "serde")] +impl<'de> Visitor<'de> for NodeVisitor { + type Value = Node; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("struct Node") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut node = Node::default(); + while let Some(key) = map.next_key()? { + match key { + DeserializeKey::Field(id) => { + deserialize_field!(node, map, id, { + (role, Role), + (actions, Actions), + (expanded, Expanded), + (selected, Selected), + (name_from, NameFrom), + (description_from, DescriptionFrom), + (invalid, Invalid), + (checked_state, CheckedState), + (live, Live), + (default_action_verb, DefaultActionVerb), + (text_direction, TextDirection), + (orientation, Orientation), + (sort_direction, SortDirection), + (aria_current, AriaCurrent), + (has_popup, HasPopup), + (list_style, ListStyle), + (text_align, TextAlign), + (vertical_offset, VerticalOffset), + (overline, Overline), + (strikethrough, Strikethrough), + (underline, Underline) + }); + } + DeserializeKey::Flag(flag) => { + if map.next_value()? { + node.flags.insert(flag); + } else { + node.flags.remove(flag); + } + } + DeserializeKey::Property(id) => { + deserialize_property!(node, map, id, { + NodeIdVec { + Children, + IndirectChildren, + Controls, + Details, + DescribedBy, + FlowTo, + LabelledBy, + RadioGroup + }, + NodeId { + ActiveDescendant, + ErrorMessage, + InPageLinkTarget, + MemberOf, + NextOnLine, + PreviousOnLine, + PopupFor, + TableHeader, + TableRowHeader, + TableColumnHeader, + NextFocus, + PreviousFocus + }, + String { + Name, + Description, + Value, + AccessKey, + AutoComplete, + CheckedStateDescription, + ClassName, + CssDisplay, + FontFamily, + HtmlTag, + InnerHtml, + InputType, + KeyShortcuts, + Language, + LiveRelevant, + Placeholder, + AriaRole, + RoleDescription, + Tooltip, + Url + }, + F64 { + ScrollX, + ScrollXMin, + ScrollXMax, + ScrollY, + ScrollYMin, + ScrollYMax, + NumericValue, + MinNumericValue, + MaxNumericValue, + NumericValueStep, + NumericValueJump, + FontSize, + FontWeight, + TextIndent + }, + Usize { + TableRowCount, + TableColumnCount, + TableRowIndex, + TableColumnIndex, + TableCellColumnIndex, + TableCellColumnSpan, + TableCellRowIndex, + TableCellRowSpan, + HierarchicalLevel, + SizeOfSet, + PositionInSet + }, + Color { + ColorValue, + BackgroundColor, + ForegroundColor + }, + LengthSlice { + CharacterLengths, + WordLengths + }, + CoordSlice { + CharacterPositions, + CharacterWidths + }, + Affine { Transform }, + Rect { Bounds }, + TextSelection { TextSelection }, + CustomActionVec { CustomActions } + }); + } + DeserializeKey::Unknown(_) => { + let _ = map.next_value::()?; + } + } + } + + Ok(node) + } +} + #[cfg(feature = "serde")] impl<'de> Deserialize<'de> for Node { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { - todo!() + deserializer.deserialize_map(NodeVisitor) } } From fbaf07b2e582690b8ef006384460f6fa776d004c Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 8 Jan 2023 21:06:47 -0600 Subject: [PATCH 17/45] Correctly use Option when serializing --- common/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index ebf48f0ef..38b88206e 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1614,8 +1614,8 @@ macro_rules! serialize_simple_fields { #[cfg(feature = "serde")] macro_rules! serialize_optional_fields { ($self:ident, $map:ident, { $(($name:ident, $id:ident)),+ }) => { - $(if let Some(value) = $self.$name { - $map.serialize_entry(&FieldId::$id, &value)?; + $(if $self.$name.is_some() { + $map.serialize_entry(&FieldId::$id, &$self.$name)?; })* } } @@ -1626,7 +1626,7 @@ macro_rules! serialize_property { match &$self.props[$index as usize] { Property::None => (), $(Property::$variant(value) => { - $map.serialize_entry(&$id, value)?; + $map.serialize_entry(&$id, &Some(value))?; })* } } From c6d5a3e251087231cec0d1bc9ed822adcad9507f Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 8 Jan 2023 21:41:37 -0600 Subject: [PATCH 18/45] Forgot to make the field ID enum serialize as camel-case --- common/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/common/src/lib.rs b/common/src/lib.rs index 38b88206e..15a154262 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1570,6 +1570,7 @@ impl Node { #[cfg(feature = "serde")] #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] enum FieldId { Role, Actions, From af12230c1b78c00d16920858d9e9c48bb8707f33 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 8 Jan 2023 21:42:29 -0600 Subject: [PATCH 19/45] Drop skip_serializing_if attribute; it's not safe for all data formats --- common/src/lib.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 15a154262..e9a7e092c 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1900,7 +1900,6 @@ pub struct Tree { /// The node that's used as the root scroller, if any. On some platforms /// like Android we need to ignore accessibility scroll offsets for /// that node and get them from the viewport instead. - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub root_scroller: Option, } @@ -1956,7 +1955,6 @@ pub struct TreeUpdate { /// if it has not changed since the previous update, but providing the same /// information again is also allowed. This is required when initializing /// a tree. - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub tree: Option, /// The node with keyboard focus within this tree, if any. @@ -1970,7 +1968,6 @@ pub struct TreeUpdate { /// render widgets (e.g. to draw or not draw a focus rectangle), /// so this focus tracking should not be duplicated between the toolkit /// and the AccessKit platform adapters. - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub focus: Option, } @@ -2008,7 +2005,6 @@ pub enum ActionData { pub struct ActionRequest { pub action: Action, pub target: NodeId, - #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub data: Option, } From e2f2c58fcd7d259e2f9b6c3d67fcb1cf7c9bed4b Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Tue, 10 Jan 2023 09:03:25 -0600 Subject: [PATCH 20/45] Box the two largest property value types --- common/src/lib.rs | 22 +++++++++++----------- consumer/src/node.rs | 4 ++-- platforms/macos/src/node.rs | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index e9a7e092c..ee8ce9553 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -658,9 +658,9 @@ enum Property { Color(u32), LengthSlice(Box<[u8]>), CoordSlice(Box<[f32]>), - Affine(Affine), + Affine(Box), Rect(Rect), - TextSelection(TextSelection), + TextSelection(Box), CustomActionVec(Vec), } @@ -1043,16 +1043,16 @@ impl Node { } } - fn get_affine(&self, id: PropertyId) -> Option { + fn get_affine(&self, id: PropertyId) -> Option<&Affine> { match self.get_property(id) { Property::None => None, - Property::Affine(value) => Some(*value), + Property::Affine(value) => Some(value), _ => panic!(), } } - fn set_affine(&mut self, id: PropertyId, value: Affine) { - self.set_property(id, Property::Affine(value)); + fn set_affine(&mut self, id: PropertyId, value: impl Into>) { + self.set_property(id, Property::Affine(value.into())); } fn get_rect(&self, id: PropertyId) -> Option { @@ -1509,7 +1509,7 @@ property_methods! { /// pixels, with the y coordinate being top-down. /// /// [`bounds`]: Node::bounds - (transform, Transform, affine, Option, Affine) + (transform, Transform, affine, Option<&Affine>, impl Into>) /// The bounding box of this node, in the node's coordinate space. /// This property does not affect the coordinate space of either this node @@ -1525,15 +1525,15 @@ property_methods! { } impl Node { - pub fn text_selection(&self) -> Option { + pub fn text_selection(&self) -> Option<&TextSelection> { match self.get_property(PropertyId::TextSelection) { Property::None => None, - Property::TextSelection(value) => Some(*value), + Property::TextSelection(value) => Some(value), _ => panic!(), } } - pub fn set_text_selection(&mut self, value: TextSelection) { - self.set_property(PropertyId::TextSelection, Property::TextSelection(value)); + pub fn set_text_selection(&mut self, value: impl Into>) { + self.set_property(PropertyId::TextSelection, Property::TextSelection(value.into())); } pub fn clear_text_selection(&mut self) { self.clear_property(PropertyId::TextSelection); diff --git a/consumer/src/node.rs b/consumer/src/node.rs index 631a3a126..e4dd19b7c 100644 --- a/consumer/src/node.rs +++ b/consumer/src/node.rs @@ -247,7 +247,7 @@ impl NodeState { /// Returns the transform defined directly on this node, or the identity /// transform, without taking into account transforms on ancestors. pub fn direct_transform(&self) -> Affine { - self.data().transform().unwrap_or(Affine::IDENTITY) + self.data().transform().map_or(Affine::IDENTITY, |value| *value) } } @@ -601,7 +601,7 @@ impl NodeState { self.data().is_selected() } - pub fn raw_text_selection(&self) -> Option { + pub fn raw_text_selection(&self) -> Option<&TextSelection> { self.data().text_selection() } } diff --git a/platforms/macos/src/node.rs b/platforms/macos/src/node.rs index fa0b0dd8a..44af9cb0f 100644 --- a/platforms/macos/src/node.rs +++ b/platforms/macos/src/node.rs @@ -342,7 +342,7 @@ impl<'a> NodeWrapper<'a> { } } - pub(crate) fn raw_text_selection(&self) -> Option { + pub(crate) fn raw_text_selection(&self) -> Option<&TextSelection> { self.node_state().raw_text_selection() } } From cc0821167f2b19dcdc5a4b29452cfb63896c5f21 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Tue, 10 Jan 2023 09:15:15 -0600 Subject: [PATCH 21/45] reformat --- common/src/lib.rs | 5 ++++- consumer/src/node.rs | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index ee8ce9553..dab539f64 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1533,7 +1533,10 @@ impl Node { } } pub fn set_text_selection(&mut self, value: impl Into>) { - self.set_property(PropertyId::TextSelection, Property::TextSelection(value.into())); + self.set_property( + PropertyId::TextSelection, + Property::TextSelection(value.into()), + ); } pub fn clear_text_selection(&mut self) { self.clear_property(PropertyId::TextSelection); diff --git a/consumer/src/node.rs b/consumer/src/node.rs index e4dd19b7c..ab28de42a 100644 --- a/consumer/src/node.rs +++ b/consumer/src/node.rs @@ -247,7 +247,9 @@ impl NodeState { /// Returns the transform defined directly on this node, or the identity /// transform, without taking into account transforms on ancestors. pub fn direct_transform(&self) -> Affine { - self.data().transform().map_or(Affine::IDENTITY, |value| *value) + self.data() + .transform() + .map_or(Affine::IDENTITY, |value| *value) } } From 74511750b9ef5bb146c556e911b1ef3b7b4642a1 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Wed, 11 Jan 2023 19:52:21 -0600 Subject: [PATCH 22/45] Rename the Property enum to PropertyValue to indicate that it contains the value of a property but not its identity --- common/src/lib.rs | 102 +++++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index dab539f64..f7a267d0b 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -648,7 +648,7 @@ enum Flag { // https://viruta.org/reducing-memory-consumption-in-librsvg-2.html #[derive(Clone, Debug, PartialEq)] -enum Property { +enum PropertyValue { None, NodeIdVec(Vec), NodeId(NodeId), @@ -978,7 +978,7 @@ pub struct Node { role: Role, actions: EnumSet, indices: PropertyIndices, - props: Vec, + props: Vec, flags: EnumSet, expanded: Option, selected: Option, @@ -1002,16 +1002,16 @@ pub struct Node { } impl Node { - fn get_property(&self, id: PropertyId) -> &Property { + fn get_property(&self, id: PropertyId) -> &PropertyValue { let index = self.indices.0[id as usize]; if index == PropertyId::Unset as u8 { - &Property::None + &PropertyValue::None } else { &self.props[index as usize] } } - fn get_property_mut(&mut self, id: PropertyId, default: Property) -> &mut Property { + fn get_property_mut(&mut self, id: PropertyId, default: PropertyValue) -> &mut PropertyValue { let index = self.indices.0[id as usize] as usize; if index == PropertyId::Unset as usize { self.props.push(default); @@ -1019,14 +1019,14 @@ impl Node { self.indices.0[id as usize] = index as u8; &mut self.props[index] } else { - if matches!(self.props[index], Property::None) { + if matches!(self.props[index], PropertyValue::None) { self.props[index] = default; } &mut self.props[index] } } - fn set_property(&mut self, id: PropertyId, value: Property) { + fn set_property(&mut self, id: PropertyId, value: PropertyValue) { let index = self.indices.0[id as usize]; if index == PropertyId::Unset as u8 { self.props.push(value); @@ -1039,45 +1039,45 @@ impl Node { fn clear_property(&mut self, id: PropertyId) { let index = self.indices.0[id as usize]; if index != PropertyId::Unset as u8 { - self.props[index as usize] = Property::None; + self.props[index as usize] = PropertyValue::None; } } fn get_affine(&self, id: PropertyId) -> Option<&Affine> { match self.get_property(id) { - Property::None => None, - Property::Affine(value) => Some(value), + PropertyValue::None => None, + PropertyValue::Affine(value) => Some(value), _ => panic!(), } } fn set_affine(&mut self, id: PropertyId, value: impl Into>) { - self.set_property(id, Property::Affine(value.into())); + self.set_property(id, PropertyValue::Affine(value.into())); } fn get_rect(&self, id: PropertyId) -> Option { match self.get_property(id) { - Property::None => None, - Property::Rect(value) => Some(*value), + PropertyValue::None => None, + PropertyValue::Rect(value) => Some(*value), _ => panic!(), } } fn set_rect(&mut self, id: PropertyId, value: Rect) { - self.set_property(id, Property::Rect(value)); + self.set_property(id, PropertyValue::Rect(value)); } fn get_node_id_vec(&self, id: PropertyId) -> &[NodeId] { match self.get_property(id) { - Property::None => &[], - Property::NodeIdVec(value) => value, + PropertyValue::None => &[], + PropertyValue::NodeIdVec(value) => value, _ => panic!(), } } fn push_to_node_id_vec(&mut self, property_id: PropertyId, node_id: NodeId) { - match self.get_property_mut(property_id, Property::NodeIdVec(Vec::new())) { - Property::NodeIdVec(v) => { + match self.get_property_mut(property_id, PropertyValue::NodeIdVec(Vec::new())) { + PropertyValue::NodeIdVec(v) => { v.push(node_id); } _ => panic!(), @@ -1085,91 +1085,91 @@ impl Node { } fn set_node_id_vec(&mut self, id: PropertyId, value: impl Into>) { - self.set_property(id, Property::NodeIdVec(value.into())); + self.set_property(id, PropertyValue::NodeIdVec(value.into())); } fn get_node_id(&self, id: PropertyId) -> Option { match self.get_property(id) { - Property::None => None, - Property::NodeId(value) => Some(*value), + PropertyValue::None => None, + PropertyValue::NodeId(value) => Some(*value), _ => panic!(), } } fn set_node_id(&mut self, id: PropertyId, value: NodeId) { - self.set_property(id, Property::NodeId(value)); + self.set_property(id, PropertyValue::NodeId(value)); } fn get_string(&self, id: PropertyId) -> Option<&str> { match self.get_property(id) { - Property::None => None, - Property::String(value) => Some(value), + PropertyValue::None => None, + PropertyValue::String(value) => Some(value), _ => panic!(), } } fn set_string(&mut self, id: PropertyId, value: impl Into>) { - self.set_property(id, Property::String(value.into())); + self.set_property(id, PropertyValue::String(value.into())); } fn get_f64(&self, id: PropertyId) -> Option { match self.get_property(id) { - Property::None => None, - Property::F64(value) => Some(*value), + PropertyValue::None => None, + PropertyValue::F64(value) => Some(*value), _ => panic!(), } } fn set_f64(&mut self, id: PropertyId, value: f64) { - self.set_property(id, Property::F64(value)); + self.set_property(id, PropertyValue::F64(value)); } fn get_usize(&self, id: PropertyId) -> Option { match self.get_property(id) { - Property::None => None, - Property::Usize(value) => Some(*value), + PropertyValue::None => None, + PropertyValue::Usize(value) => Some(*value), _ => panic!(), } } fn set_usize(&mut self, id: PropertyId, value: usize) { - self.set_property(id, Property::Usize(value)); + self.set_property(id, PropertyValue::Usize(value)); } fn get_color(&self, id: PropertyId) -> Option { match self.get_property(id) { - Property::None => None, - Property::Color(value) => Some(*value), + PropertyValue::None => None, + PropertyValue::Color(value) => Some(*value), _ => panic!(), } } fn set_color(&mut self, id: PropertyId, value: u32) { - self.set_property(id, Property::Color(value)); + self.set_property(id, PropertyValue::Color(value)); } fn get_length_slice(&self, id: PropertyId) -> &[u8] { match self.get_property(id) { - Property::None => &[], - Property::LengthSlice(value) => value, + PropertyValue::None => &[], + PropertyValue::LengthSlice(value) => value, _ => panic!(), } } fn set_length_slice(&mut self, id: PropertyId, value: impl Into>) { - self.set_property(id, Property::LengthSlice(value.into())); + self.set_property(id, PropertyValue::LengthSlice(value.into())); } fn get_coord_slice(&self, id: PropertyId) -> Option<&[f32]> { match self.get_property(id) { - Property::None => None, - Property::CoordSlice(value) => Some(value), + PropertyValue::None => None, + PropertyValue::CoordSlice(value) => Some(value), _ => panic!(), } } fn set_coord_slice(&mut self, id: PropertyId, value: impl Into>) { - self.set_property(id, Property::CoordSlice(value.into())); + self.set_property(id, PropertyValue::CoordSlice(value.into())); } pub fn new(role: Role) -> Self { @@ -1527,15 +1527,15 @@ property_methods! { impl Node { pub fn text_selection(&self) -> Option<&TextSelection> { match self.get_property(PropertyId::TextSelection) { - Property::None => None, - Property::TextSelection(value) => Some(value), + PropertyValue::None => None, + PropertyValue::TextSelection(value) => Some(value), _ => panic!(), } } pub fn set_text_selection(&mut self, value: impl Into>) { self.set_property( PropertyId::TextSelection, - Property::TextSelection(value.into()), + PropertyValue::TextSelection(value.into()), ); } pub fn clear_text_selection(&mut self) { @@ -1544,23 +1544,23 @@ impl Node { pub fn custom_actions(&self) -> &[CustomAction] { match self.get_property(PropertyId::CustomActions) { - Property::None => &[], - Property::CustomActionVec(value) => value, + PropertyValue::None => &[], + PropertyValue::CustomActionVec(value) => value, _ => panic!(), } } pub fn set_custom_actions(&mut self, value: impl Into>) { self.set_property( PropertyId::CustomActions, - Property::CustomActionVec(value.into()), + PropertyValue::CustomActionVec(value.into()), ); } pub fn push_to_custom_actions(&mut self, action: CustomAction) { match self.get_property_mut( PropertyId::CustomActions, - Property::CustomActionVec(Vec::new()), + PropertyValue::CustomActionVec(Vec::new()), ) { - Property::CustomActionVec(v) => { + PropertyValue::CustomActionVec(v) => { v.push(action); } _ => panic!(), @@ -1628,8 +1628,8 @@ macro_rules! serialize_optional_fields { macro_rules! serialize_property { ($self:ident, $map:ident, $index:ident, $id:ident, { $($variant:ident),+ }) => { match &$self.props[$index as usize] { - Property::None => (), - $(Property::$variant(value) => { + PropertyValue::None => (), + $(PropertyValue::$variant(value) => { $map.serialize_entry(&$id, &Some(value))?; })* } @@ -1653,7 +1653,7 @@ macro_rules! deserialize_property { match $key { $($(PropertyId::$id => { if let Some(value) = $map.next_value()? { - $node.set_property(PropertyId::$id, Property::$type(value)); + $node.set_property(PropertyId::$id, PropertyValue::$type(value)); } else { $node.clear_property(PropertyId::$id); } From 2285cda26259a67d6e688b556e0c5d4494eb6e2f Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Wed, 11 Jan 2023 20:21:59 -0600 Subject: [PATCH 23/45] Change macro syntax so lists are comma-delimited as one would expect --- common/src/lib.rs | 312 +++++++++++++++++++++++----------------------- 1 file changed, 156 insertions(+), 156 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index f7a267d0b..7e3a35095 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -779,7 +779,7 @@ impl Default for PropertyIndices { } macro_rules! flag_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident))+) => { + ($($(#[$doc:meta])* ($base_name:ident, $id:ident)),+) => { paste! { impl Node { $($(#[$doc])* @@ -798,7 +798,7 @@ macro_rules! flag_methods { } macro_rules! irregular_flag_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident))+) => { + ($($(#[$doc:meta])* ($base_name:ident, $id:ident)),+) => { paste! { impl Node { $($(#[$doc])* @@ -817,7 +817,7 @@ macro_rules! irregular_flag_methods { } macro_rules! optional_bool_methods { - ($($(#[$doc:meta])* ($base_name:ident))+) => { + ($($(#[$doc:meta])* ($base_name:ident)),+) => { paste! { impl Node { $($(#[$doc])* @@ -836,7 +836,7 @@ macro_rules! optional_bool_methods { } macro_rules! optional_enum_methods { - ($($(#[$doc:meta])* ($base_name:ident, $type:ty))+) => { + ($($(#[$doc:meta])* ($base_name:ident, $type:ty)),+) => { paste! { impl Node { $($(#[$doc])* @@ -855,7 +855,7 @@ macro_rules! optional_enum_methods { } macro_rules! property_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident, $type_method_base:ident, $getter_result:ty, $setter_param:ty))+) => { + ($($(#[$doc:meta])* ($base_name:ident, $id:ident, $type_method_base:ident, $getter_result:ty, $setter_param:ty)),+) => { paste! { impl Node { $($(#[$doc])* @@ -874,7 +874,7 @@ macro_rules! property_methods { } macro_rules! vec_property_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident, $type_method_base:ident, $item_type:ty))+) => { + ($($(#[$doc:meta])* ($base_name:ident, $id:ident, $type_method_base:ident, $item_type:ty)),+) => { paste! { impl Node { $($(#[$doc])* @@ -896,74 +896,74 @@ macro_rules! vec_property_methods { } macro_rules! node_id_vec_property_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident))+) => { - vec_property_methods! { - $($(#[$doc])* - ($base_name, $id, node_id_vec, NodeId))* - } + ($($(#[$doc:meta])* ($base_name:ident, $id:ident)),+) => { + $(vec_property_methods! { + $(#[$doc])* + ($base_name, $id, node_id_vec, NodeId) + })* } } macro_rules! node_id_property_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident))+) => { - property_methods! { - $($(#[$doc])* - ($base_name, $id, node_id, Option, NodeId))* - } + ($($(#[$doc:meta])* ($base_name:ident, $id:ident)),+) => { + $(property_methods! { + $(#[$doc])* + ($base_name, $id, node_id, Option, NodeId) + })* } } macro_rules! string_property_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident))+) => { - property_methods! { - $($(#[$doc])* - ($base_name, $id, string, Option<&str>, impl Into>))* - } + ($($(#[$doc:meta])* ($base_name:ident, $id:ident)),+) => { + $(property_methods! { + $(#[$doc])* + ($base_name, $id, string, Option<&str>, impl Into>) + })* } } macro_rules! f64_property_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident))+) => { - property_methods! { - $($(#[$doc])* - ($base_name, $id, f64, Option, f64))* - } + ($($(#[$doc:meta])* ($base_name:ident, $id:ident)),+) => { + $(property_methods! { + $(#[$doc])* + ($base_name, $id, f64, Option, f64) + })* } } macro_rules! usize_property_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident))+) => { - property_methods! { - $($(#[$doc])* - ($base_name, $id, usize, Option, usize))* - } + ($($(#[$doc:meta])* ($base_name:ident, $id:ident)),+) => { + $(property_methods! { + $(#[$doc])* + ($base_name, $id, usize, Option, usize) + })* } } macro_rules! color_property_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident))+) => { - property_methods! { - $($(#[$doc])* - ($base_name, $id, color, Option, u32))* - } + ($($(#[$doc:meta])* ($base_name:ident, $id:ident)),+) => { + $(property_methods! { + $(#[$doc])* + ($base_name, $id, color, Option, u32) + })* } } macro_rules! length_slice_property_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident))+) => { - property_methods! { - $($(#[$doc])* - ($base_name, $id, length_slice, &[u8], impl Into>))* - } + ($($(#[$doc:meta])* ($base_name:ident, $id:ident)),+) => { + $(property_methods! { + $(#[$doc])* + ($base_name, $id, length_slice, &[u8], impl Into>) + })* } } macro_rules! coord_slice_property_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident))+) => { - property_methods! { - $($(#[$doc])* - ($base_name, $id, coord_slice, Option<&[f32]>, impl Into>))* - } + ($($(#[$doc:meta])* ($base_name:ident, $id:ident)),+) => { + $(property_methods! { + $(#[$doc])* + ($base_name, $id, coord_slice, Option<&[f32]>, impl Into>) + })* } } @@ -1201,56 +1201,56 @@ impl Node { } flag_methods! { - (autofill_available, AutofillAvailable) - (default, Default) - (editable, Editable) - (hovered, Hovered) + (autofill_available, AutofillAvailable), + (default, Default), + (editable, Editable), + (hovered, Hovered), /// Exclude this node and its descendants from the tree presented to /// assistive technologies, and from hit testing. - (hidden, Hidden) - (linked, Linked) - (multiline, Multiline) - (multiselectable, Multiselectable) - (protected, Protected) - (required, Required) - (visited, Visited) - (busy, Busy) - (live_atomic, LiveAtomic) + (hidden, Hidden), + (linked, Linked), + (multiline, Multiline), + (multiselectable, Multiselectable), + (protected, Protected), + (required, Required), + (visited, Visited), + (busy, Busy), + (live_atomic, LiveAtomic), /// If a dialog box is marked as explicitly modal. - (modal, Modal) + (modal, Modal), /// Indicates this node is user-scrollable, e.g. `overflow: scroll|auto`, as /// opposed to only programmatically scrollable, like `overflow: hidden`, or /// not scrollable at all, e.g. `overflow: visible`. - (scrollable, Scrollable) + (scrollable, Scrollable), /// Indicates whether this node is selected due to selection follows focus. - (selected_from_focus, SelectedFromFocus) + (selected_from_focus, SelectedFromFocus), /// This element allows touches to be passed through when a screen reader /// is in touch exploration mode, e.g. a virtual keyboard normally /// behaves this way. - (touch_pass_through, TouchPassThrough) + (touch_pass_through, TouchPassThrough), /// Use for a textbox that allows focus/selection but not input. - (read_only, ReadOnly) + (read_only, ReadOnly), /// Use for a control or group of controls that disallows input. - (disabled, Disabled) - (bold, Bold) + (disabled, Disabled), + (bold, Bold), (italic, Italic) } irregular_flag_methods! { /// Set on a canvas element if it has fallback content. - (canvas_has_fallback, CanvasHasFallback) + (canvas_has_fallback, CanvasHasFallback), /// Indicates that this node clips its children, i.e. may have /// `overflow: hidden` or clip children by default. - (clips_children, ClipsChildren) + (clips_children, ClipsChildren), /// Indicates whether this node causes a hard line-break /// (e.g. block level elements, or `
`). - (is_line_breaking_object, IsLineBreakingObject) + (is_line_breaking_object, IsLineBreakingObject), /// Indicates whether this node causes a page break. - (is_page_breaking_object, IsPageBreakingObject) - (is_spelling_error, IsSpellingError) - (is_grammar_error, IsGrammarError) - (is_search_match, IsSearchMatch) - (is_suggestion, IsSuggestion) + (is_page_breaking_object, IsPageBreakingObject), + (is_spelling_error, IsSpellingError), + (is_grammar_error, IsGrammarError), + (is_search_match, IsSearchMatch), + (is_suggestion, IsSuggestion), /// The object functions as a text field which exposes its descendants. /// /// Use cases include the root of a content-editable region, an ARIA @@ -1264,7 +1264,7 @@ optional_bool_methods! { /// /// Setting this to `false` means the node is collapsed; omitting it means this state /// isn't applicable. - (expanded) + (expanded), /// Indicates whether this node is selected or unselected. /// @@ -1280,132 +1280,132 @@ optional_bool_methods! { optional_enum_methods! { /// What information was used to compute the object's name. - (name_from, NameFrom) + (name_from, NameFrom), /// What information was used to compute the object's description. - (description_from, DescriptionFrom) - (invalid, Invalid) - (checked_state, CheckedState) - (live, Live) - (default_action_verb, DefaultActionVerb) - (text_direction, TextDirection) - (orientation, Orientation) - (sort_direction, SortDirection) - (aria_current, AriaCurrent) - (has_popup, HasPopup) + (description_from, DescriptionFrom), + (invalid, Invalid), + (checked_state, CheckedState), + (live, Live), + (default_action_verb, DefaultActionVerb), + (text_direction, TextDirection), + (orientation, Orientation), + (sort_direction, SortDirection), + (aria_current, AriaCurrent), + (has_popup, HasPopup), /// The list style type. Only available on list items. - (list_style, ListStyle) - (text_align, TextAlign) - (vertical_offset, VerticalOffset) - (overline, TextDecoration) - (strikethrough, TextDecoration) + (list_style, ListStyle), + (text_align, TextAlign), + (vertical_offset, VerticalOffset), + (overline, TextDecoration), + (strikethrough, TextDecoration), (underline, TextDecoration) } node_id_vec_property_methods! { - (children, Children) + (children, Children), /// Ids of nodes that are children of this node logically, but are /// not children of this node in the tree structure. As an example, /// a table cell is a child of a row, and an 'indirect' child of a /// column. - (indirect_children, IndirectChildren) - (controls, Controls) - (details, Details) - (described_by, DescribedBy) - (flow_to, FlowTo) - (labelled_by, LabelledBy) + (indirect_children, IndirectChildren), + (controls, Controls), + (details, Details), + (described_by, DescribedBy), + (flow_to, FlowTo), + (labelled_by, LabelledBy), /// On radio buttons this should be set to a list of all of the buttons /// in the same group as this one, including this radio button itself. (radio_group, RadioGroup) } node_id_property_methods! { - (active_descendant, ActiveDescendant) - (error_message, ErrorMessage) - (in_page_link_target, InPageLinkTarget) - (member_of, MemberOf) - (next_on_line, NextOnLine) - (previous_on_line, PreviousOnLine) - (popup_for, PopupFor) - (table_header, TableHeader) - (table_row_header, TableRowHeader) - (table_column_header, TableColumnHeader) - (next_focus, NextFocus) + (active_descendant, ActiveDescendant), + (error_message, ErrorMessage), + (in_page_link_target, InPageLinkTarget), + (member_of, MemberOf), + (next_on_line, NextOnLine), + (previous_on_line, PreviousOnLine), + (popup_for, PopupFor), + (table_header, TableHeader), + (table_row_header, TableRowHeader), + (table_column_header, TableColumnHeader), + (next_focus, NextFocus), (previous_focus, PreviousFocus) } string_property_methods! { - (name, Name) - (description, Description) - (value, Value) - (access_key, AccessKey) - (auto_complete, AutoComplete) - (checked_state_description, CheckedStateDescription) - (class_name, ClassName) - (css_display, CssDisplay) + (name, Name), + (description, Description), + (value, Value), + (access_key, AccessKey), + (auto_complete, AutoComplete), + (checked_state_description, CheckedStateDescription), + (class_name, ClassName), + (css_display, CssDisplay), /// Only present when different from parent. - (font_family, FontFamily) - (html_tag, HtmlTag) + (font_family, FontFamily), + (html_tag, HtmlTag), /// Inner HTML of an element. Only used for a top-level math element, /// to support third-party math accessibility products that parse MathML. - (inner_html, InnerHtml) - (input_type, InputType) - (key_shortcuts, KeyShortcuts) + (inner_html, InnerHtml), + (input_type, InputType), + (key_shortcuts, KeyShortcuts), /// Only present when different from parent. - (language, Language) - (live_relevant, LiveRelevant) + (language, Language), + (live_relevant, LiveRelevant), /// Only if not already exposed in [`name`] ([`NameFrom::Placeholder`]). /// /// [`name`]: Node::name - (placeholder, Placeholder) - (aria_role, AriaRole) - (role_description, RoleDescription) + (placeholder, Placeholder), + (aria_role, AriaRole), + (role_description, RoleDescription), /// Only if not already exposed in [`name`] ([`NameFrom::Title`]). /// /// [`name`]: Node::name - (tooltip, Tooltip) + (tooltip, Tooltip), (url, Url) } f64_property_methods! { - (scroll_x, ScrollX) - (scroll_x_min, ScrollXMin) - (scroll_x_max, ScrollXMax) - (scroll_y, ScrollY) - (scroll_y_min, ScrollYMin) - (scroll_y_max, ScrollYMax) - (numeric_value, NumericValue) - (min_numeric_value, MinNumericValue) - (max_numeric_value, MaxNumericValue) - (numeric_value_step, NumericValueStep) - (numeric_value_jump, NumericValueJump) + (scroll_x, ScrollX), + (scroll_x_min, ScrollXMin), + (scroll_x_max, ScrollXMax), + (scroll_y, ScrollY), + (scroll_y_min, ScrollYMin), + (scroll_y_max, ScrollYMax), + (numeric_value, NumericValue), + (min_numeric_value, MinNumericValue), + (max_numeric_value, MaxNumericValue), + (numeric_value_step, NumericValueStep), + (numeric_value_jump, NumericValueJump), /// Font size is in pixels. - (font_size, FontSize) + (font_size, FontSize), /// Font weight can take on any arbitrary numeric value. Increments of 100 in /// range `[0, 900]` represent keywords such as light, normal, bold, etc. - (font_weight, FontWeight) + (font_weight, FontWeight), /// The indentation of the text, in mm. (text_indent, TextIndent) } usize_property_methods! { - (table_row_count, TableRowCount) - (table_column_count, TableColumnCount) - (table_row_index, TableRowIndex) - (table_column_index, TableColumnIndex) - (table_cell_column_index, TableCellColumnIndex) - (table_cell_column_span, TableCellColumnSpan) - (table_cell_row_index, TableCellRowIndex) - (table_cell_row_span, TableCellRowSpan) - (hierarchical_level, HierarchicalLevel) - (size_of_set, SizeOfSet) + (table_row_count, TableRowCount), + (table_column_count, TableColumnCount), + (table_row_index, TableRowIndex), + (table_column_index, TableColumnIndex), + (table_cell_column_index, TableCellColumnIndex), + (table_cell_column_span, TableCellColumnSpan), + (table_cell_row_index, TableCellRowIndex), + (table_cell_row_span, TableCellRowSpan), + (hierarchical_level, HierarchicalLevel), + (size_of_set, SizeOfSet), (position_in_set, PositionInSet) } color_property_methods! { /// For [`Role::ColorWell`], specifies the selected color in RGBA. - (color_value, ColorValue) + (color_value, ColorValue), /// Background color in RGBA. - (background_color, BackgroundColor) + (background_color, BackgroundColor), /// Foreground color in RGBA. (foreground_color, ForegroundColor) } @@ -1429,7 +1429,7 @@ length_slice_property_methods! { /// selection should be on the line break, not after it. /// /// [`value`]: Node::value - (character_lengths, CharacterLengths) + (character_lengths, CharacterLengths), /// For inline text. The length of each word in characters, as defined /// in [`character_lengths`]. The sum of these lengths must equal @@ -1473,7 +1473,7 @@ coord_slice_property_methods! { /// /// [`text_direction`]: Node::text_direction /// [`character_lengths`]: Node::character_lengths - (character_positions, CharacterPositions) + (character_positions, CharacterPositions), /// For inline text. This is the advance width of each character, /// in the direction given by [`text_direction`], in the coordinate @@ -1509,7 +1509,7 @@ property_methods! { /// pixels, with the y coordinate being top-down. /// /// [`bounds`]: Node::bounds - (transform, Transform, affine, Option<&Affine>, impl Into>) + (transform, Transform, affine, Option<&Affine>, impl Into>), /// The bounding box of this node, in the node's coordinate space. /// This property does not affect the coordinate space of either this node From 40222cb96f25895256f6c6bf715a8bd9627dc833 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Wed, 11 Jan 2023 21:28:08 -0600 Subject: [PATCH 24/45] Go back to not wrapping Node in Arc at the API level --- common/src/lib.rs | 7 +- consumer/src/lib.rs | 49 ++- consumer/src/node.rs | 160 ++++------ consumer/src/text.rs | 367 ++++++++++------------ consumer/src/tree.rs | 121 +++---- platforms/windows/examples/hello_world.rs | 30 +- platforms/windows/src/tests/simple.rs | 18 +- platforms/windows/src/tests/subclassed.rs | 18 +- platforms/winit/examples/simple.rs | 28 +- 9 files changed, 351 insertions(+), 447 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 7e3a35095..5fba3b026 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -28,10 +28,7 @@ use serde::{ }; #[cfg(feature = "serde")] use std::fmt; -use std::{ - num::{NonZeroU128, NonZeroU64}, - sync::Arc, -}; +use std::num::{NonZeroU128, NonZeroU64}; /// The type of an accessibility node. /// @@ -1952,7 +1949,7 @@ pub struct TreeUpdate { /// an updated version of the parent node with the child's ID removed /// from [`Node::children`]. Neither the child nor any of its descendants /// may be included in this list. - pub nodes: Vec<(NodeId, Arc)>, + pub nodes: Vec<(NodeId, Node)>, /// Rarely updated information about the tree as a whole. This may be omitted /// if it has not changed since the previous update, but providing the same diff --git a/consumer/src/lib.rs b/consumer/src/lib.rs index ee2628acd..52009273f 100644 --- a/consumer/src/lib.rs +++ b/consumer/src/lib.rs @@ -23,7 +23,6 @@ mod tests { use accesskit::kurbo::{Affine, Rect, Vec2}; use accesskit::{ActionHandler, ActionRequest, Node, NodeId, Role, Tree, TreeUpdate}; use std::num::NonZeroU128; - use std::sync::Arc; use crate::FilterResult; @@ -50,7 +49,7 @@ mod tests { } pub fn test_tree() -> crate::tree::Tree { - let root = Arc::new({ + let root = { let mut node = Node::new(Role::RootWebArea); node.set_children(vec![ PARAGRAPH_0_ID, @@ -59,18 +58,18 @@ mod tests { PARAGRAPH_3_IGNORED_ID, ]); node - }); - let paragraph_0 = Arc::new({ + }; + let paragraph_0 = { let mut node = Node::new(Role::Paragraph); node.set_children(vec![STATIC_TEXT_0_0_IGNORED_ID]); node - }); - let static_text_0_0_ignored = Arc::new({ + }; + let static_text_0_0_ignored = { let mut node = Node::new(Role::StaticText); node.set_name("static_text_0_0_ignored"); node - }); - let paragraph_1_ignored = Arc::new({ + }; + let paragraph_1_ignored = { let mut node = Node::new(Role::Paragraph); node.set_transform(Affine::translate(Vec2::new(10.0, 40.0))); node.set_bounds(Rect { @@ -81,8 +80,8 @@ mod tests { }); node.set_children(vec![STATIC_TEXT_1_0_ID]); node - }); - let static_text_1_0 = Arc::new({ + }; + let static_text_1_0 = { let mut node = Node::new(Role::StaticText); node.set_bounds(Rect { x0: 10.0, @@ -92,18 +91,18 @@ mod tests { }); node.set_name("static_text_1_0"); node - }); - let paragraph_2 = Arc::new({ + }; + let paragraph_2 = { let mut node = Node::new(Role::Paragraph); node.set_children(vec![STATIC_TEXT_2_0_ID]); node - }); - let static_text_2_0 = Arc::new({ + }; + let static_text_2_0 = { let mut node = Node::new(Role::StaticText); node.set_name("static_text_2_0"); node - }); - let paragraph_3_ignored = Arc::new({ + }; + let paragraph_3_ignored = { let mut node = Node::new(Role::Paragraph); node.set_children(vec![ EMPTY_CONTAINER_3_0_IGNORED_ID, @@ -112,25 +111,25 @@ mod tests { EMPTY_CONTAINER_3_3_IGNORED_ID, ]); node - }); - let empty_container_3_0_ignored = Arc::new(Node::new(Role::GenericContainer)); - let link_3_1_ignored = Arc::new({ + }; + let empty_container_3_0_ignored = Node::new(Role::GenericContainer); + let link_3_1_ignored = { let mut node = Node::new(Role::Link); node.set_children(vec![STATIC_TEXT_3_1_0_ID]); node.set_linked(); node - }); - let static_text_3_1_0 = Arc::new({ + }; + let static_text_3_1_0 = { let mut node = Node::new(Role::StaticText); node.set_name("static_text_3_1_0"); node - }); - let button_3_2 = Arc::new({ + }; + let button_3_2 = { let mut node = Node::new(Role::Button); node.set_name("button_3_2"); node - }); - let empty_container_3_3_ignored = Arc::new(Node::new(Role::GenericContainer)); + }; + let empty_container_3_3_ignored = Node::new(Role::GenericContainer); let initial_update = TreeUpdate { nodes: vec![ (ROOT_ID, root), diff --git a/consumer/src/node.rs b/consumer/src/node.rs index ab28de42a..ba05a63ed 100644 --- a/consumer/src/node.rs +++ b/consumer/src/node.rs @@ -722,7 +722,7 @@ impl Deref for DetachedNode { mod tests { use accesskit::kurbo::{Point, Rect}; use accesskit::{Node, NodeId, Role, Tree, TreeUpdate}; - use std::{num::NonZeroU128, sync::Arc}; + use std::num::NonZeroU128; use crate::tests::*; @@ -994,15 +994,12 @@ mod tests { fn no_name_or_labelled_by() { let update = TreeUpdate { nodes: vec![ - ( - NODE_ID_1, - Arc::new({ - let mut node = Node::new(Role::Window); - node.set_children(vec![NODE_ID_2]); - node - }), - ), - (NODE_ID_2, Arc::new(Node::new(Role::Button))), + (NODE_ID_1, { + let mut node = Node::new(Role::Window); + node.set_children(vec![NODE_ID_2]); + node + }), + (NODE_ID_2, Node::new(Role::Button)), ], tree: Some(Tree::new(NODE_ID_1)), focus: None, @@ -1020,46 +1017,31 @@ mod tests { let update = TreeUpdate { nodes: vec![ - ( - NODE_ID_1, - Arc::new({ - let mut node = Node::new(Role::Window); - node.set_children(vec![NODE_ID_2, NODE_ID_3, NODE_ID_4, NODE_ID_5]); - node - }), - ), - ( - NODE_ID_2, - Arc::new({ - let mut node = Node::new(Role::CheckBox); - node.set_labelled_by(vec![NODE_ID_3, NODE_ID_5]); - node - }), - ), - ( - NODE_ID_3, - Arc::new({ - let mut node = Node::new(Role::StaticText); - node.set_name(LABEL_1); - node - }), - ), - ( - NODE_ID_4, - Arc::new({ - let mut node = Node::new(Role::TextField); - node.push_to_labelled_by(NODE_ID_5); - node - }), - ), - ( - NODE_ID_5, - Arc::new({ - let mut node = Node::new(Role::StaticText); - node.set_name(LABEL_2); - node - }), - ), + (NODE_ID_1, { + let mut node = Node::new(Role::Window); + node.set_children(vec![NODE_ID_2, NODE_ID_3, NODE_ID_4, NODE_ID_5]); + node + }), + (NODE_ID_2, { + let mut node = Node::new(Role::CheckBox); + node.set_labelled_by(vec![NODE_ID_3, NODE_ID_5]); + node + }), + (NODE_ID_3, { + let mut node = Node::new(Role::StaticText); + node.set_name(LABEL_1); + node + }), + (NODE_ID_4, { + let mut node = Node::new(Role::TextField); + node.push_to_labelled_by(NODE_ID_5); + node + }), + (NODE_ID_5, { + let mut node = Node::new(Role::StaticText); + node.set_name(LABEL_2); + node + }), ], tree: Some(Tree::new(NODE_ID_1)), focus: None, @@ -1082,54 +1064,36 @@ mod tests { let update = TreeUpdate { nodes: vec![ - ( - NODE_ID_1, - Arc::new({ - let mut node = Node::new(Role::Window); - node.set_children(vec![NODE_ID_2, NODE_ID_4]); - node - }), - ), - ( - NODE_ID_2, - Arc::new({ - let mut node = Node::new(Role::Button); - node.push_to_children(NODE_ID_3); - node - }), - ), - ( - NODE_ID_3, - Arc::new({ - let mut node = Node::new(Role::Image); - node.set_name(BUTTON_LABEL); - node - }), - ), - ( - NODE_ID_4, - Arc::new({ - let mut node = Node::new(Role::Link); - node.push_to_children(NODE_ID_5); - node - }), - ), - ( - NODE_ID_5, - Arc::new({ - let mut node = Node::new(Role::GenericContainer); - node.push_to_children(NODE_ID_6); - node - }), - ), - ( - NODE_ID_6, - Arc::new({ - let mut node = Node::new(Role::StaticText); - node.set_name(LINK_LABEL); - node - }), - ), + (NODE_ID_1, { + let mut node = Node::new(Role::Window); + node.set_children(vec![NODE_ID_2, NODE_ID_4]); + node + }), + (NODE_ID_2, { + let mut node = Node::new(Role::Button); + node.push_to_children(NODE_ID_3); + node + }), + (NODE_ID_3, { + let mut node = Node::new(Role::Image); + node.set_name(BUTTON_LABEL); + node + }), + (NODE_ID_4, { + let mut node = Node::new(Role::Link); + node.push_to_children(NODE_ID_5); + node + }), + (NODE_ID_5, { + let mut node = Node::new(Role::GenericContainer); + node.push_to_children(NODE_ID_6); + node + }), + (NODE_ID_6, { + let mut node = Node::new(Role::StaticText); + node.set_name(LINK_LABEL); + node + }), ], tree: Some(Tree::new(NODE_ID_1)), focus: None, diff --git a/consumer/src/text.rs b/consumer/src/text.rs index d884955b0..73781885e 100644 --- a/consumer/src/text.rs +++ b/consumer/src/text.rs @@ -1015,7 +1015,7 @@ impl<'a> Node<'a> { mod tests { use accesskit::kurbo::{Point, Rect}; use accesskit::{NodeId, TextSelection}; - use std::{num::NonZeroU128, sync::Arc}; + use std::num::NonZeroU128; use crate::tests::NullActionHandler; @@ -1035,202 +1035,175 @@ mod tests { let update = TreeUpdate { nodes: vec![ - ( - NODE_ID_1, - Arc::new({ - let mut node = Node::new(Role::Window); - node.set_transform(Affine::scale(1.5)); - node.set_children(vec![NODE_ID_2]); - node - }), - ), - ( - NODE_ID_2, - Arc::new({ - let mut node = Node::new(Role::TextField); - node.set_bounds(Rect { - x0: 8.0, - y0: 31.666664123535156, - x1: 296.0, - y1: 123.66666412353516, - }); - node.set_children(vec![ - NODE_ID_3, NODE_ID_4, NODE_ID_5, NODE_ID_6, NODE_ID_7, NODE_ID_8, - ]); - node.add_action(Action::Focus); - if let Some(selection) = selection { - node.set_text_selection(selection); - } - node - }), - ), - ( - NODE_ID_3, - Arc::new({ - let mut node = Node::new(Role::InlineTextBox); - node.set_bounds(Rect { - x0: 12.0, - y0: 33.666664123535156, - x1: 290.9189147949219, - y1: 48.33333206176758, - }); - // The non-breaking space in the following text - // is in an arbitrary spot; its only purpose - // is to test conversion between UTF-8 and UTF-16 - // indices. - node.set_value("This paragraph is\u{a0}long enough to wrap "); - node.set_text_direction(TextDirection::LeftToRight); - node.set_character_lengths([ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - ]); - node.set_character_positions([ - 0.0, 7.3333335, 14.666667, 22.0, 29.333334, 36.666668, 44.0, 51.333332, - 58.666668, 66.0, 73.333336, 80.666664, 88.0, 95.333336, 102.666664, - 110.0, 117.333336, 124.666664, 132.0, 139.33333, 146.66667, 154.0, - 161.33333, 168.66667, 176.0, 183.33333, 190.66667, 198.0, 205.33333, - 212.66667, 220.0, 227.33333, 234.66667, 242.0, 249.33333, 256.66666, - 264.0, 271.33334, - ]); - node.set_character_widths([ - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, - ]); - node.set_word_lengths([5, 10, 3, 5, 7, 3, 5]); - node - }), - ), - ( - NODE_ID_4, - Arc::new({ - let mut node = Node::new(Role::InlineTextBox); - node.set_bounds(Rect { - x0: 12.0, - y0: 48.33333206176758, - x1: 129.5855712890625, - y1: 63.0, - }); - node.set_value("to another line.\n"); - node.set_text_direction(TextDirection::LeftToRight); - node.set_character_lengths([ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - ]); - node.set_character_positions([ - 0.0, 7.3333435, 14.666687, 22.0, 29.333344, 36.666687, 44.0, 51.333344, - 58.666687, 66.0, 73.33334, 80.66669, 88.0, 95.33334, 102.66669, 110.0, - 117.58557, - ]); - node.set_character_widths([ - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, - 0.0, - ]); - node.set_word_lengths([3, 8, 6]); - node - }), - ), - ( - NODE_ID_5, - Arc::new({ - let mut node = Node::new(Role::InlineTextBox); - node.set_bounds(Rect { - x0: 12.0, - y0: 63.0, - x1: 144.25222778320313, - y1: 77.66666412353516, - }); - node.set_value("Another paragraph.\n"); - node.set_text_direction(TextDirection::LeftToRight); - node.set_character_lengths([ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - ]); - node.set_character_positions([ - 0.0, 7.3333335, 14.666667, 22.0, 29.333334, 36.666668, 44.0, 51.333332, - 58.666668, 66.0, 73.333336, 80.666664, 88.0, 95.333336, 102.666664, - 110.0, 117.333336, 124.666664, 132.25223, - ]); - node.set_character_widths([ - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, - 7.58557, 7.58557, 0.0, - ]); - node.set_word_lengths([8, 11]); - node - }), - ), - ( - NODE_ID_6, - Arc::new({ - let mut node = Node::new(Role::InlineTextBox); - node.set_bounds(Rect { - x0: 12.0, - y0: 77.66666412353516, - x1: 12.0, - y1: 92.33332824707031, - }); - node.set_value("\n"); - node.set_text_direction(TextDirection::LeftToRight); - node.set_character_lengths([1]); - node.set_character_positions([0.0]); - node.set_character_widths([0.0]); - node.set_word_lengths([1]); - node - }), - ), - ( - NODE_ID_7, - Arc::new({ - let mut node = Node::new(Role::InlineTextBox); - node.set_bounds(Rect { - x0: 12.0, - y0: 92.33332824707031, - x1: 158.9188995361328, - y1: 107.0, - }); - // Use an arbitrary emoji that encodes to two - // UTF-16 code units to fully test conversion between - // UTF-8, UTF-16, and character indices. - node.set_value("Last non-blank line\u{1f60a}\n"); - node.set_text_direction(TextDirection::LeftToRight); - node.set_character_lengths([ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, - ]); - node.set_character_positions([ - 0.0, 7.3333335, 14.666667, 22.0, 29.333334, 36.666668, 44.0, 51.333332, - 58.666668, 66.0, 73.333336, 80.666664, 88.0, 95.333336, 102.666664, - 110.0, 117.333336, 124.666664, 132.0, 139.33333, 146.9189, - ]); - node.set_character_widths([ - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, - 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, - 7.58557, 7.58557, 7.58557, 7.58557, 0.0, - ]); - node.set_word_lengths([5, 4, 6, 6]); - node - }), - ), - ( - NODE_ID_8, - Arc::new({ - let mut node = Node::new(Role::InlineTextBox); - node.set_bounds(Rect { - x0: 12.0, - y0: 107.0, - x1: 12.0, - y1: 121.66666412353516, - }); - node.set_value(""); - node.set_text_direction(TextDirection::LeftToRight); - node.set_character_lengths([]); - node.set_character_positions([]); - node.set_character_widths([]); - node.set_word_lengths([0]); - node - }), - ), + (NODE_ID_1, { + let mut node = Node::new(Role::Window); + node.set_transform(Affine::scale(1.5)); + node.set_children(vec![NODE_ID_2]); + node + }), + (NODE_ID_2, { + let mut node = Node::new(Role::TextField); + node.set_bounds(Rect { + x0: 8.0, + y0: 31.666664123535156, + x1: 296.0, + y1: 123.66666412353516, + }); + node.set_children(vec![ + NODE_ID_3, NODE_ID_4, NODE_ID_5, NODE_ID_6, NODE_ID_7, NODE_ID_8, + ]); + node.add_action(Action::Focus); + if let Some(selection) = selection { + node.set_text_selection(selection); + } + node + }), + (NODE_ID_3, { + let mut node = Node::new(Role::InlineTextBox); + node.set_bounds(Rect { + x0: 12.0, + y0: 33.666664123535156, + x1: 290.9189147949219, + y1: 48.33333206176758, + }); + // The non-breaking space in the following text + // is in an arbitrary spot; its only purpose + // is to test conversion between UTF-8 and UTF-16 + // indices. + node.set_value("This paragraph is\u{a0}long enough to wrap "); + node.set_text_direction(TextDirection::LeftToRight); + node.set_character_lengths([ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + ]); + node.set_character_positions([ + 0.0, 7.3333335, 14.666667, 22.0, 29.333334, 36.666668, 44.0, 51.333332, + 58.666668, 66.0, 73.333336, 80.666664, 88.0, 95.333336, 102.666664, 110.0, + 117.333336, 124.666664, 132.0, 139.33333, 146.66667, 154.0, 161.33333, + 168.66667, 176.0, 183.33333, 190.66667, 198.0, 205.33333, 212.66667, 220.0, + 227.33333, 234.66667, 242.0, 249.33333, 256.66666, 264.0, 271.33334, + ]); + node.set_character_widths([ + 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, + 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, + 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, + 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, + 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, + ]); + node.set_word_lengths([5, 10, 3, 5, 7, 3, 5]); + node + }), + (NODE_ID_4, { + let mut node = Node::new(Role::InlineTextBox); + node.set_bounds(Rect { + x0: 12.0, + y0: 48.33333206176758, + x1: 129.5855712890625, + y1: 63.0, + }); + node.set_value("to another line.\n"); + node.set_text_direction(TextDirection::LeftToRight); + node.set_character_lengths([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]); + node.set_character_positions([ + 0.0, 7.3333435, 14.666687, 22.0, 29.333344, 36.666687, 44.0, 51.333344, + 58.666687, 66.0, 73.33334, 80.66669, 88.0, 95.33334, 102.66669, 110.0, + 117.58557, + ]); + node.set_character_widths([ + 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, + 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, + 0.0, + ]); + node.set_word_lengths([3, 8, 6]); + node + }), + (NODE_ID_5, { + let mut node = Node::new(Role::InlineTextBox); + node.set_bounds(Rect { + x0: 12.0, + y0: 63.0, + x1: 144.25222778320313, + y1: 77.66666412353516, + }); + node.set_value("Another paragraph.\n"); + node.set_text_direction(TextDirection::LeftToRight); + node.set_character_lengths([ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + ]); + node.set_character_positions([ + 0.0, 7.3333335, 14.666667, 22.0, 29.333334, 36.666668, 44.0, 51.333332, + 58.666668, 66.0, 73.333336, 80.666664, 88.0, 95.333336, 102.666664, 110.0, + 117.333336, 124.666664, 132.25223, + ]); + node.set_character_widths([ + 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, + 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, + 7.58557, 7.58557, 0.0, + ]); + node.set_word_lengths([8, 11]); + node + }), + (NODE_ID_6, { + let mut node = Node::new(Role::InlineTextBox); + node.set_bounds(Rect { + x0: 12.0, + y0: 77.66666412353516, + x1: 12.0, + y1: 92.33332824707031, + }); + node.set_value("\n"); + node.set_text_direction(TextDirection::LeftToRight); + node.set_character_lengths([1]); + node.set_character_positions([0.0]); + node.set_character_widths([0.0]); + node.set_word_lengths([1]); + node + }), + (NODE_ID_7, { + let mut node = Node::new(Role::InlineTextBox); + node.set_bounds(Rect { + x0: 12.0, + y0: 92.33332824707031, + x1: 158.9188995361328, + y1: 107.0, + }); + // Use an arbitrary emoji that encodes to two + // UTF-16 code units to fully test conversion between + // UTF-8, UTF-16, and character indices. + node.set_value("Last non-blank line\u{1f60a}\n"); + node.set_text_direction(TextDirection::LeftToRight); + node.set_character_lengths([ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, + ]); + node.set_character_positions([ + 0.0, 7.3333335, 14.666667, 22.0, 29.333334, 36.666668, 44.0, 51.333332, + 58.666668, 66.0, 73.333336, 80.666664, 88.0, 95.333336, 102.666664, 110.0, + 117.333336, 124.666664, 132.0, 139.33333, 146.9189, + ]); + node.set_character_widths([ + 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, + 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, + 7.58557, 7.58557, 7.58557, 7.58557, 0.0, + ]); + node.set_word_lengths([5, 4, 6, 6]); + node + }), + (NODE_ID_8, { + let mut node = Node::new(Role::InlineTextBox); + node.set_bounds(Rect { + x0: 12.0, + y0: 107.0, + x1: 12.0, + y1: 121.66666412353516, + }); + node.set_value(""); + node.set_text_direction(TextDirection::LeftToRight); + node.set_character_lengths([]); + node.set_character_positions([]); + node.set_character_widths([]); + node.set_word_lengths([0]); + node + }), ], tree: Some(Tree::new(NODE_ID_1)), focus: Some(NODE_ID_2), diff --git a/consumer/src/tree.rs b/consumer/src/tree.rs index f8d465583..57e9f3e63 100644 --- a/consumer/src/tree.rs +++ b/consumer/src/tree.rs @@ -84,12 +84,12 @@ impl State { changes: &mut Option<&mut InternalChanges>, parent_and_index: Option, id: NodeId, - data: Arc, + data: NodeData, ) { let state = NodeState { id, parent_and_index, - data, + data: Arc::new(data), }; nodes.insert(id, state); if let Some(changes) = changes { @@ -132,7 +132,7 @@ impl State { orphans.insert(*child_id); } } - node_state.data = node_data; + node_state.data = Arc::new(node_data); } else if let Some(parent_and_index) = pending_children.remove(&node_id) { add_node( &mut self.nodes, @@ -209,9 +209,9 @@ impl State { pub fn serialize(&self) -> TreeUpdate { let mut nodes = Vec::new(); - fn traverse(state: &State, nodes: &mut Vec<(NodeId, Arc)>, id: NodeId) { + fn traverse(state: &State, nodes: &mut Vec<(NodeId, NodeData)>, id: NodeId) { let node = state.nodes.get(&id).unwrap(); - nodes.push((id, Arc::clone(&node.data))); + nodes.push((id, (*node.data).clone())); for child_id in node.data.children().iter() { traverse(state, nodes, *child_id); @@ -427,7 +427,7 @@ impl Tree { #[cfg(test)] mod tests { use accesskit::{Node, NodeId, Role, Tree, TreeUpdate}; - use std::{num::NonZeroU128, sync::Arc}; + use std::num::NonZeroU128; use crate::tests::NullActionHandler; @@ -438,7 +438,7 @@ mod tests { #[test] fn init_tree_with_root_node() { let update = TreeUpdate { - nodes: vec![(NODE_ID_1, Arc::new(Node::new(Role::Window)))], + nodes: vec![(NODE_ID_1, Node::new(Role::Window))], tree: Some(Tree::new(NODE_ID_1)), focus: None, }; @@ -452,16 +452,13 @@ mod tests { fn root_node_has_children() { let update = TreeUpdate { nodes: vec![ - ( - NODE_ID_1, - Arc::new({ - let mut node = Node::new(Role::Window); - node.set_children(vec![NODE_ID_2, NODE_ID_3]); - node - }), - ), - (NODE_ID_2, Arc::new(Node::new(Role::Button))), - (NODE_ID_3, Arc::new(Node::new(Role::Button))), + (NODE_ID_1, { + let mut node = Node::new(Role::Window); + node.set_children(vec![NODE_ID_2, NODE_ID_3]); + node + }), + (NODE_ID_2, Node::new(Role::Button)), + (NODE_ID_3, Node::new(Role::Button)), ], tree: Some(Tree::new(NODE_ID_1)), focus: None, @@ -483,7 +480,7 @@ mod tests { fn add_child_to_root_node() { let root_node = Node::new(Role::Window); let first_update = TreeUpdate { - nodes: vec![(NODE_ID_1, Arc::new(root_node.clone()))], + nodes: vec![(NODE_ID_1, root_node.clone())], tree: Some(Tree::new(NODE_ID_1)), focus: None, }; @@ -491,15 +488,12 @@ mod tests { assert_eq!(0, tree.read().root().children().count()); let second_update = TreeUpdate { nodes: vec![ - ( - NODE_ID_1, - Arc::new({ - let mut node = root_node; - node.push_to_children(NODE_ID_2); - node - }), - ), - (NODE_ID_2, Arc::new(Node::new(Role::RootWebArea))), + (NODE_ID_1, { + let mut node = root_node; + node.push_to_children(NODE_ID_2); + node + }), + (NODE_ID_2, Node::new(Role::RootWebArea)), ], tree: None, focus: None, @@ -565,15 +559,12 @@ mod tests { let root_node = Node::new(Role::Window); let first_update = TreeUpdate { nodes: vec![ - ( - NODE_ID_1, - Arc::new({ - let mut node = root_node.clone(); - node.push_to_children(NODE_ID_2); - node - }), - ), - (NODE_ID_2, Arc::new(Node::new(Role::RootWebArea))), + (NODE_ID_1, { + let mut node = root_node.clone(); + node.push_to_children(NODE_ID_2); + node + }), + (NODE_ID_2, Node::new(Role::RootWebArea)), ], tree: Some(Tree::new(NODE_ID_1)), focus: None, @@ -581,7 +572,7 @@ mod tests { let tree = super::Tree::new(first_update, Box::new(NullActionHandler {})); assert_eq!(1, tree.read().root().children().count()); let second_update = TreeUpdate { - nodes: vec![(NODE_ID_1, Arc::new(root_node))], + nodes: vec![(NODE_ID_1, root_node)], tree: None, focus: None, }; @@ -640,16 +631,13 @@ mod tests { fn move_focus_between_siblings() { let first_update = TreeUpdate { nodes: vec![ - ( - NODE_ID_1, - Arc::new({ - let mut node = Node::new(Role::Window); - node.set_children(vec![NODE_ID_2, NODE_ID_3]); - node - }), - ), - (NODE_ID_2, Arc::new(Node::new(Role::Button))), - (NODE_ID_3, Arc::new(Node::new(Role::Button))), + (NODE_ID_1, { + let mut node = Node::new(Role::Window); + node.set_children(vec![NODE_ID_2, NODE_ID_3]); + node + }), + (NODE_ID_2, Node::new(Role::Button)), + (NODE_ID_3, Node::new(Role::Button)), ], tree: Some(Tree::new(NODE_ID_1)), focus: Some(NODE_ID_2), @@ -731,22 +719,16 @@ mod tests { let child_node = Node::new(Role::Button); let first_update = TreeUpdate { nodes: vec![ - ( - NODE_ID_1, - Arc::new({ - let mut node = Node::new(Role::Window); - node.set_children(vec![NODE_ID_2]); - node - }), - ), - ( - NODE_ID_2, - Arc::new({ - let mut node = child_node.clone(); - node.set_name("foo"); - node - }), - ), + (NODE_ID_1, { + let mut node = Node::new(Role::Window); + node.set_children(vec![NODE_ID_2]); + node + }), + (NODE_ID_2, { + let mut node = child_node.clone(); + node.set_name("foo"); + node + }), ], tree: Some(Tree::new(NODE_ID_1)), focus: None, @@ -757,14 +739,11 @@ mod tests { tree.read().node_by_id(NODE_ID_2).unwrap().name() ); let second_update = TreeUpdate { - nodes: vec![( - NODE_ID_2, - Arc::new({ - let mut node = child_node; - node.set_name("bar"); - node - }), - )], + nodes: vec![(NODE_ID_2, { + let mut node = child_node; + node.set_name("bar"); + node + })], tree: None, focus: None, }; diff --git a/platforms/windows/examples/hello_world.rs b/platforms/windows/examples/hello_world.rs index 61df5f45d..2f02c871f 100644 --- a/platforms/windows/examples/hello_world.rs +++ b/platforms/windows/examples/hello_world.rs @@ -7,7 +7,7 @@ use accesskit::{ }; use accesskit_windows::UiaInitMarker; use once_cell::{sync::Lazy, unsync::OnceCell}; -use std::{cell::RefCell, convert::TryInto, num::NonZeroU128, sync::Arc}; +use std::{cell::RefCell, convert::TryInto, num::NonZeroU128}; use windows::{ core::*, Win32::{ @@ -63,29 +63,27 @@ const BUTTON_2_RECT: Rect = Rect { const SET_FOCUS_MSG: u32 = WM_USER; const DO_DEFAULT_ACTION_MSG: u32 = WM_USER + 1; -fn make_button(id: NodeId, name: &str) -> Arc { +fn make_button(id: NodeId, name: &str) -> Node { let rect = match id { BUTTON_1_ID => BUTTON_1_RECT, BUTTON_2_ID => BUTTON_2_RECT, _ => unreachable!(), }; - Arc::new({ - let mut node = Node::new(Role::Button); - node.set_bounds(rect); - node.set_name(name); - node.add_action(Action::Focus); - node.set_default_action_verb(DefaultActionVerb::Click); - node - }) + let mut node = Node::new(Role::Button); + node.set_bounds(rect); + node.set_name(name); + node.add_action(Action::Focus); + node.set_default_action_verb(DefaultActionVerb::Click); + node } fn get_initial_state() -> TreeUpdate { - let root = Arc::new({ + let root = { let mut node = Node::new(Role::Window); node.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); node - }); + }; let button_1 = make_button(BUTTON_1_ID, "Button 1"); let button_2 = make_button(BUTTON_2_ID, "Button 2"); TreeUpdate { @@ -152,17 +150,17 @@ impl WindowState { } else { "You pressed button 2" }; - let node = Arc::new({ + let node = { let mut node = Node::new(Role::StaticText); node.set_name(name); node.set_live(Live::Polite); node - }); - let root = Arc::new({ + }; + let root = { let mut node = Node::new(Role::Window); node.set_children(vec![BUTTON_1_ID, BUTTON_2_ID, PRESSED_TEXT_ID]); node - }); + }; let update = TreeUpdate { nodes: vec![(PRESSED_TEXT_ID, node), (WINDOW_ID, root)], tree: None, diff --git a/platforms/windows/src/tests/simple.rs b/platforms/windows/src/tests/simple.rs index 899312804..f2f12f6be 100644 --- a/platforms/windows/src/tests/simple.rs +++ b/platforms/windows/src/tests/simple.rs @@ -3,7 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use std::{convert::TryInto, num::NonZeroU128, sync::Arc}; +use std::{convert::TryInto, num::NonZeroU128}; use accesskit::{Action, ActionHandler, ActionRequest, Node, NodeId, Role, Tree, TreeUpdate}; use windows::{core::*, Win32::UI::Accessibility::*}; @@ -16,21 +16,19 @@ const WINDOW_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(1) }); const BUTTON_1_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(2) }); const BUTTON_2_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(3) }); -fn make_button(name: &str) -> Arc { - Arc::new({ - let mut node = Node::new(Role::Button); - node.set_name(name); - node.add_action(Action::Focus); - node - }) +fn make_button(name: &str) -> Node { + let mut node = Node::new(Role::Button); + node.set_name(name); + node.add_action(Action::Focus); + node } fn get_initial_state() -> TreeUpdate { - let root = Arc::new({ + let root = { let mut node = Node::new(Role::Window); node.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); node - }); + }; let button_1 = make_button("Button 1"); let button_2 = make_button("Button 2"); TreeUpdate { diff --git a/platforms/windows/src/tests/subclassed.rs b/platforms/windows/src/tests/subclassed.rs index f615b490c..314bdb57b 100644 --- a/platforms/windows/src/tests/subclassed.rs +++ b/platforms/windows/src/tests/subclassed.rs @@ -3,7 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use std::{num::NonZeroU128, sync::Arc}; +use std::num::NonZeroU128; use accesskit::{Action, ActionHandler, ActionRequest, Node, NodeId, Role, Tree, TreeUpdate}; use windows::Win32::{Foundation::*, UI::Accessibility::*}; @@ -21,22 +21,20 @@ const WINDOW_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(1) }); const BUTTON_1_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(2) }); const BUTTON_2_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(3) }); -fn make_button(name: &str) -> Arc { - Arc::new({ - let mut node = Node::new(Role::Button); - node.set_name(name); - node.add_action(Action::Focus); - node - }) +fn make_button(name: &str) -> Node { + let mut node = Node::new(Role::Button); + node.set_name(name); + node.add_action(Action::Focus); + node } fn get_initial_state() -> TreeUpdate { - let root = Arc::new({ + let root = { let mut node = Node::new(Role::Window); node.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); node.set_name(WINDOW_TITLE); node - }); + }; let button_1 = make_button("Button 1"); let button_2 = make_button("Button 2"); TreeUpdate { diff --git a/platforms/winit/examples/simple.rs b/platforms/winit/examples/simple.rs index fe1b847d7..88586591d 100644 --- a/platforms/winit/examples/simple.rs +++ b/platforms/winit/examples/simple.rs @@ -35,21 +35,19 @@ const BUTTON_2_RECT: Rect = Rect { y1: 100.0, }; -fn make_button(id: NodeId, name: &str) -> Arc { +fn make_button(id: NodeId, name: &str) -> Node { let rect = match id { BUTTON_1_ID => BUTTON_1_RECT, BUTTON_2_ID => BUTTON_2_RECT, _ => unreachable!(), }; - Arc::new({ - let mut node = Node::new(Role::Button); - node.set_bounds(rect); - node.set_name(name); - node.add_action(Action::Focus); - node.set_default_action_verb(DefaultActionVerb::Click); - node - }) + let mut node = Node::new(Role::Button); + node.set_bounds(rect); + node.set_name(name); + node.add_action(Action::Focus); + node.set_default_action_verb(DefaultActionVerb::Click); + node } #[derive(Debug)] @@ -90,18 +88,18 @@ impl State { } else { "You pressed button 2" }; - let node = Arc::new({ + let node = { let mut node = Node::new(Role::StaticText); node.set_name(name); node.set_live(Live::Polite); node - }); - let root = Arc::new({ + }; + let root = { let mut node = Node::new(Role::Window); node.set_children(vec![BUTTON_1_ID, BUTTON_2_ID, PRESSED_TEXT_ID]); node.set_name(WINDOW_TITLE); node - }); + }; let update = TreeUpdate { nodes: vec![(PRESSED_TEXT_ID, node), (WINDOW_ID, root)], tree: None, @@ -112,12 +110,12 @@ impl State { } fn initial_tree_update(state: &State) -> TreeUpdate { - let root = Arc::new({ + let root = { let mut node = Node::new(Role::Window); node.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); node.set_name(WINDOW_TITLE); node - }); + }; let button_1 = make_button(BUTTON_1_ID, "Button 1"); let button_2 = make_button(BUTTON_2_ID, "Button 2"); TreeUpdate { From a7ccaebefbf677812d30fd0e99bcab1f3ba6428d Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Fri, 13 Jan 2023 10:12:10 -0600 Subject: [PATCH 25/45] Eliminate the dependency on the heavy proc-macro-based enumset --- Cargo.lock | 24 --------- common/Cargo.toml | 5 +- common/src/lib.rs | 123 ++++++++++++++++++++++++++++++++++++---------- 3 files changed, 99 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e52b0d4d3..aadd012ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,7 +6,6 @@ version = 3 name = "accesskit" version = "0.8.1" dependencies = [ - "enumset", "kurbo", "num-derive", "num-traits", @@ -629,28 +628,6 @@ dependencies = [ "syn", ] -[[package]] -name = "enumset" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6216d2c19a6fb5f29d1ada1dc7bc4367a8cbf0fa4af5cf12e07b5bbdde6b5b2c" -dependencies = [ - "enumset_derive", - "serde", -] - -[[package]] -name = "enumset_derive" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6451128aa6655d880755345d085494cf7561a6bee7c8dc821e5d77e6d267ecd4" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "event-listener" version = "2.5.3" @@ -1422,7 +1399,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271ac0c667b8229adf70f0f957697c96fafd7486ab7481e15dc5e45e3e6a4368" dependencies = [ "dyn-clone", - "enumset", "schemars_derive", "serde", "serde_json", diff --git a/common/Cargo.toml b/common/Cargo.toml index 5fa7cc7b6..4de0ee328 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -14,14 +14,13 @@ edition = "2021" features = ["schemars", "serde"] [dependencies] -enumset = "1.0.8" kurbo = "0.8.3" num-derive = { version = "0.3", optional = true } num-traits = { version = "0.2", optional = true } paste = "1.0" -schemars = { version = "0.8.7", features = ["enumset"], optional = true } +schemars = { version = "0.8.7", optional = true } serde = { version = "1.0", features = ["derive", "rc"], optional = true } [features] schemars = ["serde", "dep:schemars", "kurbo/schemars"] -serde = ["dep:serde", "enumset/serde", "kurbo/serde", "num-derive", "num-traits"] +serde = ["dep:serde", "kurbo/serde", "num-derive", "num-traits"] diff --git a/common/src/lib.rs b/common/src/lib.rs index 5fba3b026..fd8864535 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -12,7 +12,6 @@ #[macro_use] extern crate num_derive; -use enumset::{EnumSet, EnumSetType}; pub use kurbo; use kurbo::{Affine, Point, Rect}; #[cfg(feature = "serde")] @@ -22,13 +21,13 @@ use paste::paste; use schemars::JsonSchema; #[cfg(feature = "serde")] use serde::{ - de::{Deserializer, IgnoredAny, MapAccess, Visitor}, - ser::{SerializeMap, Serializer}, + de::{Deserializer, IgnoredAny, MapAccess, SeqAccess, Visitor}, + ser::{SerializeMap, SerializeSeq, Serializer}, Deserialize, Serialize, }; -#[cfg(feature = "serde")] -use std::fmt; use std::num::{NonZeroU128, NonZeroU64}; +#[cfg(feature = "serde")] +use std::{fmt, mem::size_of_val}; /// The type of an accessibility node. /// @@ -259,11 +258,11 @@ impl Default for Role { /// /// In contrast to [`DefaultActionVerb`], these describe what happens to the /// object, e.g. "focus". -#[derive(EnumSetType, Debug)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, FromPrimitive))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -#[cfg_attr(feature = "serde", enumset(serialize_as_list))] +#[repr(u8)] pub enum Action { /// Do the default action for an object, typically this means "click". Default, @@ -338,6 +337,67 @@ pub enum Action { ShowContextMenu, } +impl Action { + fn mask(self) -> u32 { + 1 << (self as u8) + } +} + +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +#[repr(transparent)] +struct Actions(u32); + +#[cfg(feature = "serde")] +impl Serialize for Actions { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(None)?; + for i in 0..(size_of_val(&self.0) * 8) { + if let Some(action) = Action::from_usize(i) { + if (self.0 & action.mask()) != 0 { + seq.serialize_element(&action)?; + } + } + } + seq.end() + } +} + +#[cfg(feature = "serde")] +struct ActionsVisitor; + +#[cfg(feature = "serde")] +impl<'de> Visitor<'de> for ActionsVisitor { + type Value = Actions; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("action set") + } + + fn visit_seq(self, mut seq: V) -> Result + where + V: SeqAccess<'de>, + { + let mut actions = Actions::default(); + while let Some(action) = seq.next_element::()? { + actions.0 |= action.mask(); + } + Ok(actions) + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Actions { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_seq(ActionsVisitor) + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] @@ -604,10 +664,11 @@ pub struct TextSelection { pub focus: TextPosition, } -#[derive(EnumSetType, Debug)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, FromPrimitive))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +#[repr(u8)] enum Flag { AutofillAvailable, Default, @@ -641,6 +702,12 @@ enum Flag { IsNonatomicTextFieldRoot, } +impl Flag { + fn mask(self) -> u32 { + 1 << (self as u8) + } +} + // The following is based on the technique described here: // https://viruta.org/reducing-memory-consumption-in-librsvg-2.html @@ -781,13 +848,13 @@ macro_rules! flag_methods { impl Node { $($(#[$doc])* pub fn [< is_ $base_name >](&self) -> bool { - self.flags.contains(Flag::$id) + (self.flags & (Flag::$id).mask()) != 0 } pub fn [< set_ $base_name >](&mut self) { - self.flags.insert(Flag::$id); + self.flags |= (Flag::$id).mask(); } pub fn [< clear_ $base_name >](&mut self) { - self.flags.remove(Flag::$id); + self.flags &= !((Flag::$id).mask()); })* } } @@ -800,13 +867,13 @@ macro_rules! irregular_flag_methods { impl Node { $($(#[$doc])* pub fn $base_name(&self) -> bool { - self.flags.contains(Flag::$id) + (self.flags & (Flag::$id).mask()) != 0 } pub fn [< set_ $base_name >](&mut self) { - self.flags.insert(Flag::$id); + self.flags |= (Flag::$id).mask(); } pub fn [< clear_ $base_name >](&mut self) { - self.flags.remove(Flag::$id); + self.flags &= !((Flag::$id).mask()); })* } } @@ -973,10 +1040,10 @@ macro_rules! coord_slice_property_methods { #[derive(Clone, Debug, Default, PartialEq)] pub struct Node { role: Role, - actions: EnumSet, + actions: Actions, indices: PropertyIndices, props: Vec, - flags: EnumSet, + flags: u32, expanded: Option, selected: Option, name_from: Option, @@ -1184,16 +1251,16 @@ impl Node { } pub fn supports_action(&self, action: Action) -> bool { - self.actions.contains(action) + (self.actions.0 & action.mask()) != 0 } pub fn add_action(&mut self, action: Action) { - self.actions.insert(action); + self.actions.0 |= action.mask(); } pub fn remove_action(&mut self, action: Action) { - self.actions.remove(action); + self.actions.0 &= !(action.mask()); } pub fn clear_actions(&mut self) { - self.actions.clear(); + self.actions.0 = 0; } } @@ -1673,8 +1740,12 @@ impl Serialize for Node { (role, Role), (actions, Actions) }); - for flag in self.flags { - map.serialize_entry(&flag, &true)?; + for i in 0..(size_of_val(&self.flags) * 8) { + if let Some(flag) = Flag::from_usize(i) { + if (self.flags & flag.mask()) != 0 { + map.serialize_entry(&flag, &true)?; + } + } } serialize_optional_fields!(self, map, { (expanded, Expanded), @@ -1766,9 +1837,9 @@ impl<'de> Visitor<'de> for NodeVisitor { } DeserializeKey::Flag(flag) => { if map.next_value()? { - node.flags.insert(flag); + node.flags |= flag.mask(); } else { - node.flags.remove(flag); + node.flags &= !(flag.mask()); } } DeserializeKey::Property(id) => { From b7966fbe28124d328514bc6fc8668dca5eabb951 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Fri, 13 Jan 2023 15:17:12 -0600 Subject: [PATCH 26/45] We no longer need serde's rc feature --- common/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/Cargo.toml b/common/Cargo.toml index 4de0ee328..935560271 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -19,7 +19,7 @@ num-derive = { version = "0.3", optional = true } num-traits = { version = "0.2", optional = true } paste = "1.0" schemars = { version = "0.8.7", optional = true } -serde = { version = "1.0", features = ["derive", "rc"], optional = true } +serde = { version = "1.0", features = ["derive"], optional = true } [features] schemars = ["serde", "dep:schemars", "kurbo/schemars"] From f24087f430ba9ecfc43c3c18e513d8363eaee62f Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sat, 14 Jan 2023 09:16:30 -0600 Subject: [PATCH 27/45] Absorb the bits of kurbo we need rather than depending on and re-exporting kurbo itself --- Cargo.lock | 12 - common/Cargo.toml | 4 +- common/src/geometry.rs | 875 ++++++++++++++++++++++ common/src/lib.rs | 5 +- consumer/src/lib.rs | 5 +- consumer/src/node.rs | 7 +- consumer/src/text.rs | 9 +- consumer/src/tree.rs | 3 +- platforms/macos/src/util.rs | 2 +- platforms/unix/src/adapter.rs | 2 +- platforms/unix/src/atspi/mod.rs | 4 +- platforms/unix/src/node.rs | 5 +- platforms/unix/src/util.rs | 2 +- platforms/windows/examples/hello_world.rs | 3 +- platforms/windows/src/node.rs | 3 +- platforms/windows/src/util.rs | 2 +- platforms/winit/examples/simple.rs | 3 +- platforms/winit/src/lib.rs | 2 +- platforms/winit/src/platform_impl/unix.rs | 2 +- 19 files changed, 901 insertions(+), 49 deletions(-) create mode 100644 common/src/geometry.rs diff --git a/Cargo.lock b/Cargo.lock index aadd012ce..ac4829778 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,7 +6,6 @@ version = 3 name = "accesskit" version = "0.8.1" dependencies = [ - "kurbo", "num-derive", "num-traits", "paste", @@ -852,17 +851,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "kurbo" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a53776d271cfb873b17c618af0298445c88afc52837f3e948fa3fafd131f449" -dependencies = [ - "arrayvec 0.7.1", - "schemars", - "serde", -] - [[package]] name = "lazy_static" version = "1.4.0" diff --git a/common/Cargo.toml b/common/Cargo.toml index 935560271..a9ee9a287 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -14,7 +14,6 @@ edition = "2021" features = ["schemars", "serde"] [dependencies] -kurbo = "0.8.3" num-derive = { version = "0.3", optional = true } num-traits = { version = "0.2", optional = true } paste = "1.0" @@ -22,5 +21,4 @@ schemars = { version = "0.8.7", optional = true } serde = { version = "1.0", features = ["derive"], optional = true } [features] -schemars = ["serde", "dep:schemars", "kurbo/schemars"] -serde = ["dep:serde", "kurbo/serde", "num-derive", "num-traits"] +serde = ["dep:serde", "num-derive", "num-traits"] diff --git a/common/src/geometry.rs b/common/src/geometry.rs new file mode 100644 index 000000000..07a09b15a --- /dev/null +++ b/common/src/geometry.rs @@ -0,0 +1,875 @@ +// Copyright 2023 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + +// Derived from kurbo. +// Copyright 2018 The kurbo Authors. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + +use std::{ + fmt, + ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}, +}; + +/// A 2D affine transform. Derived from [kurbo](https://github.com/linebender/kurbo). +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Affine([f64; 6]); + +impl Affine { + /// The identity transform. + pub const IDENTITY: Affine = Affine::scale(1.0); + + /// A transform that is flipped on the y-axis. Useful for converting between + /// y-up and y-down spaces. + pub const FLIP_Y: Affine = Affine::new([1.0, 0., 0., -1.0, 0., 0.]); + + /// A transform that is flipped on the x-axis. + pub const FLIP_X: Affine = Affine::new([-1.0, 0., 0., 1.0, 0., 0.]); + + /// Construct an affine transform from coefficients. + /// + /// If the coefficients are `(a, b, c, d, e, f)`, then the resulting + /// transformation represents this augmented matrix: + /// + /// ```text + /// | a c e | + /// | b d f | + /// | 0 0 1 | + /// ``` + /// + /// Note that this convention is transposed from PostScript and + /// Direct2D, but is consistent with the + /// [Wikipedia](https://en.wikipedia.org/wiki/Affine_transformation) + /// formulation of affine transformation as augmented matrix. The + /// idea is that `(A * B) * v == A * (B * v)`, where `*` is the + /// [`Mul`](std::ops::Mul) trait. + #[inline] + pub const fn new(c: [f64; 6]) -> Affine { + Affine(c) + } + + /// An affine transform representing uniform scaling. + #[inline] + pub const fn scale(s: f64) -> Affine { + Affine([s, 0.0, 0.0, s, 0.0, 0.0]) + } + + /// An affine transform representing non-uniform scaling + /// with different scale values for x and y + #[inline] + pub const fn scale_non_uniform(s_x: f64, s_y: f64) -> Affine { + Affine([s_x, 0.0, 0.0, s_y, 0.0, 0.0]) + } + + /// An affine transform representing rotation. + /// + /// The convention for rotation is that a positive angle rotates a + /// positive X direction into positive Y. Thus, in a Y-down coordinate + /// system (as is common for graphics), it is a clockwise rotation, and + /// in Y-up (traditional for math), it is anti-clockwise. + /// + /// The angle, `th`, is expressed in radians. + #[inline] + pub fn rotate(th: f64) -> Affine { + let (s, c) = th.sin_cos(); + Affine([c, s, -s, c, 0.0, 0.0]) + } + + /// An affine transform representing translation. + #[inline] + pub fn translate>(p: V) -> Affine { + let p = p.into(); + Affine([1.0, 0.0, 0.0, 1.0, p.x, p.y]) + } + + /// Creates an affine transformation that takes the unit square to the given rectangle. + /// + /// Useful when you want to draw into the unit square but have your output fill any rectangle. + /// In this case push the `Affine` onto the transform stack. + pub fn map_unit_square(rect: Rect) -> Affine { + Affine([rect.width(), 0., 0., rect.height(), rect.x0, rect.y0]) + } + + /// Get the coefficients of the transform. + #[inline] + pub fn as_coeffs(self) -> [f64; 6] { + self.0 + } + + /// Compute the determinant of this transform. + pub fn determinant(self) -> f64 { + self.0[0] * self.0[3] - self.0[1] * self.0[2] + } + + /// Compute the inverse transform. + /// + /// Produces NaN values when the determinant is zero. + pub fn inverse(self) -> Affine { + let inv_det = self.determinant().recip(); + Affine([ + inv_det * self.0[3], + -inv_det * self.0[1], + -inv_det * self.0[2], + inv_det * self.0[0], + inv_det * (self.0[2] * self.0[5] - self.0[3] * self.0[4]), + inv_det * (self.0[1] * self.0[4] - self.0[0] * self.0[5]), + ]) + } + + /// Compute the bounding box of a transformed rectangle. + /// + /// Returns the minimal `Rect` that encloses the given `Rect` after affine transformation. + /// If the transform is axis-aligned, then this bounding box is "tight", in other words the + /// returned `Rect` is the transformed rectangle. + /// + /// The returned rectangle always has non-negative width and height. + pub fn transform_rect_bbox(self, rect: Rect) -> Rect { + let p00 = self * Point::new(rect.x0, rect.y0); + let p01 = self * Point::new(rect.x0, rect.y1); + let p10 = self * Point::new(rect.x1, rect.y0); + let p11 = self * Point::new(rect.x1, rect.y1); + Rect::from_points(p00, p01).union(Rect::from_points(p10, p11)) + } + + /// Is this map finite? + #[inline] + pub fn is_finite(&self) -> bool { + self.0[0].is_finite() + && self.0[1].is_finite() + && self.0[2].is_finite() + && self.0[3].is_finite() + && self.0[4].is_finite() + && self.0[5].is_finite() + } + + /// Is this map NaN? + #[inline] + pub fn is_nan(&self) -> bool { + self.0[0].is_nan() + || self.0[1].is_nan() + || self.0[2].is_nan() + || self.0[3].is_nan() + || self.0[4].is_nan() + || self.0[5].is_nan() + } +} + +impl Default for Affine { + #[inline] + fn default() -> Affine { + Affine::IDENTITY + } +} + +impl Mul for Affine { + type Output = Point; + + #[inline] + fn mul(self, other: Point) -> Point { + Point::new( + self.0[0] * other.x + self.0[2] * other.y + self.0[4], + self.0[1] * other.x + self.0[3] * other.y + self.0[5], + ) + } +} + +impl Mul for Affine { + type Output = Affine; + + #[inline] + fn mul(self, other: Affine) -> Affine { + Affine([ + self.0[0] * other.0[0] + self.0[2] * other.0[1], + self.0[1] * other.0[0] + self.0[3] * other.0[1], + self.0[0] * other.0[2] + self.0[2] * other.0[3], + self.0[1] * other.0[2] + self.0[3] * other.0[3], + self.0[0] * other.0[4] + self.0[2] * other.0[5] + self.0[4], + self.0[1] * other.0[4] + self.0[3] * other.0[5] + self.0[5], + ]) + } +} + +impl MulAssign for Affine { + #[inline] + fn mul_assign(&mut self, other: Affine) { + *self = self.mul(other); + } +} + +impl Mul for f64 { + type Output = Affine; + + #[inline] + fn mul(self, other: Affine) -> Affine { + Affine([ + self * other.0[0], + self * other.0[1], + self * other.0[2], + self * other.0[3], + self * other.0[4], + self * other.0[5], + ]) + } +} + +/// A 2D point. Derived from [kurbo](https://github.com/linebender/kurbo). +#[derive(Clone, Copy, Default, PartialEq)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Point { + /// The x coordinate. + pub x: f64, + /// The y coordinate. + pub y: f64, +} + +impl Point { + /// The point (0, 0). + pub const ZERO: Point = Point::new(0., 0.); + + /// The point at the origin; (0, 0). + pub const ORIGIN: Point = Point::new(0., 0.); + + /// Create a new `Point` with the provided `x` and `y` coordinates. + #[inline] + pub const fn new(x: f64, y: f64) -> Self { + Point { x, y } + } + + /// Convert this point into a `Vec2`. + #[inline] + pub const fn to_vec2(self) -> Vec2 { + Vec2::new(self.x, self.y) + } +} + +impl From<(f64, f64)> for Point { + #[inline] + fn from(v: (f64, f64)) -> Point { + Point { x: v.0, y: v.1 } + } +} + +impl From for (f64, f64) { + #[inline] + fn from(v: Point) -> (f64, f64) { + (v.x, v.y) + } +} + +impl Add for Point { + type Output = Point; + + #[inline] + fn add(self, other: Vec2) -> Self { + Point::new(self.x + other.x, self.y + other.y) + } +} + +impl AddAssign for Point { + #[inline] + fn add_assign(&mut self, other: Vec2) { + *self = Point::new(self.x + other.x, self.y + other.y) + } +} + +impl Sub for Point { + type Output = Point; + + #[inline] + fn sub(self, other: Vec2) -> Self { + Point::new(self.x - other.x, self.y - other.y) + } +} + +impl SubAssign for Point { + #[inline] + fn sub_assign(&mut self, other: Vec2) { + *self = Point::new(self.x - other.x, self.y - other.y) + } +} + +impl Add<(f64, f64)> for Point { + type Output = Point; + + #[inline] + fn add(self, (x, y): (f64, f64)) -> Self { + Point::new(self.x + x, self.y + y) + } +} + +impl AddAssign<(f64, f64)> for Point { + #[inline] + fn add_assign(&mut self, (x, y): (f64, f64)) { + *self = Point::new(self.x + x, self.y + y) + } +} + +impl Sub<(f64, f64)> for Point { + type Output = Point; + + #[inline] + fn sub(self, (x, y): (f64, f64)) -> Self { + Point::new(self.x - x, self.y - y) + } +} + +impl SubAssign<(f64, f64)> for Point { + #[inline] + fn sub_assign(&mut self, (x, y): (f64, f64)) { + *self = Point::new(self.x - x, self.y - y) + } +} + +impl Sub for Point { + type Output = Vec2; + + #[inline] + fn sub(self, other: Point) -> Vec2 { + Vec2::new(self.x - other.x, self.y - other.y) + } +} + +impl fmt::Debug for Point { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "({:?}, {:?})", self.x, self.y) + } +} + +/// A rectangle. Derived from [kurbo](https://github.com/linebender/kurbo). +#[derive(Clone, Copy, Default, PartialEq)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Rect { + /// The minimum x coordinate (left edge). + pub x0: f64, + /// The minimum y coordinate (top edge in y-down spaces). + pub y0: f64, + /// The maximum x coordinate (right edge). + pub x1: f64, + /// The maximum y coordinate (bottom edge in y-down spaces). + pub y1: f64, +} + +impl From<(Point, Point)> for Rect { + fn from(points: (Point, Point)) -> Rect { + Rect::from_points(points.0, points.1) + } +} + +impl From<(Point, Size)> for Rect { + fn from(params: (Point, Size)) -> Rect { + Rect::from_origin_size(params.0, params.1) + } +} + +impl Add for Rect { + type Output = Rect; + + #[inline] + fn add(self, v: Vec2) -> Rect { + Rect::new(self.x0 + v.x, self.y0 + v.y, self.x1 + v.x, self.y1 + v.y) + } +} + +impl Sub for Rect { + type Output = Rect; + + #[inline] + fn sub(self, v: Vec2) -> Rect { + Rect::new(self.x0 - v.x, self.y0 - v.y, self.x1 - v.x, self.y1 - v.y) + } +} + +impl fmt::Debug for Rect { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if f.alternate() { + write!( + f, + "Rect {{ origin: {:?}, size: {:?} }}", + self.origin(), + self.size() + ) + } else { + write!( + f, + "Rect {{ x0: {:?}, y0: {:?}, x1: {:?}, y1: {:?} }}", + self.x0, self.y0, self.x1, self.y1 + ) + } + } +} + +impl Rect { + /// The empty rectangle at the origin. + pub const ZERO: Rect = Rect::new(0., 0., 0., 0.); + + /// A new rectangle from minimum and maximum coordinates. + #[inline] + pub const fn new(x0: f64, y0: f64, x1: f64, y1: f64) -> Rect { + Rect { x0, y0, x1, y1 } + } + + /// A new rectangle from two points. + /// + /// The result will have non-negative width and height. + #[inline] + pub fn from_points(p0: impl Into, p1: impl Into) -> Rect { + let p0 = p0.into(); + let p1 = p1.into(); + Rect::new(p0.x, p0.y, p1.x, p1.y).abs() + } + + /// A new rectangle from origin and size. + /// + /// The result will have non-negative width and height. + #[inline] + pub fn from_origin_size(origin: impl Into, size: impl Into) -> Rect { + let origin = origin.into(); + Rect::from_points(origin, origin + size.into().to_vec2()) + } + + /// Create a new `Rect` with the same size as `self` and a new origin. + #[inline] + pub fn with_origin(self, origin: impl Into) -> Rect { + Rect::from_origin_size(origin, self.size()) + } + + /// Create a new `Rect` with the same origin as `self` and a new size. + #[inline] + pub fn with_size(self, size: impl Into) -> Rect { + Rect::from_origin_size(self.origin(), size) + } + + /// The width of the rectangle. + /// + /// Note: nothing forbids negative width. + #[inline] + pub fn width(&self) -> f64 { + self.x1 - self.x0 + } + + /// The height of the rectangle. + /// + /// Note: nothing forbids negative height. + #[inline] + pub fn height(&self) -> f64 { + self.y1 - self.y0 + } + + /// Returns the minimum value for the x-coordinate of the rectangle. + #[inline] + pub fn min_x(&self) -> f64 { + self.x0.min(self.x1) + } + + /// Returns the maximum value for the x-coordinate of the rectangle. + #[inline] + pub fn max_x(&self) -> f64 { + self.x0.max(self.x1) + } + + /// Returns the minimum value for the y-coordinate of the rectangle. + #[inline] + pub fn min_y(&self) -> f64 { + self.y0.min(self.y1) + } + + /// Returns the maximum value for the y-coordinate of the rectangle. + #[inline] + pub fn max_y(&self) -> f64 { + self.y0.max(self.y1) + } + + /// The origin of the rectangle. + /// + /// This is the top left corner in a y-down space and with + /// non-negative width and height. + #[inline] + pub fn origin(&self) -> Point { + Point::new(self.x0, self.y0) + } + + /// The size of the rectangle. + #[inline] + pub fn size(&self) -> Size { + Size::new(self.width(), self.height()) + } + + /// Take absolute value of width and height. + /// + /// The resulting rect has the same extents as the original, but is + /// guaranteed to have non-negative width and height. + #[inline] + pub fn abs(&self) -> Rect { + let Rect { x0, y0, x1, y1 } = *self; + Rect::new(x0.min(x1), y0.min(y1), x0.max(x1), y0.max(y1)) + } + + /// The area of the rectangle. + #[inline] + pub fn area(&self) -> f64 { + self.width() * self.height() + } + + /// Whether this rectangle has zero area. + /// + /// Note: a rectangle with negative area is not considered empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.area() == 0.0 + } + + /// Returns `true` if `point` lies within `self`. + #[inline] + pub fn contains(&self, point: Point) -> bool { + point.x >= self.x0 && point.x < self.x1 && point.y >= self.y0 && point.y < self.y1 + } + + /// The smallest rectangle enclosing two rectangles. + /// + /// Results are valid only if width and height are non-negative. + #[inline] + pub fn union(&self, other: Rect) -> Rect { + Rect::new( + self.x0.min(other.x0), + self.y0.min(other.y0), + self.x1.max(other.x1), + self.y1.max(other.y1), + ) + } + + /// Compute the union with one point. + /// + /// This method includes the perimeter of zero-area rectangles. + /// Thus, a succession of `union_pt` operations on a series of + /// points yields their enclosing rectangle. + /// + /// Results are valid only if width and height are non-negative. + pub fn union_pt(&self, pt: Point) -> Rect { + Rect::new( + self.x0.min(pt.x), + self.y0.min(pt.y), + self.x1.max(pt.x), + self.y1.max(pt.y), + ) + } + + /// The intersection of two rectangles. + /// + /// The result is zero-area if either input has negative width or + /// height. The result always has non-negative width and height. + #[inline] + pub fn intersect(&self, other: Rect) -> Rect { + let x0 = self.x0.max(other.x0); + let y0 = self.y0.max(other.y0); + let x1 = self.x1.min(other.x1); + let y1 = self.y1.min(other.y1); + Rect::new(x0, y0, x1.max(x0), y1.max(y0)) + } +} + +/// A 2D size. Derived from [kurbo](https://github.com/linebender/kurbo). +#[derive(Clone, Copy, Default, PartialEq)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Size { + /// The width. + pub width: f64, + /// The height. + pub height: f64, +} + +impl Size { + /// A size with zero width or height. + pub const ZERO: Size = Size::new(0., 0.); + + /// Create a new `Size` with the provided `width` and `height`. + #[inline] + pub const fn new(width: f64, height: f64) -> Self { + Size { width, height } + } + + /// Convert this size into a [`Vec2`], with `width` mapped to `x` and `height` + /// mapped to `y`. + #[inline] + pub const fn to_vec2(self) -> Vec2 { + Vec2::new(self.width, self.height) + } +} + +impl fmt::Debug for Size { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}W×{:?}H", self.width, self.height) + } +} + +impl MulAssign for Size { + #[inline] + fn mul_assign(&mut self, other: f64) { + *self = Size { + width: self.width * other, + height: self.height * other, + }; + } +} + +impl Mul for f64 { + type Output = Size; + + #[inline] + fn mul(self, other: Size) -> Size { + other * self + } +} + +impl Mul for Size { + type Output = Size; + + #[inline] + fn mul(self, other: f64) -> Size { + Size { + width: self.width * other, + height: self.height * other, + } + } +} + +impl DivAssign for Size { + #[inline] + fn div_assign(&mut self, other: f64) { + *self = Size { + width: self.width / other, + height: self.height / other, + }; + } +} + +impl Div for Size { + type Output = Size; + + #[inline] + fn div(self, other: f64) -> Size { + Size { + width: self.width / other, + height: self.height / other, + } + } +} + +impl Add for Size { + type Output = Size; + #[inline] + fn add(self, other: Size) -> Size { + Size { + width: self.width + other.width, + height: self.height + other.height, + } + } +} + +impl AddAssign for Size { + #[inline] + fn add_assign(&mut self, other: Size) { + *self = *self + other; + } +} + +impl Sub for Size { + type Output = Size; + #[inline] + fn sub(self, other: Size) -> Size { + Size { + width: self.width - other.width, + height: self.height - other.height, + } + } +} + +impl SubAssign for Size { + #[inline] + fn sub_assign(&mut self, other: Size) { + *self = *self - other; + } +} + +impl From<(f64, f64)> for Size { + #[inline] + fn from(v: (f64, f64)) -> Size { + Size { + width: v.0, + height: v.1, + } + } +} + +impl From for (f64, f64) { + #[inline] + fn from(v: Size) -> (f64, f64) { + (v.width, v.height) + } +} + +/// A 2D vector. Derived from [kurbo](https://github.com/linebender/kurbo). +/// +/// This is intended primarily for a vector in the mathematical sense, +/// but it can be interpreted as a translation, and converted to and +/// from a point (vector relative to the origin) and size. +#[derive(Clone, Copy, Default, Debug, PartialEq)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Vec2 { + /// The x-coordinate. + pub x: f64, + /// The y-coordinate. + pub y: f64, +} + +impl Vec2 { + /// The vector (0, 0). + pub const ZERO: Vec2 = Vec2::new(0., 0.); + + /// Create a new vector. + #[inline] + pub const fn new(x: f64, y: f64) -> Vec2 { + Vec2 { x, y } + } + + /// Convert this vector into a `Point`. + #[inline] + pub const fn to_point(self) -> Point { + Point::new(self.x, self.y) + } + + /// Convert this vector into a `Size`. + #[inline] + pub const fn to_size(self) -> Size { + Size::new(self.x, self.y) + } +} + +impl From<(f64, f64)> for Vec2 { + #[inline] + fn from(v: (f64, f64)) -> Vec2 { + Vec2 { x: v.0, y: v.1 } + } +} + +impl From for (f64, f64) { + #[inline] + fn from(v: Vec2) -> (f64, f64) { + (v.x, v.y) + } +} + +impl Add for Vec2 { + type Output = Vec2; + + #[inline] + fn add(self, other: Vec2) -> Vec2 { + Vec2 { + x: self.x + other.x, + y: self.y + other.y, + } + } +} + +impl AddAssign for Vec2 { + #[inline] + fn add_assign(&mut self, other: Vec2) { + *self = Vec2 { + x: self.x + other.x, + y: self.y + other.y, + } + } +} + +impl Sub for Vec2 { + type Output = Vec2; + + #[inline] + fn sub(self, other: Vec2) -> Vec2 { + Vec2 { + x: self.x - other.x, + y: self.y - other.y, + } + } +} + +impl SubAssign for Vec2 { + #[inline] + fn sub_assign(&mut self, other: Vec2) { + *self = Vec2 { + x: self.x - other.x, + y: self.y - other.y, + } + } +} + +impl Mul for Vec2 { + type Output = Vec2; + + #[inline] + fn mul(self, other: f64) -> Vec2 { + Vec2 { + x: self.x * other, + y: self.y * other, + } + } +} + +impl MulAssign for Vec2 { + #[inline] + fn mul_assign(&mut self, other: f64) { + *self = Vec2 { + x: self.x * other, + y: self.y * other, + }; + } +} + +impl Mul for f64 { + type Output = Vec2; + + #[inline] + fn mul(self, other: Vec2) -> Vec2 { + other * self + } +} + +impl Div for Vec2 { + type Output = Vec2; + + /// Note: division by a scalar is implemented by multiplying by the reciprocal. + /// + /// This is more efficient but has different roundoff behavior than division. + #[inline] + #[allow(clippy::suspicious_arithmetic_impl)] + fn div(self, other: f64) -> Vec2 { + self * other.recip() + } +} + +impl DivAssign for Vec2 { + #[inline] + fn div_assign(&mut self, other: f64) { + self.mul_assign(other.recip()); + } +} + +impl Neg for Vec2 { + type Output = Vec2; + + #[inline] + fn neg(self) -> Vec2 { + Vec2 { + x: -self.x, + y: -self.y, + } + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index fd8864535..b90b2211e 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -12,8 +12,6 @@ #[macro_use] extern crate num_derive; -pub use kurbo; -use kurbo::{Affine, Point, Rect}; #[cfg(feature = "serde")] use num_traits::FromPrimitive; use paste::paste; @@ -29,6 +27,9 @@ use std::num::{NonZeroU128, NonZeroU64}; #[cfg(feature = "serde")] use std::{fmt, mem::size_of_val}; +mod geometry; +pub use geometry::{Affine, Point, Rect, Size, Vec2}; + /// The type of an accessibility node. /// /// The majority of these roles come from the ARIA specification. Reference diff --git a/consumer/src/lib.rs b/consumer/src/lib.rs index 52009273f..bc32e177f 100644 --- a/consumer/src/lib.rs +++ b/consumer/src/lib.rs @@ -20,8 +20,9 @@ pub use text::{ #[cfg(test)] mod tests { - use accesskit::kurbo::{Affine, Rect, Vec2}; - use accesskit::{ActionHandler, ActionRequest, Node, NodeId, Role, Tree, TreeUpdate}; + use accesskit::{ + ActionHandler, ActionRequest, Affine, Node, NodeId, Rect, Role, Tree, TreeUpdate, Vec2, + }; use std::num::NonZeroU128; use crate::FilterResult; diff --git a/consumer/src/node.rs b/consumer/src/node.rs index ba05a63ed..59d3e6f0e 100644 --- a/consumer/src/node.rs +++ b/consumer/src/node.rs @@ -10,9 +10,9 @@ use std::{iter::FusedIterator, ops::Deref, sync::Arc}; -use accesskit::kurbo::{Affine, Point, Rect}; use accesskit::{ - Action, CheckedState, DefaultActionVerb, Live, Node as NodeData, NodeId, Role, TextSelection, + Action, Affine, CheckedState, DefaultActionVerb, Live, Node as NodeData, NodeId, Point, Rect, + Role, TextSelection, }; use crate::iterators::{ @@ -720,8 +720,7 @@ impl Deref for DetachedNode { #[cfg(test)] mod tests { - use accesskit::kurbo::{Point, Rect}; - use accesskit::{Node, NodeId, Role, Tree, TreeUpdate}; + use accesskit::{Node, NodeId, Point, Rect, Role, Tree, TreeUpdate}; use std::num::NonZeroU128; use crate::tests::*; diff --git a/consumer/src/text.rs b/consumer/src/text.rs index 73781885e..886c1743d 100644 --- a/consumer/src/text.rs +++ b/consumer/src/text.rs @@ -3,8 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit::kurbo::{Point, Rect}; -use accesskit::{NodeId, Role, TextDirection, TextPosition as WeakPosition}; +use accesskit::{NodeId, Point, Rect, Role, TextDirection, TextPosition as WeakPosition}; use std::{cmp::Ordering, iter::FusedIterator}; use crate::{FilterResult, Node, TreeState}; @@ -1013,8 +1012,7 @@ impl<'a> Node<'a> { #[cfg(test)] mod tests { - use accesskit::kurbo::{Point, Rect}; - use accesskit::{NodeId, TextSelection}; + use accesskit::{NodeId, Point, Rect, TextSelection}; use std::num::NonZeroU128; use crate::tests::NullActionHandler; @@ -1030,8 +1028,7 @@ mod tests { // This is based on an actual tree produced by egui. fn main_multiline_tree(selection: Option) -> crate::Tree { - use accesskit::kurbo::Affine; - use accesskit::{Action, Node, Role, TextDirection, Tree, TreeUpdate}; + use accesskit::{Action, Affine, Node, Role, TextDirection, Tree, TreeUpdate}; let update = TreeUpdate { nodes: vec![ diff --git a/consumer/src/tree.rs b/consumer/src/tree.rs index 57e9f3e63..1bf88c507 100644 --- a/consumer/src/tree.rs +++ b/consumer/src/tree.rs @@ -3,9 +3,8 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit::kurbo::Point; use accesskit::{ - Action, ActionData, ActionHandler, ActionRequest, Live, Node as NodeData, NodeId, + Action, ActionData, ActionHandler, ActionRequest, Live, Node as NodeData, NodeId, Point, TextSelection, Tree as TreeData, TreeUpdate, }; use parking_lot::{RwLock, RwLockWriteGuard}; diff --git a/platforms/macos/src/util.rs b/platforms/macos/src/util.rs index 39cd25de3..4de7b306a 100644 --- a/platforms/macos/src/util.rs +++ b/platforms/macos/src/util.rs @@ -3,7 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit::kurbo::{Point, Rect}; +use accesskit::{Point, Rect}; use accesskit_consumer::{Node, TextPosition, TextRange}; use objc2::foundation::{NSPoint, NSRange, NSRect, NSSize}; diff --git a/platforms/unix/src/adapter.rs b/platforms/unix/src/adapter.rs index d9fca322d..a95071ddd 100644 --- a/platforms/unix/src/adapter.rs +++ b/platforms/unix/src/adapter.rs @@ -14,7 +14,7 @@ use crate::{ node::{filter, filter_detached, NodeWrapper, PlatformNode, PlatformRootNode}, util::{AppContext, WindowBounds}, }; -use accesskit::{kurbo::Rect, ActionHandler, NodeId, Role, TreeUpdate}; +use accesskit::{ActionHandler, NodeId, Rect, Role, TreeUpdate}; use accesskit_consumer::{DetachedNode, FilterResult, Node, Tree, TreeChangeHandler, TreeState}; use async_channel::{Receiver, Sender}; use atspi::{Interface, InterfaceSet, State}; diff --git a/platforms/unix/src/atspi/mod.rs b/platforms/unix/src/atspi/mod.rs index fc85461d7..342f50de4 100644 --- a/platforms/unix/src/atspi/mod.rs +++ b/platforms/unix/src/atspi/mod.rs @@ -29,8 +29,8 @@ impl Rect { }; } -impl From for Rect { - fn from(value: accesskit::kurbo::Rect) -> Rect { +impl From for Rect { + fn from(value: accesskit::Rect) -> Rect { Rect { x: value.x0 as i32, y: value.y0 as i32, diff --git a/platforms/unix/src/node.rs b/platforms/unix/src/node.rs index 3baad926b..5ebbbcab5 100644 --- a/platforms/unix/src/node.rs +++ b/platforms/unix/src/node.rs @@ -15,10 +15,7 @@ use crate::{ }, util::{AppContext, WindowBounds}, }; -use accesskit::{ - kurbo::{Affine, Point, Rect}, - CheckedState, DefaultActionVerb, NodeId, Role, -}; +use accesskit::{Affine, CheckedState, DefaultActionVerb, NodeId, Point, Rect, Role}; use accesskit_consumer::{DetachedNode, FilterResult, Node, NodeState, Tree, TreeState}; use async_channel::Sender; use atspi::{ diff --git a/platforms/unix/src/util.rs b/platforms/unix/src/util.rs index 35c0fed5b..995b57a9b 100644 --- a/platforms/unix/src/util.rs +++ b/platforms/unix/src/util.rs @@ -4,7 +4,7 @@ // the LICENSE-MIT file), at your option. use crate::atspi::OwnedObjectAddress; -use accesskit::kurbo::{Point, Rect}; +use accesskit::{Point, Rect}; use atspi::CoordType; pub(crate) struct AppContext { diff --git a/platforms/windows/examples/hello_world.rs b/platforms/windows/examples/hello_world.rs index 2f02c871f..03a2a24da 100644 --- a/platforms/windows/examples/hello_world.rs +++ b/platforms/windows/examples/hello_world.rs @@ -1,8 +1,7 @@ // Based on the create_window sample in windows-samples-rs. -use accesskit::kurbo::Rect; use accesskit::{ - Action, ActionHandler, ActionRequest, DefaultActionVerb, Live, Node, NodeId, Role, Tree, + Action, ActionHandler, ActionRequest, DefaultActionVerb, Live, Node, NodeId, Rect, Role, Tree, TreeUpdate, }; use accesskit_windows::UiaInitMarker; diff --git a/platforms/windows/src/node.rs b/platforms/windows/src/node.rs index e621f4216..d104ec7e5 100644 --- a/platforms/windows/src/node.rs +++ b/platforms/windows/src/node.rs @@ -10,8 +10,7 @@ #![allow(non_upper_case_globals)] -use accesskit::kurbo::Point; -use accesskit::{CheckedState, Live, NodeId, NodeIdContent, Role}; +use accesskit::{CheckedState, Live, NodeId, NodeIdContent, Point, Role}; use accesskit_consumer::{DetachedNode, FilterResult, Node, NodeState, Tree, TreeState}; use arrayvec::ArrayVec; use paste::paste; diff --git a/platforms/windows/src/util.rs b/platforms/windows/src/util.rs index 05228eae6..c34b44361 100644 --- a/platforms/windows/src/util.rs +++ b/platforms/windows/src/util.rs @@ -3,7 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit::kurbo::Point; +use accesskit::Point; use std::{convert::TryInto, mem::ManuallyDrop}; use windows::{ core::*, diff --git a/platforms/winit/examples/simple.rs b/platforms/winit/examples/simple.rs index 88586591d..76ea8a6db 100644 --- a/platforms/winit/examples/simple.rs +++ b/platforms/winit/examples/simple.rs @@ -1,6 +1,5 @@ -use accesskit::kurbo::Rect; use accesskit::{ - Action, ActionRequest, DefaultActionVerb, Live, Node, NodeId, Role, Tree, TreeUpdate, + Action, ActionRequest, DefaultActionVerb, Live, Node, NodeId, Rect, Role, Tree, TreeUpdate, }; use accesskit_winit::{ActionRequestEvent, Adapter}; use std::{ diff --git a/platforms/winit/src/lib.rs b/platforms/winit/src/lib.rs index 2fdcb80b7..9d0c18975 100644 --- a/platforms/winit/src/lib.rs +++ b/platforms/winit/src/lib.rs @@ -84,7 +84,7 @@ impl Adapter { ))] #[must_use] pub fn on_event(&self, window: &Window, event: &WindowEvent) -> bool { - use accesskit::kurbo::Rect; + use accesskit::Rect; match event { WindowEvent::Moved(outer_position) => { diff --git a/platforms/winit/src/platform_impl/unix.rs b/platforms/winit/src/platform_impl/unix.rs index 3a0d1c0d8..5a58347fe 100644 --- a/platforms/winit/src/platform_impl/unix.rs +++ b/platforms/winit/src/platform_impl/unix.rs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0 (found in // the LICENSE-APACHE file). -use accesskit::{kurbo::Rect, ActionHandler, TreeUpdate}; +use accesskit::{ActionHandler, Rect, TreeUpdate}; use accesskit_unix::Adapter as UnixAdapter; use winit::window::Window; From bd14914b8d0f365a0237c098995df7363b5b3321 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sat, 14 Jan 2023 09:47:49 -0600 Subject: [PATCH 28/45] Use enumn rather tha num-traits and num-derive --- Cargo.lock | 34 ++++++++++++---------------------- common/Cargo.toml | 5 ++--- common/src/lib.rs | 22 ++++++++-------------- 3 files changed, 22 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac4829778..3a9f095f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,8 +6,7 @@ version = 3 name = "accesskit" version = "0.8.1" dependencies = [ - "num-derive", - "num-traits", + "enumn", "paste", "schemars", "serde", @@ -627,6 +626,17 @@ dependencies = [ "syn", ] +[[package]] +name = "enumn" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88bcb3a067a6555d577aba299e75eff9942da276e6506fc6274327daa026132" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -1059,26 +1069,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "num-derive" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - [[package]] name = "num_enum" version = "0.5.7" diff --git a/common/Cargo.toml b/common/Cargo.toml index a9ee9a287..e87ba14ff 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -14,11 +14,10 @@ edition = "2021" features = ["schemars", "serde"] [dependencies] -num-derive = { version = "0.3", optional = true } -num-traits = { version = "0.2", optional = true } +enumn = { version = "0.1.6", optional = true } paste = "1.0" schemars = { version = "0.8.7", optional = true } serde = { version = "1.0", features = ["derive"], optional = true } [features] -serde = ["dep:serde", "num-derive", "num-traits"] +serde = ["dep:serde", "enumn"] diff --git a/common/src/lib.rs b/common/src/lib.rs index b90b2211e..9f356b851 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -8,12 +8,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE.chromium file. -#[cfg(feature = "serde")] -#[macro_use] -extern crate num_derive; - -#[cfg(feature = "serde")] -use num_traits::FromPrimitive; use paste::paste; #[cfg(feature = "schemars")] use schemars::JsonSchema; @@ -260,7 +254,7 @@ impl Default for Role { /// In contrast to [`DefaultActionVerb`], these describe what happens to the /// object, e.g. "focus". #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, FromPrimitive))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, enumn::N))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[repr(u8)] @@ -355,8 +349,8 @@ impl Serialize for Actions { S: Serializer, { let mut seq = serializer.serialize_seq(None)?; - for i in 0..(size_of_val(&self.0) * 8) { - if let Some(action) = Action::from_usize(i) { + for i in 0..((size_of_val(&self.0) as u8) * 8) { + if let Some(action) = Action::n(i) { if (self.0 & action.mask()) != 0 { seq.serialize_element(&action)?; } @@ -666,7 +660,7 @@ pub struct TextSelection { } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, FromPrimitive))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, enumn::N))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[repr(u8)] @@ -730,7 +724,7 @@ enum PropertyValue { } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, FromPrimitive))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, enumn::N))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[repr(u8)] @@ -1741,8 +1735,8 @@ impl Serialize for Node { (role, Role), (actions, Actions) }); - for i in 0..(size_of_val(&self.flags) * 8) { - if let Some(flag) = Flag::from_usize(i) { + for i in 0..((size_of_val(&self.flags) as u8) * 8) { + if let Some(flag) = Flag::n(i) { if (self.flags & flag.mask()) != 0 { map.serialize_entry(&flag, &true)?; } @@ -1773,7 +1767,7 @@ impl Serialize for Node { if index == PropertyId::Unset as u8 { continue; } - let id = PropertyId::from_usize(id).unwrap(); + let id = PropertyId::n(id as _).unwrap(); serialize_property!(self, map, index, id, { NodeIdVec, NodeId, From 28d5421eb92ab775574c3abe4623c42e912c57b8 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sat, 14 Jan 2023 11:26:42 -0600 Subject: [PATCH 29/45] Eliminate the dependency on paste in the accesskit crate. No mandatory dependencies in that crate now! --- Cargo.lock | 1 - common/Cargo.toml | 1 - common/src/lib.rs | 421 +++++++++++++++++++++------------------------- 3 files changed, 191 insertions(+), 232 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a9f095f1..448f01784 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,7 +7,6 @@ name = "accesskit" version = "0.8.1" dependencies = [ "enumn", - "paste", "schemars", "serde", ] diff --git a/common/Cargo.toml b/common/Cargo.toml index e87ba14ff..16324e3fc 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -15,7 +15,6 @@ features = ["schemars", "serde"] [dependencies] enumn = { version = "0.1.6", optional = true } -paste = "1.0" schemars = { version = "0.8.7", optional = true } serde = { version = "1.0", features = ["derive"], optional = true } diff --git a/common/src/lib.rs b/common/src/lib.rs index 9f356b851..78747f5fc 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -8,7 +8,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE.chromium file. -use paste::paste; #[cfg(feature = "schemars")] use schemars::JsonSchema; #[cfg(feature = "serde")] @@ -838,190 +837,155 @@ impl Default for PropertyIndices { } macro_rules! flag_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident)),+) => { - paste! { - impl Node { - $($(#[$doc])* - pub fn [< is_ $base_name >](&self) -> bool { - (self.flags & (Flag::$id).mask()) != 0 - } - pub fn [< set_ $base_name >](&mut self) { - self.flags |= (Flag::$id).mask(); - } - pub fn [< clear_ $base_name >](&mut self) { - self.flags &= !((Flag::$id).mask()); - })* + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { + impl Node { + $($(#[$doc])* + pub fn $getter(&self) -> bool { + (self.flags & (Flag::$id).mask()) != 0 } - } - } -} - -macro_rules! irregular_flag_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident)),+) => { - paste! { - impl Node { - $($(#[$doc])* - pub fn $base_name(&self) -> bool { - (self.flags & (Flag::$id).mask()) != 0 - } - pub fn [< set_ $base_name >](&mut self) { - self.flags |= (Flag::$id).mask(); - } - pub fn [< clear_ $base_name >](&mut self) { - self.flags &= !((Flag::$id).mask()); - })* + pub fn $setter(&mut self) { + self.flags |= (Flag::$id).mask(); } + pub fn $clearer(&mut self) { + self.flags &= !((Flag::$id).mask()); + })* } } } macro_rules! optional_bool_methods { - ($($(#[$doc:meta])* ($base_name:ident)),+) => { - paste! { - impl Node { - $($(#[$doc])* - pub fn [< is_ $base_name >](&self) -> Option { - self.$base_name - } - pub fn [< set_ $base_name >](&mut self, value: bool) { - self.$base_name = Some(value); - } - pub fn [< clear_ $base_name >](&mut self) { - self.$base_name = None; - })* + ($($(#[$doc:meta])* ($field:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { + impl Node { + $($(#[$doc])* + pub fn $getter(&self) -> Option { + self.$field + } + pub fn $setter(&mut self, value: bool) { + self.$field = Some(value); } + pub fn $clearer(&mut self) { + self.$field = None; + })* } } } macro_rules! optional_enum_methods { - ($($(#[$doc:meta])* ($base_name:ident, $type:ty)),+) => { - paste! { - impl Node { - $($(#[$doc])* - pub fn $base_name(&self) -> Option<$type> { - self.$base_name - } - pub fn [< set_ $base_name >](&mut self, value: $type) { - self.$base_name = Some(value); - } - pub fn [< clear_ $base_name >](&mut self) { - self.$base_name = None; - })* + ($($(#[$doc:meta])* ($field:ident, $type:ty, $setter:ident, $clearer:ident)),+) => { + impl Node { + $($(#[$doc])* + pub fn $field(&self) -> Option<$type> { + self.$field + } + pub fn $setter(&mut self, value: $type) { + self.$field = Some(value); } + pub fn $clearer(&mut self) { + self.$field = None; + })* } } } macro_rules! property_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident, $type_method_base:ident, $getter_result:ty, $setter_param:ty)),+) => { - paste! { - impl Node { - $($(#[$doc])* - pub fn $base_name(&self) -> $getter_result { - self.[< get_ $type_method_base >](PropertyId::$id) - } - pub fn [< set_ $base_name >](&mut self, value: $setter_param) { - self.[< set_ $type_method_base >](PropertyId::$id, value); - } - pub fn [< clear_ $base_name >](&mut self) { - self.clear_property(PropertyId::$id); - })* + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $type_getter:ident, $getter_result:ty, $setter:ident, $type_setter:ident, $setter_param:ty, $clearer:ident)),+) => { + impl Node { + $($(#[$doc])* + pub fn $getter(&self) -> $getter_result { + self.$type_getter(PropertyId::$id) + } + pub fn $setter(&mut self, value: $setter_param) { + self.$type_setter(PropertyId::$id, value); } + pub fn $clearer(&mut self) { + self.clear_property(PropertyId::$id); + })* } } } macro_rules! vec_property_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident, $type_method_base:ident, $item_type:ty)),+) => { - paste! { - impl Node { - $($(#[$doc])* - pub fn $base_name(&self) -> &[$item_type] { - self.[< get_ $type_method_base >](PropertyId::$id) - } - pub fn [< set_ $base_name >](&mut self, value: impl Into>) { - self.[< set_ $type_method_base >](PropertyId::$id, value); - } - pub fn [< push_to_ $base_name >](&mut self, item: $item_type) { - self.[< push_to_ $type_method_base >](PropertyId::$id, item); - } - pub fn [< clear_ $base_name >](&mut self) { - self.clear_property(PropertyId::$id); - })* - } + ($($(#[$doc:meta])* ($id:ident, $item_type:ty, $getter:ident, $type_getter:ident, $setter:ident, $type_setter:ident, $pusher:ident, $type_pusher:ident, $clearer:ident)),+) => { + $(property_methods! { + $(#[$doc])* + ($id, $getter, $type_getter, &[$item_type], $setter, $type_setter, impl Into>, $clearer) } + impl Node { + pub fn $pusher(&mut self, item: $item_type) { + self.$type_pusher(PropertyId::$id, item); + } + })* } } macro_rules! node_id_vec_property_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident)),+) => { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $pusher:ident, $clearer:ident)),+) => { $(vec_property_methods! { $(#[$doc])* - ($base_name, $id, node_id_vec, NodeId) + ($id, NodeId, $getter, get_node_id_vec, $setter, set_node_id_vec, $pusher, push_to_node_id_vec, $clearer) })* } } macro_rules! node_id_property_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident)),+) => { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* - ($base_name, $id, node_id, Option, NodeId) + ($id, $getter, get_node_id, Option, $setter, set_node_id, NodeId, $clearer) })* } } macro_rules! string_property_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident)),+) => { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* - ($base_name, $id, string, Option<&str>, impl Into>) + ($id, $getter, get_string, Option<&str>, $setter, set_string, impl Into>, $clearer) })* } } macro_rules! f64_property_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident)),+) => { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* - ($base_name, $id, f64, Option, f64) + ($id, $getter, get_f64, Option, $setter, set_f64, f64, $clearer) })* } } macro_rules! usize_property_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident)),+) => { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* - ($base_name, $id, usize, Option, usize) + ($id, $getter, get_usize, Option, $setter, set_usize, usize, $clearer) })* } } macro_rules! color_property_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident)),+) => { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* - ($base_name, $id, color, Option, u32) + ($id, $getter, get_color, Option, $setter, set_color, u32, $clearer) })* } } macro_rules! length_slice_property_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident)),+) => { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* - ($base_name, $id, length_slice, &[u8], impl Into>) + ($id, $getter, get_length_slice, &[u8], $setter, set_length_slice, impl Into>, $clearer) })* } } macro_rules! coord_slice_property_methods { - ($($(#[$doc:meta])* ($base_name:ident, $id:ident)),+) => { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* - ($base_name, $id, coord_slice, Option<&[f32]>, impl Into>) + ($id, $getter, get_coord_slice, Option<&[f32]>, $setter, set_coord_slice, impl Into>, $clearer) })* } } @@ -1260,62 +1224,59 @@ impl Node { } flag_methods! { - (autofill_available, AutofillAvailable), - (default, Default), - (editable, Editable), - (hovered, Hovered), + (AutofillAvailable, is_autofill_available, set_autofill_available, clear_autofill_available), + (Default, is_default, set_default, clear_default), + (Editable, is_editable, set_editable, clear_editable), + (Hovered, is_hovered, set_hovered, clear_hovered), /// Exclude this node and its descendants from the tree presented to /// assistive technologies, and from hit testing. - (hidden, Hidden), - (linked, Linked), - (multiline, Multiline), - (multiselectable, Multiselectable), - (protected, Protected), - (required, Required), - (visited, Visited), - (busy, Busy), - (live_atomic, LiveAtomic), + (Hidden, is_hidden, set_hidden, clear_hidden), + (Linked, is_linked, set_linked, clear_linked), + (Multiline, is_multiline, set_multiline, clear_multiline), + (Multiselectable, is_multiselectable, set_multiselectable, clear_multiselectable), + (Protected, is_protected, set_protected, clear_protected), + (Required, is_required, set_required, clear_required), + (Visited, is_visited, set_visited, clear_visited), + (Busy, is_busy, set_busy, clear_busy), + (LiveAtomic, is_live_atomic, set_live_atomic, clear_live_atomic), /// If a dialog box is marked as explicitly modal. - (modal, Modal), + (Modal, is_modal, set_modal, clear_modal), /// Indicates this node is user-scrollable, e.g. `overflow: scroll|auto`, as /// opposed to only programmatically scrollable, like `overflow: hidden`, or /// not scrollable at all, e.g. `overflow: visible`. - (scrollable, Scrollable), + (Scrollable, is_scrollable, set_scrollable, clear_scrollable), /// Indicates whether this node is selected due to selection follows focus. - (selected_from_focus, SelectedFromFocus), + (SelectedFromFocus, is_selected_from_focus, set_selected_from_focus, clear_selected_from_focus), /// This element allows touches to be passed through when a screen reader /// is in touch exploration mode, e.g. a virtual keyboard normally /// behaves this way. - (touch_pass_through, TouchPassThrough), + (TouchPassThrough, is_touch_pass_through, set_touch_pass_through, clear_touch_pass_through), /// Use for a textbox that allows focus/selection but not input. - (read_only, ReadOnly), + (ReadOnly, is_read_only, set_read_only, clear_read_only), /// Use for a control or group of controls that disallows input. - (disabled, Disabled), - (bold, Bold), - (italic, Italic) -} - -irregular_flag_methods! { + (Disabled, is_disabled, set_disabled, clear_disabled), + (Bold, is_bold, set_bold, clear_bold), + (Italic, is_italic, set_italic, clear_italic), /// Set on a canvas element if it has fallback content. - (canvas_has_fallback, CanvasHasFallback), + (CanvasHasFallback, canvas_has_fallback, set_canvas_has_fallback, clear_canvas_has_fallback), /// Indicates that this node clips its children, i.e. may have /// `overflow: hidden` or clip children by default. - (clips_children, ClipsChildren), + (ClipsChildren, clips_children, set_clips_children, clear_clips_children), /// Indicates whether this node causes a hard line-break /// (e.g. block level elements, or `
`). - (is_line_breaking_object, IsLineBreakingObject), + (IsLineBreakingObject, is_line_breaking_object, set_is_line_breaking_object, clear_is_line_breaking_object), /// Indicates whether this node causes a page break. - (is_page_breaking_object, IsPageBreakingObject), - (is_spelling_error, IsSpellingError), - (is_grammar_error, IsGrammarError), - (is_search_match, IsSearchMatch), - (is_suggestion, IsSuggestion), + (IsPageBreakingObject, is_page_breaking_object, set_is_page_breaking_object, clear_is_page_breaking_object), + (IsSpellingError, is_spelling_error, set_is_spelling_error, clear_is_spelling_error), + (IsGrammarError, is_grammar_error, set_is_grammar_error, clear_is_grammar_error), + (IsSearchMatch, is_search_match, set_is_search_match, clear_is_search_match), + (IsSuggestion, is_suggestion, set_is_suggestion, clear_is_suggestion), /// The object functions as a text field which exposes its descendants. /// /// Use cases include the root of a content-editable region, an ARIA /// textbox which isn't currently editable and which has interactive /// descendants, and a `` element that has "design-mode" set to "on". - (is_nonatomic_text_field_root, IsNonatomicTextFieldRoot) + (IsNonatomicTextFieldRoot, is_nonatomic_text_field_root, set_is_nonatomic_text_field_root, clear_is_nonatomic_text_field_root) } optional_bool_methods! { @@ -1323,7 +1284,7 @@ optional_bool_methods! { /// /// Setting this to `false` means the node is collapsed; omitting it means this state /// isn't applicable. - (expanded), + (expanded, is_expanded, set_expanded, clear_expanded), /// Indicates whether this node is selected or unselected. /// @@ -1334,139 +1295,139 @@ optional_bool_methods! { /// to announce "not selected". The ambiguity of this flag /// in platform accessibility APIs has made extraneous /// "not selected" announcements a common annoyance. - (selected) + (selected, is_selected, set_selected, clear_selected) } optional_enum_methods! { /// What information was used to compute the object's name. - (name_from, NameFrom), + (name_from, NameFrom, set_name_from, clear_name_from), /// What information was used to compute the object's description. - (description_from, DescriptionFrom), - (invalid, Invalid), - (checked_state, CheckedState), - (live, Live), - (default_action_verb, DefaultActionVerb), - (text_direction, TextDirection), - (orientation, Orientation), - (sort_direction, SortDirection), - (aria_current, AriaCurrent), - (has_popup, HasPopup), + (description_from, DescriptionFrom, set_description_from, clear_description_from), + (invalid, Invalid, set_invalid, clear_invalid), + (checked_state, CheckedState, set_checked_state, clear_checked_state), + (live, Live, set_live, clear_live), + (default_action_verb, DefaultActionVerb, set_default_action_verb, clear_default_action_verb), + (text_direction, TextDirection, set_text_direction, clear_text_direction), + (orientation, Orientation, set_orientation, clear_orientation), + (sort_direction, SortDirection, set_sort_direction, clear_sort_direction), + (aria_current, AriaCurrent, set_aria_current, clear_aria_current), + (has_popup, HasPopup, set_has_popup, clear_has_popup), /// The list style type. Only available on list items. - (list_style, ListStyle), - (text_align, TextAlign), - (vertical_offset, VerticalOffset), - (overline, TextDecoration), - (strikethrough, TextDecoration), - (underline, TextDecoration) + (list_style, ListStyle, set_list_style, clear_list_style), + (text_align, TextAlign, set_text_align, clear_text_align), + (vertical_offset, VerticalOffset, set_vertical_offset, clear_vertical_offset), + (overline, TextDecoration, set_overline, clear_overline), + (strikethrough, TextDecoration, set_strikethrough, clear_strikethrough), + (underline, TextDecoration, set_underline, clear_underline) } node_id_vec_property_methods! { - (children, Children), + (Children, children, set_children, push_to_children, clear_children), /// Ids of nodes that are children of this node logically, but are /// not children of this node in the tree structure. As an example, /// a table cell is a child of a row, and an 'indirect' child of a /// column. - (indirect_children, IndirectChildren), - (controls, Controls), - (details, Details), - (described_by, DescribedBy), - (flow_to, FlowTo), - (labelled_by, LabelledBy), + (IndirectChildren, indirect_children, set_indirect_children, push_to_indirect_children, clear_indirect_children), + (Controls, controls, set_controls, push_to_controls, clear_controls), + (Details, details, set_details, push_to_details, clear_details), + (DescribedBy, described_by, set_described_by, push_to_described_by, clear_described_by), + (FlowTo, flow_to, set_flow_to, push_to_flow_to, clear_flow_to), + (LabelledBy, labelled_by, set_labelled_by, push_to_labelled_by, clear_labelled_by), /// On radio buttons this should be set to a list of all of the buttons /// in the same group as this one, including this radio button itself. - (radio_group, RadioGroup) + (RadioGroup, radio_group, set_radio_group, push_to_radio_group, clear_radio_group) } node_id_property_methods! { - (active_descendant, ActiveDescendant), - (error_message, ErrorMessage), - (in_page_link_target, InPageLinkTarget), - (member_of, MemberOf), - (next_on_line, NextOnLine), - (previous_on_line, PreviousOnLine), - (popup_for, PopupFor), - (table_header, TableHeader), - (table_row_header, TableRowHeader), - (table_column_header, TableColumnHeader), - (next_focus, NextFocus), - (previous_focus, PreviousFocus) + (ActiveDescendant, active_descendant, set_active_descendant, clear_active_descendant), + (ErrorMessage, error_message, set_error_message, clear_error_message), + (InPageLinkTarget, in_page_link_target, set_in_page_link_target, clear_in_page_link_target), + (MemberOf, member_of, set_member_of, clear_member_of), + (NextOnLine, next_on_line, set_next_on_line, clear_next_on_line), + (PreviousOnLine, previous_on_line, set_previous_on_line, clear_previous_on_line), + (PopupFor, popup_for, set_popup_for, clear_popup_for), + (TableHeader, table_header, set_table_header, clear_table_header), + (TableRowHeader, table_row_header, set_table_row_header, clear_table_row_header), + (TableColumnHeader, table_column_header, set_table_column_header, clear_table_column_header), + (NextFocus, next_focus, set_next_focus, clear_next_focus), + (PreviousFocus, previous_focus, set_previous_focus, clear_previous_focus) } string_property_methods! { - (name, Name), - (description, Description), - (value, Value), - (access_key, AccessKey), - (auto_complete, AutoComplete), - (checked_state_description, CheckedStateDescription), - (class_name, ClassName), - (css_display, CssDisplay), + (Name, name, set_name, clear_name), + (Description, description, set_description, clear_description), + (Value, value, set_value, clear_value), + (AccessKey, access_key, set_access_key, clear_access_key), + (AutoComplete, auto_complete, set_auto_complete, clear_auto_complete), + (CheckedStateDescription, checked_state_description, set_checked_state_description, clear_checked_state_description), + (ClassName, class_name, set_class_name, clear_class_name), + (CssDisplay, css_display, set_css_display, clear_css_display), /// Only present when different from parent. - (font_family, FontFamily), - (html_tag, HtmlTag), + (FontFamily, font_family, set_font_family, clear_font_family), + (HtmlTag, html_tag, set_html_tag, clear_html_tag), /// Inner HTML of an element. Only used for a top-level math element, /// to support third-party math accessibility products that parse MathML. - (inner_html, InnerHtml), - (input_type, InputType), - (key_shortcuts, KeyShortcuts), + (InnerHtml, inner_html, set_inner_html, clear_inner_html), + (InputType, input_type, set_input_type, clear_input_type), + (KeyShortcuts, key_shortcuts, set_key_shortcuts, clear_key_shortcuts), /// Only present when different from parent. - (language, Language), - (live_relevant, LiveRelevant), + (Language, language, set_language, clear_language), + (LiveRelevant, live_relevant, set_live_relevant, clear_live_relevant), /// Only if not already exposed in [`name`] ([`NameFrom::Placeholder`]). /// /// [`name`]: Node::name - (placeholder, Placeholder), - (aria_role, AriaRole), - (role_description, RoleDescription), + (Placeholder, placeholder, set_placeholder, clear_placeholder), + (AriaRole, aria_role, set_aria_role, clear_aria_role), + (RoleDescription, role_description, set_role_description, clear_role_description), /// Only if not already exposed in [`name`] ([`NameFrom::Title`]). /// /// [`name`]: Node::name - (tooltip, Tooltip), - (url, Url) + (Tooltip, tooltip, set_tooltip, clear_tooltip), + (Url, url, set_url, clear_url) } f64_property_methods! { - (scroll_x, ScrollX), - (scroll_x_min, ScrollXMin), - (scroll_x_max, ScrollXMax), - (scroll_y, ScrollY), - (scroll_y_min, ScrollYMin), - (scroll_y_max, ScrollYMax), - (numeric_value, NumericValue), - (min_numeric_value, MinNumericValue), - (max_numeric_value, MaxNumericValue), - (numeric_value_step, NumericValueStep), - (numeric_value_jump, NumericValueJump), + (ScrollX, scroll_x, set_scroll_x, clear_scroll_x), + (ScrollXMin, scroll_x_min, set_scroll_x_min, clear_scroll_x_min), + (ScrollXMax, scroll_x_max, set_scroll_x_max, clear_scroll_x_max), + (ScrollY, scroll_y, set_scroll_y, clear_scroll_y), + (ScrollYMin, scroll_y_min, set_scroll_y_min, clear_scroll_y_min), + (ScrollYMax, scroll_y_max, set_scroll_y_max, clear_scroll_y_max), + (NumericValue, numeric_value, set_numeric_value, clear_numeric_value), + (MinNumericValue, min_numeric_value, set_min_numeric_value, clear_min_numeric_value), + (MaxNumericValue, max_numeric_value, set_max_numeric_value, clear_max_numeric_value), + (NumericValueStep, numeric_value_step, set_numeric_value_step, clear_numeric_value_step), + (NumericValueJump, numeric_value_jump, set_numeric_value_jump, clear_numeric_value_jump), /// Font size is in pixels. - (font_size, FontSize), + (FontSize, font_size, set_font_size, clear_font_size), /// Font weight can take on any arbitrary numeric value. Increments of 100 in /// range `[0, 900]` represent keywords such as light, normal, bold, etc. - (font_weight, FontWeight), + (FontWeight, font_weight, set_font_weight, clear_font_weight), /// The indentation of the text, in mm. - (text_indent, TextIndent) + (TextIndent, text_indent, set_text_indent, clear_text_indent) } usize_property_methods! { - (table_row_count, TableRowCount), - (table_column_count, TableColumnCount), - (table_row_index, TableRowIndex), - (table_column_index, TableColumnIndex), - (table_cell_column_index, TableCellColumnIndex), - (table_cell_column_span, TableCellColumnSpan), - (table_cell_row_index, TableCellRowIndex), - (table_cell_row_span, TableCellRowSpan), - (hierarchical_level, HierarchicalLevel), - (size_of_set, SizeOfSet), - (position_in_set, PositionInSet) + (TableRowCount, table_row_count, set_table_row_count, clear_table_row_count), + (TableColumnCount, table_column_count, set_table_column_count, clear_table_column_count), + (TableRowIndex, table_row_index, set_table_row_index, clear_table_row_index), + (TableColumnIndex, table_column_index, set_table_column_index, clear_table_column_index), + (TableCellColumnIndex, table_cell_column_index, set_table_cell_column_index, clear_table_cell_column_index), + (TableCellColumnSpan, table_cell_column_span, set_table_cell_column_span, clear_table_cell_column_span), + (TableCellRowIndex, table_cell_row_index, set_table_cell_row_index, clear_table_cell_row_index), + (TableCellRowSpan, table_cell_row_span, set_table_cell_row_span, clear_table_cell_row_span), + (HierarchicalLevel, hierarchical_level, set_hierarchical_level, clear_hierarchical_level), + (SizeOfSet, size_of_set, set_size_of_set, clear_size_of_set), + (PositionInSet, position_in_set, set_position_in_set, clear_position_in_set) } color_property_methods! { /// For [`Role::ColorWell`], specifies the selected color in RGBA. - (color_value, ColorValue), + (ColorValue, color_value, set_color_value, clear_color_value), /// Background color in RGBA. - (background_color, BackgroundColor), + (BackgroundColor, background_color, set_background_color, clear_background_color), /// Foreground color in RGBA. - (foreground_color, ForegroundColor) + (ForegroundColor, foreground_color, set_foreground_color, clear_foreground_color) } length_slice_property_methods! { @@ -1488,7 +1449,7 @@ length_slice_property_methods! { /// selection should be on the line break, not after it. /// /// [`value`]: Node::value - (character_lengths, CharacterLengths), + (CharacterLengths, character_lengths, set_character_lengths, clear_character_lengths), /// For inline text. The length of each word in characters, as defined /// in [`character_lengths`]. The sum of these lengths must equal @@ -1512,7 +1473,7 @@ length_slice_property_methods! { /// word boundaries itself. /// /// [`character_lengths`]: Node::character_lengths - (word_lengths, WordLengths) + (WordLengths, word_lengths, set_word_lengths, clear_word_lengths) } coord_slice_property_methods! { @@ -1532,7 +1493,7 @@ coord_slice_property_methods! { /// /// [`text_direction`]: Node::text_direction /// [`character_lengths`]: Node::character_lengths - (character_positions, CharacterPositions), + (CharacterPositions, character_positions, set_character_positions, clear_character_positions), /// For inline text. This is the advance width of each character, /// in the direction given by [`text_direction`], in the coordinate @@ -1552,7 +1513,7 @@ coord_slice_property_methods! { /// /// [`text_direction`]: Node::text_direction /// [`character_lengths`]: Node::character_lengths - (character_widths, CharacterWidths) + (CharacterWidths, character_widths, set_character_widths, clear_character_widths) } property_methods! { @@ -1568,7 +1529,7 @@ property_methods! { /// pixels, with the y coordinate being top-down. /// /// [`bounds`]: Node::bounds - (transform, Transform, affine, Option<&Affine>, impl Into>), + (Transform, transform, get_affine, Option<&Affine>, set_transform, set_affine, impl Into>, clear_transform), /// The bounding box of this node, in the node's coordinate space. /// This property does not affect the coordinate space of either this node @@ -1580,7 +1541,7 @@ property_methods! { /// the tree's container (e.g. window). /// /// [`transform`]: Node::transform - (bounds, Bounds, rect, Option, Rect) + (Bounds, bounds, get_rect, Option, set_bounds, set_rect, Rect, clear_bounds) } impl Node { From 5c428424c3dc3e7448208c4eb52547c00d0186aa Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sat, 14 Jan 2023 11:33:27 -0600 Subject: [PATCH 30/45] Fix awkward method naming forced by the previous macros --- common/src/lib.rs | 16 ++++++++-------- consumer/src/node.rs | 8 ++++---- consumer/src/tree.rs | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 78747f5fc..6619d9364 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1322,17 +1322,17 @@ optional_enum_methods! { } node_id_vec_property_methods! { - (Children, children, set_children, push_to_children, clear_children), + (Children, children, set_children, push_child, clear_children), /// Ids of nodes that are children of this node logically, but are /// not children of this node in the tree structure. As an example, /// a table cell is a child of a row, and an 'indirect' child of a /// column. - (IndirectChildren, indirect_children, set_indirect_children, push_to_indirect_children, clear_indirect_children), - (Controls, controls, set_controls, push_to_controls, clear_controls), - (Details, details, set_details, push_to_details, clear_details), - (DescribedBy, described_by, set_described_by, push_to_described_by, clear_described_by), - (FlowTo, flow_to, set_flow_to, push_to_flow_to, clear_flow_to), - (LabelledBy, labelled_by, set_labelled_by, push_to_labelled_by, clear_labelled_by), + (IndirectChildren, indirect_children, set_indirect_children, push_indirect_child, clear_indirect_children), + (Controls, controls, set_controls, push_controlled, clear_controls), + (Details, details, set_details, push_detail, clear_details), + (DescribedBy, described_by, set_described_by, push_described_by, clear_described_by), + (FlowTo, flow_to, set_flow_to, push_flow_to, clear_flow_to), + (LabelledBy, labelled_by, set_labelled_by, push_labelled_by, clear_labelled_by), /// On radio buttons this should be set to a list of all of the buttons /// in the same group as this one, including this radio button itself. (RadioGroup, radio_group, set_radio_group, push_to_radio_group, clear_radio_group) @@ -1575,7 +1575,7 @@ impl Node { PropertyValue::CustomActionVec(value.into()), ); } - pub fn push_to_custom_actions(&mut self, action: CustomAction) { + pub fn push_custom_action(&mut self, action: CustomAction) { match self.get_property_mut( PropertyId::CustomActions, PropertyValue::CustomActionVec(Vec::new()), diff --git a/consumer/src/node.rs b/consumer/src/node.rs index 59d3e6f0e..b1d966330 100644 --- a/consumer/src/node.rs +++ b/consumer/src/node.rs @@ -1033,7 +1033,7 @@ mod tests { }), (NODE_ID_4, { let mut node = Node::new(Role::TextField); - node.push_to_labelled_by(NODE_ID_5); + node.push_labelled_by(NODE_ID_5); node }), (NODE_ID_5, { @@ -1070,7 +1070,7 @@ mod tests { }), (NODE_ID_2, { let mut node = Node::new(Role::Button); - node.push_to_children(NODE_ID_3); + node.push_child(NODE_ID_3); node }), (NODE_ID_3, { @@ -1080,12 +1080,12 @@ mod tests { }), (NODE_ID_4, { let mut node = Node::new(Role::Link); - node.push_to_children(NODE_ID_5); + node.push_child(NODE_ID_5); node }), (NODE_ID_5, { let mut node = Node::new(Role::GenericContainer); - node.push_to_children(NODE_ID_6); + node.push_child(NODE_ID_6); node }), (NODE_ID_6, { diff --git a/consumer/src/tree.rs b/consumer/src/tree.rs index 1bf88c507..5701960fc 100644 --- a/consumer/src/tree.rs +++ b/consumer/src/tree.rs @@ -489,7 +489,7 @@ mod tests { nodes: vec![ (NODE_ID_1, { let mut node = root_node; - node.push_to_children(NODE_ID_2); + node.push_child(NODE_ID_2); node }), (NODE_ID_2, Node::new(Role::RootWebArea)), @@ -560,7 +560,7 @@ mod tests { nodes: vec![ (NODE_ID_1, { let mut node = root_node.clone(); - node.push_to_children(NODE_ID_2); + node.push_child(NODE_ID_2); node }), (NODE_ID_2, Node::new(Role::RootWebArea)), From 694e47176944d9399c50de029d19be8cb05693c7 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sat, 14 Jan 2023 12:54:55 -0600 Subject: [PATCH 31/45] Store everything optional as a property, even the optional bools and 1-byte enums --- common/src/lib.rs | 353 +++++++++++++++++++++++++--------------------- 1 file changed, 189 insertions(+), 164 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 6619d9364..ee779256b 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -714,8 +714,24 @@ enum PropertyValue { F64(f64), Usize(usize), Color(u32), + TextDecoration(TextDecoration), LengthSlice(Box<[u8]>), CoordSlice(Box<[f32]>), + Bool(bool), + NameFrom(NameFrom), + DescriptionFrom(DescriptionFrom), + Invalid(Invalid), + CheckedState(CheckedState), + Live(Live), + DefaultActionVerb(DefaultActionVerb), + TextDirection(TextDirection), + Orientation(Orientation), + SortDirection(SortDirection), + AriaCurrent(AriaCurrent), + HasPopup(HasPopup), + ListStyle(ListStyle), + TextAlign(TextAlign), + VerticalOffset(VerticalOffset), Affine(Box), Rect(Rect), TextSelection(Box), @@ -808,6 +824,11 @@ enum PropertyId { BackgroundColor, ForegroundColor, + // TextDecoration + Overline, + Strikethrough, + Underline, + // LengthSlice CharacterLengths, WordLengths, @@ -816,6 +837,26 @@ enum PropertyId { CharacterPositions, CharacterWidths, + // bool + Expanded, + Selected, + + // Unique enums + NameFrom, + DescriptionFrom, + Invalid, + CheckedState, + Live, + DefaultActionVerb, + TextDirection, + Orientation, + SortDirection, + AriaCurrent, + HasPopup, + ListStyle, + TextAlign, + VerticalOffset, + // Other Transform, Bounds, @@ -853,40 +894,6 @@ macro_rules! flag_methods { } } -macro_rules! optional_bool_methods { - ($($(#[$doc:meta])* ($field:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { - impl Node { - $($(#[$doc])* - pub fn $getter(&self) -> Option { - self.$field - } - pub fn $setter(&mut self, value: bool) { - self.$field = Some(value); - } - pub fn $clearer(&mut self) { - self.$field = None; - })* - } - } -} - -macro_rules! optional_enum_methods { - ($($(#[$doc:meta])* ($field:ident, $type:ty, $setter:ident, $clearer:ident)),+) => { - impl Node { - $($(#[$doc])* - pub fn $field(&self) -> Option<$type> { - self.$field - } - pub fn $setter(&mut self, value: $type) { - self.$field = Some(value); - } - pub fn $clearer(&mut self) { - self.$field = None; - })* - } - } -} - macro_rules! property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $type_getter:ident, $getter_result:ty, $setter:ident, $type_setter:ident, $setter_param:ty, $clearer:ident)),+) => { impl Node { @@ -972,6 +979,15 @@ macro_rules! color_property_methods { } } +macro_rules! text_decoration_property_methods { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { + $(property_methods! { + $(#[$doc])* + ($id, $getter, get_text_decoration, Option, $setter, set_text_decoration, TextDecoration, $clearer) + })* + } +} + macro_rules! length_slice_property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { @@ -990,6 +1006,36 @@ macro_rules! coord_slice_property_methods { } } +macro_rules! bool_property_methods { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { + $(property_methods! { + $(#[$doc])* + ($id, $getter, get_bool, Option, $setter, set_bool, bool, $clearer) + })* + } +} + +macro_rules! unique_enum_property_methods { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { + impl Node { + $($(#[$doc])* + pub fn $getter(&self) -> Option<$id> { + match self.get_property(PropertyId::$id) { + PropertyValue::None => None, + PropertyValue::$id(value) => Some(*value), + _ => panic!(), + } + } + pub fn $setter(&mut self, value: $id) { + self.set_property(PropertyId::$id, PropertyValue::$id(value)); + } + pub fn $clearer(&mut self) { + self.clear_property(PropertyId::$id); + })* + } + } +} + /// A single accessible object. A complete UI is represented as a tree of these. /// /// For brevity, and to make more of the documentation usable in bindings @@ -1003,25 +1049,6 @@ pub struct Node { indices: PropertyIndices, props: Vec, flags: u32, - expanded: Option, - selected: Option, - name_from: Option, - description_from: Option, - invalid: Option, - checked_state: Option, - live: Option, - default_action_verb: Option, - text_direction: Option, - orientation: Option, - sort_direction: Option, - aria_current: Option, - has_popup: Option, - list_style: Option, - text_align: Option, - vertical_offset: Option, - overline: Option, - strikethrough: Option, - underline: Option, } impl Node { @@ -1171,6 +1198,18 @@ impl Node { self.set_property(id, PropertyValue::Color(value)); } + fn get_text_decoration(&self, id: PropertyId) -> Option { + match self.get_property(id) { + PropertyValue::None => None, + PropertyValue::TextDecoration(value) => Some(*value), + _ => panic!(), + } + } + + fn set_text_decoration(&mut self, id: PropertyId, value: TextDecoration) { + self.set_property(id, PropertyValue::TextDecoration(value)); + } + fn get_length_slice(&self, id: PropertyId) -> &[u8] { match self.get_property(id) { PropertyValue::None => &[], @@ -1195,6 +1234,18 @@ impl Node { self.set_property(id, PropertyValue::CoordSlice(value.into())); } + fn get_bool(&self, id: PropertyId) -> Option { + match self.get_property(id) { + PropertyValue::None => None, + PropertyValue::Bool(value) => Some(*value), + _ => panic!(), + } + } + + fn set_bool(&mut self, id: PropertyId, value: bool) { + self.set_property(id, PropertyValue::Bool(value)); + } + pub fn new(role: Role) -> Self { Self { role, @@ -1279,48 +1330,6 @@ flag_methods! { (IsNonatomicTextFieldRoot, is_nonatomic_text_field_root, set_is_nonatomic_text_field_root, clear_is_nonatomic_text_field_root) } -optional_bool_methods! { - /// Whether this node is expanded, collapsed, or neither. - /// - /// Setting this to `false` means the node is collapsed; omitting it means this state - /// isn't applicable. - (expanded, is_expanded, set_expanded, clear_expanded), - - /// Indicates whether this node is selected or unselected. - /// - /// The absence of this flag (as opposed to a `false` setting) - /// means that the concept of "selected" doesn't apply. - /// When deciding whether to set the flag to false or omit it, - /// consider whether it would be appropriate for a screen reader - /// to announce "not selected". The ambiguity of this flag - /// in platform accessibility APIs has made extraneous - /// "not selected" announcements a common annoyance. - (selected, is_selected, set_selected, clear_selected) -} - -optional_enum_methods! { - /// What information was used to compute the object's name. - (name_from, NameFrom, set_name_from, clear_name_from), - /// What information was used to compute the object's description. - (description_from, DescriptionFrom, set_description_from, clear_description_from), - (invalid, Invalid, set_invalid, clear_invalid), - (checked_state, CheckedState, set_checked_state, clear_checked_state), - (live, Live, set_live, clear_live), - (default_action_verb, DefaultActionVerb, set_default_action_verb, clear_default_action_verb), - (text_direction, TextDirection, set_text_direction, clear_text_direction), - (orientation, Orientation, set_orientation, clear_orientation), - (sort_direction, SortDirection, set_sort_direction, clear_sort_direction), - (aria_current, AriaCurrent, set_aria_current, clear_aria_current), - (has_popup, HasPopup, set_has_popup, clear_has_popup), - /// The list style type. Only available on list items. - (list_style, ListStyle, set_list_style, clear_list_style), - (text_align, TextAlign, set_text_align, clear_text_align), - (vertical_offset, VerticalOffset, set_vertical_offset, clear_vertical_offset), - (overline, TextDecoration, set_overline, clear_overline), - (strikethrough, TextDecoration, set_strikethrough, clear_strikethrough), - (underline, TextDecoration, set_underline, clear_underline) -} - node_id_vec_property_methods! { (Children, children, set_children, push_child, clear_children), /// Ids of nodes that are children of this node logically, but are @@ -1430,6 +1439,12 @@ color_property_methods! { (ForegroundColor, foreground_color, set_foreground_color, clear_foreground_color) } +text_decoration_property_methods! { + (Overline, overline, set_overline, clear_overline), + (Strikethrough, strikethrough, set_strikethrough, clear_strikethrough), + (Underline, underline, set_underline, clear_underline) +} + length_slice_property_methods! { /// For inline text. The length (non-inclusive) of each character /// in UTF-8 code units (bytes). The sum of these lengths must equal @@ -1516,6 +1531,45 @@ coord_slice_property_methods! { (CharacterWidths, character_widths, set_character_widths, clear_character_widths) } +bool_property_methods! { + /// Whether this node is expanded, collapsed, or neither. + /// + /// Setting this to `false` means the node is collapsed; omitting it means this state + /// isn't applicable. + (Expanded, is_expanded, set_expanded, clear_expanded), + + /// Indicates whether this node is selected or unselected. + /// + /// The absence of this flag (as opposed to a `false` setting) + /// means that the concept of "selected" doesn't apply. + /// When deciding whether to set the flag to false or omit it, + /// consider whether it would be appropriate for a screen reader + /// to announce "not selected". The ambiguity of this flag + /// in platform accessibility APIs has made extraneous + /// "not selected" announcements a common annoyance. + (Selected, is_selected, set_selected, clear_selected) +} + +unique_enum_property_methods! { + /// What information was used to compute the object's name. + (NameFrom, name_from, set_name_from, clear_name_from), + /// What information was used to compute the object's description. + (DescriptionFrom, description_from, set_description_from, clear_description_from), + (Invalid, invalid, set_invalid, clear_invalid), + (CheckedState, checked_state, set_checked_state, clear_checked_state), + (Live, live, set_live, clear_live), + (DefaultActionVerb, default_action_verb, set_default_action_verb, clear_default_action_verb), + (TextDirection, text_direction, set_text_direction, clear_text_direction), + (Orientation, orientation, set_orientation, clear_orientation), + (SortDirection, sort_direction, set_sort_direction, clear_sort_direction), + (AriaCurrent, aria_current, set_aria_current, clear_aria_current), + (HasPopup, has_popup, set_has_popup, clear_has_popup), + /// The list style type. Only available on list items. + (ListStyle, list_style, set_list_style, clear_list_style), + (TextAlign, text_align, set_text_align, clear_text_align), + (VerticalOffset, vertical_offset, set_vertical_offset, clear_vertical_offset) +} + property_methods! { /// An affine transform to apply to any coordinates within this node /// and its descendants, including the [`bounds`] property of this node. @@ -1597,25 +1651,6 @@ impl Node { enum FieldId { Role, Actions, - Expanded, - Selected, - NameFrom, - DescriptionFrom, - Invalid, - CheckedState, - Live, - DefaultActionVerb, - TextDirection, - Orientation, - SortDirection, - AriaCurrent, - HasPopup, - ListStyle, - TextAlign, - VerticalOffset, - Overline, - Strikethrough, - Underline, } #[cfg(feature = "serde")] @@ -1635,15 +1670,6 @@ macro_rules! serialize_simple_fields { } } -#[cfg(feature = "serde")] -macro_rules! serialize_optional_fields { - ($self:ident, $map:ident, { $(($name:ident, $id:ident)),+ }) => { - $(if $self.$name.is_some() { - $map.serialize_entry(&FieldId::$id, &$self.$name)?; - })* - } -} - #[cfg(feature = "serde")] macro_rules! serialize_property { ($self:ident, $map:ident, $index:ident, $id:ident, { $($variant:ident),+ }) => { @@ -1703,27 +1729,6 @@ impl Serialize for Node { } } } - serialize_optional_fields!(self, map, { - (expanded, Expanded), - (selected, Selected), - (name_from, NameFrom), - (description_from, DescriptionFrom), - (invalid, Invalid), - (checked_state, CheckedState), - (live, Live), - (default_action_verb, DefaultActionVerb), - (text_direction, TextDirection), - (orientation, Orientation), - (sort_direction, SortDirection), - (aria_current, AriaCurrent), - (has_popup, HasPopup), - (list_style, ListStyle), - (text_align, TextAlign), - (vertical_offset, VerticalOffset), - (overline, Overline), - (strikethrough, Strikethrough), - (underline, Underline) - }); for (id, index) in self.indices.0.iter().copied().enumerate() { if index == PropertyId::Unset as u8 { continue; @@ -1736,8 +1741,24 @@ impl Serialize for Node { F64, Usize, Color, + TextDecoration, LengthSlice, CoordSlice, + Bool, + NameFrom, + DescriptionFrom, + Invalid, + CheckedState, + Live, + DefaultActionVerb, + TextDirection, + Orientation, + SortDirection, + AriaCurrent, + HasPopup, + ListStyle, + TextAlign, + VerticalOffset, Affine, Rect, TextSelection, @@ -1769,26 +1790,7 @@ impl<'de> Visitor<'de> for NodeVisitor { DeserializeKey::Field(id) => { deserialize_field!(node, map, id, { (role, Role), - (actions, Actions), - (expanded, Expanded), - (selected, Selected), - (name_from, NameFrom), - (description_from, DescriptionFrom), - (invalid, Invalid), - (checked_state, CheckedState), - (live, Live), - (default_action_verb, DefaultActionVerb), - (text_direction, TextDirection), - (orientation, Orientation), - (sort_direction, SortDirection), - (aria_current, AriaCurrent), - (has_popup, HasPopup), - (list_style, ListStyle), - (text_align, TextAlign), - (vertical_offset, VerticalOffset), - (overline, Overline), - (strikethrough, Strikethrough), - (underline, Underline) + (actions, Actions) }); } DeserializeKey::Flag(flag) => { @@ -1880,6 +1882,11 @@ impl<'de> Visitor<'de> for NodeVisitor { BackgroundColor, ForegroundColor }, + TextDecoration { + Overline, + Strikethrough, + Underline + }, LengthSlice { CharacterLengths, WordLengths @@ -1888,6 +1895,24 @@ impl<'de> Visitor<'de> for NodeVisitor { CharacterPositions, CharacterWidths }, + Bool { + Expanded, + Selected + }, + NameFrom { NameFrom }, + DescriptionFrom { DescriptionFrom }, + Invalid { Invalid }, + CheckedState { CheckedState }, + Live { Live }, + DefaultActionVerb { DefaultActionVerb }, + TextDirection { TextDirection }, + Orientation { Orientation }, + SortDirection { SortDirection }, + AriaCurrent { AriaCurrent }, + HasPopup { HasPopup }, + ListStyle { ListStyle }, + TextAlign { TextAlign }, + VerticalOffset { VerticalOffset }, Affine { Transform }, Rect { Bounds }, TextSelection { TextSelection }, From 58780c96672c707c4494b142ac08fd43bf430632 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sat, 14 Jan 2023 13:13:47 -0600 Subject: [PATCH 32/45] Reorder code slightly --- common/src/lib.rs | 320 +++++++++++++++++++++++----------------------- 1 file changed, 161 insertions(+), 159 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index ee779256b..9e5be36f0 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -877,165 +877,6 @@ impl Default for PropertyIndices { } } -macro_rules! flag_methods { - ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { - impl Node { - $($(#[$doc])* - pub fn $getter(&self) -> bool { - (self.flags & (Flag::$id).mask()) != 0 - } - pub fn $setter(&mut self) { - self.flags |= (Flag::$id).mask(); - } - pub fn $clearer(&mut self) { - self.flags &= !((Flag::$id).mask()); - })* - } - } -} - -macro_rules! property_methods { - ($($(#[$doc:meta])* ($id:ident, $getter:ident, $type_getter:ident, $getter_result:ty, $setter:ident, $type_setter:ident, $setter_param:ty, $clearer:ident)),+) => { - impl Node { - $($(#[$doc])* - pub fn $getter(&self) -> $getter_result { - self.$type_getter(PropertyId::$id) - } - pub fn $setter(&mut self, value: $setter_param) { - self.$type_setter(PropertyId::$id, value); - } - pub fn $clearer(&mut self) { - self.clear_property(PropertyId::$id); - })* - } - } -} - -macro_rules! vec_property_methods { - ($($(#[$doc:meta])* ($id:ident, $item_type:ty, $getter:ident, $type_getter:ident, $setter:ident, $type_setter:ident, $pusher:ident, $type_pusher:ident, $clearer:ident)),+) => { - $(property_methods! { - $(#[$doc])* - ($id, $getter, $type_getter, &[$item_type], $setter, $type_setter, impl Into>, $clearer) - } - impl Node { - pub fn $pusher(&mut self, item: $item_type) { - self.$type_pusher(PropertyId::$id, item); - } - })* - } -} - -macro_rules! node_id_vec_property_methods { - ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $pusher:ident, $clearer:ident)),+) => { - $(vec_property_methods! { - $(#[$doc])* - ($id, NodeId, $getter, get_node_id_vec, $setter, set_node_id_vec, $pusher, push_to_node_id_vec, $clearer) - })* - } -} - -macro_rules! node_id_property_methods { - ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { - $(property_methods! { - $(#[$doc])* - ($id, $getter, get_node_id, Option, $setter, set_node_id, NodeId, $clearer) - })* - } -} - -macro_rules! string_property_methods { - ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { - $(property_methods! { - $(#[$doc])* - ($id, $getter, get_string, Option<&str>, $setter, set_string, impl Into>, $clearer) - })* - } -} - -macro_rules! f64_property_methods { - ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { - $(property_methods! { - $(#[$doc])* - ($id, $getter, get_f64, Option, $setter, set_f64, f64, $clearer) - })* - } -} - -macro_rules! usize_property_methods { - ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { - $(property_methods! { - $(#[$doc])* - ($id, $getter, get_usize, Option, $setter, set_usize, usize, $clearer) - })* - } -} - -macro_rules! color_property_methods { - ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { - $(property_methods! { - $(#[$doc])* - ($id, $getter, get_color, Option, $setter, set_color, u32, $clearer) - })* - } -} - -macro_rules! text_decoration_property_methods { - ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { - $(property_methods! { - $(#[$doc])* - ($id, $getter, get_text_decoration, Option, $setter, set_text_decoration, TextDecoration, $clearer) - })* - } -} - -macro_rules! length_slice_property_methods { - ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { - $(property_methods! { - $(#[$doc])* - ($id, $getter, get_length_slice, &[u8], $setter, set_length_slice, impl Into>, $clearer) - })* - } -} - -macro_rules! coord_slice_property_methods { - ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { - $(property_methods! { - $(#[$doc])* - ($id, $getter, get_coord_slice, Option<&[f32]>, $setter, set_coord_slice, impl Into>, $clearer) - })* - } -} - -macro_rules! bool_property_methods { - ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { - $(property_methods! { - $(#[$doc])* - ($id, $getter, get_bool, Option, $setter, set_bool, bool, $clearer) - })* - } -} - -macro_rules! unique_enum_property_methods { - ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { - impl Node { - $($(#[$doc])* - pub fn $getter(&self) -> Option<$id> { - match self.get_property(PropertyId::$id) { - PropertyValue::None => None, - PropertyValue::$id(value) => Some(*value), - _ => panic!(), - } - } - pub fn $setter(&mut self, value: $id) { - self.set_property(PropertyId::$id, PropertyValue::$id(value)); - } - pub fn $clearer(&mut self) { - self.clear_property(PropertyId::$id); - })* - } - } -} - /// A single accessible object. A complete UI is represented as a tree of these. /// /// For brevity, and to make more of the documentation usable in bindings @@ -1252,7 +1093,168 @@ impl Node { ..Default::default() } } +} + +macro_rules! flag_methods { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { + impl Node { + $($(#[$doc])* + pub fn $getter(&self) -> bool { + (self.flags & (Flag::$id).mask()) != 0 + } + pub fn $setter(&mut self) { + self.flags |= (Flag::$id).mask(); + } + pub fn $clearer(&mut self) { + self.flags &= !((Flag::$id).mask()); + })* + } + } +} + +macro_rules! property_methods { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $type_getter:ident, $getter_result:ty, $setter:ident, $type_setter:ident, $setter_param:ty, $clearer:ident)),+) => { + impl Node { + $($(#[$doc])* + pub fn $getter(&self) -> $getter_result { + self.$type_getter(PropertyId::$id) + } + pub fn $setter(&mut self, value: $setter_param) { + self.$type_setter(PropertyId::$id, value); + } + pub fn $clearer(&mut self) { + self.clear_property(PropertyId::$id); + })* + } + } +} + +macro_rules! vec_property_methods { + ($($(#[$doc:meta])* ($id:ident, $item_type:ty, $getter:ident, $type_getter:ident, $setter:ident, $type_setter:ident, $pusher:ident, $type_pusher:ident, $clearer:ident)),+) => { + $(property_methods! { + $(#[$doc])* + ($id, $getter, $type_getter, &[$item_type], $setter, $type_setter, impl Into>, $clearer) + } + impl Node { + pub fn $pusher(&mut self, item: $item_type) { + self.$type_pusher(PropertyId::$id, item); + } + })* + } +} + +macro_rules! node_id_vec_property_methods { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $pusher:ident, $clearer:ident)),+) => { + $(vec_property_methods! { + $(#[$doc])* + ($id, NodeId, $getter, get_node_id_vec, $setter, set_node_id_vec, $pusher, push_to_node_id_vec, $clearer) + })* + } +} + +macro_rules! node_id_property_methods { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { + $(property_methods! { + $(#[$doc])* + ($id, $getter, get_node_id, Option, $setter, set_node_id, NodeId, $clearer) + })* + } +} + +macro_rules! string_property_methods { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { + $(property_methods! { + $(#[$doc])* + ($id, $getter, get_string, Option<&str>, $setter, set_string, impl Into>, $clearer) + })* + } +} + +macro_rules! f64_property_methods { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { + $(property_methods! { + $(#[$doc])* + ($id, $getter, get_f64, Option, $setter, set_f64, f64, $clearer) + })* + } +} + +macro_rules! usize_property_methods { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { + $(property_methods! { + $(#[$doc])* + ($id, $getter, get_usize, Option, $setter, set_usize, usize, $clearer) + })* + } +} + +macro_rules! color_property_methods { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { + $(property_methods! { + $(#[$doc])* + ($id, $getter, get_color, Option, $setter, set_color, u32, $clearer) + })* + } +} + +macro_rules! text_decoration_property_methods { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { + $(property_methods! { + $(#[$doc])* + ($id, $getter, get_text_decoration, Option, $setter, set_text_decoration, TextDecoration, $clearer) + })* + } +} + +macro_rules! length_slice_property_methods { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { + $(property_methods! { + $(#[$doc])* + ($id, $getter, get_length_slice, &[u8], $setter, set_length_slice, impl Into>, $clearer) + })* + } +} + +macro_rules! coord_slice_property_methods { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { + $(property_methods! { + $(#[$doc])* + ($id, $getter, get_coord_slice, Option<&[f32]>, $setter, set_coord_slice, impl Into>, $clearer) + })* + } +} + +macro_rules! bool_property_methods { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { + $(property_methods! { + $(#[$doc])* + ($id, $getter, get_bool, Option, $setter, set_bool, bool, $clearer) + })* + } +} + +macro_rules! unique_enum_property_methods { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { + impl Node { + $($(#[$doc])* + pub fn $getter(&self) -> Option<$id> { + match self.get_property(PropertyId::$id) { + PropertyValue::None => None, + PropertyValue::$id(value) => Some(*value), + _ => panic!(), + } + } + pub fn $setter(&mut self, value: $id) { + self.set_property(PropertyId::$id, PropertyValue::$id(value)); + } + pub fn $clearer(&mut self) { + self.clear_property(PropertyId::$id); + })* + } + } +} +impl Node { pub fn role(&self) -> Role { self.role } From 5c44cfe124675c82c498514c4dc47715a41b657c Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sat, 14 Jan 2023 13:34:15 -0600 Subject: [PATCH 33/45] Introduce the concept of a node class --- common/src/lib.rs | 80 ++++++++++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 9e5be36f0..b515fc3dd 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -337,7 +337,7 @@ impl Action { } } -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] #[repr(transparent)] struct Actions(u32); @@ -867,7 +867,7 @@ enum PropertyId { Unset, } -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[repr(transparent)] struct PropertyIndices([u8; PropertyId::Unset as usize]); @@ -877,6 +877,13 @@ impl Default for PropertyIndices { } } +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +struct NodeClass { + role: Role, + actions: Actions, + indices: PropertyIndices, +} + /// A single accessible object. A complete UI is represented as a tree of these. /// /// For brevity, and to make more of the documentation usable in bindings @@ -885,16 +892,14 @@ impl Default for PropertyIndices { /// as properties. #[derive(Clone, Debug, Default, PartialEq)] pub struct Node { - role: Role, - actions: Actions, - indices: PropertyIndices, - props: Vec, + class: NodeClass, flags: u32, + props: Vec, } impl Node { fn get_property(&self, id: PropertyId) -> &PropertyValue { - let index = self.indices.0[id as usize]; + let index = self.class.indices.0[id as usize]; if index == PropertyId::Unset as u8 { &PropertyValue::None } else { @@ -903,11 +908,11 @@ impl Node { } fn get_property_mut(&mut self, id: PropertyId, default: PropertyValue) -> &mut PropertyValue { - let index = self.indices.0[id as usize] as usize; + let index = self.class.indices.0[id as usize] as usize; if index == PropertyId::Unset as usize { self.props.push(default); let index = self.props.len() - 1; - self.indices.0[id as usize] = index as u8; + self.class.indices.0[id as usize] = index as u8; &mut self.props[index] } else { if matches!(self.props[index], PropertyValue::None) { @@ -918,17 +923,17 @@ impl Node { } fn set_property(&mut self, id: PropertyId, value: PropertyValue) { - let index = self.indices.0[id as usize]; + let index = self.class.indices.0[id as usize]; if index == PropertyId::Unset as u8 { self.props.push(value); - self.indices.0[id as usize] = (self.props.len() - 1) as u8; + self.class.indices.0[id as usize] = (self.props.len() - 1) as u8; } else { self.props[index as usize] = value; } } fn clear_property(&mut self, id: PropertyId) { - let index = self.indices.0[id as usize]; + let index = self.class.indices.0[id as usize]; if index != PropertyId::Unset as u8 { self.props[index as usize] = PropertyValue::None; } @@ -1086,13 +1091,6 @@ impl Node { fn set_bool(&mut self, id: PropertyId, value: bool) { self.set_property(id, PropertyValue::Bool(value)); } - - pub fn new(role: Role) -> Self { - Self { - role, - ..Default::default() - } - } } macro_rules! flag_methods { @@ -1255,24 +1253,34 @@ macro_rules! unique_enum_property_methods { } impl Node { + pub fn new(role: Role) -> Self { + Self { + class: NodeClass { + role, + ..Default::default() + }, + ..Default::default() + } + } + pub fn role(&self) -> Role { - self.role + self.class.role } pub fn set_role(&mut self, value: Role) { - self.role = value; + self.class.role = value; } pub fn supports_action(&self, action: Action) -> bool { - (self.actions.0 & action.mask()) != 0 + (self.class.actions.0 & action.mask()) != 0 } pub fn add_action(&mut self, action: Action) { - self.actions.0 |= action.mask(); + self.class.actions.0 |= action.mask(); } pub fn remove_action(&mut self, action: Action) { - self.actions.0 &= !(action.mask()); + self.class.actions.0 &= !(action.mask()); } pub fn clear_actions(&mut self) { - self.actions.0 = 0; + self.class.actions.0 = 0; } } @@ -1650,7 +1658,7 @@ impl Node { #[cfg(feature = "serde")] #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -enum FieldId { +enum ClassFieldId { Role, Actions, } @@ -1659,16 +1667,16 @@ enum FieldId { #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[serde(untagged)] enum DeserializeKey { - Field(FieldId), + ClassField(ClassFieldId), Flag(Flag), Property(PropertyId), Unknown(String), } #[cfg(feature = "serde")] -macro_rules! serialize_simple_fields { +macro_rules! serialize_class_fields { ($self:ident, $map:ident, { $(($name:ident, $id:ident)),+ }) => { - $($map.serialize_entry(&FieldId::$id, &$self.$name)?;)* + $($map.serialize_entry(&ClassFieldId::$id, &$self.class.$name)?;)* } } @@ -1685,11 +1693,11 @@ macro_rules! serialize_property { } #[cfg(feature = "serde")] -macro_rules! deserialize_field { +macro_rules! deserialize_class_field { ($node:ident, $map:ident, $key:ident, { $(($name:ident, $id:ident)),+ }) => { match $key { - $(FieldId::$id => { - $node.$name = $map.next_value()?; + $(ClassFieldId::$id => { + $node.class.$name = $map.next_value()?; })* } } @@ -1720,7 +1728,7 @@ impl Serialize for Node { S: Serializer, { let mut map = serializer.serialize_map(None)?; - serialize_simple_fields!(self, map, { + serialize_class_fields!(self, map, { (role, Role), (actions, Actions) }); @@ -1731,7 +1739,7 @@ impl Serialize for Node { } } } - for (id, index) in self.indices.0.iter().copied().enumerate() { + for (id, index) in self.class.indices.0.iter().copied().enumerate() { if index == PropertyId::Unset as u8 { continue; } @@ -1789,8 +1797,8 @@ impl<'de> Visitor<'de> for NodeVisitor { let mut node = Node::default(); while let Some(key) = map.next_key()? { match key { - DeserializeKey::Field(id) => { - deserialize_field!(node, map, id, { + DeserializeKey::ClassField(id) => { + deserialize_class_field!(node, map, id, { (role, Role), (actions, Actions) }); From 855004e26f8ff6bf720c8cf80d22a31e6f43d962 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sat, 14 Jan 2023 15:40:12 -0600 Subject: [PATCH 34/45] Split node setters out into NodeBuilder, to enable more optimizations --- common/src/lib.rs | 239 +++++++++++++++++----- consumer/src/lib.rs | 81 ++++---- consumer/src/node.rs | 76 +++---- consumer/src/text.rs | 131 ++++++------ consumer/src/tree.rs | 68 +++--- platforms/windows/examples/hello_world.rs | 36 ++-- platforms/windows/src/tests/simple.rs | 18 +- platforms/windows/src/tests/subclassed.rs | 20 +- platforms/winit/examples/simple.rs | 39 ++-- 9 files changed, 430 insertions(+), 278 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index b515fc3dd..c1401af5c 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -16,9 +16,12 @@ use serde::{ ser::{SerializeMap, SerializeSeq, Serializer}, Deserialize, Serialize, }; -use std::num::{NonZeroU128, NonZeroU64}; #[cfg(feature = "serde")] use std::{fmt, mem::size_of_val}; +use std::{ + num::{NonZeroU128, NonZeroU64}, + sync::Arc, +}; mod geometry; pub use geometry::{Affine, Point, Rect, Size, Vec2}; @@ -890,23 +893,33 @@ struct NodeClass { /// to other languages, documentation of getter methods is written as if /// documenting fields in a struct, and such methods are referred to /// as properties. -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct Node { + class: NodeClass, + flags: u32, + props: Arc<[PropertyValue]>, +} + +/// Builds a [`Node`]. +#[derive(Clone, Debug, Default, PartialEq)] +pub struct NodeBuilder { class: NodeClass, flags: u32, props: Vec, } -impl Node { - fn get_property(&self, id: PropertyId) -> &PropertyValue { - let index = self.class.indices.0[id as usize]; +impl NodeClass { + fn get_property<'a>(&self, props: &'a [PropertyValue], id: PropertyId) -> &'a PropertyValue { + let index = self.indices.0[id as usize]; if index == PropertyId::Unset as u8 { &PropertyValue::None } else { - &self.props[index as usize] + &props[index as usize] } } +} +impl NodeBuilder { fn get_property_mut(&mut self, id: PropertyId, default: PropertyValue) -> &mut PropertyValue { let index = self.class.indices.0[id as usize] as usize; if index == PropertyId::Unset as usize { @@ -938,39 +951,51 @@ impl Node { self.props[index as usize] = PropertyValue::None; } } +} - fn get_affine(&self, id: PropertyId) -> Option<&Affine> { - match self.get_property(id) { +impl NodeClass { + fn get_affine<'a>(&self, props: &'a [PropertyValue], id: PropertyId) -> Option<&'a Affine> { + match self.get_property(props, id) { PropertyValue::None => None, PropertyValue::Affine(value) => Some(value), _ => panic!(), } } +} +impl NodeBuilder { fn set_affine(&mut self, id: PropertyId, value: impl Into>) { self.set_property(id, PropertyValue::Affine(value.into())); } +} - fn get_rect(&self, id: PropertyId) -> Option { - match self.get_property(id) { +impl NodeClass { + fn get_rect(&self, props: &[PropertyValue], id: PropertyId) -> Option { + match self.get_property(props, id) { PropertyValue::None => None, PropertyValue::Rect(value) => Some(*value), _ => panic!(), } } +} +impl NodeBuilder { fn set_rect(&mut self, id: PropertyId, value: Rect) { self.set_property(id, PropertyValue::Rect(value)); } +} - fn get_node_id_vec(&self, id: PropertyId) -> &[NodeId] { - match self.get_property(id) { +impl NodeClass { + fn get_node_id_vec<'a>(&self, props: &'a [PropertyValue], id: PropertyId) -> &'a [NodeId] { + match self.get_property(props, id) { PropertyValue::None => &[], PropertyValue::NodeIdVec(value) => value, _ => panic!(), } } +} +impl NodeBuilder { fn push_to_node_id_vec(&mut self, property_id: PropertyId, node_id: NodeId) { match self.get_property_mut(property_id, PropertyValue::NodeIdVec(Vec::new())) { PropertyValue::NodeIdVec(v) => { @@ -983,111 +1008,151 @@ impl Node { fn set_node_id_vec(&mut self, id: PropertyId, value: impl Into>) { self.set_property(id, PropertyValue::NodeIdVec(value.into())); } +} - fn get_node_id(&self, id: PropertyId) -> Option { - match self.get_property(id) { +impl NodeClass { + fn get_node_id(&self, props: &[PropertyValue], id: PropertyId) -> Option { + match self.get_property(props, id) { PropertyValue::None => None, PropertyValue::NodeId(value) => Some(*value), _ => panic!(), } } +} +impl NodeBuilder { fn set_node_id(&mut self, id: PropertyId, value: NodeId) { self.set_property(id, PropertyValue::NodeId(value)); } +} - fn get_string(&self, id: PropertyId) -> Option<&str> { - match self.get_property(id) { +impl NodeClass { + fn get_string<'a>(&self, props: &'a [PropertyValue], id: PropertyId) -> Option<&'a str> { + match self.get_property(props, id) { PropertyValue::None => None, PropertyValue::String(value) => Some(value), _ => panic!(), } } +} +impl NodeBuilder { fn set_string(&mut self, id: PropertyId, value: impl Into>) { self.set_property(id, PropertyValue::String(value.into())); } +} - fn get_f64(&self, id: PropertyId) -> Option { - match self.get_property(id) { +impl NodeClass { + fn get_f64(&self, props: &[PropertyValue], id: PropertyId) -> Option { + match self.get_property(props, id) { PropertyValue::None => None, PropertyValue::F64(value) => Some(*value), _ => panic!(), } } +} +impl NodeBuilder { fn set_f64(&mut self, id: PropertyId, value: f64) { self.set_property(id, PropertyValue::F64(value)); } +} - fn get_usize(&self, id: PropertyId) -> Option { - match self.get_property(id) { +impl NodeClass { + fn get_usize(&self, props: &[PropertyValue], id: PropertyId) -> Option { + match self.get_property(props, id) { PropertyValue::None => None, PropertyValue::Usize(value) => Some(*value), _ => panic!(), } } +} +impl NodeBuilder { fn set_usize(&mut self, id: PropertyId, value: usize) { self.set_property(id, PropertyValue::Usize(value)); } +} - fn get_color(&self, id: PropertyId) -> Option { - match self.get_property(id) { +impl NodeClass { + fn get_color(&self, props: &[PropertyValue], id: PropertyId) -> Option { + match self.get_property(props, id) { PropertyValue::None => None, PropertyValue::Color(value) => Some(*value), _ => panic!(), } } +} +impl NodeBuilder { fn set_color(&mut self, id: PropertyId, value: u32) { self.set_property(id, PropertyValue::Color(value)); } +} - fn get_text_decoration(&self, id: PropertyId) -> Option { - match self.get_property(id) { +impl NodeClass { + fn get_text_decoration( + &self, + props: &[PropertyValue], + id: PropertyId, + ) -> Option { + match self.get_property(props, id) { PropertyValue::None => None, PropertyValue::TextDecoration(value) => Some(*value), _ => panic!(), } } +} +impl NodeBuilder { fn set_text_decoration(&mut self, id: PropertyId, value: TextDecoration) { self.set_property(id, PropertyValue::TextDecoration(value)); } +} - fn get_length_slice(&self, id: PropertyId) -> &[u8] { - match self.get_property(id) { +impl NodeClass { + fn get_length_slice<'a>(&self, props: &'a [PropertyValue], id: PropertyId) -> &'a [u8] { + match self.get_property(props, id) { PropertyValue::None => &[], PropertyValue::LengthSlice(value) => value, _ => panic!(), } } +} +impl NodeBuilder { fn set_length_slice(&mut self, id: PropertyId, value: impl Into>) { self.set_property(id, PropertyValue::LengthSlice(value.into())); } +} - fn get_coord_slice(&self, id: PropertyId) -> Option<&[f32]> { - match self.get_property(id) { +impl NodeClass { + fn get_coord_slice<'a>(&self, props: &'a [PropertyValue], id: PropertyId) -> Option<&'a [f32]> { + match self.get_property(props, id) { PropertyValue::None => None, PropertyValue::CoordSlice(value) => Some(value), _ => panic!(), } } +} +impl NodeBuilder { fn set_coord_slice(&mut self, id: PropertyId, value: impl Into>) { self.set_property(id, PropertyValue::CoordSlice(value.into())); } +} - fn get_bool(&self, id: PropertyId) -> Option { - match self.get_property(id) { +impl NodeClass { + fn get_bool(&self, props: &[PropertyValue], id: PropertyId) -> Option { + match self.get_property(props, id) { PropertyValue::None => None, PropertyValue::Bool(value) => Some(*value), _ => panic!(), } } +} +impl NodeBuilder { fn set_bool(&mut self, id: PropertyId, value: bool) { self.set_property(id, PropertyValue::Bool(value)); } @@ -1096,6 +1161,12 @@ impl Node { macro_rules! flag_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { impl Node { + $($(#[$doc])* + pub fn $getter(&self) -> bool { + (self.flags & (Flag::$id).mask()) != 0 + })* + } + impl NodeBuilder { $($(#[$doc])* pub fn $getter(&self) -> bool { (self.flags & (Flag::$id).mask()) != 0 @@ -1115,7 +1186,13 @@ macro_rules! property_methods { impl Node { $($(#[$doc])* pub fn $getter(&self) -> $getter_result { - self.$type_getter(PropertyId::$id) + self.class.$type_getter(&self.props, PropertyId::$id) + })* + } + impl NodeBuilder { + $($(#[$doc])* + pub fn $getter(&self) -> $getter_result { + self.class.$type_getter(&self.props, PropertyId::$id) } pub fn $setter(&mut self, value: $setter_param) { self.$type_setter(PropertyId::$id, value); @@ -1133,7 +1210,7 @@ macro_rules! vec_property_methods { $(#[$doc])* ($id, $getter, $type_getter, &[$item_type], $setter, $type_setter, impl Into>, $clearer) } - impl Node { + impl NodeBuilder { pub fn $pusher(&mut self, item: $item_type) { self.$type_pusher(PropertyId::$id, item); } @@ -1236,7 +1313,17 @@ macro_rules! unique_enum_property_methods { impl Node { $($(#[$doc])* pub fn $getter(&self) -> Option<$id> { - match self.get_property(PropertyId::$id) { + match self.class.get_property(&self.props, PropertyId::$id) { + PropertyValue::None => None, + PropertyValue::$id(value) => Some(*value), + _ => panic!(), + } + })* + } + impl NodeBuilder { + $($(#[$doc])* + pub fn $getter(&self) -> Option<$id> { + match self.class.get_property(&self.props, PropertyId::$id) { PropertyValue::None => None, PropertyValue::$id(value) => Some(*value), _ => panic!(), @@ -1252,7 +1339,7 @@ macro_rules! unique_enum_property_methods { } } -impl Node { +impl NodeBuilder { pub fn new(role: Role) -> Self { Self { class: NodeClass { @@ -1263,13 +1350,37 @@ impl Node { } } + pub fn build(self) -> Node { + Node { + class: self.class, + flags: self.flags, + props: self.props.into(), + } + } +} + +impl Node { + pub fn role(&self) -> Role { + self.class.role + } +} + +impl NodeBuilder { pub fn role(&self) -> Role { self.class.role } pub fn set_role(&mut self, value: Role) { self.class.role = value; } +} + +impl Node { + pub fn supports_action(&self, action: Action) -> bool { + (self.class.actions.0 & action.mask()) != 0 + } +} +impl NodeBuilder { pub fn supports_action(&self, action: Action) -> bool { (self.class.actions.0 & action.mask()) != 0 } @@ -1610,7 +1721,23 @@ property_methods! { impl Node { pub fn text_selection(&self) -> Option<&TextSelection> { - match self.get_property(PropertyId::TextSelection) { + match self + .class + .get_property(&self.props, PropertyId::TextSelection) + { + PropertyValue::None => None, + PropertyValue::TextSelection(value) => Some(value), + _ => panic!(), + } + } +} + +impl NodeBuilder { + pub fn text_selection(&self) -> Option<&TextSelection> { + match self + .class + .get_property(&self.props, PropertyId::TextSelection) + { PropertyValue::None => None, PropertyValue::TextSelection(value) => Some(value), _ => panic!(), @@ -1625,9 +1752,27 @@ impl Node { pub fn clear_text_selection(&mut self) { self.clear_property(PropertyId::TextSelection); } +} + +impl Node { + pub fn custom_actions(&self) -> &[CustomAction] { + match self + .class + .get_property(&self.props, PropertyId::CustomActions) + { + PropertyValue::None => &[], + PropertyValue::CustomActionVec(value) => value, + _ => panic!(), + } + } +} +impl NodeBuilder { pub fn custom_actions(&self) -> &[CustomAction] { - match self.get_property(PropertyId::CustomActions) { + match self + .class + .get_property(&self.props, PropertyId::CustomActions) + { PropertyValue::None => &[], PropertyValue::CustomActionVec(value) => value, _ => panic!(), @@ -1694,10 +1839,10 @@ macro_rules! serialize_property { #[cfg(feature = "serde")] macro_rules! deserialize_class_field { - ($node:ident, $map:ident, $key:ident, { $(($name:ident, $id:ident)),+ }) => { + ($builder:ident, $map:ident, $key:ident, { $(($name:ident, $id:ident)),+ }) => { match $key { $(ClassFieldId::$id => { - $node.class.$name = $map.next_value()?; + $builder.class.$name = $map.next_value()?; })* } } @@ -1705,13 +1850,13 @@ macro_rules! deserialize_class_field { #[cfg(feature = "serde")] macro_rules! deserialize_property { - ($node:ident, $map:ident, $key:ident, { $($type:ident { $($id:ident),+ }),+ }) => { + ($builder:ident, $map:ident, $key:ident, { $($type:ident { $($id:ident),+ }),+ }) => { match $key { $($(PropertyId::$id => { if let Some(value) = $map.next_value()? { - $node.set_property(PropertyId::$id, PropertyValue::$type(value)); + $builder.set_property(PropertyId::$id, PropertyValue::$type(value)); } else { - $node.clear_property(PropertyId::$id); + $builder.clear_property(PropertyId::$id); } })*)* PropertyId::Unset => { @@ -1794,24 +1939,24 @@ impl<'de> Visitor<'de> for NodeVisitor { where V: MapAccess<'de>, { - let mut node = Node::default(); + let mut builder = NodeBuilder::default(); while let Some(key) = map.next_key()? { match key { DeserializeKey::ClassField(id) => { - deserialize_class_field!(node, map, id, { + deserialize_class_field!(builder, map, id, { (role, Role), (actions, Actions) }); } DeserializeKey::Flag(flag) => { if map.next_value()? { - node.flags |= flag.mask(); + builder.flags |= flag.mask(); } else { - node.flags &= !(flag.mask()); + builder.flags &= !(flag.mask()); } } DeserializeKey::Property(id) => { - deserialize_property!(node, map, id, { + deserialize_property!(builder, map, id, { NodeIdVec { Children, IndirectChildren, @@ -1935,7 +2080,7 @@ impl<'de> Visitor<'de> for NodeVisitor { } } - Ok(node) + Ok(builder.build()) } } diff --git a/consumer/src/lib.rs b/consumer/src/lib.rs index bc32e177f..e9ed5c162 100644 --- a/consumer/src/lib.rs +++ b/consumer/src/lib.rs @@ -21,7 +21,8 @@ pub use text::{ #[cfg(test)] mod tests { use accesskit::{ - ActionHandler, ActionRequest, Affine, Node, NodeId, Rect, Role, Tree, TreeUpdate, Vec2, + ActionHandler, ActionRequest, Affine, NodeBuilder, NodeId, Rect, Role, Tree, TreeUpdate, + Vec2, }; use std::num::NonZeroU128; @@ -51,86 +52,86 @@ mod tests { pub fn test_tree() -> crate::tree::Tree { let root = { - let mut node = Node::new(Role::RootWebArea); - node.set_children(vec![ + let mut builder = NodeBuilder::new(Role::RootWebArea); + builder.set_children(vec![ PARAGRAPH_0_ID, PARAGRAPH_1_IGNORED_ID, PARAGRAPH_2_ID, PARAGRAPH_3_IGNORED_ID, ]); - node + builder.build() }; let paragraph_0 = { - let mut node = Node::new(Role::Paragraph); - node.set_children(vec![STATIC_TEXT_0_0_IGNORED_ID]); - node + let mut builder = NodeBuilder::new(Role::Paragraph); + builder.set_children(vec![STATIC_TEXT_0_0_IGNORED_ID]); + builder.build() }; let static_text_0_0_ignored = { - let mut node = Node::new(Role::StaticText); - node.set_name("static_text_0_0_ignored"); - node + let mut builder = NodeBuilder::new(Role::StaticText); + builder.set_name("static_text_0_0_ignored"); + builder.build() }; let paragraph_1_ignored = { - let mut node = Node::new(Role::Paragraph); - node.set_transform(Affine::translate(Vec2::new(10.0, 40.0))); - node.set_bounds(Rect { + let mut builder = NodeBuilder::new(Role::Paragraph); + builder.set_transform(Affine::translate(Vec2::new(10.0, 40.0))); + builder.set_bounds(Rect { x0: 0.0, y0: 0.0, x1: 800.0, y1: 40.0, }); - node.set_children(vec![STATIC_TEXT_1_0_ID]); - node + builder.set_children(vec![STATIC_TEXT_1_0_ID]); + builder.build() }; let static_text_1_0 = { - let mut node = Node::new(Role::StaticText); - node.set_bounds(Rect { + let mut builder = NodeBuilder::new(Role::StaticText); + builder.set_bounds(Rect { x0: 10.0, y0: 10.0, x1: 90.0, y1: 30.0, }); - node.set_name("static_text_1_0"); - node + builder.set_name("static_text_1_0"); + builder.build() }; let paragraph_2 = { - let mut node = Node::new(Role::Paragraph); - node.set_children(vec![STATIC_TEXT_2_0_ID]); - node + let mut builder = NodeBuilder::new(Role::Paragraph); + builder.set_children(vec![STATIC_TEXT_2_0_ID]); + builder.build() }; let static_text_2_0 = { - let mut node = Node::new(Role::StaticText); - node.set_name("static_text_2_0"); - node + let mut builder = NodeBuilder::new(Role::StaticText); + builder.set_name("static_text_2_0"); + builder.build() }; let paragraph_3_ignored = { - let mut node = Node::new(Role::Paragraph); - node.set_children(vec![ + let mut builder = NodeBuilder::new(Role::Paragraph); + builder.set_children(vec![ EMPTY_CONTAINER_3_0_IGNORED_ID, LINK_3_1_IGNORED_ID, BUTTON_3_2_ID, EMPTY_CONTAINER_3_3_IGNORED_ID, ]); - node + builder.build() }; - let empty_container_3_0_ignored = Node::new(Role::GenericContainer); + let empty_container_3_0_ignored = NodeBuilder::new(Role::GenericContainer).build(); let link_3_1_ignored = { - let mut node = Node::new(Role::Link); - node.set_children(vec![STATIC_TEXT_3_1_0_ID]); - node.set_linked(); - node + let mut builder = NodeBuilder::new(Role::Link); + builder.set_children(vec![STATIC_TEXT_3_1_0_ID]); + builder.set_linked(); + builder.build() }; let static_text_3_1_0 = { - let mut node = Node::new(Role::StaticText); - node.set_name("static_text_3_1_0"); - node + let mut builder = NodeBuilder::new(Role::StaticText); + builder.set_name("static_text_3_1_0"); + builder.build() }; let button_3_2 = { - let mut node = Node::new(Role::Button); - node.set_name("button_3_2"); - node + let mut builder = NodeBuilder::new(Role::Button); + builder.set_name("button_3_2"); + builder.build() }; - let empty_container_3_3_ignored = Node::new(Role::GenericContainer); + let empty_container_3_3_ignored = NodeBuilder::new(Role::GenericContainer).build(); let initial_update = TreeUpdate { nodes: vec![ (ROOT_ID, root), diff --git a/consumer/src/node.rs b/consumer/src/node.rs index b1d966330..4d991fec1 100644 --- a/consumer/src/node.rs +++ b/consumer/src/node.rs @@ -720,7 +720,7 @@ impl Deref for DetachedNode { #[cfg(test)] mod tests { - use accesskit::{Node, NodeId, Point, Rect, Role, Tree, TreeUpdate}; + use accesskit::{NodeBuilder, NodeId, Point, Rect, Role, Tree, TreeUpdate}; use std::num::NonZeroU128; use crate::tests::*; @@ -994,11 +994,11 @@ mod tests { let update = TreeUpdate { nodes: vec![ (NODE_ID_1, { - let mut node = Node::new(Role::Window); - node.set_children(vec![NODE_ID_2]); - node + let mut builder = NodeBuilder::new(Role::Window); + builder.set_children(vec![NODE_ID_2]); + builder.build() }), - (NODE_ID_2, Node::new(Role::Button)), + (NODE_ID_2, NodeBuilder::new(Role::Button).build()), ], tree: Some(Tree::new(NODE_ID_1)), focus: None, @@ -1017,29 +1017,29 @@ mod tests { let update = TreeUpdate { nodes: vec![ (NODE_ID_1, { - let mut node = Node::new(Role::Window); - node.set_children(vec![NODE_ID_2, NODE_ID_3, NODE_ID_4, NODE_ID_5]); - node + let mut builder = NodeBuilder::new(Role::Window); + builder.set_children(vec![NODE_ID_2, NODE_ID_3, NODE_ID_4, NODE_ID_5]); + builder.build() }), (NODE_ID_2, { - let mut node = Node::new(Role::CheckBox); - node.set_labelled_by(vec![NODE_ID_3, NODE_ID_5]); - node + let mut builder = NodeBuilder::new(Role::CheckBox); + builder.set_labelled_by(vec![NODE_ID_3, NODE_ID_5]); + builder.build() }), (NODE_ID_3, { - let mut node = Node::new(Role::StaticText); - node.set_name(LABEL_1); - node + let mut builder = NodeBuilder::new(Role::StaticText); + builder.set_name(LABEL_1); + builder.build() }), (NODE_ID_4, { - let mut node = Node::new(Role::TextField); - node.push_labelled_by(NODE_ID_5); - node + let mut builder = NodeBuilder::new(Role::TextField); + builder.push_labelled_by(NODE_ID_5); + builder.build() }), (NODE_ID_5, { - let mut node = Node::new(Role::StaticText); - node.set_name(LABEL_2); - node + let mut builder = NodeBuilder::new(Role::StaticText); + builder.set_name(LABEL_2); + builder.build() }), ], tree: Some(Tree::new(NODE_ID_1)), @@ -1064,34 +1064,34 @@ mod tests { let update = TreeUpdate { nodes: vec![ (NODE_ID_1, { - let mut node = Node::new(Role::Window); - node.set_children(vec![NODE_ID_2, NODE_ID_4]); - node + let mut builder = NodeBuilder::new(Role::Window); + builder.set_children(vec![NODE_ID_2, NODE_ID_4]); + builder.build() }), (NODE_ID_2, { - let mut node = Node::new(Role::Button); - node.push_child(NODE_ID_3); - node + let mut builder = NodeBuilder::new(Role::Button); + builder.push_child(NODE_ID_3); + builder.build() }), (NODE_ID_3, { - let mut node = Node::new(Role::Image); - node.set_name(BUTTON_LABEL); - node + let mut builder = NodeBuilder::new(Role::Image); + builder.set_name(BUTTON_LABEL); + builder.build() }), (NODE_ID_4, { - let mut node = Node::new(Role::Link); - node.push_child(NODE_ID_5); - node + let mut builder = NodeBuilder::new(Role::Link); + builder.push_child(NODE_ID_5); + builder.build() }), (NODE_ID_5, { - let mut node = Node::new(Role::GenericContainer); - node.push_child(NODE_ID_6); - node + let mut builder = NodeBuilder::new(Role::GenericContainer); + builder.push_child(NODE_ID_6); + builder.build() }), (NODE_ID_6, { - let mut node = Node::new(Role::StaticText); - node.set_name(LINK_LABEL); - node + let mut builder = NodeBuilder::new(Role::StaticText); + builder.set_name(LINK_LABEL); + builder.build() }), ], tree: Some(Tree::new(NODE_ID_1)), diff --git a/consumer/src/text.rs b/consumer/src/text.rs index 886c1743d..736641e30 100644 --- a/consumer/src/text.rs +++ b/consumer/src/text.rs @@ -1028,36 +1028,36 @@ mod tests { // This is based on an actual tree produced by egui. fn main_multiline_tree(selection: Option) -> crate::Tree { - use accesskit::{Action, Affine, Node, Role, TextDirection, Tree, TreeUpdate}; + use accesskit::{Action, Affine, NodeBuilder, Role, TextDirection, Tree, TreeUpdate}; let update = TreeUpdate { nodes: vec![ (NODE_ID_1, { - let mut node = Node::new(Role::Window); - node.set_transform(Affine::scale(1.5)); - node.set_children(vec![NODE_ID_2]); - node + let mut builder = NodeBuilder::new(Role::Window); + builder.set_transform(Affine::scale(1.5)); + builder.set_children(vec![NODE_ID_2]); + builder.build() }), (NODE_ID_2, { - let mut node = Node::new(Role::TextField); - node.set_bounds(Rect { + let mut builder = NodeBuilder::new(Role::TextField); + builder.set_bounds(Rect { x0: 8.0, y0: 31.666664123535156, x1: 296.0, y1: 123.66666412353516, }); - node.set_children(vec![ + builder.set_children(vec![ NODE_ID_3, NODE_ID_4, NODE_ID_5, NODE_ID_6, NODE_ID_7, NODE_ID_8, ]); - node.add_action(Action::Focus); + builder.add_action(Action::Focus); if let Some(selection) = selection { - node.set_text_selection(selection); + builder.set_text_selection(selection); } - node + builder.build() }), (NODE_ID_3, { - let mut node = Node::new(Role::InlineTextBox); - node.set_bounds(Rect { + let mut builder = NodeBuilder::new(Role::InlineTextBox); + builder.set_bounds(Rect { x0: 12.0, y0: 33.666664123535156, x1: 290.9189147949219, @@ -1067,98 +1067,99 @@ mod tests { // is in an arbitrary spot; its only purpose // is to test conversion between UTF-8 and UTF-16 // indices. - node.set_value("This paragraph is\u{a0}long enough to wrap "); - node.set_text_direction(TextDirection::LeftToRight); - node.set_character_lengths([ + builder.set_value("This paragraph is\u{a0}long enough to wrap "); + builder.set_text_direction(TextDirection::LeftToRight); + builder.set_character_lengths([ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ]); - node.set_character_positions([ + builder.set_character_positions([ 0.0, 7.3333335, 14.666667, 22.0, 29.333334, 36.666668, 44.0, 51.333332, 58.666668, 66.0, 73.333336, 80.666664, 88.0, 95.333336, 102.666664, 110.0, 117.333336, 124.666664, 132.0, 139.33333, 146.66667, 154.0, 161.33333, 168.66667, 176.0, 183.33333, 190.66667, 198.0, 205.33333, 212.66667, 220.0, 227.33333, 234.66667, 242.0, 249.33333, 256.66666, 264.0, 271.33334, ]); - node.set_character_widths([ + builder.set_character_widths([ 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, ]); - node.set_word_lengths([5, 10, 3, 5, 7, 3, 5]); - node + builder.set_word_lengths([5, 10, 3, 5, 7, 3, 5]); + builder.build() }), (NODE_ID_4, { - let mut node = Node::new(Role::InlineTextBox); - node.set_bounds(Rect { + let mut builder = NodeBuilder::new(Role::InlineTextBox); + builder.set_bounds(Rect { x0: 12.0, y0: 48.33333206176758, x1: 129.5855712890625, y1: 63.0, }); - node.set_value("to another line.\n"); - node.set_text_direction(TextDirection::LeftToRight); - node.set_character_lengths([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]); - node.set_character_positions([ + builder.set_value("to another line.\n"); + builder.set_text_direction(TextDirection::LeftToRight); + builder + .set_character_lengths([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]); + builder.set_character_positions([ 0.0, 7.3333435, 14.666687, 22.0, 29.333344, 36.666687, 44.0, 51.333344, 58.666687, 66.0, 73.33334, 80.66669, 88.0, 95.33334, 102.66669, 110.0, 117.58557, ]); - node.set_character_widths([ + builder.set_character_widths([ 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 0.0, ]); - node.set_word_lengths([3, 8, 6]); - node + builder.set_word_lengths([3, 8, 6]); + builder.build() }), (NODE_ID_5, { - let mut node = Node::new(Role::InlineTextBox); - node.set_bounds(Rect { + let mut builder = NodeBuilder::new(Role::InlineTextBox); + builder.set_bounds(Rect { x0: 12.0, y0: 63.0, x1: 144.25222778320313, y1: 77.66666412353516, }); - node.set_value("Another paragraph.\n"); - node.set_text_direction(TextDirection::LeftToRight); - node.set_character_lengths([ + builder.set_value("Another paragraph.\n"); + builder.set_text_direction(TextDirection::LeftToRight); + builder.set_character_lengths([ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ]); - node.set_character_positions([ + builder.set_character_positions([ 0.0, 7.3333335, 14.666667, 22.0, 29.333334, 36.666668, 44.0, 51.333332, 58.666668, 66.0, 73.333336, 80.666664, 88.0, 95.333336, 102.666664, 110.0, 117.333336, 124.666664, 132.25223, ]); - node.set_character_widths([ + builder.set_character_widths([ 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 0.0, ]); - node.set_word_lengths([8, 11]); - node + builder.set_word_lengths([8, 11]); + builder.build() }), (NODE_ID_6, { - let mut node = Node::new(Role::InlineTextBox); - node.set_bounds(Rect { + let mut builder = NodeBuilder::new(Role::InlineTextBox); + builder.set_bounds(Rect { x0: 12.0, y0: 77.66666412353516, x1: 12.0, y1: 92.33332824707031, }); - node.set_value("\n"); - node.set_text_direction(TextDirection::LeftToRight); - node.set_character_lengths([1]); - node.set_character_positions([0.0]); - node.set_character_widths([0.0]); - node.set_word_lengths([1]); - node + builder.set_value("\n"); + builder.set_text_direction(TextDirection::LeftToRight); + builder.set_character_lengths([1]); + builder.set_character_positions([0.0]); + builder.set_character_widths([0.0]); + builder.set_word_lengths([1]); + builder.build() }), (NODE_ID_7, { - let mut node = Node::new(Role::InlineTextBox); - node.set_bounds(Rect { + let mut builder = NodeBuilder::new(Role::InlineTextBox); + builder.set_bounds(Rect { x0: 12.0, y0: 92.33332824707031, x1: 158.9188995361328, @@ -1167,39 +1168,39 @@ mod tests { // Use an arbitrary emoji that encodes to two // UTF-16 code units to fully test conversion between // UTF-8, UTF-16, and character indices. - node.set_value("Last non-blank line\u{1f60a}\n"); - node.set_text_direction(TextDirection::LeftToRight); - node.set_character_lengths([ + builder.set_value("Last non-blank line\u{1f60a}\n"); + builder.set_text_direction(TextDirection::LeftToRight); + builder.set_character_lengths([ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, ]); - node.set_character_positions([ + builder.set_character_positions([ 0.0, 7.3333335, 14.666667, 22.0, 29.333334, 36.666668, 44.0, 51.333332, 58.666668, 66.0, 73.333336, 80.666664, 88.0, 95.333336, 102.666664, 110.0, 117.333336, 124.666664, 132.0, 139.33333, 146.9189, ]); - node.set_character_widths([ + builder.set_character_widths([ 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 0.0, ]); - node.set_word_lengths([5, 4, 6, 6]); - node + builder.set_word_lengths([5, 4, 6, 6]); + builder.build() }), (NODE_ID_8, { - let mut node = Node::new(Role::InlineTextBox); - node.set_bounds(Rect { + let mut builder = NodeBuilder::new(Role::InlineTextBox); + builder.set_bounds(Rect { x0: 12.0, y0: 107.0, x1: 12.0, y1: 121.66666412353516, }); - node.set_value(""); - node.set_text_direction(TextDirection::LeftToRight); - node.set_character_lengths([]); - node.set_character_positions([]); - node.set_character_widths([]); - node.set_word_lengths([0]); - node + builder.set_value(""); + builder.set_text_direction(TextDirection::LeftToRight); + builder.set_character_lengths([]); + builder.set_character_positions([]); + builder.set_character_widths([]); + builder.set_word_lengths([0]); + builder.build() }), ], tree: Some(Tree::new(NODE_ID_1)), diff --git a/consumer/src/tree.rs b/consumer/src/tree.rs index 5701960fc..dbd8bd480 100644 --- a/consumer/src/tree.rs +++ b/consumer/src/tree.rs @@ -425,7 +425,7 @@ impl Tree { #[cfg(test)] mod tests { - use accesskit::{Node, NodeId, Role, Tree, TreeUpdate}; + use accesskit::{NodeBuilder, NodeId, Role, Tree, TreeUpdate}; use std::num::NonZeroU128; use crate::tests::NullActionHandler; @@ -437,7 +437,7 @@ mod tests { #[test] fn init_tree_with_root_node() { let update = TreeUpdate { - nodes: vec![(NODE_ID_1, Node::new(Role::Window))], + nodes: vec![(NODE_ID_1, NodeBuilder::new(Role::Window).build())], tree: Some(Tree::new(NODE_ID_1)), focus: None, }; @@ -452,12 +452,12 @@ mod tests { let update = TreeUpdate { nodes: vec![ (NODE_ID_1, { - let mut node = Node::new(Role::Window); - node.set_children(vec![NODE_ID_2, NODE_ID_3]); - node + let mut builder = NodeBuilder::new(Role::Window); + builder.set_children(vec![NODE_ID_2, NODE_ID_3]); + builder.build() }), - (NODE_ID_2, Node::new(Role::Button)), - (NODE_ID_3, Node::new(Role::Button)), + (NODE_ID_2, NodeBuilder::new(Role::Button).build()), + (NODE_ID_3, NodeBuilder::new(Role::Button).build()), ], tree: Some(Tree::new(NODE_ID_1)), focus: None, @@ -477,9 +477,9 @@ mod tests { #[test] fn add_child_to_root_node() { - let root_node = Node::new(Role::Window); + let root_builder = NodeBuilder::new(Role::Window); let first_update = TreeUpdate { - nodes: vec![(NODE_ID_1, root_node.clone())], + nodes: vec![(NODE_ID_1, root_builder.clone().build())], tree: Some(Tree::new(NODE_ID_1)), focus: None, }; @@ -488,11 +488,11 @@ mod tests { let second_update = TreeUpdate { nodes: vec![ (NODE_ID_1, { - let mut node = root_node; - node.push_child(NODE_ID_2); - node + let mut builder = root_builder; + builder.push_child(NODE_ID_2); + builder.build() }), - (NODE_ID_2, Node::new(Role::RootWebArea)), + (NODE_ID_2, NodeBuilder::new(Role::RootWebArea).build()), ], tree: None, focus: None, @@ -555,15 +555,15 @@ mod tests { #[test] fn remove_child_from_root_node() { - let root_node = Node::new(Role::Window); + let root_builder = NodeBuilder::new(Role::Window); let first_update = TreeUpdate { nodes: vec![ (NODE_ID_1, { - let mut node = root_node.clone(); - node.push_child(NODE_ID_2); - node + let mut builder = root_builder.clone(); + builder.push_child(NODE_ID_2); + builder.build() }), - (NODE_ID_2, Node::new(Role::RootWebArea)), + (NODE_ID_2, NodeBuilder::new(Role::RootWebArea).build()), ], tree: Some(Tree::new(NODE_ID_1)), focus: None, @@ -571,7 +571,7 @@ mod tests { let tree = super::Tree::new(first_update, Box::new(NullActionHandler {})); assert_eq!(1, tree.read().root().children().count()); let second_update = TreeUpdate { - nodes: vec![(NODE_ID_1, root_node)], + nodes: vec![(NODE_ID_1, root_builder.build())], tree: None, focus: None, }; @@ -631,12 +631,12 @@ mod tests { let first_update = TreeUpdate { nodes: vec![ (NODE_ID_1, { - let mut node = Node::new(Role::Window); - node.set_children(vec![NODE_ID_2, NODE_ID_3]); - node + let mut builder = NodeBuilder::new(Role::Window); + builder.set_children(vec![NODE_ID_2, NODE_ID_3]); + builder.build() }), - (NODE_ID_2, Node::new(Role::Button)), - (NODE_ID_3, Node::new(Role::Button)), + (NODE_ID_2, NodeBuilder::new(Role::Button).build()), + (NODE_ID_3, NodeBuilder::new(Role::Button).build()), ], tree: Some(Tree::new(NODE_ID_1)), focus: Some(NODE_ID_2), @@ -715,18 +715,18 @@ mod tests { #[test] fn update_node() { - let child_node = Node::new(Role::Button); + let child_builder = NodeBuilder::new(Role::Button); let first_update = TreeUpdate { nodes: vec![ (NODE_ID_1, { - let mut node = Node::new(Role::Window); - node.set_children(vec![NODE_ID_2]); - node + let mut builder = NodeBuilder::new(Role::Window); + builder.set_children(vec![NODE_ID_2]); + builder.build() }), (NODE_ID_2, { - let mut node = child_node.clone(); - node.set_name("foo"); - node + let mut builder = child_builder.clone(); + builder.set_name("foo"); + builder.build() }), ], tree: Some(Tree::new(NODE_ID_1)), @@ -739,9 +739,9 @@ mod tests { ); let second_update = TreeUpdate { nodes: vec![(NODE_ID_2, { - let mut node = child_node; - node.set_name("bar"); - node + let mut builder = child_builder; + builder.set_name("bar"); + builder.build() })], tree: None, focus: None, diff --git a/platforms/windows/examples/hello_world.rs b/platforms/windows/examples/hello_world.rs index 03a2a24da..ee4af1cec 100644 --- a/platforms/windows/examples/hello_world.rs +++ b/platforms/windows/examples/hello_world.rs @@ -1,8 +1,8 @@ // Based on the create_window sample in windows-samples-rs. use accesskit::{ - Action, ActionHandler, ActionRequest, DefaultActionVerb, Live, Node, NodeId, Rect, Role, Tree, - TreeUpdate, + Action, ActionHandler, ActionRequest, DefaultActionVerb, Live, Node, NodeBuilder, NodeId, Rect, + Role, Tree, TreeUpdate, }; use accesskit_windows::UiaInitMarker; use once_cell::{sync::Lazy, unsync::OnceCell}; @@ -69,19 +69,19 @@ fn make_button(id: NodeId, name: &str) -> Node { _ => unreachable!(), }; - let mut node = Node::new(Role::Button); - node.set_bounds(rect); - node.set_name(name); - node.add_action(Action::Focus); - node.set_default_action_verb(DefaultActionVerb::Click); - node + let mut builder = NodeBuilder::new(Role::Button); + builder.set_bounds(rect); + builder.set_name(name); + builder.add_action(Action::Focus); + builder.set_default_action_verb(DefaultActionVerb::Click); + builder.build() } fn get_initial_state() -> TreeUpdate { let root = { - let mut node = Node::new(Role::Window); - node.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); - node + let mut builder = NodeBuilder::new(Role::Window); + builder.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); + builder.build() }; let button_1 = make_button(BUTTON_1_ID, "Button 1"); let button_2 = make_button(BUTTON_2_ID, "Button 2"); @@ -150,15 +150,15 @@ impl WindowState { "You pressed button 2" }; let node = { - let mut node = Node::new(Role::StaticText); - node.set_name(name); - node.set_live(Live::Polite); - node + let mut builder = NodeBuilder::new(Role::StaticText); + builder.set_name(name); + builder.set_live(Live::Polite); + builder.build() }; let root = { - let mut node = Node::new(Role::Window); - node.set_children(vec![BUTTON_1_ID, BUTTON_2_ID, PRESSED_TEXT_ID]); - node + let mut builder = NodeBuilder::new(Role::Window); + builder.set_children(vec![BUTTON_1_ID, BUTTON_2_ID, PRESSED_TEXT_ID]); + builder.build() }; let update = TreeUpdate { nodes: vec![(PRESSED_TEXT_ID, node), (WINDOW_ID, root)], diff --git a/platforms/windows/src/tests/simple.rs b/platforms/windows/src/tests/simple.rs index f2f12f6be..1401adfa9 100644 --- a/platforms/windows/src/tests/simple.rs +++ b/platforms/windows/src/tests/simple.rs @@ -5,7 +5,9 @@ use std::{convert::TryInto, num::NonZeroU128}; -use accesskit::{Action, ActionHandler, ActionRequest, Node, NodeId, Role, Tree, TreeUpdate}; +use accesskit::{ + Action, ActionHandler, ActionRequest, Node, NodeBuilder, NodeId, Role, Tree, TreeUpdate, +}; use windows::{core::*, Win32::UI::Accessibility::*}; use super::*; @@ -17,17 +19,17 @@ const BUTTON_1_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(2) }); const BUTTON_2_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(3) }); fn make_button(name: &str) -> Node { - let mut node = Node::new(Role::Button); - node.set_name(name); - node.add_action(Action::Focus); - node + let mut builder = NodeBuilder::new(Role::Button); + builder.set_name(name); + builder.add_action(Action::Focus); + builder.build() } fn get_initial_state() -> TreeUpdate { let root = { - let mut node = Node::new(Role::Window); - node.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); - node + let mut builder = NodeBuilder::new(Role::Window); + builder.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); + builder.build() }; let button_1 = make_button("Button 1"); let button_2 = make_button("Button 2"); diff --git a/platforms/windows/src/tests/subclassed.rs b/platforms/windows/src/tests/subclassed.rs index 314bdb57b..c41310356 100644 --- a/platforms/windows/src/tests/subclassed.rs +++ b/platforms/windows/src/tests/subclassed.rs @@ -5,7 +5,9 @@ use std::num::NonZeroU128; -use accesskit::{Action, ActionHandler, ActionRequest, Node, NodeId, Role, Tree, TreeUpdate}; +use accesskit::{ + Action, ActionHandler, ActionRequest, Node, NodeBuilder, NodeId, Role, Tree, TreeUpdate, +}; use windows::Win32::{Foundation::*, UI::Accessibility::*}; use winit::{ event_loop::EventLoopBuilder, @@ -22,18 +24,18 @@ const BUTTON_1_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(2) }); const BUTTON_2_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(3) }); fn make_button(name: &str) -> Node { - let mut node = Node::new(Role::Button); - node.set_name(name); - node.add_action(Action::Focus); - node + let mut builder = NodeBuilder::new(Role::Button); + builder.set_name(name); + builder.add_action(Action::Focus); + builder.build() } fn get_initial_state() -> TreeUpdate { let root = { - let mut node = Node::new(Role::Window); - node.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); - node.set_name(WINDOW_TITLE); - node + let mut builder = NodeBuilder::new(Role::Window); + builder.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); + builder.set_name(WINDOW_TITLE); + builder.build() }; let button_1 = make_button("Button 1"); let button_2 = make_button("Button 2"); diff --git a/platforms/winit/examples/simple.rs b/platforms/winit/examples/simple.rs index 76ea8a6db..a0b2d10c4 100644 --- a/platforms/winit/examples/simple.rs +++ b/platforms/winit/examples/simple.rs @@ -1,5 +1,6 @@ use accesskit::{ - Action, ActionRequest, DefaultActionVerb, Live, Node, NodeId, Rect, Role, Tree, TreeUpdate, + Action, ActionRequest, DefaultActionVerb, Live, Node, NodeBuilder, NodeId, Rect, Role, Tree, + TreeUpdate, }; use accesskit_winit::{ActionRequestEvent, Adapter}; use std::{ @@ -41,12 +42,12 @@ fn make_button(id: NodeId, name: &str) -> Node { _ => unreachable!(), }; - let mut node = Node::new(Role::Button); - node.set_bounds(rect); - node.set_name(name); - node.add_action(Action::Focus); - node.set_default_action_verb(DefaultActionVerb::Click); - node + let mut builder = NodeBuilder::new(Role::Button); + builder.set_bounds(rect); + builder.set_name(name); + builder.add_action(Action::Focus); + builder.set_default_action_verb(DefaultActionVerb::Click); + builder.build() } #[derive(Debug)] @@ -88,16 +89,16 @@ impl State { "You pressed button 2" }; let node = { - let mut node = Node::new(Role::StaticText); - node.set_name(name); - node.set_live(Live::Polite); - node + let mut builder = NodeBuilder::new(Role::StaticText); + builder.set_name(name); + builder.set_live(Live::Polite); + builder.build() }; let root = { - let mut node = Node::new(Role::Window); - node.set_children(vec![BUTTON_1_ID, BUTTON_2_ID, PRESSED_TEXT_ID]); - node.set_name(WINDOW_TITLE); - node + let mut builder = NodeBuilder::new(Role::Window); + builder.set_children(vec![BUTTON_1_ID, BUTTON_2_ID, PRESSED_TEXT_ID]); + builder.set_name(WINDOW_TITLE); + builder.build() }; let update = TreeUpdate { nodes: vec![(PRESSED_TEXT_ID, node), (WINDOW_ID, root)], @@ -110,10 +111,10 @@ impl State { fn initial_tree_update(state: &State) -> TreeUpdate { let root = { - let mut node = Node::new(Role::Window); - node.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); - node.set_name(WINDOW_TITLE); - node + let mut builder = NodeBuilder::new(Role::Window); + builder.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); + builder.set_name(WINDOW_TITLE); + builder.build() }; let button_1 = make_button(BUTTON_1_ID, "Button 1"); let button_2 = make_button(BUTTON_2_ID, "Button 2"); From 9f33e78a19920f853f376ee6011bc975a77f6300 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sat, 14 Jan 2023 16:26:30 -0600 Subject: [PATCH 35/45] The payoff: shared node classes --- common/src/lib.rs | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index c1401af5c..f15ab4d10 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -16,12 +16,13 @@ use serde::{ ser::{SerializeMap, SerializeSeq, Serializer}, Deserialize, Serialize, }; -#[cfg(feature = "serde")] -use std::{fmt, mem::size_of_val}; use std::{ + collections::BTreeSet, num::{NonZeroU128, NonZeroU64}, - sync::Arc, + sync::{Arc, Mutex}, }; +#[cfg(feature = "serde")] +use std::{fmt, mem::size_of_val}; mod geometry; pub use geometry::{Affine, Point, Rect, Size, Vec2}; @@ -36,7 +37,7 @@ pub use geometry::{Affine, Point, Rect, Size, Vec2}; /// is ordered roughly by expected usage frequency (with the notable exception /// of [`Role::Unknown`]). This is more efficient in serialization formats /// where integers use a variable-length encoding. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] @@ -340,7 +341,7 @@ impl Action { } } -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] #[repr(transparent)] struct Actions(u32); @@ -870,7 +871,7 @@ enum PropertyId { Unset, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] struct PropertyIndices([u8; PropertyId::Unset as usize]); @@ -880,13 +881,15 @@ impl Default for PropertyIndices { } } -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] struct NodeClass { role: Role, actions: Actions, indices: PropertyIndices, } +static NODE_CLASSES: Mutex>> = Mutex::new(BTreeSet::new()); + /// A single accessible object. A complete UI is represented as a tree of these. /// /// For brevity, and to make more of the documentation usable in bindings @@ -895,7 +898,7 @@ struct NodeClass { /// as properties. #[derive(Clone, Debug, PartialEq)] pub struct Node { - class: NodeClass, + class: Arc, flags: u32, props: Arc<[PropertyValue]>, } @@ -1351,8 +1354,16 @@ impl NodeBuilder { } pub fn build(self) -> Node { + let mut classes = NODE_CLASSES.lock().unwrap(); + let class = if let Some(class) = classes.get(&self.class) { + Arc::clone(class) + } else { + let class = Arc::new(self.class); + classes.insert(Arc::clone(&class)); + class + }; Node { - class: self.class, + class, flags: self.flags, props: self.props.into(), } From 7a11daa340786c9a8de7290d35ff278dca9b1ff6 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sat, 14 Jan 2023 16:37:50 -0600 Subject: [PATCH 36/45] accesskit::Node now uses Arc internally, so don't use it in the consumer lib --- consumer/src/node.rs | 4 ++-- consumer/src/tree.rs | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/consumer/src/node.rs b/consumer/src/node.rs index 4d991fec1..9a0d6bf91 100644 --- a/consumer/src/node.rs +++ b/consumer/src/node.rs @@ -8,7 +8,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE.chromium file. -use std::{iter::FusedIterator, ops::Deref, sync::Arc}; +use std::{iter::FusedIterator, ops::Deref}; use accesskit::{ Action, Affine, CheckedState, DefaultActionVerb, Live, Node as NodeData, NodeId, Point, Rect, @@ -28,7 +28,7 @@ pub(crate) struct ParentAndIndex(pub(crate) NodeId, pub(crate) usize); pub struct NodeState { pub(crate) id: NodeId, pub(crate) parent_and_index: Option, - pub(crate) data: Arc, + pub(crate) data: NodeData, } #[derive(Copy, Clone)] diff --git a/consumer/src/tree.rs b/consumer/src/tree.rs index dbd8bd480..e4cedaeee 100644 --- a/consumer/src/tree.rs +++ b/consumer/src/tree.rs @@ -11,7 +11,6 @@ use parking_lot::{RwLock, RwLockWriteGuard}; use std::{ collections::{HashMap, HashSet}, ops::Deref, - sync::Arc, }; use crate::{ @@ -88,7 +87,7 @@ impl State { let state = NodeState { id, parent_and_index, - data: Arc::new(data), + data, }; nodes.insert(id, state); if let Some(changes) = changes { @@ -131,7 +130,7 @@ impl State { orphans.insert(*child_id); } } - node_state.data = Arc::new(node_data); + node_state.data = node_data; } else if let Some(parent_and_index) = pending_children.remove(&node_id) { add_node( &mut self.nodes, @@ -210,7 +209,7 @@ impl State { fn traverse(state: &State, nodes: &mut Vec<(NodeId, NodeData)>, id: NodeId) { let node = state.nodes.get(&id).unwrap(); - nodes.push((id, (*node.data).clone())); + nodes.push((id, node.data.clone())); for child_id in node.data.children().iter() { traverse(state, nodes, *child_id); From f25a3ab5d1a2392151c40511400c7636000cbef7 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sat, 14 Jan 2023 17:08:52 -0600 Subject: [PATCH 37/45] Do the unexpected-property-type panic in just one place --- common/src/lib.rs | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index f15ab4d10..3d4d96409 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -922,6 +922,10 @@ impl NodeClass { } } +fn unexpected_property_type(id: PropertyId) -> ! { + panic!("unexpected type for property {:?}", id); +} + impl NodeBuilder { fn get_property_mut(&mut self, id: PropertyId, default: PropertyValue) -> &mut PropertyValue { let index = self.class.indices.0[id as usize] as usize; @@ -961,7 +965,7 @@ impl NodeClass { match self.get_property(props, id) { PropertyValue::None => None, PropertyValue::Affine(value) => Some(value), - _ => panic!(), + _ => unexpected_property_type(id), } } } @@ -977,7 +981,7 @@ impl NodeClass { match self.get_property(props, id) { PropertyValue::None => None, PropertyValue::Rect(value) => Some(*value), - _ => panic!(), + _ => unexpected_property_type(id), } } } @@ -993,7 +997,7 @@ impl NodeClass { match self.get_property(props, id) { PropertyValue::None => &[], PropertyValue::NodeIdVec(value) => value, - _ => panic!(), + _ => unexpected_property_type(id), } } } @@ -1004,7 +1008,7 @@ impl NodeBuilder { PropertyValue::NodeIdVec(v) => { v.push(node_id); } - _ => panic!(), + _ => unexpected_property_type(property_id), } } @@ -1018,7 +1022,7 @@ impl NodeClass { match self.get_property(props, id) { PropertyValue::None => None, PropertyValue::NodeId(value) => Some(*value), - _ => panic!(), + _ => unexpected_property_type(id), } } } @@ -1034,7 +1038,7 @@ impl NodeClass { match self.get_property(props, id) { PropertyValue::None => None, PropertyValue::String(value) => Some(value), - _ => panic!(), + _ => unexpected_property_type(id), } } } @@ -1050,7 +1054,7 @@ impl NodeClass { match self.get_property(props, id) { PropertyValue::None => None, PropertyValue::F64(value) => Some(*value), - _ => panic!(), + _ => unexpected_property_type(id), } } } @@ -1066,7 +1070,7 @@ impl NodeClass { match self.get_property(props, id) { PropertyValue::None => None, PropertyValue::Usize(value) => Some(*value), - _ => panic!(), + _ => unexpected_property_type(id), } } } @@ -1082,7 +1086,7 @@ impl NodeClass { match self.get_property(props, id) { PropertyValue::None => None, PropertyValue::Color(value) => Some(*value), - _ => panic!(), + _ => unexpected_property_type(id), } } } @@ -1102,7 +1106,7 @@ impl NodeClass { match self.get_property(props, id) { PropertyValue::None => None, PropertyValue::TextDecoration(value) => Some(*value), - _ => panic!(), + _ => unexpected_property_type(id), } } } @@ -1118,7 +1122,7 @@ impl NodeClass { match self.get_property(props, id) { PropertyValue::None => &[], PropertyValue::LengthSlice(value) => value, - _ => panic!(), + _ => unexpected_property_type(id), } } } @@ -1134,7 +1138,7 @@ impl NodeClass { match self.get_property(props, id) { PropertyValue::None => None, PropertyValue::CoordSlice(value) => Some(value), - _ => panic!(), + _ => unexpected_property_type(id), } } } @@ -1150,7 +1154,7 @@ impl NodeClass { match self.get_property(props, id) { PropertyValue::None => None, PropertyValue::Bool(value) => Some(*value), - _ => panic!(), + _ => unexpected_property_type(id), } } } @@ -1319,7 +1323,7 @@ macro_rules! unique_enum_property_methods { match self.class.get_property(&self.props, PropertyId::$id) { PropertyValue::None => None, PropertyValue::$id(value) => Some(*value), - _ => panic!(), + _ => unexpected_property_type(PropertyId::$id), } })* } @@ -1329,7 +1333,7 @@ macro_rules! unique_enum_property_methods { match self.class.get_property(&self.props, PropertyId::$id) { PropertyValue::None => None, PropertyValue::$id(value) => Some(*value), - _ => panic!(), + _ => unexpected_property_type(PropertyId::$id), } } pub fn $setter(&mut self, value: $id) { @@ -1738,7 +1742,7 @@ impl Node { { PropertyValue::None => None, PropertyValue::TextSelection(value) => Some(value), - _ => panic!(), + _ => unexpected_property_type(PropertyId::TextSelection), } } } @@ -1751,7 +1755,7 @@ impl NodeBuilder { { PropertyValue::None => None, PropertyValue::TextSelection(value) => Some(value), - _ => panic!(), + _ => unexpected_property_type(PropertyId::TextSelection), } } pub fn set_text_selection(&mut self, value: impl Into>) { @@ -1773,7 +1777,7 @@ impl Node { { PropertyValue::None => &[], PropertyValue::CustomActionVec(value) => value, - _ => panic!(), + _ => unexpected_property_type(PropertyId::CustomActions), } } } @@ -1786,7 +1790,7 @@ impl NodeBuilder { { PropertyValue::None => &[], PropertyValue::CustomActionVec(value) => value, - _ => panic!(), + _ => unexpected_property_type(PropertyId::CustomActions), } } pub fn set_custom_actions(&mut self, value: impl Into>) { @@ -1803,7 +1807,7 @@ impl NodeBuilder { PropertyValue::CustomActionVec(v) => { v.push(action); } - _ => panic!(), + _ => unexpected_property_type(PropertyId::CustomActions), } } pub fn clear_custom_actions(&mut self) { From af4d246c17da5f2684041dd85aef3957bfa0d0e9 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sat, 14 Jan 2023 18:51:41 -0600 Subject: [PATCH 38/45] Reduce boilerplate with some new macros --- common/src/lib.rs | 415 +++++++++++++++------------------------------- 1 file changed, 131 insertions(+), 284 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 3d4d96409..4edad748c 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -960,229 +960,107 @@ impl NodeBuilder { } } -impl NodeClass { - fn get_affine<'a>(&self, props: &'a [PropertyValue], id: PropertyId) -> Option<&'a Affine> { - match self.get_property(props, id) { - PropertyValue::None => None, - PropertyValue::Affine(value) => Some(value), - _ => unexpected_property_type(id), - } - } -} - -impl NodeBuilder { - fn set_affine(&mut self, id: PropertyId, value: impl Into>) { - self.set_property(id, PropertyValue::Affine(value.into())); - } -} - -impl NodeClass { - fn get_rect(&self, props: &[PropertyValue], id: PropertyId) -> Option { - match self.get_property(props, id) { - PropertyValue::None => None, - PropertyValue::Rect(value) => Some(*value), - _ => unexpected_property_type(id), - } - } -} - -impl NodeBuilder { - fn set_rect(&mut self, id: PropertyId, value: Rect) { - self.set_property(id, PropertyValue::Rect(value)); - } -} - -impl NodeClass { - fn get_node_id_vec<'a>(&self, props: &'a [PropertyValue], id: PropertyId) -> &'a [NodeId] { - match self.get_property(props, id) { - PropertyValue::None => &[], - PropertyValue::NodeIdVec(value) => value, - _ => unexpected_property_type(id), +macro_rules! flag_methods { + ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { + impl Node { + $($(#[$doc])* + pub fn $getter(&self) -> bool { + (self.flags & (Flag::$id).mask()) != 0 + })* } - } -} - -impl NodeBuilder { - fn push_to_node_id_vec(&mut self, property_id: PropertyId, node_id: NodeId) { - match self.get_property_mut(property_id, PropertyValue::NodeIdVec(Vec::new())) { - PropertyValue::NodeIdVec(v) => { - v.push(node_id); + impl NodeBuilder { + $($(#[$doc])* + pub fn $getter(&self) -> bool { + (self.flags & (Flag::$id).mask()) != 0 } - _ => unexpected_property_type(property_id), - } - } - - fn set_node_id_vec(&mut self, id: PropertyId, value: impl Into>) { - self.set_property(id, PropertyValue::NodeIdVec(value.into())); - } -} - -impl NodeClass { - fn get_node_id(&self, props: &[PropertyValue], id: PropertyId) -> Option { - match self.get_property(props, id) { - PropertyValue::None => None, - PropertyValue::NodeId(value) => Some(*value), - _ => unexpected_property_type(id), - } - } -} - -impl NodeBuilder { - fn set_node_id(&mut self, id: PropertyId, value: NodeId) { - self.set_property(id, PropertyValue::NodeId(value)); - } -} - -impl NodeClass { - fn get_string<'a>(&self, props: &'a [PropertyValue], id: PropertyId) -> Option<&'a str> { - match self.get_property(props, id) { - PropertyValue::None => None, - PropertyValue::String(value) => Some(value), - _ => unexpected_property_type(id), - } - } -} - -impl NodeBuilder { - fn set_string(&mut self, id: PropertyId, value: impl Into>) { - self.set_property(id, PropertyValue::String(value.into())); - } -} - -impl NodeClass { - fn get_f64(&self, props: &[PropertyValue], id: PropertyId) -> Option { - match self.get_property(props, id) { - PropertyValue::None => None, - PropertyValue::F64(value) => Some(*value), - _ => unexpected_property_type(id), - } - } -} - -impl NodeBuilder { - fn set_f64(&mut self, id: PropertyId, value: f64) { - self.set_property(id, PropertyValue::F64(value)); - } -} - -impl NodeClass { - fn get_usize(&self, props: &[PropertyValue], id: PropertyId) -> Option { - match self.get_property(props, id) { - PropertyValue::None => None, - PropertyValue::Usize(value) => Some(*value), - _ => unexpected_property_type(id), + pub fn $setter(&mut self) { + self.flags |= (Flag::$id).mask(); + } + pub fn $clearer(&mut self) { + self.flags &= !((Flag::$id).mask()); + })* } } } -impl NodeBuilder { - fn set_usize(&mut self, id: PropertyId, value: usize) { - self.set_property(id, PropertyValue::Usize(value)); - } -} - -impl NodeClass { - fn get_color(&self, props: &[PropertyValue], id: PropertyId) -> Option { - match self.get_property(props, id) { - PropertyValue::None => None, - PropertyValue::Color(value) => Some(*value), - _ => unexpected_property_type(id), +macro_rules! option_ref_type_getters { + ($(($method:ident, $type:ty, $variant:ident)),+) => { + impl NodeClass { + $(fn $method<'a>(&self, props: &'a [PropertyValue], id: PropertyId) -> Option<&'a $type> { + match self.get_property(props, id) { + PropertyValue::None => None, + PropertyValue::$variant(value) => Some(value), + _ => unexpected_property_type(id), + } + })* } } } -impl NodeBuilder { - fn set_color(&mut self, id: PropertyId, value: u32) { - self.set_property(id, PropertyValue::Color(value)); - } -} - -impl NodeClass { - fn get_text_decoration( - &self, - props: &[PropertyValue], - id: PropertyId, - ) -> Option { - match self.get_property(props, id) { - PropertyValue::None => None, - PropertyValue::TextDecoration(value) => Some(*value), - _ => unexpected_property_type(id), +macro_rules! slice_type_getters { + ($(($method:ident, $type:ty, $variant:ident)),+) => { + impl NodeClass { + $(fn $method<'a>(&self, props: &'a [PropertyValue], id: PropertyId) -> &'a [$type] { + match self.get_property(props, id) { + PropertyValue::None => &[], + PropertyValue::$variant(value) => value, + _ => unexpected_property_type(id), + } + })* } } } -impl NodeBuilder { - fn set_text_decoration(&mut self, id: PropertyId, value: TextDecoration) { - self.set_property(id, PropertyValue::TextDecoration(value)); - } -} - -impl NodeClass { - fn get_length_slice<'a>(&self, props: &'a [PropertyValue], id: PropertyId) -> &'a [u8] { - match self.get_property(props, id) { - PropertyValue::None => &[], - PropertyValue::LengthSlice(value) => value, - _ => unexpected_property_type(id), +macro_rules! copy_type_getters { + ($(($method:ident, $type:ty, $variant:ident)),+) => { + impl NodeClass { + $(fn $method(&self, props: &[PropertyValue], id: PropertyId) -> Option<$type> { + match self.get_property(props, id) { + PropertyValue::None => None, + PropertyValue::$variant(value) => Some(*value), + _ => unexpected_property_type(id), + } + })* } } } -impl NodeBuilder { - fn set_length_slice(&mut self, id: PropertyId, value: impl Into>) { - self.set_property(id, PropertyValue::LengthSlice(value.into())); - } -} - -impl NodeClass { - fn get_coord_slice<'a>(&self, props: &'a [PropertyValue], id: PropertyId) -> Option<&'a [f32]> { - match self.get_property(props, id) { - PropertyValue::None => None, - PropertyValue::CoordSlice(value) => Some(value), - _ => unexpected_property_type(id), +macro_rules! box_type_setters { + ($(($method:ident, $type:ty, $variant:ident)),+) => { + impl NodeBuilder { + $(fn $method(&mut self, id: PropertyId, value: impl Into>) { + self.set_property(id, PropertyValue::$variant(value.into())); + })* } } } -impl NodeBuilder { - fn set_coord_slice(&mut self, id: PropertyId, value: impl Into>) { - self.set_property(id, PropertyValue::CoordSlice(value.into())); - } -} - -impl NodeClass { - fn get_bool(&self, props: &[PropertyValue], id: PropertyId) -> Option { - match self.get_property(props, id) { - PropertyValue::None => None, - PropertyValue::Bool(value) => Some(*value), - _ => unexpected_property_type(id), +macro_rules! copy_type_setters { + ($(($method:ident, $type:ty, $variant:ident)),+) => { + impl NodeBuilder { + $(fn $method(&mut self, id: PropertyId, value: $type) { + self.set_property(id, PropertyValue::$variant(value)); + })* } } } -impl NodeBuilder { - fn set_bool(&mut self, id: PropertyId, value: bool) { - self.set_property(id, PropertyValue::Bool(value)); - } -} - -macro_rules! flag_methods { - ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { - impl Node { - $($(#[$doc])* - pub fn $getter(&self) -> bool { - (self.flags & (Flag::$id).mask()) != 0 - })* - } +macro_rules! vec_type_methods { + ($(($type:ty, $variant:ident, $getter:ident, $setter:ident, $pusher:ident)),+) => { + $(slice_type_getters! { + ($getter, $type, $variant) + })* impl NodeBuilder { - $($(#[$doc])* - pub fn $getter(&self) -> bool { - (self.flags & (Flag::$id).mask()) != 0 + $(fn $setter(&mut self, id: PropertyId, value: impl Into>) { + self.set_property(id, PropertyValue::$variant(value.into())); } - pub fn $setter(&mut self) { - self.flags |= (Flag::$id).mask(); - } - pub fn $clearer(&mut self) { - self.flags &= !((Flag::$id).mask()); + fn $pusher(&mut self, id: PropertyId, item: $type) { + match self.get_property_mut(id, PropertyValue::$variant(Vec::new())) { + PropertyValue::$variant(v) => { + v.push(item); + } + _ => unexpected_property_type(id), + } })* } } @@ -1238,7 +1116,7 @@ macro_rules! node_id_property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* - ($id, $getter, get_node_id, Option, $setter, set_node_id, NodeId, $clearer) + ($id, $getter, get_node_id_property, Option, $setter, set_node_id_property, NodeId, $clearer) })* } } @@ -1247,7 +1125,7 @@ macro_rules! string_property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* - ($id, $getter, get_string, Option<&str>, $setter, set_string, impl Into>, $clearer) + ($id, $getter, get_string_property, Option<&str>, $setter, set_string_property, impl Into>, $clearer) })* } } @@ -1256,7 +1134,7 @@ macro_rules! f64_property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* - ($id, $getter, get_f64, Option, $setter, set_f64, f64, $clearer) + ($id, $getter, get_f64_property, Option, $setter, set_f64_property, f64, $clearer) })* } } @@ -1265,7 +1143,7 @@ macro_rules! usize_property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* - ($id, $getter, get_usize, Option, $setter, set_usize, usize, $clearer) + ($id, $getter, get_usize_property, Option, $setter, set_usize_property, usize, $clearer) })* } } @@ -1274,7 +1152,7 @@ macro_rules! color_property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* - ($id, $getter, get_color, Option, $setter, set_color, u32, $clearer) + ($id, $getter, get_color_property, Option, $setter, set_color_property, u32, $clearer) })* } } @@ -1283,7 +1161,7 @@ macro_rules! text_decoration_property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* - ($id, $getter, get_text_decoration, Option, $setter, set_text_decoration, TextDecoration, $clearer) + ($id, $getter, get_text_decoration_property, Option, $setter, set_text_decoration_property, TextDecoration, $clearer) })* } } @@ -1292,7 +1170,7 @@ macro_rules! length_slice_property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* - ($id, $getter, get_length_slice, &[u8], $setter, set_length_slice, impl Into>, $clearer) + ($id, $getter, get_length_slice_property, &[u8], $setter, set_length_slice_property, impl Into>, $clearer) })* } } @@ -1301,7 +1179,7 @@ macro_rules! coord_slice_property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* - ($id, $getter, get_coord_slice, Option<&[f32]>, $setter, set_coord_slice, impl Into>, $clearer) + ($id, $getter, get_coord_slice_property, Option<&[f32]>, $setter, set_coord_slice_property, impl Into>, $clearer) })* } } @@ -1310,7 +1188,7 @@ macro_rules! bool_property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* - ($id, $getter, get_bool, Option, $setter, set_bool, bool, $clearer) + ($id, $getter, get_bool_property, Option, $setter, set_bool_property, bool, $clearer) })* } } @@ -1466,6 +1344,50 @@ flag_methods! { (IsNonatomicTextFieldRoot, is_nonatomic_text_field_root, set_is_nonatomic_text_field_root, clear_is_nonatomic_text_field_root) } +option_ref_type_getters! { + (get_affine_property, Affine, Affine), + (get_string_property, str, String), + (get_coord_slice_property, [f32], CoordSlice), + (get_text_selection_property, TextSelection, TextSelection) +} + +slice_type_getters! { + (get_length_slice_property, u8, LengthSlice) +} + +copy_type_getters! { + (get_rect_property, Rect, Rect), + (get_node_id_property, NodeId, NodeId), + (get_f64_property, f64, F64), + (get_usize_property, usize, Usize), + (get_color_property, u32, Color), + (get_text_decoration_property, TextDecoration, TextDecoration), + (get_bool_property, bool, Bool) +} + +box_type_setters! { + (set_affine_property, Affine, Affine), + (set_string_property, str, String), + (set_length_slice_property, [u8], LengthSlice), + (set_coord_slice_property, [f32], CoordSlice), + (set_text_selection_property, TextSelection, TextSelection) +} + +copy_type_setters! { + (set_rect_property, Rect, Rect), + (set_node_id_property, NodeId, NodeId), + (set_f64_property, f64, F64), + (set_usize_property, usize, Usize), + (set_color_property, u32, Color), + (set_text_decoration_property, TextDecoration, TextDecoration), + (set_bool_property, bool, Bool) +} + +vec_type_methods! { + (NodeId, NodeIdVec, get_node_id_vec, set_node_id_vec, push_to_node_id_vec), + (CustomAction, CustomActionVec, get_custom_action_vec, set_custom_action_vec, push_to_custom_action_vec) +} + node_id_vec_property_methods! { (Children, children, set_children, push_child, clear_children), /// Ids of nodes that are children of this node logically, but are @@ -1719,7 +1641,7 @@ property_methods! { /// pixels, with the y coordinate being top-down. /// /// [`bounds`]: Node::bounds - (Transform, transform, get_affine, Option<&Affine>, set_transform, set_affine, impl Into>, clear_transform), + (Transform, transform, get_affine_property, Option<&Affine>, set_transform, set_affine_property, impl Into>, clear_transform), /// The bounding box of this node, in the node's coordinate space. /// This property does not affect the coordinate space of either this node @@ -1731,88 +1653,13 @@ property_methods! { /// the tree's container (e.g. window). /// /// [`transform`]: Node::transform - (Bounds, bounds, get_rect, Option, set_bounds, set_rect, Rect, clear_bounds) -} - -impl Node { - pub fn text_selection(&self) -> Option<&TextSelection> { - match self - .class - .get_property(&self.props, PropertyId::TextSelection) - { - PropertyValue::None => None, - PropertyValue::TextSelection(value) => Some(value), - _ => unexpected_property_type(PropertyId::TextSelection), - } - } -} + (Bounds, bounds, get_rect_property, Option, set_bounds, set_rect_property, Rect, clear_bounds), -impl NodeBuilder { - pub fn text_selection(&self) -> Option<&TextSelection> { - match self - .class - .get_property(&self.props, PropertyId::TextSelection) - { - PropertyValue::None => None, - PropertyValue::TextSelection(value) => Some(value), - _ => unexpected_property_type(PropertyId::TextSelection), - } - } - pub fn set_text_selection(&mut self, value: impl Into>) { - self.set_property( - PropertyId::TextSelection, - PropertyValue::TextSelection(value.into()), - ); - } - pub fn clear_text_selection(&mut self) { - self.clear_property(PropertyId::TextSelection); - } + (TextSelection, text_selection, get_text_selection_property, Option<&TextSelection>, set_text_selection, set_text_selection_property, impl Into>, clear_text_selection) } -impl Node { - pub fn custom_actions(&self) -> &[CustomAction] { - match self - .class - .get_property(&self.props, PropertyId::CustomActions) - { - PropertyValue::None => &[], - PropertyValue::CustomActionVec(value) => value, - _ => unexpected_property_type(PropertyId::CustomActions), - } - } -} - -impl NodeBuilder { - pub fn custom_actions(&self) -> &[CustomAction] { - match self - .class - .get_property(&self.props, PropertyId::CustomActions) - { - PropertyValue::None => &[], - PropertyValue::CustomActionVec(value) => value, - _ => unexpected_property_type(PropertyId::CustomActions), - } - } - pub fn set_custom_actions(&mut self, value: impl Into>) { - self.set_property( - PropertyId::CustomActions, - PropertyValue::CustomActionVec(value.into()), - ); - } - pub fn push_custom_action(&mut self, action: CustomAction) { - match self.get_property_mut( - PropertyId::CustomActions, - PropertyValue::CustomActionVec(Vec::new()), - ) { - PropertyValue::CustomActionVec(v) => { - v.push(action); - } - _ => unexpected_property_type(PropertyId::CustomActions), - } - } - pub fn clear_custom_actions(&mut self) { - self.clear_property(PropertyId::CustomActions); - } +vec_property_methods! { + (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) } #[cfg(feature = "serde")] From 81c4d746db9781ff56d95c6ea9f86d02b83b9134 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sat, 14 Jan 2023 20:31:09 -0600 Subject: [PATCH 39/45] Implement schemars support for the new node struct --- common/Cargo.toml | 1 + common/src/lib.rs | 231 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 231 insertions(+), 1 deletion(-) diff --git a/common/Cargo.toml b/common/Cargo.toml index 16324e3fc..76540de6d 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -20,3 +20,4 @@ serde = { version = "1.0", features = ["derive"], optional = true } [features] serde = ["dep:serde", "enumn"] +schemars = ["dep:schemars", "serde"] diff --git a/common/src/lib.rs b/common/src/lib.rs index 4edad748c..3867411fc 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -9,7 +9,11 @@ // found in the LICENSE.chromium file. #[cfg(feature = "schemars")] -use schemars::JsonSchema; +use schemars::{ + gen::SchemaGenerator, + schema::{ArrayValidation, InstanceType, ObjectValidation, Schema, SchemaObject}, + JsonSchema, Map as SchemaMap, +}; #[cfg(feature = "serde")] use serde::{ de::{Deserializer, IgnoredAny, MapAccess, SeqAccess, Visitor}, @@ -396,6 +400,33 @@ impl<'de> Deserialize<'de> for Actions { } } +#[cfg(feature = "schemars")] +impl JsonSchema for Actions { + fn schema_name() -> String { + "Actions".into() + } + + fn is_referenceable() -> bool { + false + } + + fn json_schema(gen: &mut SchemaGenerator) -> Schema { + SchemaObject { + instance_type: Some(InstanceType::Array.into()), + array: Some( + ArrayValidation { + unique_items: Some(true), + items: Some(gen.subschema_for::().into()), + ..Default::default() + } + .into(), + ), + ..Default::default() + } + .into() + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] @@ -1956,6 +1987,204 @@ impl<'de> Deserialize<'de> for Node { } } +#[cfg(feature = "schemars")] +macro_rules! add_schema_property { + ($gen:ident, $properties:ident, $enum_value:expr, $type:ty) => {{ + let name = format!("{:?}", $enum_value); + let name = name[..1].to_ascii_lowercase() + &name[1..]; + let subschema = $gen.subschema_for::<$type>(); + $properties.insert(name, subschema); + }}; +} + +#[cfg(feature = "schemars")] +macro_rules! add_flags_to_schema { + ($gen:ident, $properties:ident, { $($variant:ident),+ }) => { + $(add_schema_property!($gen, $properties, Flag::$variant, bool);)* + } +} + +#[cfg(feature = "schemars")] +macro_rules! add_properties_to_schema { + ($gen:ident, $properties:ident, { $($type:ty { $($id:ident),+ }),+ }) => { + $($(add_schema_property!($gen, $properties, PropertyId::$id, Option<$type>);)*)* + } +} + +#[cfg(feature = "schemars")] +impl JsonSchema for Node { + fn schema_name() -> String { + "Node".into() + } + + fn json_schema(gen: &mut SchemaGenerator) -> Schema { + let mut properties = SchemaMap::::new(); + add_schema_property!(gen, properties, ClassFieldId::Role, Role); + add_schema_property!(gen, properties, ClassFieldId::Actions, Actions); + add_flags_to_schema!(gen, properties, { + AutofillAvailable, + Default, + Editable, + Hovered, + Hidden, + Linked, + Multiline, + Multiselectable, + Protected, + Required, + Visited, + Busy, + LiveAtomic, + Modal, + Scrollable, + SelectedFromFocus, + TouchPassThrough, + ReadOnly, + Disabled, + Bold, + Italic, + CanvasHasFallback, + ClipsChildren, + IsLineBreakingObject, + IsPageBreakingObject, + IsSpellingError, + IsGrammarError, + IsSearchMatch, + IsSuggestion, + IsNonatomicTextFieldRoot + }); + add_properties_to_schema!(gen, properties, { + Vec { + Children, + IndirectChildren, + Controls, + Details, + DescribedBy, + FlowTo, + LabelledBy, + RadioGroup + }, + NodeId { + ActiveDescendant, + ErrorMessage, + InPageLinkTarget, + MemberOf, + NextOnLine, + PreviousOnLine, + PopupFor, + TableHeader, + TableRowHeader, + TableColumnHeader, + NextFocus, + PreviousFocus + }, + Box { + Name, + Description, + Value, + AccessKey, + AutoComplete, + CheckedStateDescription, + ClassName, + CssDisplay, + FontFamily, + HtmlTag, + InnerHtml, + InputType, + KeyShortcuts, + Language, + LiveRelevant, + Placeholder, + AriaRole, + RoleDescription, + Tooltip, + Url + }, + f64 { + ScrollX, + ScrollXMin, + ScrollXMax, + ScrollY, + ScrollYMin, + ScrollYMax, + NumericValue, + MinNumericValue, + MaxNumericValue, + NumericValueStep, + NumericValueJump, + FontSize, + FontWeight, + TextIndent + }, + usize { + TableRowCount, + TableColumnCount, + TableRowIndex, + TableColumnIndex, + TableCellColumnIndex, + TableCellColumnSpan, + TableCellRowIndex, + TableCellRowSpan, + HierarchicalLevel, + SizeOfSet, + PositionInSet + }, + u32 { + ColorValue, + BackgroundColor, + ForegroundColor + }, + TextDecoration { + Overline, + Strikethrough, + Underline + }, + Box<[u8]> { + CharacterLengths, + WordLengths + }, + Box<[f32]> { + CharacterPositions, + CharacterWidths + }, + bool { + Expanded, + Selected + }, + NameFrom { NameFrom }, + DescriptionFrom { DescriptionFrom }, + Invalid { Invalid }, + CheckedState { CheckedState }, + Live { Live }, + DefaultActionVerb { DefaultActionVerb }, + TextDirection { TextDirection }, + Orientation { Orientation }, + SortDirection { SortDirection }, + AriaCurrent { AriaCurrent }, + HasPopup { HasPopup }, + ListStyle { ListStyle }, + TextAlign { TextAlign }, + VerticalOffset { VerticalOffset }, + Affine { Transform }, + Rect { Bounds }, + TextSelection { TextSelection }, + Vec { CustomActions } + }); + SchemaObject { + instance_type: Some(InstanceType::Object.into()), + object: Some( + ObjectValidation { + properties, + ..Default::default() + } + .into(), + ), + ..Default::default() + } + .into() + } +} + /// The data associated with an accessibility tree that's global to the /// tree and not associated with any particular node. #[derive(Clone, Debug, PartialEq, Eq)] From 5ca9bb6c6832999d98cbe567f828307d97c4980e Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 15 Jan 2023 08:12:21 -0600 Subject: [PATCH 40/45] Eliminate use of a too-recently-stabilized library feature --- common/src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 3867411fc..8b04f2c88 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -919,7 +919,11 @@ struct NodeClass { indices: PropertyIndices, } -static NODE_CLASSES: Mutex>> = Mutex::new(BTreeSet::new()); +// We don't want to add a dependency like once_cell just like this, and +// once_cell would add a second level of synchronization anyway. We could use +// const initialization of BTreeSet, but that wasn't stabilized until +// Rust 1.65. So we just use Option. +static NODE_CLASSES: Mutex>>> = Mutex::new(None); /// A single accessible object. A complete UI is represented as a tree of these. /// @@ -1268,6 +1272,7 @@ impl NodeBuilder { pub fn build(self) -> Node { let mut classes = NODE_CLASSES.lock().unwrap(); + let classes = classes.get_or_insert_with(|| BTreeSet::new()); let class = if let Some(class) = classes.get(&self.class) { Arc::clone(class) } else { From e750009589770bf075a0e2016231d4a96fdf4666 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 15 Jan 2023 08:13:49 -0600 Subject: [PATCH 41/45] Got the version number wrong in the comment --- common/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 8b04f2c88..1dbc9b6a5 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -922,7 +922,7 @@ struct NodeClass { // We don't want to add a dependency like once_cell just like this, and // once_cell would add a second level of synchronization anyway. We could use // const initialization of BTreeSet, but that wasn't stabilized until -// Rust 1.65. So we just use Option. +// Rust 1.66. So we just use Option. static NODE_CLASSES: Mutex>>> = Mutex::new(None); /// A single accessible object. A complete UI is represented as a tree of these. From 542b526372250ea053b1c937de44926fde2844db Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 15 Jan 2023 08:27:05 -0600 Subject: [PATCH 42/45] Fix clippy --- common/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 1dbc9b6a5..c7a3dcd15 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1272,7 +1272,7 @@ impl NodeBuilder { pub fn build(self) -> Node { let mut classes = NODE_CLASSES.lock().unwrap(); - let classes = classes.get_or_insert_with(|| BTreeSet::new()); + let classes = classes.get_or_insert_with(BTreeSet::new); let class = if let Some(class) = classes.get(&self.class) { Arc::clone(class) } else { From 3949b7d22484b922c5c94e3a765f7ed1e6dc1955 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 22 Jan 2023 11:42:18 -0600 Subject: [PATCH 43/45] Make the shared node class set explicit; also clean up examples --- common/src/lib.rs | 76 ++++++++++-- consumer/src/lib.rs | 33 ++--- consumer/src/node.rs | 34 +++--- consumer/src/text.rs | 21 ++-- consumer/src/tree.rs | 61 +++++++--- platforms/windows/examples/hello_world.rs | 142 +++++++++++----------- platforms/windows/src/tests/simple.rs | 14 ++- platforms/windows/src/tests/subclassed.rs | 14 ++- platforms/winit/examples/simple.rs | 131 ++++++++++---------- 9 files changed, 317 insertions(+), 209 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index c7a3dcd15..b465b21b3 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -23,7 +23,8 @@ use serde::{ use std::{ collections::BTreeSet, num::{NonZeroU128, NonZeroU64}, - sync::{Arc, Mutex}, + ops::DerefMut, + sync::Arc, }; #[cfg(feature = "serde")] use std::{fmt, mem::size_of_val}; @@ -919,11 +920,64 @@ struct NodeClass { indices: PropertyIndices, } -// We don't want to add a dependency like once_cell just like this, and -// once_cell would add a second level of synchronization anyway. We could use -// const initialization of BTreeSet, but that wasn't stabilized until -// Rust 1.66. So we just use Option. -static NODE_CLASSES: Mutex>>> = Mutex::new(None); +/// Allows nodes that have the same role, actions, and set of defined properties +/// to share metadata. Each node has a class which is created by [`NodeBuilder`], +/// and when [`NodeBuilder::build`] is called, the node's class is added +/// to the provided instance of this struct if an identical class isn't +/// in that set already. Once a class is added to a class set, it currently +/// remains in that set for the life of that set, whether or not any nodes +/// are still using the class. +/// +/// It's not an error for different nodes in the same tree, or even subsequent +/// versions of the same node, to be built from different class sets; +/// it's merely suboptimal. +/// +/// Note: This struct's `Default` implementation doesn't provide access to +/// a shared set, as one might assume; it creates a new set. For a shared set, +/// use [`NodeClassSet::lock_global`]. +#[derive(Clone, Default)] +#[repr(transparent)] +pub struct NodeClassSet(BTreeSet>); + +impl NodeClassSet { + pub fn new() -> Self { + Default::default() + } + + /// Accesses a shared class set guarded by a mutex. + pub fn lock_global() -> impl DerefMut { + use std::{ + ops::Deref, + sync::{Mutex, MutexGuard}, + }; + + // We don't want to add a dependency like once_cell just like this, and + // once_cell would add a second level of synchronization anyway. We could + // use const initialization of BTreeSet, but that wasn't stabilized until + // Rust 1.66. So we just use Option. + static INSTANCE: Mutex> = Mutex::new(None); + + struct Guard<'a>(MutexGuard<'a, Option>); + + impl<'a> Deref for Guard<'a> { + type Target = NodeClassSet; + + fn deref(&self) -> &Self::Target { + self.0.as_ref().unwrap() + } + } + + impl<'a> DerefMut for Guard<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.as_mut().unwrap() + } + } + + let mut instance = INSTANCE.lock().unwrap(); + instance.get_or_insert_with(Default::default); + Guard(instance) + } +} /// A single accessible object. A complete UI is represented as a tree of these. /// @@ -1270,14 +1324,12 @@ impl NodeBuilder { } } - pub fn build(self) -> Node { - let mut classes = NODE_CLASSES.lock().unwrap(); - let classes = classes.get_or_insert_with(BTreeSet::new); - let class = if let Some(class) = classes.get(&self.class) { + pub fn build(self, classes: &mut NodeClassSet) -> Node { + let class = if let Some(class) = classes.0.get(&self.class) { Arc::clone(class) } else { let class = Arc::new(self.class); - classes.insert(Arc::clone(&class)); + classes.0.insert(Arc::clone(&class)); class }; Node { @@ -1978,7 +2030,7 @@ impl<'de> Visitor<'de> for NodeVisitor { } } - Ok(builder.build()) + Ok(builder.build(&mut NodeClassSet::lock_global())) } } diff --git a/consumer/src/lib.rs b/consumer/src/lib.rs index e9ed5c162..6edd1b0a6 100644 --- a/consumer/src/lib.rs +++ b/consumer/src/lib.rs @@ -21,8 +21,8 @@ pub use text::{ #[cfg(test)] mod tests { use accesskit::{ - ActionHandler, ActionRequest, Affine, NodeBuilder, NodeId, Rect, Role, Tree, TreeUpdate, - Vec2, + ActionHandler, ActionRequest, Affine, NodeBuilder, NodeClassSet, NodeId, Rect, Role, Tree, + TreeUpdate, Vec2, }; use std::num::NonZeroU128; @@ -51,6 +51,7 @@ mod tests { } pub fn test_tree() -> crate::tree::Tree { + let mut classes = NodeClassSet::new(); let root = { let mut builder = NodeBuilder::new(Role::RootWebArea); builder.set_children(vec![ @@ -59,17 +60,17 @@ mod tests { PARAGRAPH_2_ID, PARAGRAPH_3_IGNORED_ID, ]); - builder.build() + builder.build(&mut classes) }; let paragraph_0 = { let mut builder = NodeBuilder::new(Role::Paragraph); builder.set_children(vec![STATIC_TEXT_0_0_IGNORED_ID]); - builder.build() + builder.build(&mut classes) }; let static_text_0_0_ignored = { let mut builder = NodeBuilder::new(Role::StaticText); builder.set_name("static_text_0_0_ignored"); - builder.build() + builder.build(&mut classes) }; let paragraph_1_ignored = { let mut builder = NodeBuilder::new(Role::Paragraph); @@ -81,7 +82,7 @@ mod tests { y1: 40.0, }); builder.set_children(vec![STATIC_TEXT_1_0_ID]); - builder.build() + builder.build(&mut classes) }; let static_text_1_0 = { let mut builder = NodeBuilder::new(Role::StaticText); @@ -92,17 +93,17 @@ mod tests { y1: 30.0, }); builder.set_name("static_text_1_0"); - builder.build() + builder.build(&mut classes) }; let paragraph_2 = { let mut builder = NodeBuilder::new(Role::Paragraph); builder.set_children(vec![STATIC_TEXT_2_0_ID]); - builder.build() + builder.build(&mut classes) }; let static_text_2_0 = { let mut builder = NodeBuilder::new(Role::StaticText); builder.set_name("static_text_2_0"); - builder.build() + builder.build(&mut classes) }; let paragraph_3_ignored = { let mut builder = NodeBuilder::new(Role::Paragraph); @@ -112,26 +113,28 @@ mod tests { BUTTON_3_2_ID, EMPTY_CONTAINER_3_3_IGNORED_ID, ]); - builder.build() + builder.build(&mut classes) }; - let empty_container_3_0_ignored = NodeBuilder::new(Role::GenericContainer).build(); + let empty_container_3_0_ignored = + NodeBuilder::new(Role::GenericContainer).build(&mut classes); let link_3_1_ignored = { let mut builder = NodeBuilder::new(Role::Link); builder.set_children(vec![STATIC_TEXT_3_1_0_ID]); builder.set_linked(); - builder.build() + builder.build(&mut classes) }; let static_text_3_1_0 = { let mut builder = NodeBuilder::new(Role::StaticText); builder.set_name("static_text_3_1_0"); - builder.build() + builder.build(&mut classes) }; let button_3_2 = { let mut builder = NodeBuilder::new(Role::Button); builder.set_name("button_3_2"); - builder.build() + builder.build(&mut classes) }; - let empty_container_3_3_ignored = NodeBuilder::new(Role::GenericContainer).build(); + let empty_container_3_3_ignored = + NodeBuilder::new(Role::GenericContainer).build(&mut classes); let initial_update = TreeUpdate { nodes: vec![ (ROOT_ID, root), diff --git a/consumer/src/node.rs b/consumer/src/node.rs index 9a0d6bf91..c66089945 100644 --- a/consumer/src/node.rs +++ b/consumer/src/node.rs @@ -720,7 +720,7 @@ impl Deref for DetachedNode { #[cfg(test)] mod tests { - use accesskit::{NodeBuilder, NodeId, Point, Rect, Role, Tree, TreeUpdate}; + use accesskit::{NodeBuilder, NodeClassSet, NodeId, Point, Rect, Role, Tree, TreeUpdate}; use std::num::NonZeroU128; use crate::tests::*; @@ -991,14 +991,18 @@ mod tests { #[test] fn no_name_or_labelled_by() { + let mut classes = NodeClassSet::new(); let update = TreeUpdate { nodes: vec![ (NODE_ID_1, { let mut builder = NodeBuilder::new(Role::Window); builder.set_children(vec![NODE_ID_2]); - builder.build() + builder.build(&mut classes) }), - (NODE_ID_2, NodeBuilder::new(Role::Button).build()), + ( + NODE_ID_2, + NodeBuilder::new(Role::Button).build(&mut classes), + ), ], tree: Some(Tree::new(NODE_ID_1)), focus: None, @@ -1014,32 +1018,33 @@ mod tests { const LABEL_1: &str = "Check email every"; const LABEL_2: &str = "minutes"; + let mut classes = NodeClassSet::new(); let update = TreeUpdate { nodes: vec![ (NODE_ID_1, { let mut builder = NodeBuilder::new(Role::Window); builder.set_children(vec![NODE_ID_2, NODE_ID_3, NODE_ID_4, NODE_ID_5]); - builder.build() + builder.build(&mut classes) }), (NODE_ID_2, { let mut builder = NodeBuilder::new(Role::CheckBox); builder.set_labelled_by(vec![NODE_ID_3, NODE_ID_5]); - builder.build() + builder.build(&mut classes) }), (NODE_ID_3, { let mut builder = NodeBuilder::new(Role::StaticText); builder.set_name(LABEL_1); - builder.build() + builder.build(&mut classes) }), (NODE_ID_4, { let mut builder = NodeBuilder::new(Role::TextField); builder.push_labelled_by(NODE_ID_5); - builder.build() + builder.build(&mut classes) }), (NODE_ID_5, { let mut builder = NodeBuilder::new(Role::StaticText); builder.set_name(LABEL_2); - builder.build() + builder.build(&mut classes) }), ], tree: Some(Tree::new(NODE_ID_1)), @@ -1061,37 +1066,38 @@ mod tests { const BUTTON_LABEL: &str = "Play"; const LINK_LABEL: &str = "Watch in browser"; + let mut classes = NodeClassSet::new(); let update = TreeUpdate { nodes: vec![ (NODE_ID_1, { let mut builder = NodeBuilder::new(Role::Window); builder.set_children(vec![NODE_ID_2, NODE_ID_4]); - builder.build() + builder.build(&mut classes) }), (NODE_ID_2, { let mut builder = NodeBuilder::new(Role::Button); builder.push_child(NODE_ID_3); - builder.build() + builder.build(&mut classes) }), (NODE_ID_3, { let mut builder = NodeBuilder::new(Role::Image); builder.set_name(BUTTON_LABEL); - builder.build() + builder.build(&mut classes) }), (NODE_ID_4, { let mut builder = NodeBuilder::new(Role::Link); builder.push_child(NODE_ID_5); - builder.build() + builder.build(&mut classes) }), (NODE_ID_5, { let mut builder = NodeBuilder::new(Role::GenericContainer); builder.push_child(NODE_ID_6); - builder.build() + builder.build(&mut classes) }), (NODE_ID_6, { let mut builder = NodeBuilder::new(Role::StaticText); builder.set_name(LINK_LABEL); - builder.build() + builder.build(&mut classes) }), ], tree: Some(Tree::new(NODE_ID_1)), diff --git a/consumer/src/text.rs b/consumer/src/text.rs index 736641e30..0e89d728b 100644 --- a/consumer/src/text.rs +++ b/consumer/src/text.rs @@ -1028,15 +1028,18 @@ mod tests { // This is based on an actual tree produced by egui. fn main_multiline_tree(selection: Option) -> crate::Tree { - use accesskit::{Action, Affine, NodeBuilder, Role, TextDirection, Tree, TreeUpdate}; + use accesskit::{ + Action, Affine, NodeBuilder, NodeClassSet, Role, TextDirection, Tree, TreeUpdate, + }; + let mut classes = NodeClassSet::new(); let update = TreeUpdate { nodes: vec![ (NODE_ID_1, { let mut builder = NodeBuilder::new(Role::Window); builder.set_transform(Affine::scale(1.5)); builder.set_children(vec![NODE_ID_2]); - builder.build() + builder.build(&mut classes) }), (NODE_ID_2, { let mut builder = NodeBuilder::new(Role::TextField); @@ -1053,7 +1056,7 @@ mod tests { if let Some(selection) = selection { builder.set_text_selection(selection); } - builder.build() + builder.build(&mut classes) }), (NODE_ID_3, { let mut builder = NodeBuilder::new(Role::InlineTextBox); @@ -1088,7 +1091,7 @@ mod tests { 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, ]); builder.set_word_lengths([5, 10, 3, 5, 7, 3, 5]); - builder.build() + builder.build(&mut classes) }), (NODE_ID_4, { let mut builder = NodeBuilder::new(Role::InlineTextBox); @@ -1113,7 +1116,7 @@ mod tests { 0.0, ]); builder.set_word_lengths([3, 8, 6]); - builder.build() + builder.build(&mut classes) }), (NODE_ID_5, { let mut builder = NodeBuilder::new(Role::InlineTextBox); @@ -1139,7 +1142,7 @@ mod tests { 7.58557, 7.58557, 0.0, ]); builder.set_word_lengths([8, 11]); - builder.build() + builder.build(&mut classes) }), (NODE_ID_6, { let mut builder = NodeBuilder::new(Role::InlineTextBox); @@ -1155,7 +1158,7 @@ mod tests { builder.set_character_positions([0.0]); builder.set_character_widths([0.0]); builder.set_word_lengths([1]); - builder.build() + builder.build(&mut classes) }), (NODE_ID_7, { let mut builder = NodeBuilder::new(Role::InlineTextBox); @@ -1184,7 +1187,7 @@ mod tests { 7.58557, 7.58557, 7.58557, 7.58557, 0.0, ]); builder.set_word_lengths([5, 4, 6, 6]); - builder.build() + builder.build(&mut classes) }), (NODE_ID_8, { let mut builder = NodeBuilder::new(Role::InlineTextBox); @@ -1200,7 +1203,7 @@ mod tests { builder.set_character_positions([]); builder.set_character_widths([]); builder.set_word_lengths([0]); - builder.build() + builder.build(&mut classes) }), ], tree: Some(Tree::new(NODE_ID_1)), diff --git a/consumer/src/tree.rs b/consumer/src/tree.rs index e4cedaeee..8a4c2bf6c 100644 --- a/consumer/src/tree.rs +++ b/consumer/src/tree.rs @@ -424,7 +424,7 @@ impl Tree { #[cfg(test)] mod tests { - use accesskit::{NodeBuilder, NodeId, Role, Tree, TreeUpdate}; + use accesskit::{NodeBuilder, NodeClassSet, NodeId, Role, Tree, TreeUpdate}; use std::num::NonZeroU128; use crate::tests::NullActionHandler; @@ -435,8 +435,12 @@ mod tests { #[test] fn init_tree_with_root_node() { + let mut classes = NodeClassSet::new(); let update = TreeUpdate { - nodes: vec![(NODE_ID_1, NodeBuilder::new(Role::Window).build())], + nodes: vec![( + NODE_ID_1, + NodeBuilder::new(Role::Window).build(&mut classes), + )], tree: Some(Tree::new(NODE_ID_1)), focus: None, }; @@ -448,15 +452,22 @@ mod tests { #[test] fn root_node_has_children() { + let mut classes = NodeClassSet::new(); let update = TreeUpdate { nodes: vec![ (NODE_ID_1, { let mut builder = NodeBuilder::new(Role::Window); builder.set_children(vec![NODE_ID_2, NODE_ID_3]); - builder.build() + builder.build(&mut classes) }), - (NODE_ID_2, NodeBuilder::new(Role::Button).build()), - (NODE_ID_3, NodeBuilder::new(Role::Button).build()), + ( + NODE_ID_2, + NodeBuilder::new(Role::Button).build(&mut classes), + ), + ( + NODE_ID_3, + NodeBuilder::new(Role::Button).build(&mut classes), + ), ], tree: Some(Tree::new(NODE_ID_1)), focus: None, @@ -476,9 +487,10 @@ mod tests { #[test] fn add_child_to_root_node() { + let mut classes = NodeClassSet::new(); let root_builder = NodeBuilder::new(Role::Window); let first_update = TreeUpdate { - nodes: vec![(NODE_ID_1, root_builder.clone().build())], + nodes: vec![(NODE_ID_1, root_builder.clone().build(&mut classes))], tree: Some(Tree::new(NODE_ID_1)), focus: None, }; @@ -489,9 +501,12 @@ mod tests { (NODE_ID_1, { let mut builder = root_builder; builder.push_child(NODE_ID_2); - builder.build() + builder.build(&mut classes) }), - (NODE_ID_2, NodeBuilder::new(Role::RootWebArea).build()), + ( + NODE_ID_2, + NodeBuilder::new(Role::RootWebArea).build(&mut classes), + ), ], tree: None, focus: None, @@ -554,15 +569,19 @@ mod tests { #[test] fn remove_child_from_root_node() { + let mut classes = NodeClassSet::new(); let root_builder = NodeBuilder::new(Role::Window); let first_update = TreeUpdate { nodes: vec![ (NODE_ID_1, { let mut builder = root_builder.clone(); builder.push_child(NODE_ID_2); - builder.build() + builder.build(&mut classes) }), - (NODE_ID_2, NodeBuilder::new(Role::RootWebArea).build()), + ( + NODE_ID_2, + NodeBuilder::new(Role::RootWebArea).build(&mut classes), + ), ], tree: Some(Tree::new(NODE_ID_1)), focus: None, @@ -570,7 +589,7 @@ mod tests { let tree = super::Tree::new(first_update, Box::new(NullActionHandler {})); assert_eq!(1, tree.read().root().children().count()); let second_update = TreeUpdate { - nodes: vec![(NODE_ID_1, root_builder.build())], + nodes: vec![(NODE_ID_1, root_builder.build(&mut classes))], tree: None, focus: None, }; @@ -627,15 +646,22 @@ mod tests { #[test] fn move_focus_between_siblings() { + let mut classes = NodeClassSet::new(); let first_update = TreeUpdate { nodes: vec![ (NODE_ID_1, { let mut builder = NodeBuilder::new(Role::Window); builder.set_children(vec![NODE_ID_2, NODE_ID_3]); - builder.build() + builder.build(&mut classes) }), - (NODE_ID_2, NodeBuilder::new(Role::Button).build()), - (NODE_ID_3, NodeBuilder::new(Role::Button).build()), + ( + NODE_ID_2, + NodeBuilder::new(Role::Button).build(&mut classes), + ), + ( + NODE_ID_3, + NodeBuilder::new(Role::Button).build(&mut classes), + ), ], tree: Some(Tree::new(NODE_ID_1)), focus: Some(NODE_ID_2), @@ -714,18 +740,19 @@ mod tests { #[test] fn update_node() { + let mut classes = NodeClassSet::new(); let child_builder = NodeBuilder::new(Role::Button); let first_update = TreeUpdate { nodes: vec![ (NODE_ID_1, { let mut builder = NodeBuilder::new(Role::Window); builder.set_children(vec![NODE_ID_2]); - builder.build() + builder.build(&mut classes) }), (NODE_ID_2, { let mut builder = child_builder.clone(); builder.set_name("foo"); - builder.build() + builder.build(&mut classes) }), ], tree: Some(Tree::new(NODE_ID_1)), @@ -740,7 +767,7 @@ mod tests { nodes: vec![(NODE_ID_2, { let mut builder = child_builder; builder.set_name("bar"); - builder.build() + builder.build(&mut classes) })], tree: None, focus: None, diff --git a/platforms/windows/examples/hello_world.rs b/platforms/windows/examples/hello_world.rs index ee4af1cec..6c72ab0e8 100644 --- a/platforms/windows/examples/hello_world.rs +++ b/platforms/windows/examples/hello_world.rs @@ -1,8 +1,8 @@ // Based on the create_window sample in windows-samples-rs. use accesskit::{ - Action, ActionHandler, ActionRequest, DefaultActionVerb, Live, Node, NodeBuilder, NodeId, Rect, - Role, Tree, TreeUpdate, + Action, ActionHandler, ActionRequest, DefaultActionVerb, Live, Node, NodeBuilder, NodeClassSet, + NodeId, Rect, Role, Tree, TreeUpdate, }; use accesskit_windows::UiaInitMarker; use once_cell::{sync::Lazy, unsync::OnceCell}; @@ -42,7 +42,7 @@ const WINDOW_TITLE: &str = "Hello world"; const WINDOW_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(1) }); const BUTTON_1_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(2) }); const BUTTON_2_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(3) }); -const PRESSED_TEXT_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(4) }); +const ANNOUNCEMENT_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(4) }); const INITIAL_FOCUS: NodeId = BUTTON_1_ID; const BUTTON_1_RECT: Rect = Rect { @@ -62,7 +62,7 @@ const BUTTON_2_RECT: Rect = Rect { const SET_FOCUS_MSG: u32 = WM_USER; const DO_DEFAULT_ACTION_MSG: u32 = WM_USER + 1; -fn make_button(id: NodeId, name: &str) -> Node { +fn build_button(id: NodeId, name: &str, classes: &mut NodeClassSet) -> Node { let rect = match id { BUTTON_1_ID => BUTTON_1_RECT, BUTTON_2_ID => BUTTON_2_RECT, @@ -74,31 +74,58 @@ fn make_button(id: NodeId, name: &str) -> Node { builder.set_name(name); builder.add_action(Action::Focus); builder.set_default_action_verb(DefaultActionVerb::Click); - builder.build() + builder.build(classes) } -fn get_initial_state() -> TreeUpdate { - let root = { - let mut builder = NodeBuilder::new(Role::Window); - builder.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); - builder.build() - }; - let button_1 = make_button(BUTTON_1_ID, "Button 1"); - let button_2 = make_button(BUTTON_2_ID, "Button 2"); - TreeUpdate { - nodes: vec![ - (WINDOW_ID, root), - (BUTTON_1_ID, button_1), - (BUTTON_2_ID, button_2), - ], - tree: Some(Tree::new(WINDOW_ID)), - focus: None, - } +fn build_announcement(text: &str, classes: &mut NodeClassSet) -> Node { + let mut builder = NodeBuilder::new(Role::StaticText); + builder.set_name(text); + builder.set_live(Live::Polite); + builder.build(classes) } struct InnerWindowState { focus: NodeId, is_window_focused: bool, + announcement: Option, + node_classes: NodeClassSet, +} + +impl InnerWindowState { + fn focus(&self) -> Option { + self.is_window_focused.then_some(self.focus) + } + + fn build_root(&mut self) -> Node { + let mut builder = NodeBuilder::new(Role::Window); + builder.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); + if self.announcement.is_some() { + builder.push_child(ANNOUNCEMENT_ID); + } + builder.build(&mut self.node_classes) + } + + fn build_initial_tree(&mut self) -> TreeUpdate { + let root = self.build_root(); + let button_1 = build_button(BUTTON_1_ID, "Button 1", &mut self.node_classes); + let button_2 = build_button(BUTTON_2_ID, "Button 2", &mut self.node_classes); + let mut result = TreeUpdate { + nodes: vec![ + (WINDOW_ID, root), + (BUTTON_1_ID, button_1), + (BUTTON_2_ID, button_2), + ], + tree: Some(Tree::new(WINDOW_ID)), + focus: self.focus(), + }; + if let Some(announcement) = &self.announcement { + result.nodes.push(( + ANNOUNCEMENT_ID, + build_announcement(announcement, &mut self.node_classes), + )); + } + result + } } struct WindowState { @@ -110,14 +137,9 @@ struct WindowState { impl WindowState { fn get_or_init_accesskit_adapter(&self, window: HWND) -> &accesskit_windows::Adapter { self.adapter.get_or_init(|| { - // Note: For this simple example, the initial tree is mostly static. - // In a real GUI framework, the initial tree would be dynamically - // generated by the framework based on application logic and data - // associated with the window. Here, only the focus is based on - // window state. - let mut initial_tree = get_initial_state(); - let inner_state = self.inner_state.borrow(); - initial_tree.focus = inner_state.is_window_focused.then_some(inner_state.focus); + let mut inner_state = self.inner_state.borrow_mut(); + let initial_tree = inner_state.build_initial_tree(); + drop(inner_state); let action_handler = Box::new(SimpleActionHandler { window }); accesskit_windows::Adapter::new( window, @@ -128,45 +150,25 @@ impl WindowState { }) } - fn press_button(&self, window: HWND, id: NodeId) { - // This is a pretty hacky way of adding or updating a node. - // A real GUI framework would have a consistent way - // of building nodes from underlying data. - // Also, this update isn't as lazy as it could be; - // we force the AccessKit tree to be initialized. - // This is expedient in this case, because that tree - // is the only place where the state of the announcement - // is stored. It's not a problem because we're really - // only concerned with testing lazy updates in the context - // of focus changes. - let adapter = self.get_or_init_accesskit_adapter(window); - let inner_state = self.inner_state.borrow(); - let is_window_focused = inner_state.is_window_focused; - let focus = inner_state.focus; - drop(inner_state); - let name = if id == BUTTON_1_ID { + fn press_button(&self, id: NodeId) { + let mut inner_state = self.inner_state.borrow_mut(); + let text = if id == BUTTON_1_ID { "You pressed button 1" } else { "You pressed button 2" }; - let node = { - let mut builder = NodeBuilder::new(Role::StaticText); - builder.set_name(name); - builder.set_live(Live::Polite); - builder.build() - }; - let root = { - let mut builder = NodeBuilder::new(Role::Window); - builder.set_children(vec![BUTTON_1_ID, BUTTON_2_ID, PRESSED_TEXT_ID]); - builder.build() - }; - let update = TreeUpdate { - nodes: vec![(PRESSED_TEXT_ID, node), (WINDOW_ID, root)], - tree: None, - focus: is_window_focused.then_some(focus), - }; - let events = adapter.update(update); - events.raise(); + inner_state.announcement = Some(text.into()); + if let Some(adapter) = self.adapter.get() { + let announcement = build_announcement(text, &mut inner_state.node_classes); + let root = inner_state.build_root(); + let update = TreeUpdate { + nodes: vec![(ANNOUNCEMENT_ID, announcement), (WINDOW_ID, root)], + tree: None, + focus: inner_state.focus(), + }; + let events = adapter.update(update); + events.raise(); + } } } @@ -178,13 +180,13 @@ fn update_focus(window: HWND, is_window_focused: bool) { let window_state = unsafe { &*get_window_state(window) }; let mut inner_state = window_state.inner_state.borrow_mut(); inner_state.is_window_focused = is_window_focused; - let focus = inner_state.focus; + let focus = inner_state.focus(); drop(inner_state); if let Some(adapter) = window_state.adapter.get() { let events = adapter.update(TreeUpdate { nodes: vec![], tree: None, - focus: is_window_focused.then_some(focus), + focus, }); events.raise(); } @@ -234,6 +236,8 @@ extern "system" fn wndproc(window: HWND, message: u32, wparam: WPARAM, lparam: L let inner_state = RefCell::new(InnerWindowState { focus: initial_focus, is_window_focused: false, + announcement: None, + node_classes: NodeClassSet::new(), }); let state = Box::new(WindowState { uia_init_marker: UiaInitMarker::new(), @@ -295,7 +299,7 @@ extern "system" fn wndproc(window: HWND, message: u32, wparam: WPARAM, lparam: L VK_SPACE => { let window_state = unsafe { &*get_window_state(window) }; let id = window_state.inner_state.borrow().focus; - window_state.press_button(window, id); + window_state.press_button(id); LRESULT(0) } _ => unsafe { DefWindowProcW(window, message, wparam, lparam) }, @@ -319,7 +323,7 @@ extern "system" fn wndproc(window: HWND, message: u32, wparam: WPARAM, lparam: L let id = NodeId(id); if id == BUTTON_1_ID || id == BUTTON_2_ID { let window_state = unsafe { &*get_window_state(window) }; - window_state.press_button(window, id); + window_state.press_button(id); } } LRESULT(0) diff --git a/platforms/windows/src/tests/simple.rs b/platforms/windows/src/tests/simple.rs index 1401adfa9..c8e66d3ef 100644 --- a/platforms/windows/src/tests/simple.rs +++ b/platforms/windows/src/tests/simple.rs @@ -6,7 +6,8 @@ use std::{convert::TryInto, num::NonZeroU128}; use accesskit::{ - Action, ActionHandler, ActionRequest, Node, NodeBuilder, NodeId, Role, Tree, TreeUpdate, + Action, ActionHandler, ActionRequest, Node, NodeBuilder, NodeClassSet, NodeId, Role, Tree, + TreeUpdate, }; use windows::{core::*, Win32::UI::Accessibility::*}; @@ -18,21 +19,22 @@ const WINDOW_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(1) }); const BUTTON_1_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(2) }); const BUTTON_2_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(3) }); -fn make_button(name: &str) -> Node { +fn make_button(name: &str, classes: &mut NodeClassSet) -> Node { let mut builder = NodeBuilder::new(Role::Button); builder.set_name(name); builder.add_action(Action::Focus); - builder.build() + builder.build(classes) } fn get_initial_state() -> TreeUpdate { + let mut classes = NodeClassSet::new(); let root = { let mut builder = NodeBuilder::new(Role::Window); builder.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); - builder.build() + builder.build(&mut classes) }; - let button_1 = make_button("Button 1"); - let button_2 = make_button("Button 2"); + let button_1 = make_button("Button 1", &mut classes); + let button_2 = make_button("Button 2", &mut classes); TreeUpdate { nodes: vec![ (WINDOW_ID, root), diff --git a/platforms/windows/src/tests/subclassed.rs b/platforms/windows/src/tests/subclassed.rs index c41310356..142ab9a80 100644 --- a/platforms/windows/src/tests/subclassed.rs +++ b/platforms/windows/src/tests/subclassed.rs @@ -6,7 +6,8 @@ use std::num::NonZeroU128; use accesskit::{ - Action, ActionHandler, ActionRequest, Node, NodeBuilder, NodeId, Role, Tree, TreeUpdate, + Action, ActionHandler, ActionRequest, Node, NodeBuilder, NodeClassSet, NodeId, Role, Tree, + TreeUpdate, }; use windows::Win32::{Foundation::*, UI::Accessibility::*}; use winit::{ @@ -23,22 +24,23 @@ const WINDOW_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(1) }); const BUTTON_1_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(2) }); const BUTTON_2_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(3) }); -fn make_button(name: &str) -> Node { +fn make_button(name: &str, classes: &mut NodeClassSet) -> Node { let mut builder = NodeBuilder::new(Role::Button); builder.set_name(name); builder.add_action(Action::Focus); - builder.build() + builder.build(classes) } fn get_initial_state() -> TreeUpdate { + let mut classes = NodeClassSet::new(); let root = { let mut builder = NodeBuilder::new(Role::Window); builder.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); builder.set_name(WINDOW_TITLE); - builder.build() + builder.build(&mut classes) }; - let button_1 = make_button("Button 1"); - let button_2 = make_button("Button 2"); + let button_1 = make_button("Button 1", &mut classes); + let button_2 = make_button("Button 2", &mut classes); TreeUpdate { nodes: vec![ (WINDOW_ID, root), diff --git a/platforms/winit/examples/simple.rs b/platforms/winit/examples/simple.rs index a0b2d10c4..81033e778 100644 --- a/platforms/winit/examples/simple.rs +++ b/platforms/winit/examples/simple.rs @@ -1,6 +1,6 @@ use accesskit::{ - Action, ActionRequest, DefaultActionVerb, Live, Node, NodeBuilder, NodeId, Rect, Role, Tree, - TreeUpdate, + Action, ActionRequest, DefaultActionVerb, Live, Node, NodeBuilder, NodeClassSet, NodeId, Rect, + Role, Tree, TreeUpdate, }; use accesskit_winit::{ActionRequestEvent, Adapter}; use std::{ @@ -18,7 +18,7 @@ const WINDOW_TITLE: &str = "Hello world"; const WINDOW_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(1) }); const BUTTON_1_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(2) }); const BUTTON_2_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(3) }); -const PRESSED_TEXT_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(4) }); +const ANNOUNCEMENT_ID: NodeId = NodeId(unsafe { NonZeroU128::new_unchecked(4) }); const INITIAL_FOCUS: NodeId = BUTTON_1_ID; const BUTTON_1_RECT: Rect = Rect { @@ -35,7 +35,7 @@ const BUTTON_2_RECT: Rect = Rect { y1: 100.0, }; -fn make_button(id: NodeId, name: &str) -> Node { +fn build_button(id: NodeId, name: &str, classes: &mut NodeClassSet) -> Node { let rect = match id { BUTTON_1_ID => BUTTON_1_RECT, BUTTON_2_ID => BUTTON_2_RECT, @@ -47,13 +47,21 @@ fn make_button(id: NodeId, name: &str) -> Node { builder.set_name(name); builder.add_action(Action::Focus); builder.set_default_action_verb(DefaultActionVerb::Click); - builder.build() + builder.build(classes) +} + +fn build_announcement(text: &str, classes: &mut NodeClassSet) -> Node { + let mut builder = NodeBuilder::new(Role::StaticText); + builder.set_name(text); + builder.set_live(Live::Polite); + builder.build(classes) } -#[derive(Debug)] struct State { focus: NodeId, is_window_focused: bool, + announcement: Option, + node_classes: NodeClassSet, } impl State { @@ -61,71 +69,71 @@ impl State { Arc::new(Mutex::new(Self { focus: INITIAL_FOCUS, is_window_focused: false, + announcement: None, + node_classes: NodeClassSet::new(), })) } + fn focus(&self) -> Option { + self.is_window_focused.then_some(self.focus) + } + + fn build_root(&mut self) -> Node { + let mut builder = NodeBuilder::new(Role::Window); + builder.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); + if self.announcement.is_some() { + builder.push_child(ANNOUNCEMENT_ID); + } + builder.set_name(WINDOW_TITLE); + builder.build(&mut self.node_classes) + } + + fn build_initial_tree(&mut self) -> TreeUpdate { + let root = self.build_root(); + let button_1 = build_button(BUTTON_1_ID, "Button 1", &mut self.node_classes); + let button_2 = build_button(BUTTON_2_ID, "Button 2", &mut self.node_classes); + let mut result = TreeUpdate { + nodes: vec![ + (WINDOW_ID, root), + (BUTTON_1_ID, button_1), + (BUTTON_2_ID, button_2), + ], + tree: Some(Tree::new(WINDOW_ID)), + focus: self.focus(), + }; + if let Some(announcement) = &self.announcement { + result.nodes.push(( + ANNOUNCEMENT_ID, + build_announcement(announcement, &mut self.node_classes), + )); + } + result + } + fn update_focus(&mut self, adapter: &Adapter) { adapter.update_if_active(|| TreeUpdate { nodes: vec![], tree: None, - focus: self.is_window_focused.then_some(self.focus), + focus: self.focus(), }); } - fn press_button(&self, adapter: &Adapter, id: NodeId) { - // This is a pretty hacky way of adding or updating a node. - // A real GUI framework would have a consistent way - // of building nodes from underlying data. - // Also, this update isn't as lazy as it could be; - // we force the AccessKit tree to be initialized. - // This is expedient in this case, because that tree - // is the only place where the state of the announcement - // is stored. It's not a problem because we're really - // only concerned with testing lazy updates in the context - // of focus changes. - let name = if id == BUTTON_1_ID { + fn press_button(&mut self, adapter: &Adapter, id: NodeId) { + let text = if id == BUTTON_1_ID { "You pressed button 1" } else { "You pressed button 2" }; - let node = { - let mut builder = NodeBuilder::new(Role::StaticText); - builder.set_name(name); - builder.set_live(Live::Polite); - builder.build() - }; - let root = { - let mut builder = NodeBuilder::new(Role::Window); - builder.set_children(vec![BUTTON_1_ID, BUTTON_2_ID, PRESSED_TEXT_ID]); - builder.set_name(WINDOW_TITLE); - builder.build() - }; - let update = TreeUpdate { - nodes: vec![(PRESSED_TEXT_ID, node), (WINDOW_ID, root)], - tree: None, - focus: self.is_window_focused.then_some(self.focus), - }; - adapter.update(update); - } -} - -fn initial_tree_update(state: &State) -> TreeUpdate { - let root = { - let mut builder = NodeBuilder::new(Role::Window); - builder.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); - builder.set_name(WINDOW_TITLE); - builder.build() - }; - let button_1 = make_button(BUTTON_1_ID, "Button 1"); - let button_2 = make_button(BUTTON_2_ID, "Button 2"); - TreeUpdate { - nodes: vec![ - (WINDOW_ID, root), - (BUTTON_1_ID, button_1), - (BUTTON_2_ID, button_2), - ], - tree: Some(Tree::new(WINDOW_ID)), - focus: state.is_window_focused.then_some(state.focus), + self.announcement = Some(text.into()); + adapter.update_if_active(|| { + let announcement = build_announcement(text, &mut self.node_classes); + let root = self.build_root(); + TreeUpdate { + nodes: vec![(ANNOUNCEMENT_ID, announcement), (WINDOW_ID, root)], + tree: None, + focus: self.focus(), + } + }); } } @@ -159,8 +167,8 @@ fn main() { Adapter::new( &window, move || { - let state = state.lock().unwrap(); - initial_tree_update(&state) + let mut state = state.lock().unwrap(); + state.build_initial_tree() }, event_loop.create_proxy(), ) @@ -200,8 +208,9 @@ fn main() { state.update_focus(&adapter); } VirtualKeyCode::Space => { - let state = state.lock().unwrap(); - state.press_button(&adapter, state.focus); + let mut state = state.lock().unwrap(); + let id = state.focus; + state.press_button(&adapter, id); } _ => (), }, From 19d863ff548490500939c0104663c45896c67134 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 22 Jan 2023 14:42:53 -0600 Subject: [PATCH 44/45] Back to bare panics for unexpected property types, to keep the runtime footprint of handling unexpected errors to a minimum --- common/src/lib.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index b465b21b3..3d817b14d 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1011,8 +1011,8 @@ impl NodeClass { } } -fn unexpected_property_type(id: PropertyId) -> ! { - panic!("unexpected type for property {:?}", id); +fn unexpected_property_type() -> ! { + panic!(); } impl NodeBuilder { @@ -1079,7 +1079,7 @@ macro_rules! option_ref_type_getters { match self.get_property(props, id) { PropertyValue::None => None, PropertyValue::$variant(value) => Some(value), - _ => unexpected_property_type(id), + _ => unexpected_property_type(), } })* } @@ -1093,7 +1093,7 @@ macro_rules! slice_type_getters { match self.get_property(props, id) { PropertyValue::None => &[], PropertyValue::$variant(value) => value, - _ => unexpected_property_type(id), + _ => unexpected_property_type(), } })* } @@ -1107,7 +1107,7 @@ macro_rules! copy_type_getters { match self.get_property(props, id) { PropertyValue::None => None, PropertyValue::$variant(value) => Some(*value), - _ => unexpected_property_type(id), + _ => unexpected_property_type(), } })* } @@ -1148,7 +1148,7 @@ macro_rules! vec_type_methods { PropertyValue::$variant(v) => { v.push(item); } - _ => unexpected_property_type(id), + _ => unexpected_property_type(), } })* } @@ -1290,7 +1290,7 @@ macro_rules! unique_enum_property_methods { match self.class.get_property(&self.props, PropertyId::$id) { PropertyValue::None => None, PropertyValue::$id(value) => Some(*value), - _ => unexpected_property_type(PropertyId::$id), + _ => unexpected_property_type(), } })* } @@ -1300,7 +1300,7 @@ macro_rules! unique_enum_property_methods { match self.class.get_property(&self.props, PropertyId::$id) { PropertyValue::None => None, PropertyValue::$id(value) => Some(*value), - _ => unexpected_property_type(PropertyId::$id), + _ => unexpected_property_type(), } } pub fn $setter(&mut self, value: $id) { From b1b4f824b0b758a3c36969d6c49c3219214bf695 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sun, 22 Jan 2023 15:08:28 -0600 Subject: [PATCH 45/45] Add inlining hints to simple functions for the benefit of projects that don't use LTO --- common/src/lib.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/common/src/lib.rs b/common/src/lib.rs index 3d817b14d..a9b92eabb 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -252,6 +252,7 @@ pub enum Role { } impl Default for Role { + #[inline] fn default() -> Self { Self::Unknown } @@ -375,6 +376,7 @@ struct ActionsVisitor; impl<'de> Visitor<'de> for ActionsVisitor { type Value = Actions; + #[inline] fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("action set") } @@ -403,10 +405,12 @@ impl<'de> Deserialize<'de> for Actions { #[cfg(feature = "schemars")] impl JsonSchema for Actions { + #[inline] fn schema_name() -> String { "Actions".into() } + #[inline] fn is_referenceable() -> bool { false } @@ -645,6 +649,7 @@ pub type NodeIdContent = NonZeroU128; pub struct NodeId(pub NodeIdContent); impl From for NodeId { + #[inline] fn from(inner: NonZeroU64) -> Self { Self(inner.into()) } @@ -940,6 +945,7 @@ struct NodeClass { pub struct NodeClassSet(BTreeSet>); impl NodeClassSet { + #[inline] pub fn new() -> Self { Default::default() } @@ -962,12 +968,14 @@ impl NodeClassSet { impl<'a> Deref for Guard<'a> { type Target = NodeClassSet; + #[inline] fn deref(&self) -> &Self::Target { self.0.as_ref().unwrap() } } impl<'a> DerefMut for Guard<'a> { + #[inline] fn deref_mut(&mut self) -> &mut Self::Target { self.0.as_mut().unwrap() } @@ -1053,18 +1061,22 @@ macro_rules! flag_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { impl Node { $($(#[$doc])* + #[inline] pub fn $getter(&self) -> bool { (self.flags & (Flag::$id).mask()) != 0 })* } impl NodeBuilder { $($(#[$doc])* + #[inline] pub fn $getter(&self) -> bool { (self.flags & (Flag::$id).mask()) != 0 } + #[inline] pub fn $setter(&mut self) { self.flags |= (Flag::$id).mask(); } + #[inline] pub fn $clearer(&mut self) { self.flags &= !((Flag::$id).mask()); })* @@ -1159,18 +1171,22 @@ macro_rules! property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $type_getter:ident, $getter_result:ty, $setter:ident, $type_setter:ident, $setter_param:ty, $clearer:ident)),+) => { impl Node { $($(#[$doc])* + #[inline] pub fn $getter(&self) -> $getter_result { self.class.$type_getter(&self.props, PropertyId::$id) })* } impl NodeBuilder { $($(#[$doc])* + #[inline] pub fn $getter(&self) -> $getter_result { self.class.$type_getter(&self.props, PropertyId::$id) } + #[inline] pub fn $setter(&mut self, value: $setter_param) { self.$type_setter(PropertyId::$id, value); } + #[inline] pub fn $clearer(&mut self) { self.clear_property(PropertyId::$id); })* @@ -1185,6 +1201,7 @@ macro_rules! vec_property_methods { ($id, $getter, $type_getter, &[$item_type], $setter, $type_setter, impl Into>, $clearer) } impl NodeBuilder { + #[inline] pub fn $pusher(&mut self, item: $item_type) { self.$type_pusher(PropertyId::$id, item); } @@ -1286,6 +1303,7 @@ macro_rules! unique_enum_property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { impl Node { $($(#[$doc])* + #[inline] pub fn $getter(&self) -> Option<$id> { match self.class.get_property(&self.props, PropertyId::$id) { PropertyValue::None => None, @@ -1296,6 +1314,7 @@ macro_rules! unique_enum_property_methods { } impl NodeBuilder { $($(#[$doc])* + #[inline] pub fn $getter(&self) -> Option<$id> { match self.class.get_property(&self.props, PropertyId::$id) { PropertyValue::None => None, @@ -1303,9 +1322,11 @@ macro_rules! unique_enum_property_methods { _ => unexpected_property_type(), } } + #[inline] pub fn $setter(&mut self, value: $id) { self.set_property(PropertyId::$id, PropertyValue::$id(value)); } + #[inline] pub fn $clearer(&mut self) { self.clear_property(PropertyId::$id); })* @@ -1314,6 +1335,7 @@ macro_rules! unique_enum_property_methods { } impl NodeBuilder { + #[inline] pub fn new(role: Role) -> Self { Self { class: NodeClass { @@ -1341,36 +1363,44 @@ impl NodeBuilder { } impl Node { + #[inline] pub fn role(&self) -> Role { self.class.role } } impl NodeBuilder { + #[inline] pub fn role(&self) -> Role { self.class.role } + #[inline] pub fn set_role(&mut self, value: Role) { self.class.role = value; } } impl Node { + #[inline] pub fn supports_action(&self, action: Action) -> bool { (self.class.actions.0 & action.mask()) != 0 } } impl NodeBuilder { + #[inline] pub fn supports_action(&self, action: Action) -> bool { (self.class.actions.0 & action.mask()) != 0 } + #[inline] pub fn add_action(&mut self, action: Action) { self.class.actions.0 |= action.mask(); } + #[inline] pub fn remove_action(&mut self, action: Action) { self.class.actions.0 &= !(action.mask()); } + #[inline] pub fn clear_actions(&mut self) { self.class.actions.0 = 0; } @@ -1881,6 +1911,7 @@ struct NodeVisitor; impl<'de> Visitor<'de> for NodeVisitor { type Value = Node; + #[inline] fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("struct Node") } @@ -2070,6 +2101,7 @@ macro_rules! add_properties_to_schema { #[cfg(feature = "schemars")] impl JsonSchema for Node { + #[inline] fn schema_name() -> String { "Node".into() } @@ -2259,6 +2291,7 @@ pub struct Tree { } impl Tree { + #[inline] pub fn new(root: NodeId) -> Tree { Tree { root,