accesskit-0.21.0/.cargo_vcs_info.json0000644000000001440000000000100130640ustar { "git": { "sha1": "f87db98cfc7b5a18d8c665c1a27eb557aeacde58" }, "path_in_vcs": "common" }accesskit-0.21.0/CHANGELOG.md000064400000000000000000000567371046102023000135100ustar 00000000000000# Changelog ## [0.21.0](https://github.com/AccessKit/accesskit/compare/accesskit-v0.20.0...accesskit-v0.21.0) (2025-07-16) ### ⚠ BREAKING CHANGES * Implement refactored `ScrollIntoView` action across desktop platforms ([#594](https://github.com/AccessKit/accesskit/issues/594)) ### Features * Implement refactored `ScrollIntoView` action across desktop platforms ([#594](https://github.com/AccessKit/accesskit/issues/594)) ([1d9b74c](https://github.com/AccessKit/accesskit/commit/1d9b74c057051509b3ffbf63afeacfb16f544ff8)) * Let parents declare actions supported on their children ([#593](https://github.com/AccessKit/accesskit/issues/593)) ([70b534b](https://github.com/AccessKit/accesskit/commit/70b534bed168a84b84cc35199588aa8ab784fb43)) ## [0.20.0](https://github.com/AccessKit/accesskit/compare/accesskit-v0.19.0...accesskit-v0.20.0) (2025-06-26) ### ⚠ BREAKING CHANGES * Refactor actions for scrolling by discrete units ([#573](https://github.com/AccessKit/accesskit/issues/573)) ### Bug Fixes * impl `From` for `Toggled` ([#585](https://github.com/AccessKit/accesskit/issues/585)) ([d38776a](https://github.com/AccessKit/accesskit/commit/d38776a014744db849edbfc9e0a7d0378709ed4b)) * Resolve new clippy warning about using variables directly in format strings ([#590](https://github.com/AccessKit/accesskit/issues/590)) ([ccc62b7](https://github.com/AccessKit/accesskit/commit/ccc62b7f1dd32f0c372ba127a1e65c377048f670)) ### Code Refactoring * Refactor actions for scrolling by discrete units ([#573](https://github.com/AccessKit/accesskit/issues/573)) ([fad11a1](https://github.com/AccessKit/accesskit/commit/fad11a1b66340e7be6b2eb00dfd07004451a17eb)) ## [0.19.0](https://github.com/AccessKit/accesskit/compare/accesskit-v0.18.0...accesskit-v0.19.0) (2025-05-06) ### ⚠ BREAKING CHANGES * Drop redundant `HasPopup::True` ([#550](https://github.com/AccessKit/accesskit/issues/550)) * Drop unused `Node::is_linked` ([#545](https://github.com/AccessKit/accesskit/issues/545)) * Drop `FrozenNode` ([#496](https://github.com/AccessKit/accesskit/issues/496)) ### Bug Fixes * Improve `NodeId`'s debug representation ([#547](https://github.com/AccessKit/accesskit/issues/547)) ([a47bca1](https://github.com/AccessKit/accesskit/commit/a47bca1e376de7b0a22a7dfe6c23dedad315c449)) * Update pyo3 to 0.24 ([#544](https://github.com/AccessKit/accesskit/issues/544)) ([6338e45](https://github.com/AccessKit/accesskit/commit/6338e45097662bf39994e19a09054c20cb2ee782)) ### Code Refactoring * Drop `FrozenNode` ([#496](https://github.com/AccessKit/accesskit/issues/496)) ([f8c0d0a](https://github.com/AccessKit/accesskit/commit/f8c0d0a6fc9613cf1a2a6d8cfba11ebc892dfeb8)) * Drop redundant `HasPopup::True` ([#550](https://github.com/AccessKit/accesskit/issues/550)) ([56abf17](https://github.com/AccessKit/accesskit/commit/56abf17356e4c7f13f64aaeaca6a63c8f7ede553)) * Drop unused `Node::is_linked` ([#545](https://github.com/AccessKit/accesskit/issues/545)) ([3aab4ac](https://github.com/AccessKit/accesskit/commit/3aab4ac6f0193b8a06d7962f933582a4dbdf0c98)) ## [0.18.0](https://github.com/AccessKit/accesskit/compare/accesskit-v0.17.1...accesskit-v0.18.0) (2025-03-06) ### ⚠ BREAKING CHANGES * Drop `Tree::app_name` ([#492](https://github.com/AccessKit/accesskit/issues/492)) ### Features * Android adapter ([#500](https://github.com/AccessKit/accesskit/issues/500)) ([7e65ac7](https://github.com/AccessKit/accesskit/commit/7e65ac77d7e108ac5b9f3722f488a2fdf2e3b3e0)) ### Bug Fixes * Update pyo3 to 0.23 ([#512](https://github.com/AccessKit/accesskit/issues/512)) ([93d3a27](https://github.com/AccessKit/accesskit/commit/93d3a27ac4af60eef4a1faf26392a6f7ff69cf81)) ### Code Refactoring * Drop `Tree::app_name` ([#492](https://github.com/AccessKit/accesskit/issues/492)) ([089794c](https://github.com/AccessKit/accesskit/commit/089794c8f74957e91a19ae3df508e2a892f39ebc)) ## [0.17.1](https://github.com/AccessKit/accesskit/compare/accesskit-v0.17.0...accesskit-v0.17.1) (2024-11-23) ### Bug Fixes * Fix some broken links in the documentation ([#484](https://github.com/AccessKit/accesskit/issues/484)) ([0a51225](https://github.com/AccessKit/accesskit/commit/0a5122561c6f6aca5cf802464220056d763040f8)) ## [0.17.0](https://github.com/AccessKit/accesskit/compare/accesskit-v0.16.3...accesskit-v0.17.0) (2024-10-31) ### ⚠ BREAKING CHANGES * Drop the `is_hovered` property ([#479](https://github.com/AccessKit/accesskit/issues/479)) * Rename `name` to `label` and use `value` for label content ([#475](https://github.com/AccessKit/accesskit/issues/475)) * Rename `NodeBuilder` to `Node` and the old `Node` to `FrozenNode` ([#476](https://github.com/AccessKit/accesskit/issues/476)) * Rename `Role::InlineTextBox` to `TextRun` ([#473](https://github.com/AccessKit/accesskit/issues/473)) * Drop `DefaultActionVerb` ([#472](https://github.com/AccessKit/accesskit/issues/472)) * Make the core crate no-std ([#468](https://github.com/AccessKit/accesskit/issues/468)) ### Features * Make the core crate no-std ([#468](https://github.com/AccessKit/accesskit/issues/468)) ([2fa0d3f](https://github.com/AccessKit/accesskit/commit/2fa0d3f5b2b7ac11ef1751c133706f29e548bd6d)) ### Bug Fixes * Drop the `is_hovered` property ([#479](https://github.com/AccessKit/accesskit/issues/479)) ([95dfdb6](https://github.com/AccessKit/accesskit/commit/95dfdb6c88f7d705f6a7283cb8524168a9f542b2)) ### Code Refactoring * Drop `DefaultActionVerb` ([#472](https://github.com/AccessKit/accesskit/issues/472)) ([ef3b003](https://github.com/AccessKit/accesskit/commit/ef3b0038224459094f650368412650bc3b69526b)) * Rename `name` to `label` and use `value` for label content ([#475](https://github.com/AccessKit/accesskit/issues/475)) ([e0053a5](https://github.com/AccessKit/accesskit/commit/e0053a5399929e8e0d4f07aa18de604ed8766ace)) * Rename `NodeBuilder` to `Node` and the old `Node` to `FrozenNode` ([#476](https://github.com/AccessKit/accesskit/issues/476)) ([7d8910e](https://github.com/AccessKit/accesskit/commit/7d8910e35f7bc0543724cc124941a3bd0304bcc0)) * Rename `Role::InlineTextBox` to `TextRun` ([#473](https://github.com/AccessKit/accesskit/issues/473)) ([29fa341](https://github.com/AccessKit/accesskit/commit/29fa34125a811bd3a0f9da579a9f35c9da90bf29)) ## [0.16.3](https://github.com/AccessKit/accesskit/compare/accesskit-v0.16.2...accesskit-v0.16.3) (2024-10-08) ### Bug Fixes * Eliminate duplicate definitions ([#461](https://github.com/AccessKit/accesskit/issues/461)) ([59826d4](https://github.com/AccessKit/accesskit/commit/59826d4500ddfe880181f7087f9fe83ff2209fc4)) ## [0.16.2](https://github.com/AccessKit/accesskit/compare/accesskit-v0.16.1...accesskit-v0.16.2) (2024-10-07) ### Bug Fixes * Don't use a macro to generate Action debug helper function ([#459](https://github.com/AccessKit/accesskit/issues/459)) ([ed1fb73](https://github.com/AccessKit/accesskit/commit/ed1fb7370780c9dd15028cdfd13e2065642bf490)) * Update minimum supported Rust version to 1.75 ([#457](https://github.com/AccessKit/accesskit/issues/457)) ([fc622fe](https://github.com/AccessKit/accesskit/commit/fc622fe7657c80a4eedad6f6cded11d2538b54d5)) ## [0.16.1](https://github.com/AccessKit/accesskit/compare/accesskit-v0.16.0...accesskit-v0.16.1) (2024-09-24) ### Bug Fixes * Improve debug representation of `Node` and `NodeBuilder` ([#452](https://github.com/AccessKit/accesskit/issues/452)) ([119aa1d](https://github.com/AccessKit/accesskit/commit/119aa1dca8fe734112ecbd59568c876b336ccb6c)) ## [0.16.0](https://github.com/AccessKit/accesskit/compare/accesskit-v0.15.0...accesskit-v0.16.0) (2024-06-29) ### ⚠ BREAKING CHANGES * Optimize serialization and make it compatible with more data formats ([#437](https://github.com/AccessKit/accesskit/issues/437)) * Rename the `StaticText` role to `Label` ([#434](https://github.com/AccessKit/accesskit/issues/434)) ### Code Refactoring * Optimize serialization and make it compatible with more data formats ([#437](https://github.com/AccessKit/accesskit/issues/437)) ([5a80d3a](https://github.com/AccessKit/accesskit/commit/5a80d3ae46cfe85780d4900f4fa9f4feaba52053)) * Rename the `StaticText` role to `Label` ([#434](https://github.com/AccessKit/accesskit/issues/434)) ([7086bc0](https://github.com/AccessKit/accesskit/commit/7086bc0fad446d3ed4a0fd5eff641a1e75f6c599)) ## [0.15.0](https://github.com/AccessKit/accesskit/compare/accesskit-v0.14.0...accesskit-v0.15.0) (2024-06-09) ### Features * Add `author_id` property ([#424](https://github.com/AccessKit/accesskit/issues/424)) ([0d1c56f](https://github.com/AccessKit/accesskit/commit/0d1c56f0bdde58715e1c69f6015df600cb7cb8c1)) ### Bug Fixes * Add explicit cargo features for `enumn` and `pyo3` ([#425](https://github.com/AccessKit/accesskit/issues/425)) ([71ad45b](https://github.com/AccessKit/accesskit/commit/71ad45be1651409ee6918cf835b656e6b5e0fe2d)) ## [0.14.0](https://github.com/AccessKit/accesskit/compare/accesskit-v0.13.0...accesskit-v0.14.0) (2024-04-30) ### ⚠ BREAKING CHANGES * Clean up table roles and properties ([#393](https://github.com/AccessKit/accesskit/issues/393)) * Drop `SortDirection::Unsorted` ([#391](https://github.com/AccessKit/accesskit/issues/391)) * Rename `hierarchical_level` to `level` ([#390](https://github.com/AccessKit/accesskit/issues/390)) * Drop `NodeClassSet` ([#389](https://github.com/AccessKit/accesskit/issues/389)) * Rename `Checked` to `Toggled`; drop `ToggleButton` role ([#388](https://github.com/AccessKit/accesskit/issues/388)) ### Features * Add the `owns` relation ([#392](https://github.com/AccessKit/accesskit/issues/392)) ([fd668dd](https://github.com/AccessKit/accesskit/commit/fd668ddc4b64cb05ab3600972b3d3823a037f2d5)) ### Bug Fixes * Increase minimum supported Rust version to `1.70` ([#396](https://github.com/AccessKit/accesskit/issues/396)) ([a8398b8](https://github.com/AccessKit/accesskit/commit/a8398b847aa003de91042ac45e33126fc2cae053)) ### Code Refactoring * Clean up table roles and properties ([#393](https://github.com/AccessKit/accesskit/issues/393)) ([e34dad9](https://github.com/AccessKit/accesskit/commit/e34dad94448a5321ece9def3f2e054aa5f62dd79)) * Drop `NodeClassSet` ([#389](https://github.com/AccessKit/accesskit/issues/389)) ([1b153ed](https://github.com/AccessKit/accesskit/commit/1b153ed51f8421cdba2dc98beca2e8f5f8c781bc)) * Drop `SortDirection::Unsorted` ([#391](https://github.com/AccessKit/accesskit/issues/391)) ([b86f484](https://github.com/AccessKit/accesskit/commit/b86f484b7e6645e63362896b744a71ec758f810d)) * Rename `Checked` to `Toggled`; drop `ToggleButton` role ([#388](https://github.com/AccessKit/accesskit/issues/388)) ([6bc040b](https://github.com/AccessKit/accesskit/commit/6bc040b7cf75cdbd6a019cc380d8dbce804b3c81)) * Rename `hierarchical_level` to `level` ([#390](https://github.com/AccessKit/accesskit/issues/390)) ([2d61e01](https://github.com/AccessKit/accesskit/commit/2d61e01fffff1265b348c141715f6f9b6fe4081b)) ## [0.13.0](https://github.com/AccessKit/accesskit/compare/accesskit-v0.12.3...accesskit-v0.13.0) (2024-04-14) ### ⚠ BREAKING CHANGES * New approach to lazy initialization ([#375](https://github.com/AccessKit/accesskit/issues/375)) ### Code Refactoring * New approach to lazy initialization ([#375](https://github.com/AccessKit/accesskit/issues/375)) ([9baebdc](https://github.com/AccessKit/accesskit/commit/9baebdceed7300389b6768815d7ae48f1ce401e4)) ## [0.12.3](https://github.com/AccessKit/accesskit/compare/accesskit-v0.12.2...accesskit-v0.12.3) (2024-03-07) ### Bug Fixes * Derive `PartialOrd` and `Ord` on `NodeId` ([#363](https://github.com/AccessKit/accesskit/issues/363)) ([ce3bba1](https://github.com/AccessKit/accesskit/commit/ce3bba1e043d650c406d8814b4f33e9104199c8b)) * Make `NodeClassSet::new` const ([#368](https://github.com/AccessKit/accesskit/issues/368)) ([11d2968](https://github.com/AccessKit/accesskit/commit/11d2968464d50c3e3f55e9a872d0d454c19e7e51)) ## [0.12.2](https://github.com/AccessKit/accesskit/compare/accesskit-v0.12.1...accesskit-v0.12.2) (2024-01-03) ### Bug Fixes * Bump pyo3; add `rename_all` attribute to enums ([#330](https://github.com/AccessKit/accesskit/issues/330)) ([5a4c6f3](https://github.com/AccessKit/accesskit/commit/5a4c6f399837d67b066451a8fb4d43d03c8acb8b)) * Document the `role_description` node property ([#331](https://github.com/AccessKit/accesskit/issues/331)) ([936fa2c](https://github.com/AccessKit/accesskit/commit/936fa2c23190c5d7cd4eb880612295785a009721)) ## [0.12.1](https://github.com/AccessKit/accesskit/compare/accesskit-v0.12.0...accesskit-v0.12.1) (2023-11-04) ### Bug Fixes * Add missing semicolons when not returning anything ([#303](https://github.com/AccessKit/accesskit/issues/303)) ([38d4de1](https://github.com/AccessKit/accesskit/commit/38d4de1442247e701047d75122a9638a2ed99b1f)) ## [0.12.0](https://github.com/AccessKit/accesskit/compare/accesskit-v0.11.2...accesskit-v0.12.0) (2023-09-27) ### ⚠ BREAKING CHANGES * Allow providing app_name, toolkit_name and toolkit_version in Tree, remove parameters from unix adapter constructor ([#291](https://github.com/AccessKit/accesskit/issues/291)) * Make `ActionHandler::do_action` take `&mut self` ([#296](https://github.com/AccessKit/accesskit/issues/296)) * Clean up roles and properties ([#289](https://github.com/AccessKit/accesskit/issues/289)) * Drop next/previous focus properties ([#288](https://github.com/AccessKit/accesskit/issues/288)) * Drop `Tree::root_scroller` ([#279](https://github.com/AccessKit/accesskit/issues/279)) * Decouple in-tree focus from host window/view focus ([#278](https://github.com/AccessKit/accesskit/issues/278)) * Switch to simple unsigned 64-bit integer for node IDs ([#276](https://github.com/AccessKit/accesskit/issues/276)) ### Features * Add role for terminals ([#282](https://github.com/AccessKit/accesskit/issues/282)) ([ddbef37](https://github.com/AccessKit/accesskit/commit/ddbef37158b57f56217317b480e40d58f83a9c24)) * Allow providing app_name, toolkit_name and toolkit_version in Tree, remove parameters from unix adapter constructor ([#291](https://github.com/AccessKit/accesskit/issues/291)) ([5313860](https://github.com/AccessKit/accesskit/commit/531386023257150f49b5e4be942f359855fb7cb6)) ### Bug Fixes * Drop `Tree::root_scroller` ([#279](https://github.com/AccessKit/accesskit/issues/279)) ([fc6c4e0](https://github.com/AccessKit/accesskit/commit/fc6c4e0091d5b257a3869a468fca144a1453cebc)) * Drop next/previous focus properties ([#288](https://github.com/AccessKit/accesskit/issues/288)) ([d35c7c1](https://github.com/AccessKit/accesskit/commit/d35c7c149a650dfedf1b031c0668adad585659fa)) * Support the pyo3 crate in all public enums ([#270](https://github.com/AccessKit/accesskit/issues/270)) ([9b12d0c](https://github.com/AccessKit/accesskit/commit/9b12d0c3d828d4c847510b611d891872c4666984)) ### Code Refactoring * Clean up roles and properties ([#289](https://github.com/AccessKit/accesskit/issues/289)) ([4fc9c55](https://github.com/AccessKit/accesskit/commit/4fc9c55c91812472593923d93ff89d75ff305ee4)) * Decouple in-tree focus from host window/view focus ([#278](https://github.com/AccessKit/accesskit/issues/278)) ([d360d20](https://github.com/AccessKit/accesskit/commit/d360d20cf951e7643b81a5303006c9f7daa5bd56)) * Make `ActionHandler::do_action` take `&mut self` ([#296](https://github.com/AccessKit/accesskit/issues/296)) ([4fc7846](https://github.com/AccessKit/accesskit/commit/4fc7846d732d61fb45c023060ebab96801a0053e)) * Switch to simple unsigned 64-bit integer for node IDs ([#276](https://github.com/AccessKit/accesskit/issues/276)) ([3eadd48](https://github.com/AccessKit/accesskit/commit/3eadd48ec47854faa94a94ebf910ec08f514642f)) ## [0.11.2](https://github.com/AccessKit/accesskit/compare/accesskit-v0.11.1...accesskit-v0.11.2) (2023-08-08) ### Bug Fixes * Support the enumn crate in all public enums ([#264](https://github.com/AccessKit/accesskit/issues/264)) ([b9b3cd1](https://github.com/AccessKit/accesskit/commit/b9b3cd18fccdd6526fb4f58c13eb91599452a3d6)) ## [0.11.1](https://github.com/AccessKit/accesskit/compare/accesskit-v0.11.0...accesskit-v0.11.1) (2023-07-30) ### Bug Fixes * Fix broken intra-doc-link. ([#262](https://github.com/AccessKit/accesskit/issues/262)) ([63c1715](https://github.com/AccessKit/accesskit/commit/63c17152d1eb8ae6ff19c2bc4a6756372bc490c2)) ## [0.11.0](https://github.com/AccessKit/accesskit/compare/accesskit-v0.10.1...accesskit-v0.11.0) (2023-03-29) ### Features * Add C bindings ([#230](https://github.com/AccessKit/accesskit/issues/230)) ([7f7f4c7](https://github.com/AccessKit/accesskit/commit/7f7f4c755890ab8210a5a8bf8e237ba6a51dd205)) ## [0.10.1](https://github.com/AccessKit/accesskit/compare/accesskit-v0.10.0...accesskit-v0.10.1) (2023-02-20) ### Bug Fixes * Set appropriate representations on all public types that will be exposed via FFI ([54e82f6](https://github.com/AccessKit/accesskit/commit/54e82f673f5c7b46d9077fe5f946305800862bf0)) ## [0.10.0](https://github.com/AccessKit/accesskit/compare/accesskit-v0.9.0...accesskit-v0.10.0) (2023-02-12) ### ⚠ BREAKING CHANGES * Move thread synchronization into platform adapters; drop parking_lot ([#212](https://github.com/AccessKit/accesskit/issues/212)) ### Code Refactoring * Move thread synchronization into platform adapters; drop parking_lot ([#212](https://github.com/AccessKit/accesskit/issues/212)) ([5df52e5](https://github.com/AccessKit/accesskit/commit/5df52e5545faddf6a51905409013c2f5be23981e)) ## [0.9.0](https://github.com/AccessKit/accesskit/compare/accesskit-v0.8.1...accesskit-v0.9.0) (2023-02-05) ### ⚠ BREAKING CHANGES * Make `Node` opaque and optimize it for size ([#205](https://github.com/AccessKit/accesskit/issues/205)) ### Code Refactoring * Make `Node` opaque and optimize it for size ([#205](https://github.com/AccessKit/accesskit/issues/205)) ([4811152](https://github.com/AccessKit/accesskit/commit/48111521439b76c1a8687418a4b20f9b705eac6d)) ## [0.8.1](https://github.com/AccessKit/accesskit/compare/accesskit-v0.8.0...accesskit-v0.8.1) (2022-12-04) ### Documentation * Fix outdated documentation for `TreeUpdate` ([#182](https://github.com/AccessKit/accesskit/issues/182)) ([dd658c7](https://github.com/AccessKit/accesskit/commit/dd658c70df55b2234a0346220362b0b9a40bb41d)) ## [0.8.0](https://www.github.com/AccessKit/accesskit/compare/accesskit-v0.7.0...accesskit-v0.8.0) (2022-11-17) ### ⚠ BREAKING CHANGES * Be opinionated about coordinates after all (#151) ### Code Refactoring * Be opinionated about coordinates after all ([#151](https://www.github.com/AccessKit/accesskit/issues/151)) ([91a29a1](https://www.github.com/AccessKit/accesskit/commit/91a29a1bf99bca39e9a00a744025533924e45190)) ## [0.7.0](https://www.github.com/AccessKit/accesskit/compare/accesskit-v0.6.1...accesskit-v0.7.0) (2022-11-11) ### ⚠ BREAKING CHANGES * Text range support (#145) * Drop the `ignored` field and implement generic filtered tree traversal (#143) ### Features * Text range support ([#145](https://www.github.com/AccessKit/accesskit/issues/145)) ([455e6f7](https://www.github.com/AccessKit/accesskit/commit/455e6f73bc058644d299c06eeeda9cc4cbe8844f)) ### Code Refactoring * Drop the `ignored` field and implement generic filtered tree traversal ([#143](https://www.github.com/AccessKit/accesskit/issues/143)) ([a4befe6](https://www.github.com/AccessKit/accesskit/commit/a4befe6e8a5afbe4a52dfd09eb87fdf2078d6c1d)) ### [0.6.1](https://www.github.com/AccessKit/accesskit/compare/accesskit-v0.6.0...accesskit-v0.6.1) (2022-10-10) ### Bug Fixes * **common:** Restore compatibility with Rust 1.61 ([#139](https://www.github.com/AccessKit/accesskit/issues/139)) ([d8c6b16](https://www.github.com/AccessKit/accesskit/commit/d8c6b166c83796bfd6d748df60136029a9ec81d2)) ## [0.6.0](https://www.github.com/AccessKit/accesskit/compare/accesskit-v0.5.1...accesskit-v0.6.0) (2022-10-09) ### ⚠ BREAKING CHANGES * Wrap `TreeUpdate` nodes in `Arc` (#135) * Store node ID in `TreeUpdate`, not `accesskit::Node` (#132) ### Bug Fixes * Don't try to optimize tree updates with unchanged nodes ([#138](https://www.github.com/AccessKit/accesskit/issues/138)) ([7721719](https://www.github.com/AccessKit/accesskit/commit/7721719fb0ab90bf41cc30dd0469c7de90228fe9)) ### Code Refactoring * Store node ID in `TreeUpdate`, not `accesskit::Node` ([#132](https://www.github.com/AccessKit/accesskit/issues/132)) ([0bb86dd](https://www.github.com/AccessKit/accesskit/commit/0bb86ddb298cb5a253a91f07be0bad8b84b2fda3)) * Wrap `TreeUpdate` nodes in `Arc` ([#135](https://www.github.com/AccessKit/accesskit/issues/135)) ([907bc18](https://www.github.com/AccessKit/accesskit/commit/907bc1820b80d95833b6c5c3acaa2a8a4e93a6c2)) ### [0.5.1](https://www.github.com/AccessKit/accesskit/compare/accesskit-v0.5.0...accesskit-v0.5.1) (2022-10-03) ### Bug Fixes * **common:** Write a README specifically for the accesskit crate ([#130](https://www.github.com/AccessKit/accesskit/issues/130)) ([0c2f5cf](https://www.github.com/AccessKit/accesskit/commit/0c2f5cf71bdacf3142bff77defea36eeb2b4e1e9)), closes [#129](https://www.github.com/AccessKit/accesskit/issues/129) ## [0.5.0](https://www.github.com/AccessKit/accesskit/compare/accesskit-v0.4.0...accesskit-v0.5.0) (2022-09-23) ### ⚠ BREAKING CHANGES * Basic live regions (#128) ### Features * Basic live regions ([#128](https://www.github.com/AccessKit/accesskit/issues/128)) ([03d745b](https://www.github.com/AccessKit/accesskit/commit/03d745b891147175bde2693cc10b96a2f6e31f39)) ### Bug Fixes * **common:** Enable the serde feature when the schemars feature is turned on ([#122](https://www.github.com/AccessKit/accesskit/issues/122)) ([126b6e1](https://www.github.com/AccessKit/accesskit/commit/126b6e13294bee2b4c905a78147b49d763a61d05)) * **common:** Skip `ActionRequest::data` if it is `None` during serialization ([#123](https://www.github.com/AccessKit/accesskit/issues/123)) ([2d88ea8](https://www.github.com/AccessKit/accesskit/commit/2d88ea8518c99692beacfb955ef0bd4f388a4908)) ## [0.4.0](https://www.github.com/AccessKit/accesskit/compare/accesskit-v0.3.0...accesskit-v0.4.0) (2022-07-22) ### ⚠ BREAKING CHANGES * String indices are always in UTF-8 code units (#114) * Drop unused tree IDs (#113) * Switch to NonZeroU128 for NodeIDs (#99) ### Features * **common:** Conversion from `NonZeroU64` to `NodeId` ([#112](https://www.github.com/AccessKit/accesskit/issues/112)) ([b7adfb9](https://www.github.com/AccessKit/accesskit/commit/b7adfb906cb09107be71a148b5199ba87df2a6b3)) ### Bug Fixes * **common:** Various documentation fixes and improvements ([#111](https://www.github.com/AccessKit/accesskit/issues/111)) ([4d27234](https://www.github.com/AccessKit/accesskit/commit/4d27234195e96de65bf55869877405cb5e45f6fc)) * Migrate to 2021 edition ([#115](https://www.github.com/AccessKit/accesskit/issues/115)) ([f2333c8](https://www.github.com/AccessKit/accesskit/commit/f2333c8ce17d46aab6fc190338ab4cfcf8569f9e)) * Switch to NonZeroU128 for NodeIDs ([#99](https://www.github.com/AccessKit/accesskit/issues/99)) ([25a1a52](https://www.github.com/AccessKit/accesskit/commit/25a1a52c4562b163bfcc8c625a233c00a41aacf2)) ### Code Refactoring * Drop unused tree IDs ([#113](https://www.github.com/AccessKit/accesskit/issues/113)) ([ca60770](https://www.github.com/AccessKit/accesskit/commit/ca607702cee13c93fe538d2faec88e474261f7ab)) * String indices are always in UTF-8 code units ([#114](https://www.github.com/AccessKit/accesskit/issues/114)) ([386ca0a](https://www.github.com/AccessKit/accesskit/commit/386ca0a89c42fd201843f617b2fd6b6d1de77f59)) ## [0.3.0](https://www.github.com/AccessKit/accesskit/compare/accesskit-v0.2.0...accesskit-v0.3.0) (2021-12-29) ### ⚠ BREAKING CHANGES * Drop `TreeUpdate::clear` (#96) ### Code Refactoring * Drop `TreeUpdate::clear` ([#96](https://www.github.com/AccessKit/accesskit/issues/96)) ([38f520b](https://www.github.com/AccessKit/accesskit/commit/38f520b960c6db7b3927b369aee206ee6bc5e8aa)) accesskit-0.21.0/Cargo.lock0000644000000152540000000000100110470ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "accesskit" version = "0.21.0" dependencies = [ "enumn", "pyo3", "schemars", "serde", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "dyn-clone" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" [[package]] name = "enumn" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "indoc" version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" [[package]] name = "itoa" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "libc" version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "memoffset" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "portable-atomic" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "proc-macro2" version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17da310086b068fbdcefbba30aeb3721d5bb9af8db4987d6735b2183ca567229" dependencies = [ "cfg-if", "indoc", "libc", "memoffset", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", "unindent", ] [[package]] name = "pyo3-build-config" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e27165889bd793000a098bb966adc4300c312497ea25cf7a690a9f0ac5aa5fc1" dependencies = [ "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05280526e1dbf6b420062f3ef228b78c0c54ba94e157f5cb724a609d0f2faabc" dependencies = [ "libc", "pyo3-build-config", ] [[package]] name = "pyo3-macros" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3ce5686aa4d3f63359a5100c62a127c9f15e8398e5fdeb5deef1fed5cd5f44" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", "syn", ] [[package]] name = "pyo3-macros-backend" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4cf6faa0cbfb0ed08e89beb8103ae9724eb4750e3a78084ba4017cbe94f3855" dependencies = [ "heck", "proc-macro2", "pyo3-build-config", "quote", "syn", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "ryu" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "schemars" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" dependencies = [ "dyn-clone", "schemars_derive", "serde", "serde_json", ] [[package]] name = "schemars_derive" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", "syn", ] [[package]] name = "serde" version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_derive_internals" version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "syn" version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "target-lexicon" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" [[package]] name = "unicode-ident" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unindent" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" accesskit-0.21.0/Cargo.toml0000644000000027370000000000100110740ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.77.2" name = "accesskit" version = "0.21.0" authors = ["The AccessKit contributors"] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "UI accessibility infrastructure across platforms" readme = "README.md" keywords = [ "gui", "ui", "accessibility", ] categories = ["gui"] license = "MIT OR Apache-2.0" repository = "https://github.com/AccessKit/accesskit" [package.metadata.docs.rs] features = [ "schemars", "serde", ] [features] enumn = ["dep:enumn"] pyo3 = ["dep:pyo3"] schemars = [ "dep:schemars", "serde", ] serde = [ "dep:serde", "enumn", ] [lib] name = "accesskit" path = "src/lib.rs" [dependencies.enumn] version = "0.1.6" optional = true [dependencies.pyo3] version = "0.24" optional = true [dependencies.schemars] version = "0.8.7" optional = true [dependencies.serde] version = "1.0" features = [ "alloc", "derive", ] optional = true default-features = false accesskit-0.21.0/Cargo.toml.orig000064400000000000000000000014151046102023000145450ustar 00000000000000[package] name = "accesskit" version = "0.21.0" authors.workspace = true license.workspace = true description = "UI accessibility infrastructure across platforms" categories.workspace = true keywords = ["gui", "ui", "accessibility"] repository.workspace = true readme = "README.md" edition.workspace = true rust-version.workspace = true [package.metadata.docs.rs] features = ["schemars", "serde"] [dependencies] enumn = { version = "0.1.6", optional = true } pyo3 = { version = "0.24", optional = true } schemars = { version = "0.8.7", optional = true } serde = { version = "1.0", default-features = false, features = ["alloc", "derive"], optional = true } [features] enumn = ["dep:enumn"] pyo3 = ["dep:pyo3"] serde = ["dep:serde", "enumn"] schemars = ["dep:schemars", "serde"] accesskit-0.21.0/README.md000064400000000000000000000022351046102023000131360ustar 00000000000000# AccessKit This is the shared cross-platform crate for [AccessKit](https://accesskit.dev/). It defines the data structures that represent an accessibility tree, and the trait for handling action requests from assistive technologies. To use AccessKit in your application or toolkit, you will also need a platform adapter. The following platform adapters are currently available: * [accesskit_windows](https://crates.io/crates/accesskit_windows): exposes an AccessKit tree on Windows using the UI Automation API * [accesskit_macos](https://crates.io/crates/accesskit_macos): exposes an AccessKit tree on MacOS through the Cocoa `NSAccessibility` protocol * [accesskit_unix](https://crates.io/crates/accesskit_unix): exposes an AccessKit tree on Linux and Unix systems through the AT-SPI protocol * [accesskit_android](https://crates.io/crates/accesskit_android): exposes an AccessKit tree on Android through the Java-based Android accessibility API * [accesskit_winit](https://crates.io/crates/accesskit_winit): wraps other platform adapters for use with the [winit](https://crates.io/crates/winit) windowing library Some platform adapters include simple examples. accesskit-0.21.0/src/geometry.rs000064400000000000000000000533621046102023000146560ustar 00000000000000// 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 core::{ 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))] #[repr(C)] 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`](core::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 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))] #[repr(C)] 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))] #[repr(C)] 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))] #[repr(C)] 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))] #[repr(C)] 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, } } } accesskit-0.21.0/src/lib.rs000064400000000000000000003067651046102023000136010ustar 00000000000000// Copyright 2021 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 Chromium's accessibility abstraction. // Copyright 2018 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE.chromium file. #![cfg_attr(not(any(feature = "pyo3", feature = "schemars")), no_std)] extern crate alloc; use alloc::{boxed::Box, string::String, vec::Vec}; use core::fmt; #[cfg(feature = "pyo3")] use pyo3::pyclass; #[cfg(feature = "schemars")] use schemars::{ gen::SchemaGenerator, schema::{InstanceType, ObjectValidation, Schema, SchemaObject}, JsonSchema, Map as SchemaMap, }; #[cfg(feature = "serde")] use serde::{ de::{Deserializer, IgnoredAny, MapAccess, Visitor}, ser::{SerializeMap, Serializer}, Deserialize, Serialize, }; 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 /// the latest draft for proper usage. /// /// Like the AccessKit schema as a whole, this list is largely taken /// from Chromium. However, unlike Chromium's alphabetized list, this list /// 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, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "enumn", derive(enumn::N))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[cfg_attr( feature = "pyo3", pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE", eq) )] #[repr(u8)] pub enum Role { #[default] Unknown, TextRun, Cell, Label, Image, Link, Row, ListItem, /// Contains the bullet, number, or other marker for a list item. ListMarker, TreeItem, ListBoxOption, MenuItem, MenuListOption, Paragraph, /// A generic container that should be ignored by assistive technologies /// and filtered out of platform accessibility trees. Equivalent to the ARIA /// `none` or `presentation` role, or to an HTML `div` with no role. GenericContainer, CheckBox, RadioButton, TextInput, Button, DefaultButton, Pane, RowHeader, ColumnHeader, RowGroup, List, Table, LayoutTableCell, LayoutTableRow, LayoutTable, Switch, Menu, MultilineTextInput, SearchInput, DateInput, DateTimeInput, WeekInput, MonthInput, TimeInput, EmailInput, NumberInput, PasswordInput, PhoneNumberInput, UrlInput, Abbr, Alert, AlertDialog, Application, Article, Audio, Banner, Blockquote, Canvas, Caption, Caret, Code, ColorWell, ComboBox, EditableComboBox, Complementary, Comment, ContentDeletion, ContentInsertion, ContentInfo, Definition, DescriptionList, DescriptionListDetail, DescriptionListTerm, Details, Dialog, Directory, DisclosureTriangle, Document, EmbeddedObject, Emphasis, Feed, FigureCaption, Figure, Footer, FooterAsNonLandmark, Form, Grid, Group, Header, HeaderAsNonLandmark, Heading, Iframe, IframePresentational, ImeCandidate, Keyboard, Legend, LineBreak, ListBox, Log, Main, Mark, Marquee, Math, MenuBar, MenuItemCheckBox, MenuItemRadio, MenuListPopup, Meter, Navigation, Note, PluginObject, Portal, Pre, ProgressIndicator, RadioGroup, Region, RootWebArea, Ruby, RubyAnnotation, ScrollBar, ScrollView, Search, Section, Slider, SpinButton, Splitter, Status, Strong, Suggestion, SvgRoot, Tab, TabList, TabPanel, Term, Time, Timer, TitleBar, Toolbar, Tooltip, Tree, TreeGrid, Video, WebView, Window, PdfActionableHighlight, PdfRoot, // ARIA Graphics module roles: // https://rawgit.com/w3c/graphics-aam/master/#mapping_role_table GraphicsDocument, GraphicsObject, GraphicsSymbol, // DPub Roles: // https://www.w3.org/TR/dpub-aam-1.0/#mapping_role_table DocAbstract, DocAcknowledgements, DocAfterword, DocAppendix, DocBackLink, DocBiblioEntry, DocBibliography, DocBiblioRef, DocChapter, DocColophon, DocConclusion, DocCover, DocCredit, DocCredits, DocDedication, DocEndnote, DocEndnotes, DocEpigraph, DocEpilogue, DocErrata, DocExample, DocFootnote, DocForeword, DocGlossary, DocGlossRef, DocIndex, DocIntroduction, DocNoteRef, DocNotice, DocPageBreak, DocPageFooter, DocPageHeader, DocPageList, DocPart, DocPreface, DocPrologue, DocPullquote, DocQna, DocSubtitle, DocTip, DocToc, /// Behaves similar to an ARIA grid but is primarily used by Chromium's /// `TableView` and its subclasses, so they can be exposed correctly /// on certain platforms. ListGrid, /// This is just like a multi-line document, but signals that assistive /// technologies should implement behavior specific to a VT-100-style /// terminal. Terminal, } /// An action to be taken on an accessibility node. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "enumn", derive(enumn::N))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[cfg_attr( feature = "pyo3", pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE", eq) )] #[repr(u8)] pub enum Action { /// Do the equivalent of a single click or tap. Click, Focus, Blur, Collapse, Expand, /// Requires [`ActionRequest::data`] to be set to [`ActionData::CustomAction`]. CustomAction, /// Decrement a numeric value by one step. Decrement, /// Increment a numeric value by one step. Increment, HideTooltip, ShowTooltip, /// Delete any selected text in the control's text value and /// insert the specified value in its place, like when typing or pasting. /// Requires [`ActionRequest::data`] to be set to [`ActionData::Value`]. ReplaceSelectedText, /// Scroll down by the specified unit. ScrollDown, /// Scroll left by the specified unit. ScrollLeft, /// Scroll right by the specified unit. ScrollRight, /// Scroll up by the specified unit. ScrollUp, /// Scroll any scrollable containers to make the target node visible. /// Optionally set [`ActionRequest::data`] to [`ActionData::ScrollHint`]. ScrollIntoView, /// Scroll the given object to a specified point in the tree's container /// (e.g. window). Requires [`ActionRequest::data`] to be set to /// [`ActionData::ScrollToPoint`]. ScrollToPoint, /// Requires [`ActionRequest::data`] to be set to [`ActionData::SetScrollOffset`]. SetScrollOffset, /// Requires [`ActionRequest::data`] to be set to [`ActionData::SetTextSelection`]. SetTextSelection, /// Don't focus this node, but set it as the sequential focus navigation /// starting point, so that pressing Tab moves to the next element /// following this one, for example. SetSequentialFocusNavigationStartingPoint, /// Replace the value of the control with the specified value and /// reset the selection, if applicable. Requires [`ActionRequest::data`] /// to be set to [`ActionData::Value`] or [`ActionData::NumericValue`]. SetValue, ShowContextMenu, } impl Action { fn mask(self) -> u32 { 1 << (self as u8) } #[cfg(not(feature = "enumn"))] fn n(value: u8) -> Option { // Manually implement something similar to the enumn crate. We don't // want to bring this crate by default though and we can't use a // macro as it would break C bindings header file generation. match value { 0 => Some(Action::Click), 1 => Some(Action::Focus), 2 => Some(Action::Blur), 3 => Some(Action::Collapse), 4 => Some(Action::Expand), 5 => Some(Action::CustomAction), 6 => Some(Action::Decrement), 7 => Some(Action::Increment), 8 => Some(Action::HideTooltip), 9 => Some(Action::ShowTooltip), 10 => Some(Action::ReplaceSelectedText), 11 => Some(Action::ScrollDown), 12 => Some(Action::ScrollLeft), 13 => Some(Action::ScrollRight), 14 => Some(Action::ScrollUp), 15 => Some(Action::ScrollIntoView), 16 => Some(Action::ScrollToPoint), 17 => Some(Action::SetScrollOffset), 18 => Some(Action::SetTextSelection), 19 => Some(Action::SetSequentialFocusNavigationStartingPoint), 20 => Some(Action::SetValue), 21 => Some(Action::ShowContextMenu), _ => None, } } } fn action_mask_to_action_vec(mask: u32) -> Vec { let mut actions = Vec::new(); let mut i = 0; while let Some(variant) = Action::n(i) { if mask & variant.mask() != 0 { actions.push(variant); } i += 1; } actions } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "enumn", derive(enumn::N))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[cfg_attr( feature = "pyo3", pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE", eq) )] #[repr(u8)] pub enum Orientation { /// E.g. most toolbars and separators. Horizontal, /// E.g. menu or combo box. Vertical, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "enumn", derive(enumn::N))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[cfg_attr( feature = "pyo3", pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE", eq) )] #[repr(u8)] pub enum TextDirection { LeftToRight, RightToLeft, TopToBottom, BottomToTop, } /// Indicates if a form control has invalid input or if a web DOM element has an /// [`aria-invalid`] attribute. /// /// [`aria-invalid`]: https://www.w3.org/TR/wai-aria-1.1/#aria-invalid #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "enumn", derive(enumn::N))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[cfg_attr( feature = "pyo3", pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE", eq) )] #[repr(u8)] pub enum Invalid { True, Grammar, Spelling, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "enumn", derive(enumn::N))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[cfg_attr( feature = "pyo3", pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE", eq) )] #[repr(u8)] pub enum Toggled { False, True, Mixed, } impl From for Toggled { #[inline] fn from(b: bool) -> Self { match b { false => Self::False, true => Self::True, } } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "enumn", derive(enumn::N))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[cfg_attr( feature = "pyo3", pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE", eq) )] #[repr(u8)] pub enum SortDirection { Ascending, Descending, Other, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "enumn", derive(enumn::N))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[cfg_attr( feature = "pyo3", pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE", eq) )] #[repr(u8)] pub enum AriaCurrent { False, True, Page, Step, Location, Date, Time, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "enumn", derive(enumn::N))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[cfg_attr( feature = "pyo3", pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE", eq) )] #[repr(u8)] pub enum AutoComplete { Inline, List, Both, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "enumn", derive(enumn::N))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[cfg_attr( feature = "pyo3", pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE", eq) )] #[repr(u8)] pub enum Live { Off, Polite, Assertive, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "enumn", derive(enumn::N))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[cfg_attr( feature = "pyo3", pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE", eq) )] #[repr(u8)] pub enum HasPopup { Menu, Listbox, Tree, Grid, Dialog, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "enumn", derive(enumn::N))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[cfg_attr( feature = "pyo3", pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE", eq) )] #[repr(u8)] pub enum ListStyle { Circle, Disc, Image, Numeric, Square, /// Language specific ordering (alpha, roman, cjk-ideographic, etc...) Other, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "enumn", derive(enumn::N))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[cfg_attr( feature = "pyo3", pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE", eq) )] #[repr(u8)] pub enum TextAlign { Left, Right, Center, Justify, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "enumn", derive(enumn::N))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[cfg_attr( feature = "pyo3", pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE", eq) )] #[repr(u8)] pub enum VerticalOffset { Subscript, Superscript, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "enumn", derive(enumn::N))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[cfg_attr( feature = "pyo3", pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE", eq) )] #[repr(u8)] pub enum TextDecoration { Solid, Dotted, Dashed, Double, Wavy, } pub type NodeIdContent = u64; /// The stable identity of a [`Node`], unique within the node's tree. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[repr(transparent)] pub struct NodeId(pub NodeIdContent); impl From for NodeId { #[inline] fn from(inner: NodeIdContent) -> Self { Self(inner) } } impl From for NodeIdContent { #[inline] fn from(outer: NodeId) -> Self { outer.0 } } impl fmt::Debug for NodeId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "#{}", self.0) } } /// Defines a custom action for a UI element. /// /// For example, a list UI can allow a user to reorder items in the list by dragging the /// items. #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct CustomAction { pub id: i32, pub description: Box, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct TextPosition { /// The node's role must be [`Role::TextRun`]. pub node: NodeId, /// The index of an item in [`Node::character_lengths`], or the length /// of that slice if the position is at the end of the line. pub character_index: usize, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct TextSelection { /// The position where the selection started, and which does not change /// as the selection is expanded or contracted. If there is no selection /// but only a caret, this must be equal to the value of [`TextSelection::focus`]. /// This is also known as a degenerate selection. pub anchor: TextPosition, /// The active end of the selection, which changes as the selection /// is expanded or contracted, or the position of the caret if there is /// no selection. pub focus: TextPosition, } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[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)] enum Flag { Hidden, Multiselectable, Required, Visited, Busy, LiveAtomic, Modal, TouchTransparent, ReadOnly, Disabled, Bold, Italic, ClipsChildren, IsLineBreakingObject, IsPageBreakingObject, IsSpellingError, IsGrammarError, IsSearchMatch, IsSuggestion, } 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 #[derive(Clone, Debug, PartialEq)] enum PropertyValue { None, NodeIdVec(Vec), NodeId(NodeId), String(Box), F64(f64), Usize(usize), Color(u32), TextDecoration(TextDecoration), LengthSlice(Box<[u8]>), CoordSlice(Box<[f32]>), Bool(bool), Invalid(Invalid), Toggled(Toggled), Live(Live), TextDirection(TextDirection), Orientation(Orientation), SortDirection(SortDirection), AriaCurrent(AriaCurrent), AutoComplete(AutoComplete), HasPopup(HasPopup), ListStyle(ListStyle), TextAlign(TextAlign), VerticalOffset(VerticalOffset), Affine(Box), Rect(Rect), TextSelection(Box), CustomActionVec(Vec), } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[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)] enum PropertyId { // NodeIdVec Children, Controls, Details, DescribedBy, FlowTo, LabelledBy, Owns, RadioGroup, // NodeId ActiveDescendant, ErrorMessage, InPageLinkTarget, MemberOf, NextOnLine, PreviousOnLine, PopupFor, // String Label, Description, Value, AccessKey, AuthorId, ClassName, FontFamily, HtmlTag, InnerHtml, KeyboardShortcut, Language, Placeholder, RoleDescription, StateDescription, Tooltip, Url, RowIndexText, ColumnIndexText, // f64 ScrollX, ScrollXMin, ScrollXMax, ScrollY, ScrollYMin, ScrollYMax, NumericValue, MinNumericValue, MaxNumericValue, NumericValueStep, NumericValueJump, FontSize, FontWeight, // usize RowCount, ColumnCount, RowIndex, ColumnIndex, RowSpan, ColumnSpan, Level, SizeOfSet, PositionInSet, // Color ColorValue, BackgroundColor, ForegroundColor, // TextDecoration Overline, Strikethrough, Underline, // LengthSlice CharacterLengths, WordLengths, // CoordSlice CharacterPositions, CharacterWidths, // bool Expanded, Selected, // Unique enums Invalid, Toggled, Live, TextDirection, Orientation, SortDirection, AriaCurrent, AutoComplete, HasPopup, ListStyle, TextAlign, VerticalOffset, // Other Transform, Bounds, TextSelection, CustomActions, // This MUST be last. Unset, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[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]) } } #[derive(Clone, Debug, Default, PartialEq)] struct Properties { indices: PropertyIndices, values: Vec, } /// 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, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct Node { role: Role, actions: u32, child_actions: u32, flags: u32, properties: Properties, } impl PropertyIndices { fn get<'a>(&self, values: &'a [PropertyValue], id: PropertyId) -> &'a PropertyValue { let index = self.0[id as usize]; if index == PropertyId::Unset as u8 { &PropertyValue::None } else { &values[index as usize] } } } impl Properties { fn get_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.values.push(default); let index = self.values.len() - 1; self.indices.0[id as usize] = index as u8; &mut self.values[index] } else { &mut self.values[index] } } fn set(&mut self, id: PropertyId, value: PropertyValue) { let index = self.indices.0[id as usize]; if index == PropertyId::Unset as u8 { self.values.push(value); self.indices.0[id as usize] = (self.values.len() - 1) as u8; } else { self.values[index as usize] = value; } } fn clear(&mut self, id: PropertyId) { let index = self.indices.0[id as usize]; if index != PropertyId::Unset as u8 { self.values[index as usize] = PropertyValue::None; } } } 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 } #[inline] pub fn $setter(&mut self) { self.flags |= (Flag::$id).mask(); } #[inline] pub fn $clearer(&mut self) { self.flags &= !((Flag::$id).mask()); })* fn debug_flag_properties(&self, fmt: &mut fmt::DebugStruct) { $( if self.$getter() { fmt.field(stringify!($getter), &true); } )* } } $(#[cfg(test)] mod $getter { use super::{Node, Role}; #[test] fn getter_should_return_default_value() { let node = Node::new(Role::Unknown); assert!(!node.$getter()); } #[test] fn setter_should_update_the_property() { let mut node = Node::new(Role::Unknown); node.$setter(); assert!(node.$getter()); } #[test] fn clearer_should_reset_the_property() { let mut node = Node::new(Role::Unknown); node.$setter(); node.$clearer(); assert!(!node.$getter()); } })* } } macro_rules! option_ref_type_getters { ($(($method:ident, $type:ty, $variant:ident)),+) => { impl PropertyIndices { $(fn $method<'a>(&self, values: &'a [PropertyValue], id: PropertyId) -> Option<&'a $type> { match self.get(values, id) { PropertyValue::$variant(value) => Some(value), _ => None, } })* } } } macro_rules! slice_type_getters { ($(($method:ident, $type:ty, $variant:ident)),+) => { impl PropertyIndices { $(fn $method<'a>(&self, values: &'a [PropertyValue], id: PropertyId) -> &'a [$type] { match self.get(values, id) { PropertyValue::$variant(value) => value, _ => &[], } })* } } } macro_rules! copy_type_getters { ($(($method:ident, $type:ty, $variant:ident)),+) => { impl PropertyIndices { $(fn $method(&self, values: &[PropertyValue], id: PropertyId) -> Option<$type> { match self.get(values, id) { PropertyValue::$variant(value) => Some(*value), _ => None, } })* } } } macro_rules! box_type_setters { ($(($method:ident, $type:ty, $variant:ident)),+) => { impl Node { $(fn $method(&mut self, id: PropertyId, value: impl Into>) { self.properties.set(id, PropertyValue::$variant(value.into())); })* } } } macro_rules! copy_type_setters { ($(($method:ident, $type:ty, $variant:ident)),+) => { impl Node { $(fn $method(&mut self, id: PropertyId, value: $type) { self.properties.set(id, PropertyValue::$variant(value)); })* } } } macro_rules! vec_type_methods { ($(($type:ty, $variant:ident, $getter:ident, $setter:ident, $pusher:ident)),+) => { $(slice_type_getters! { ($getter, $type, $variant) })* impl Node { $(fn $setter(&mut self, id: PropertyId, value: impl Into>) { self.properties.set(id, PropertyValue::$variant(value.into())); } fn $pusher(&mut self, id: PropertyId, item: $type) { if let PropertyValue::$variant(v) = self.properties.get_mut(id, PropertyValue::$variant(Vec::new())) { v.push(item); } })* } } } 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.properties.indices.$type_getter(&self.properties.values, PropertyId::$id) } #[inline] pub fn $setter(&mut self, value: $setter_param) { self.$type_setter(PropertyId::$id, value); } #[inline] pub fn $clearer(&mut self) { self.properties.clear(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 { #[inline] pub fn $pusher(&mut self, item: $item_type) { self.$type_pusher(PropertyId::$id, item); } })* } } macro_rules! slice_properties_debug_method { ($name:ident, [$($getter:ident,)*]) => { fn $name(&self, fmt: &mut fmt::DebugStruct) { $( let value = self.$getter(); if !value.is_empty() { fmt.field(stringify!($getter), &value); } )* } } } 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) })* impl Node { slice_properties_debug_method! { debug_node_id_vec_properties, [$($getter,)*] } } $(#[cfg(test)] mod $getter { use super::{Node, NodeId, Role}; #[test] fn getter_should_return_default_value() { let node = Node::new(Role::Unknown); assert!(node.$getter().is_empty()); } #[test] fn setter_should_update_the_property() { let mut node = Node::new(Role::Unknown); node.$setter([]); assert!(node.$getter().is_empty()); node.$setter([NodeId(0), NodeId(1)]); assert_eq!(node.$getter(), &[NodeId(0), NodeId(1)]); } #[test] fn pusher_should_update_the_property() { let mut node = Node::new(Role::Unknown); node.$pusher(NodeId(0)); assert_eq!(node.$getter(), &[NodeId(0)]); node.$pusher(NodeId(1)); assert_eq!(node.$getter(), &[NodeId(0), NodeId(1)]); } #[test] fn clearer_should_reset_the_property() { let mut node = Node::new(Role::Unknown); node.$setter([NodeId(0)]); node.$clearer(); assert!(node.$getter().is_empty()); } })* } } macro_rules! option_properties_debug_method { ($name:ident, [$($getter:ident,)*]) => { fn $name(&self, fmt: &mut fmt::DebugStruct) { $( if let Some(value) = self.$getter() { fmt.field(stringify!($getter), &value); } )* } } } macro_rules! node_id_property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* ($id, $getter, get_node_id_property, Option, $setter, set_node_id_property, NodeId, $clearer) })* impl Node { option_properties_debug_method! { debug_node_id_properties, [$($getter,)*] } } $(#[cfg(test)] mod $getter { use super::{Node, NodeId, Role}; #[test] fn getter_should_return_default_value() { let node = Node::new(Role::Unknown); assert!(node.$getter().is_none()); } #[test] fn setter_should_update_the_property() { let mut node = Node::new(Role::Unknown); node.$setter(NodeId(1)); assert_eq!(node.$getter(), Some(NodeId(1))); } #[test] fn clearer_should_reset_the_property() { let mut node = Node::new(Role::Unknown); node.$setter(NodeId(1)); node.$clearer(); assert!(node.$getter().is_none()); } })* } } macro_rules! string_property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* ($id, $getter, get_string_property, Option<&str>, $setter, set_string_property, impl Into>, $clearer) })* impl Node { option_properties_debug_method! { debug_string_properties, [$($getter,)*] } } $(#[cfg(test)] mod $getter { use super::{Node, Role}; #[test] fn getter_should_return_default_value() { let node = Node::new(Role::Unknown); assert!(node.$getter().is_none()); } #[test] fn setter_should_update_the_property() { let mut node = Node::new(Role::Unknown); node.$setter("test"); assert_eq!(node.$getter(), Some("test")); } #[test] fn clearer_should_reset_the_property() { let mut node = Node::new(Role::Unknown); node.$setter("test"); node.$clearer(); assert!(node.$getter().is_none()); } })* } } macro_rules! f64_property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* ($id, $getter, get_f64_property, Option, $setter, set_f64_property, f64, $clearer) })* impl Node { option_properties_debug_method! { debug_f64_properties, [$($getter,)*] } } $(#[cfg(test)] mod $getter { use super::{Node, Role}; #[test] fn getter_should_return_default_value() { let node = Node::new(Role::Unknown); assert!(node.$getter().is_none()); } #[test] fn setter_should_update_the_property() { let mut node = Node::new(Role::Unknown); node.$setter(1.0); assert_eq!(node.$getter(), Some(1.0)); } #[test] fn clearer_should_reset_the_property() { let mut node = Node::new(Role::Unknown); node.$setter(1.0); node.$clearer(); assert!(node.$getter().is_none()); } })* } } macro_rules! usize_property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* ($id, $getter, get_usize_property, Option, $setter, set_usize_property, usize, $clearer) })* impl Node { option_properties_debug_method! { debug_usize_properties, [$($getter,)*] } } $(#[cfg(test)] mod $getter { use super::{Node, Role}; #[test] fn getter_should_return_default_value() { let node = Node::new(Role::Unknown); assert!(node.$getter().is_none()); } #[test] fn setter_should_update_the_property() { let mut node = Node::new(Role::Unknown); node.$setter(1); assert_eq!(node.$getter(), Some(1)); } #[test] fn clearer_should_reset_the_property() { let mut node = Node::new(Role::Unknown); node.$setter(1); node.$clearer(); assert!(node.$getter().is_none()); } })* } } macro_rules! color_property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* ($id, $getter, get_color_property, Option, $setter, set_color_property, u32, $clearer) })* impl Node { option_properties_debug_method! { debug_color_properties, [$($getter,)*] } } $(#[cfg(test)] mod $getter { use super::{Node, Role}; #[test] fn getter_should_return_default_value() { let node = Node::new(Role::Unknown); assert!(node.$getter().is_none()); } #[test] fn setter_should_update_the_property() { let mut node = Node::new(Role::Unknown); node.$setter(1); assert_eq!(node.$getter(), Some(1)); } #[test] fn clearer_should_reset_the_property() { let mut node = Node::new(Role::Unknown); node.$setter(1); node.$clearer(); assert!(node.$getter().is_none()); } })* } } macro_rules! text_decoration_property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* ($id, $getter, get_text_decoration_property, Option, $setter, set_text_decoration_property, TextDecoration, $clearer) })* impl Node { option_properties_debug_method! { debug_text_decoration_properties, [$($getter,)*] } } $(#[cfg(test)] mod $getter { use super::{Node, Role, TextDecoration}; #[test] fn getter_should_return_default_value() { let node = Node::new(Role::Unknown); assert!(node.$getter().is_none()); } #[test] fn setter_should_update_the_property() { let mut node = Node::new(Role::Unknown); node.$setter(TextDecoration::Dotted); assert_eq!(node.$getter(), Some(TextDecoration::Dotted)); } #[test] fn clearer_should_reset_the_property() { let mut node = Node::new(Role::Unknown); node.$setter(TextDecoration::Dotted); node.$clearer(); assert!(node.$getter().is_none()); } })* } } macro_rules! length_slice_property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* ($id, $getter, get_length_slice_property, &[u8], $setter, set_length_slice_property, impl Into>, $clearer) })* impl Node { slice_properties_debug_method! { debug_length_slice_properties, [$($getter,)*] } } $(#[cfg(test)] mod $getter { use super::{Node, Role}; #[test] fn getter_should_return_default_value() { let node = Node::new(Role::Unknown); assert!(node.$getter().is_empty()); } #[test] fn setter_should_update_the_property() { let mut node = Node::new(Role::Unknown); node.$setter([]); assert!(node.$getter().is_empty()); node.$setter([1, 2]); assert_eq!(node.$getter(), &[1, 2]); } #[test] fn clearer_should_reset_the_property() { let mut node = Node::new(Role::Unknown); node.$setter([1, 2]); node.$clearer(); assert!(node.$getter().is_empty()); } })* } } macro_rules! coord_slice_property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* ($id, $getter, get_coord_slice_property, Option<&[f32]>, $setter, set_coord_slice_property, impl Into>, $clearer) })* impl Node { option_properties_debug_method! { debug_coord_slice_properties, [$($getter,)*] } } $(#[cfg(test)] mod $getter { use super::{Node, Role}; #[test] fn getter_should_return_default_value() { let node = Node::new(Role::Unknown); assert!(node.$getter().is_none()); } #[test] fn setter_should_update_the_property() { let mut node = Node::new(Role::Unknown); node.$setter([]); let expected: Option<&[f32]> = Some(&[]); assert_eq!(node.$getter(), expected); node.$setter([1.0, 2.0]); let expected: Option<&[f32]> = Some(&[1.0, 2.0]); assert_eq!(node.$getter(), expected); } #[test] fn clearer_should_reset_the_property() { let mut node = Node::new(Role::Unknown); node.$setter([1.0, 2.0]); node.$clearer(); assert!(node.$getter().is_none()); } })* } } macro_rules! bool_property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident)),+) => { $(property_methods! { $(#[$doc])* ($id, $getter, get_bool_property, Option, $setter, set_bool_property, bool, $clearer) })* impl Node { option_properties_debug_method! { debug_bool_properties, [$($getter,)*] } } $(#[cfg(test)] mod $getter { use super::{Node, Role}; #[test] fn getter_should_return_default_value() { let node = Node::new(Role::Unknown); assert!(node.$getter().is_none()); } #[test] fn setter_should_update_the_property() { let mut node = Node::new(Role::Unknown); node.$setter(true); assert_eq!(node.$getter(), Some(true)); } #[test] fn clearer_should_reset_the_property() { let mut node = Node::new(Role::Unknown); node.$setter(true); node.$clearer(); assert!(node.$getter().is_none()); } })* } } macro_rules! unique_enum_property_methods { ($($(#[$doc:meta])* ($id:ident, $getter:ident, $setter:ident, $clearer:ident, $variant:ident)),+) => { impl Node { $($(#[$doc])* #[inline] pub fn $getter(&self) -> Option<$id> { match self.properties.indices.get(&self.properties.values, PropertyId::$id) { PropertyValue::$id(value) => Some(*value), _ => None, } } #[inline] pub fn $setter(&mut self, value: $id) { self.properties.set(PropertyId::$id, PropertyValue::$id(value)); } #[inline] pub fn $clearer(&mut self) { self.properties.clear(PropertyId::$id); })* option_properties_debug_method! { debug_unique_enum_properties, [$($getter,)*] } } $(#[cfg(test)] mod $getter { use super::{Node, Role}; #[test] fn getter_should_return_default_value() { let node = Node::new(Role::Unknown); assert!(node.$getter().is_none()); } #[test] fn setter_should_update_the_property() { let mut node = Node::new(Role::Unknown); let variant = super::$id::$variant; node.$setter(variant); assert_eq!(node.$getter(), Some(variant)); } #[test] fn clearer_should_reset_the_property() { let mut node = Node::new(Role::Unknown); node.$setter(super::$id::$variant); node.$clearer(); assert!(node.$getter().is_none()); } })* } } impl Node { #[inline] pub fn new(role: Role) -> Self { Self { role, ..Default::default() } } } impl Node { #[inline] pub fn role(&self) -> Role { self.role } #[inline] pub fn set_role(&mut self, value: Role) { self.role = value; } #[inline] pub fn supports_action(&self, action: Action) -> bool { (self.actions & action.mask()) != 0 } #[inline] pub fn add_action(&mut self, action: Action) { self.actions |= action.mask(); } #[inline] pub fn remove_action(&mut self, action: Action) { self.actions &= !(action.mask()); } #[inline] pub fn clear_actions(&mut self) { self.actions = 0; } /// Return whether the specified action is in the set supported on this node's /// direct children in the filtered tree. #[inline] pub fn child_supports_action(&self, action: Action) -> bool { (self.child_actions & action.mask()) != 0 } /// Add the specified action to the set supported on this node's direct /// children in the filtered tree. #[inline] pub fn add_child_action(&mut self, action: Action) { self.child_actions |= action.mask(); } /// Remove the specified action from the set supported on this node's direct /// children in the filtered tree. #[inline] pub fn remove_child_action(&mut self, action: Action) { self.child_actions &= !(action.mask()); } /// Clear the set of actions supported on this node's direct children in the /// filtered tree. #[inline] pub fn clear_child_actions(&mut self) { self.child_actions = 0; } } flag_methods! { /// Exclude this node and its descendants from the tree presented to /// assistive technologies, and from hit testing. (Hidden, is_hidden, set_hidden, clear_hidden), (Multiselectable, is_multiselectable, set_multiselectable, clear_multiselectable), (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, is_modal, set_modal, clear_modal), /// 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. (TouchTransparent, is_touch_transparent, set_touch_transparent, clear_touch_transparent), /// Use for a text widget that allows focus/selection but not input. (ReadOnly, is_read_only, set_read_only, clear_read_only), /// Use for a control or group of controls that disallows input. (Disabled, is_disabled, set_disabled, clear_disabled), (Bold, is_bold, set_bold, clear_bold), (Italic, is_italic, set_italic, clear_italic), /// Indicates that this node clips its children, i.e. may have /// `overflow: hidden` or clip children by default. (ClipsChildren, clips_children, set_clips_children, clear_clips_children), /// Indicates whether this node causes a hard line-break /// (e.g. block level elements, or `
`). (IsLineBreakingObject, is_line_breaking_object, set_is_line_breaking_object, clear_is_line_breaking_object), /// Indicates whether this node causes a page break. (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) } 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), (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), /// As with the `aria-owns` property in ARIA, this property should be set /// only if the nodes referenced in the property are not descendants /// of the owning node in the AccessKit tree. In the common case, where the /// owned nodes are direct children or indirect descendants, this property /// is unnecessary. (Owns, owns, set_owns, push_owned, clear_owns), /// 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) } node_id_property_methods! { (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) } string_property_methods! { /// The label of a control that can have a label. If the label is specified /// via the [`Node::labelled_by`] relation, this doesn't need to be set. /// Note that the text content of a node with the [`Role::Label`] role /// should be provided via [`Node::value`], not this property. (Label, label, set_label, clear_label), (Description, description, set_description, clear_description), (Value, value, set_value, clear_value), /// A single character, usually part of this node's name, that can be pressed, /// possibly along with a platform-specific modifier, to perform /// this node's default action. For menu items, the access key is only active /// while the menu is active, in contrast with [`keyboard_shortcut`]; /// a single menu item may in fact have both properties. /// /// [`keyboard_shortcut`]: Node::keyboard_shortcut (AccessKey, access_key, set_access_key, clear_access_key), /// A way for application authors to identify this node for automated /// testing purpose. The value must be unique among this node's siblings. (AuthorId, author_id, set_author_id, clear_author_id), (ClassName, class_name, set_class_name, clear_class_name), /// Only present when different from parent. (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. (InnerHtml, inner_html, set_inner_html, clear_inner_html), /// A keystroke or sequence of keystrokes, complete with any required /// modifiers(s), that will perform this node's default action. /// The value of this property should be in a human-friendly format. (KeyboardShortcut, keyboard_shortcut, set_keyboard_shortcut, clear_keyboard_shortcut), /// Only present when different from parent. (Language, language, set_language, clear_language), /// If a text input has placeholder text, it should be exposed /// through this property rather than [`label`]. /// /// [`label`]: Node::label (Placeholder, placeholder, set_placeholder, clear_placeholder), /// An optional string that may override an assistive technology's /// description of the node's role. Only provide this for custom control types. /// The value of this property should be in a human-friendly, localized format. (RoleDescription, role_description, set_role_description, clear_role_description), /// An optional string that may override an assistive technology's /// description of the node's state, replacing default strings such as /// "checked" or "selected". Note that most platform accessibility APIs /// and assistive technologies do not support this feature. (StateDescription, state_description, set_state_description, clear_state_description), /// If a node's only accessible name comes from a tooltip, it should be /// exposed through this property rather than [`label`]. /// /// [`label`]: Node::label (Tooltip, tooltip, set_tooltip, clear_tooltip), (Url, url, set_url, clear_url), (RowIndexText, row_index_text, set_row_index_text, clear_row_index_text), (ColumnIndexText, column_index_text, set_column_index_text, clear_column_index_text) } f64_property_methods! { (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. (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. (FontWeight, font_weight, set_font_weight, clear_font_weight) } usize_property_methods! { (RowCount, row_count, set_row_count, clear_row_count), (ColumnCount, column_count, set_column_count, clear_column_count), (RowIndex, row_index, set_row_index, clear_row_index), (ColumnIndex, column_index, set_column_index, clear_column_index), (RowSpan, row_span, set_row_span, clear_row_span), (ColumnSpan, column_span, set_column_span, clear_column_span), (Level, level, set_level, clear_level), /// For containers like [`Role::ListBox`], specifies the total number of items. (SizeOfSet, size_of_set, set_size_of_set, clear_size_of_set), /// For items like [`Role::ListBoxOption`], specifies their index in the item list. /// This may not exceed the value of [`size_of_set`] as set on the container. /// /// [`size_of_set`]: Node::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. (ColorValue, color_value, set_color_value, clear_color_value), /// Background color in RGBA. (BackgroundColor, background_color, set_background_color, clear_background_color), /// Foreground color in RGBA. (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 text runs, 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 run 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`]: Node::value (CharacterLengths, character_lengths, set_character_lengths, clear_character_lengths), /// For text runs, 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`]: Node::character_lengths (WordLengths, word_lengths, set_word_lengths, clear_word_lengths) } coord_slice_property_methods! { /// For text runs, 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`]: Node::text_direction /// [`character_lengths`]: Node::character_lengths (CharacterPositions, character_positions, set_character_positions, clear_character_positions), /// For text runs, 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`]: Node::text_direction /// [`character_lengths`]: Node::character_lengths (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! { (Invalid, invalid, set_invalid, clear_invalid, Grammar), (Toggled, toggled, set_toggled, clear_toggled, True), (Live, live, set_live, clear_live, Polite), (TextDirection, text_direction, set_text_direction, clear_text_direction, RightToLeft), (Orientation, orientation, set_orientation, clear_orientation, Vertical), (SortDirection, sort_direction, set_sort_direction, clear_sort_direction, Descending), (AriaCurrent, aria_current, set_aria_current, clear_aria_current, True), (AutoComplete, auto_complete, set_auto_complete, clear_auto_complete, List), (HasPopup, has_popup, set_has_popup, clear_has_popup, Menu), /// The list style type. Only available on list items. (ListStyle, list_style, set_list_style, clear_list_style, Disc), (TextAlign, text_align, set_text_align, clear_text_align, Right), (VerticalOffset, vertical_offset, set_vertical_offset, clear_vertical_offset, Superscript) } 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`]: Node::bounds (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 /// 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`]: Node::transform (Bounds, bounds, get_rect_property, Option, set_bounds, set_rect_property, Rect, clear_bounds), (TextSelection, text_selection, get_text_selection_property, Option<&TextSelection>, set_text_selection, set_text_selection_property, impl Into>, clear_text_selection) } impl Node { option_properties_debug_method! { debug_option_properties, [transform, bounds, text_selection,] } } #[cfg(test)] mod transform { use super::{Affine, Node, Role}; #[test] fn getter_should_return_default_value() { let node = Node::new(Role::Unknown); assert!(node.transform().is_none()); } #[test] fn setter_should_update_the_property() { let mut node = Node::new(Role::Unknown); node.set_transform(Affine::IDENTITY); assert_eq!(node.transform(), Some(&Affine::IDENTITY)); } #[test] fn clearer_should_reset_the_property() { let mut node = Node::new(Role::Unknown); node.set_transform(Affine::IDENTITY); node.clear_transform(); assert!(node.transform().is_none()); } } #[cfg(test)] mod bounds { use super::{Node, Rect, Role}; #[test] fn getter_should_return_default_value() { let node = Node::new(Role::Unknown); assert!(node.bounds().is_none()); } #[test] fn setter_should_update_the_property() { let mut node = Node::new(Role::Unknown); let value = Rect { x0: 0.0, y0: 1.0, x1: 2.0, y1: 3.0, }; node.set_bounds(value); assert_eq!(node.bounds(), Some(value)); } #[test] fn clearer_should_reset_the_property() { let mut node = Node::new(Role::Unknown); node.set_bounds(Rect { x0: 0.0, y0: 1.0, x1: 2.0, y1: 3.0, }); node.clear_bounds(); assert!(node.bounds().is_none()); } } #[cfg(test)] mod text_selection { use super::{Node, NodeId, Role, TextPosition, TextSelection}; #[test] fn getter_should_return_default_value() { let node = Node::new(Role::Unknown); assert!(node.text_selection().is_none()); } #[test] fn setter_should_update_the_property() { let mut node = Node::new(Role::Unknown); let value = TextSelection { anchor: TextPosition { node: NodeId(0), character_index: 0, }, focus: TextPosition { node: NodeId(0), character_index: 2, }, }; node.set_text_selection(value); assert_eq!(node.text_selection(), Some(&value)); } #[test] fn clearer_should_reset_the_property() { let mut node = Node::new(Role::Unknown); node.set_text_selection(TextSelection { anchor: TextPosition { node: NodeId(0), character_index: 0, }, focus: TextPosition { node: NodeId(0), character_index: 2, }, }); node.clear_text_selection(); assert!(node.text_selection().is_none()); } } 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(test)] mod custom_actions { use super::{CustomAction, Node, Role}; #[test] fn getter_should_return_default_value() { let node = Node::new(Role::Unknown); assert!(node.custom_actions().is_empty()); } #[test] fn setter_should_update_the_property() { let mut node = Node::new(Role::Unknown); let value = alloc::vec![ CustomAction { id: 0, description: "first test action".into(), }, CustomAction { id: 1, description: "second test action".into(), }, ]; node.set_custom_actions(value.clone()); assert_eq!(node.custom_actions(), value); } #[test] fn pusher_should_update_the_property() { let mut node = Node::new(Role::Unknown); let first_action = CustomAction { id: 0, description: "first test action".into(), }; let second_action = CustomAction { id: 1, description: "second test action".into(), }; node.push_custom_action(first_action.clone()); assert_eq!(node.custom_actions(), &[first_action.clone()]); node.push_custom_action(second_action.clone()); assert_eq!(node.custom_actions(), &[first_action, second_action]); } #[test] fn clearer_should_reset_the_property() { let mut node = Node::new(Role::Unknown); node.set_custom_actions([CustomAction { id: 0, description: "test action".into(), }]); node.clear_custom_actions(); assert!(node.custom_actions().is_empty()); } } impl fmt::Debug for Node { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut fmt = f.debug_struct("Node"); fmt.field("role", &self.role()); let supported_actions = action_mask_to_action_vec(self.actions); if !supported_actions.is_empty() { fmt.field("actions", &supported_actions); } let child_supported_actions = action_mask_to_action_vec(self.child_actions); if !child_supported_actions.is_empty() { fmt.field("child_actions", &child_supported_actions); } self.debug_flag_properties(&mut fmt); self.debug_node_id_vec_properties(&mut fmt); self.debug_node_id_properties(&mut fmt); self.debug_string_properties(&mut fmt); self.debug_f64_properties(&mut fmt); self.debug_usize_properties(&mut fmt); self.debug_color_properties(&mut fmt); self.debug_text_decoration_properties(&mut fmt); self.debug_length_slice_properties(&mut fmt); self.debug_coord_slice_properties(&mut fmt); self.debug_bool_properties(&mut fmt); self.debug_unique_enum_properties(&mut fmt); self.debug_option_properties(&mut fmt); let custom_actions = self.custom_actions(); if !custom_actions.is_empty() { fmt.field("custom_actions", &custom_actions); } fmt.finish() } } #[cfg(feature = "serde")] macro_rules! serialize_property { ($self:ident, $map:ident, $index:ident, $id:ident, { $($variant:ident),+ }) => { match &$self.values[$index as usize] { PropertyValue::None => (), $(PropertyValue::$variant(value) => { $map.serialize_entry(&$id, &value)?; })* } } } #[cfg(feature = "serde")] macro_rules! deserialize_property { ($props:ident, $map:ident, $key:ident, { $($type:ident { $($id:ident),+ }),+ }) => { match $key { $($(PropertyId::$id => { let value = $map.next_value()?; $props.set(PropertyId::$id, PropertyValue::$type(value)); })*)* PropertyId::Unset => { let _ = $map.next_value::()?; } } } } #[cfg(feature = "serde")] impl Serialize for Properties { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut len = 0; for value in &*self.values { if !matches!(*value, PropertyValue::None) { len += 1; } } let mut map = serializer.serialize_map(Some(len))?; for (id, index) in self.indices.0.iter().copied().enumerate() { if index == PropertyId::Unset as u8 { continue; } let id = PropertyId::n(id as _).unwrap(); serialize_property!(self, map, index, id, { NodeIdVec, NodeId, String, F64, Usize, Color, TextDecoration, LengthSlice, CoordSlice, Bool, Invalid, Toggled, Live, TextDirection, Orientation, SortDirection, AriaCurrent, AutoComplete, HasPopup, ListStyle, TextAlign, VerticalOffset, Affine, Rect, TextSelection, CustomActionVec }); } map.end() } } #[cfg(feature = "serde")] struct PropertiesVisitor; #[cfg(feature = "serde")] impl<'de> Visitor<'de> for PropertiesVisitor { type Value = Properties; #[inline] fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("property map") } fn visit_map(self, mut map: V) -> Result where V: MapAccess<'de>, { let mut props = Properties::default(); while let Some(id) = map.next_key()? { deserialize_property!(props, map, id, { NodeIdVec { Children, Controls, Details, DescribedBy, FlowTo, LabelledBy, Owns, RadioGroup }, NodeId { ActiveDescendant, ErrorMessage, InPageLinkTarget, MemberOf, NextOnLine, PreviousOnLine, PopupFor }, String { Label, Description, Value, AccessKey, AuthorId, ClassName, FontFamily, HtmlTag, InnerHtml, KeyboardShortcut, Language, Placeholder, RoleDescription, StateDescription, Tooltip, Url, RowIndexText, ColumnIndexText }, F64 { ScrollX, ScrollXMin, ScrollXMax, ScrollY, ScrollYMin, ScrollYMax, NumericValue, MinNumericValue, MaxNumericValue, NumericValueStep, NumericValueJump, FontSize, FontWeight }, Usize { RowCount, ColumnCount, RowIndex, ColumnIndex, RowSpan, ColumnSpan, Level, SizeOfSet, PositionInSet }, Color { ColorValue, BackgroundColor, ForegroundColor }, TextDecoration { Overline, Strikethrough, Underline }, LengthSlice { CharacterLengths, WordLengths }, CoordSlice { CharacterPositions, CharacterWidths }, Bool { Expanded, Selected }, Invalid { Invalid }, Toggled { Toggled }, Live { Live }, TextDirection { TextDirection }, Orientation { Orientation }, SortDirection { SortDirection }, AriaCurrent { AriaCurrent }, AutoComplete { AutoComplete }, HasPopup { HasPopup }, ListStyle { ListStyle }, TextAlign { TextAlign }, VerticalOffset { VerticalOffset }, Affine { Transform }, Rect { Bounds }, TextSelection { TextSelection }, CustomActionVec { CustomActions } }); } Ok(props) } } #[cfg(feature = "serde")] impl<'de> Deserialize<'de> for Properties { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_map(PropertiesVisitor) } } #[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_properties_to_schema { ($gen:ident, $properties:ident, { $($type:ty { $($id:ident),+ }),+ }) => { $($(add_schema_property!($gen, $properties, PropertyId::$id, $type);)*)* } } #[cfg(feature = "schemars")] impl JsonSchema for Properties { #[inline] fn schema_name() -> String { "Properties".into() } fn json_schema(gen: &mut SchemaGenerator) -> Schema { let mut properties = SchemaMap::::new(); add_properties_to_schema!(gen, properties, { Vec { Children, Controls, Details, DescribedBy, FlowTo, LabelledBy, Owns, RadioGroup }, NodeId { ActiveDescendant, ErrorMessage, InPageLinkTarget, MemberOf, NextOnLine, PreviousOnLine, PopupFor }, Box { Label, Description, Value, AccessKey, AuthorId, ClassName, FontFamily, HtmlTag, InnerHtml, KeyboardShortcut, Language, Placeholder, RoleDescription, StateDescription, Tooltip, Url, RowIndexText, ColumnIndexText }, f64 { ScrollX, ScrollXMin, ScrollXMax, ScrollY, ScrollYMin, ScrollYMax, NumericValue, MinNumericValue, MaxNumericValue, NumericValueStep, NumericValueJump, FontSize, FontWeight }, usize { RowCount, ColumnCount, RowIndex, ColumnIndex, RowSpan, ColumnSpan, Level, SizeOfSet, PositionInSet }, u32 { ColorValue, BackgroundColor, ForegroundColor }, TextDecoration { Overline, Strikethrough, Underline }, Box<[u8]> { CharacterLengths, WordLengths }, Box<[f32]> { CharacterPositions, CharacterWidths }, bool { Expanded, Selected }, Invalid { Invalid }, Toggled { Toggled }, Live { Live }, TextDirection { TextDirection }, Orientation { Orientation }, SortDirection { SortDirection }, AriaCurrent { AriaCurrent }, AutoComplete { AutoComplete }, 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)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct Tree { /// The identifier of the tree's root node. pub root: NodeId, /// The name of the UI toolkit in use. pub toolkit_name: Option, /// The version of the UI toolkit. pub toolkit_version: Option, } impl Tree { #[inline] pub fn new(root: NodeId) -> Tree { Tree { root, toolkit_name: None, toolkit_version: None, } } } /// A serializable representation of an atomic change to a [`Tree`]. /// /// The sender and receiver must be in sync; the update is only meant /// to bring the tree from a specific previous state into its next state. /// Trying to apply it to the wrong tree should immediately panic. /// /// Note that for performance, an update should only include nodes that are /// new or changed. AccessKit platform adapters will avoid raising extraneous /// events for nodes that have not changed since the previous update, /// but there is still a cost in processing these nodes and replacing /// the previous instances. #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct TreeUpdate { /// Zero or more new or updated nodes. Order doesn't matter. /// /// Each node in this list will overwrite any existing node with the same ID. /// This means that when updating a node, fields that are unchanged /// from the previous version must still be set to the same values /// as before. /// /// It is an error for any node in this list to not be either the root /// or a child of another node. For nodes other than the root, the parent /// must be either an unchanged node already in the tree, or another node /// in this list. /// /// To add a child to the tree, the list must include both the child /// and an updated version of the parent with the child's ID added to /// [`Node::children`]. /// /// To remove a child and all of its descendants, this list must include /// 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, 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 /// information again is also allowed. This is required when initializing /// a tree. pub tree: Option, /// The node within this tree that has keyboard focus when the native /// host (e.g. window) has focus. If no specific node within the tree /// has keyboard focus, this must be set to the root. The latest focus state /// must be provided with every tree update, even if the focus state /// didn't change in a given update. pub focus: NodeId, } /// The amount by which to scroll in the direction specified by one of the /// `Scroll` actions. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[cfg_attr( feature = "pyo3", pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE", eq) )] #[repr(u8)] pub enum ScrollUnit { /// A single item of a list, line of text (for vertical scrolling), /// character (for horizontal scrolling), or an approximation of /// one of these. Item, /// The amount of content that fits in the viewport. Page, } /// A suggestion about where the node being scrolled into view should be /// positioned relative to the edges of the scrollable container. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[cfg_attr( feature = "pyo3", pyclass(module = "accesskit", rename_all = "SCREAMING_SNAKE_CASE", eq) )] #[repr(u8)] pub enum ScrollHint { TopLeft, BottomRight, TopEdge, BottomEdge, LeftEdge, RightEdge, } #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] #[repr(C)] pub enum ActionData { CustomAction(i32), Value(Box), NumericValue(f64), ScrollUnit(ScrollUnit), /// Optional suggestion for [`ActionData::ScrollIntoView`], specifying /// the preferred position of the target node relative to the scrollable /// container's viewport. ScrollHint(ScrollHint), /// Target for [`Action::ScrollToPoint`], in platform-native coordinates /// relative to the origin of the tree's container (e.g. window). ScrollToPoint(Point), /// Target for [`Action::SetScrollOffset`], in the coordinate space /// of the action's target node. SetScrollOffset(Point), SetTextSelection(TextSelection), } #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct ActionRequest { pub action: Action, pub target: NodeId, pub data: Option, } /// Handles activation of the application's accessibility implementation. pub trait ActivationHandler { /// Requests a [`TreeUpdate`] with a full tree. If the application /// can generate the tree synchronously within this method call, /// it should do so and return the [`TreeUpdate`]. Otherwise, /// it must send the update to the platform adapter asynchronously, /// no later than the next display refresh, even if a frame would not /// normally be rendered due to user input or other activity. /// The application should not return or send a placeholder [`TreeUpdate`]; /// the platform adapter will provide one if necessary until the real /// tree is sent. /// /// The primary purpose of this method is to allow the application /// to lazily initialize its accessibility implementation. However, /// this method may be called consecutively without any call to /// [`DeactivationHandler::deactivate_accessibility`]; this typically happens /// if the platform adapter merely forwards tree updates to assistive /// technologies without maintaining any state. A call to this method /// must always generate a [`TreeUpdate`] with a full tree, even if /// the application normally sends incremental updates. /// /// The thread on which this method is called is platform-dependent. /// Refer to the platform adapter documentation for more details. fn request_initial_tree(&mut self) -> Option; } /// Handles requests from assistive technologies or other clients. pub trait ActionHandler { /// Perform the requested action. If the requested action is not supported, /// this method must do nothing. /// /// The thread on which this method is called is platform-dependent. /// Refer to the platform adapter documentation for more details. /// /// This method may queue the request and handle it asynchronously. /// This behavior is preferred over blocking, e.g. when dispatching /// the request to another thread. fn do_action(&mut self, request: ActionRequest); } /// Handles deactivation of the application's accessibility implementation. pub trait DeactivationHandler { /// Deactivate the application's accessibility implementation and drop any /// associated data that can be reconstructed later. After this method /// is called, if an accessibility tree is needed again, the platform /// adapter will call [`ActivationHandler::request_initial_tree`] again. /// /// The thread on which this method is called is platform-dependent. /// Refer to the platform adapter documentation for more details. fn deactivate_accessibility(&mut self); } #[cfg(test)] mod tests { use super::*; use alloc::format; #[test] fn u64_should_be_convertible_to_node_id() { assert_eq!(NodeId::from(0u64), NodeId(0)); assert_eq!(NodeId::from(1u64), NodeId(1)); } #[test] fn node_id_should_be_convertible_to_u64() { assert_eq!(u64::from(NodeId(0)), 0u64); assert_eq!(u64::from(NodeId(1)), 1u64); } #[test] fn node_id_should_have_debug_repr() { assert_eq!(&format!("{:?}", NodeId(0)), "#0"); assert_eq!(&format!("{:?}", NodeId(1)), "#1"); } #[test] fn action_n_should_return_the_corresponding_variant() { assert_eq!(Action::n(0), Some(Action::Click)); assert_eq!(Action::n(1), Some(Action::Focus)); assert_eq!(Action::n(2), Some(Action::Blur)); assert_eq!(Action::n(3), Some(Action::Collapse)); assert_eq!(Action::n(4), Some(Action::Expand)); assert_eq!(Action::n(5), Some(Action::CustomAction)); assert_eq!(Action::n(6), Some(Action::Decrement)); assert_eq!(Action::n(7), Some(Action::Increment)); assert_eq!(Action::n(8), Some(Action::HideTooltip)); assert_eq!(Action::n(9), Some(Action::ShowTooltip)); assert_eq!(Action::n(10), Some(Action::ReplaceSelectedText)); assert_eq!(Action::n(11), Some(Action::ScrollDown)); assert_eq!(Action::n(12), Some(Action::ScrollLeft)); assert_eq!(Action::n(13), Some(Action::ScrollRight)); assert_eq!(Action::n(14), Some(Action::ScrollUp)); assert_eq!(Action::n(15), Some(Action::ScrollIntoView)); assert_eq!(Action::n(16), Some(Action::ScrollToPoint)); assert_eq!(Action::n(17), Some(Action::SetScrollOffset)); assert_eq!(Action::n(18), Some(Action::SetTextSelection)); assert_eq!( Action::n(19), Some(Action::SetSequentialFocusNavigationStartingPoint) ); assert_eq!(Action::n(20), Some(Action::SetValue)); assert_eq!(Action::n(21), Some(Action::ShowContextMenu)); assert_eq!(Action::n(22), None); } #[test] fn empty_action_mask_should_be_converted_to_empty_vec() { assert_eq!( Vec::::new(), action_mask_to_action_vec(Node::new(Role::Unknown).actions) ); } #[test] fn action_mask_should_be_convertible_to_vec() { let mut node = Node::new(Role::Unknown); node.add_action(Action::Click); assert_eq!( &[Action::Click], action_mask_to_action_vec(node.actions).as_slice() ); let mut node = Node::new(Role::Unknown); node.add_action(Action::ShowContextMenu); assert_eq!( &[Action::ShowContextMenu], action_mask_to_action_vec(node.actions).as_slice() ); let mut node = Node::new(Role::Unknown); node.add_action(Action::Click); node.add_action(Action::ShowContextMenu); assert_eq!( &[Action::Click, Action::ShowContextMenu], action_mask_to_action_vec(node.actions).as_slice() ); let mut node = Node::new(Role::Unknown); node.add_action(Action::Focus); node.add_action(Action::Blur); node.add_action(Action::Collapse); assert_eq!( &[Action::Focus, Action::Blur, Action::Collapse], action_mask_to_action_vec(node.actions).as_slice() ); } #[test] fn new_node_should_have_user_provided_role() { let node = Node::new(Role::Button); assert_eq!(node.role(), Role::Button); } #[test] fn node_role_setter_should_update_the_role() { let mut node = Node::new(Role::Button); node.set_role(Role::CheckBox); assert_eq!(node.role(), Role::CheckBox); } macro_rules! assert_absent_action { ($node:ident, $action:ident) => { assert!(!$node.supports_action(Action::$action)); assert!(!$node.child_supports_action(Action::$action)); }; } #[test] fn new_node_should_not_support_anyaction() { let node = Node::new(Role::Unknown); assert_absent_action!(node, Click); assert_absent_action!(node, Focus); assert_absent_action!(node, Blur); assert_absent_action!(node, Collapse); assert_absent_action!(node, Expand); assert_absent_action!(node, CustomAction); assert_absent_action!(node, Decrement); assert_absent_action!(node, Increment); assert_absent_action!(node, HideTooltip); assert_absent_action!(node, ShowTooltip); assert_absent_action!(node, ReplaceSelectedText); assert_absent_action!(node, ScrollDown); assert_absent_action!(node, ScrollLeft); assert_absent_action!(node, ScrollRight); assert_absent_action!(node, ScrollUp); assert_absent_action!(node, ScrollIntoView); assert_absent_action!(node, ScrollToPoint); assert_absent_action!(node, SetScrollOffset); assert_absent_action!(node, SetTextSelection); assert_absent_action!(node, SetSequentialFocusNavigationStartingPoint); assert_absent_action!(node, SetValue); assert_absent_action!(node, ShowContextMenu); } #[test] fn node_add_action_should_add_the_action() { let mut node = Node::new(Role::Unknown); node.add_action(Action::Focus); assert!(node.supports_action(Action::Focus)); node.add_action(Action::Blur); assert!(node.supports_action(Action::Blur)); } #[test] fn node_add_child_action_should_add_the_action() { let mut node = Node::new(Role::Unknown); node.add_child_action(Action::Focus); assert!(node.child_supports_action(Action::Focus)); node.add_child_action(Action::Blur); assert!(node.child_supports_action(Action::Blur)); } #[test] fn node_add_action_should_do_nothing_if_the_action_is_already_supported() { let mut node = Node::new(Role::Unknown); node.add_action(Action::Focus); node.add_action(Action::Focus); assert!(node.supports_action(Action::Focus)); } #[test] fn node_add_child_action_should_do_nothing_if_the_action_is_already_supported() { let mut node = Node::new(Role::Unknown); node.add_child_action(Action::Focus); node.add_child_action(Action::Focus); assert!(node.child_supports_action(Action::Focus)); } #[test] fn node_remove_action_should_remove_the_action() { let mut node = Node::new(Role::Unknown); node.add_action(Action::Blur); node.remove_action(Action::Blur); assert!(!node.supports_action(Action::Blur)); } #[test] fn node_remove_child_action_should_remove_the_action() { let mut node = Node::new(Role::Unknown); node.add_child_action(Action::Blur); node.remove_child_action(Action::Blur); assert!(!node.child_supports_action(Action::Blur)); } #[test] fn node_clear_actions_should_remove_all_actions() { let mut node = Node::new(Role::Unknown); node.add_action(Action::Focus); node.add_action(Action::Blur); node.clear_actions(); assert!(!node.supports_action(Action::Focus)); assert!(!node.supports_action(Action::Blur)); } #[test] fn node_clear_child_actions_should_remove_all_actions() { let mut node = Node::new(Role::Unknown); node.add_child_action(Action::Focus); node.add_child_action(Action::Blur); node.clear_child_actions(); assert!(!node.child_supports_action(Action::Focus)); assert!(!node.child_supports_action(Action::Blur)); } #[test] fn node_should_have_debug_repr() { let mut node = Node::new(Role::Unknown); node.add_action(Action::Click); node.add_action(Action::Focus); node.add_child_action(Action::ScrollIntoView); node.set_hidden(); node.set_multiselectable(); node.set_children([NodeId(0), NodeId(1)]); node.set_active_descendant(NodeId(2)); node.push_custom_action(CustomAction { id: 0, description: "test action".into(), }); assert_eq!( &format!("{node:?}"), r#"Node { role: Unknown, actions: [Click, Focus], child_actions: [ScrollIntoView], is_hidden: true, is_multiselectable: true, children: [#0, #1], active_descendant: #2, custom_actions: [CustomAction { id: 0, description: "test action" }] }"# ); } #[test] fn new_tree_should_have_root_id() { let tree = Tree::new(NodeId(1)); assert_eq!(tree.root, NodeId(1)); assert_eq!(tree.toolkit_name, None); assert_eq!(tree.toolkit_version, None); } }