1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-26 14:49:06 -04:00

Update accesskit to 0.24.0 (and related deps) (#7850)

this patch updates our deps to [accesskit
0.24.0](https://docs.rs/accesskit/0.24.0/accesskit/) (was
[0.21.1](https://docs.rs/accesskit/0.21.1/accesskit/)),
[accesskit_consumer
0.35.0](https://docs.rs/accesskit_consumer/0.35.0/accesskit_consumer/)
(was
[0.30.1](https://docs.rs/accesskit_consumer/0.30.1/accesskit_consumer/)),
and [accesskit_winit
0.32.0](https://docs.rs/accesskit_winit/0.32.0/accesskit_winit/) (was
[0.29.1](https://docs.rs/accesskit_winit/0.29.1/accesskit_winit/)),
allowing egui to be used in apps that use accessibility subtrees
(AccessKit/accesskit#655).

for now, we handle the subtree-related breaking changes by assuming that
egui will use [the root
tree](https://docs.rs/accesskit/0.24.0/accesskit/struct.TreeId.html#associatedconstant.ROOT),
which is good enough for [servoshell](https://github.com/servo/servo)
and does not require any API changes.


* [x] I have followed the instructions in the PR template

<img width="1185" height="954" alt="image"
src="https://github.com/user-attachments/assets/6acfb85a-096d-4a7b-963b-d8549bfc749f"
/>

---------

Co-authored-by: Luke Warlow <lwarlow@igalia.com>
Co-authored-by: lucasmerlin <hi@lucasmerlin.me>
Co-authored-by: Arnold Loubriat <datatriny@gmail.com>
This commit is contained in:
shuppy
2026-03-16 17:34:54 +08:00
committed by GitHub
parent 14afefa252
commit 5031c47cb2
12 changed files with 383 additions and 173 deletions

View File

@@ -20,47 +20,48 @@ checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046"
[[package]]
name = "accesskit"
version = "0.21.1"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf203f9d3bd8f29f98833d1fbef628df18f759248a547e7e01cfbf63cda36a99"
checksum = "5351dcebb14b579ccab05f288596b2ae097005be7ee50a7c3d4ca9d0d5a66f6a"
dependencies = [
"enumn",
"serde",
"uuid",
]
[[package]]
name = "accesskit_atspi_common"
version = "0.14.1"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29f73a9b855b6f4af4962a94553ef0c092b80cf5e17038724d5e30945d036f69"
checksum = "842fd8203e6dfcf531d24f5bac792088edfba7d6b35844fead191603fb32a260"
dependencies = [
"accesskit",
"accesskit_consumer",
"atspi-common",
"phf 0.13.1",
"serde",
"thiserror 1.0.66",
"zvariant",
]
[[package]]
name = "accesskit_consumer"
version = "0.30.1"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdd06f5fea9819250fffd4debf926709f3593ac22f8c1541a2573e5ee0ca01cd"
checksum = "53cf47daed85312e763fbf85ceca136e0d7abc68e0a7e12abe11f48172bc3b10"
dependencies = [
"accesskit",
"hashbrown 0.15.2",
"hashbrown 0.16.1",
]
[[package]]
name = "accesskit_macos"
version = "0.22.1"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fbaf15815f39084e0cb24950c232f0e3634702c2dfbf182ae3b4919a4a1d45"
checksum = "534bc3fdc89a64a1db3c46b33c198fde2b7c3c7d094e5809c8c8bf2970c18243"
dependencies = [
"accesskit",
"accesskit_consumer",
"hashbrown 0.15.2",
"hashbrown 0.16.1",
"objc2 0.5.2",
"objc2-app-kit 0.2.2",
"objc2-foundation 0.2.2",
@@ -68,9 +69,9 @@ dependencies = [
[[package]]
name = "accesskit_unix"
version = "0.17.1"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64926a930368d52d95422b822ede15014c04536cabaa2394f99567a1f4788dc6"
checksum = "90e549dd7c6562b6a2ea807b44726e6241707db054a817dc4c7e2b8d3b39bfac"
dependencies = [
"accesskit",
"accesskit_atspi_common",
@@ -86,23 +87,23 @@ dependencies = [
[[package]]
name = "accesskit_windows"
version = "0.29.1"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "792991159fa9ba57459de59e12e918bb90c5346fea7d40ac1a11f8632b41e63a"
checksum = "eff7009f1a532e917d66970a1e80c965140c6cfbbabbdde3d64e5431e6c78e21"
dependencies = [
"accesskit",
"accesskit_consumer",
"hashbrown 0.15.2",
"hashbrown 0.16.1",
"static_assertions",
"windows 0.61.1",
"windows-core 0.61.0",
"windows 0.62.2",
"windows-core 0.62.2",
]
[[package]]
name = "accesskit_winit"
version = "0.29.1"
version = "0.32.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd9db0ea66997e3f4eae4a5f2c6b6486cf206642639ee629dbbb860ace1dec87"
checksum = "1fe9a94394896352cc4660ca2288bd4ef883d83238853c038b44070c8f134313"
dependencies = [
"accesskit",
"accesskit_macos",
@@ -452,20 +453,19 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "atspi"
version = "0.25.0"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c83247582e7508838caf5f316c00791eee0e15c0bf743e6880585b867e16815c"
checksum = "c77886257be21c9cd89a4ae7e64860c6f0eefca799bb79127913052bd0eefb3d"
dependencies = [
"atspi-common",
"atspi-connection",
"atspi-proxies",
]
[[package]]
name = "atspi-common"
version = "0.9.0"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33dfc05e7cdf90988a197803bf24f5788f94f7c94a69efa95683e8ffe76cfdfb"
checksum = "20c5617155740c98003016429ad13fe43ce7a77b007479350a9f8bf95a29f63d"
dependencies = [
"enumflags2",
"serde",
@@ -477,23 +477,11 @@ dependencies = [
"zvariant",
]
[[package]]
name = "atspi-connection"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4193d51303d8332304056ae0004714256b46b6635a5c556109b319c0d3784938"
dependencies = [
"atspi-common",
"atspi-proxies",
"futures-lite",
"zbus",
]
[[package]]
name = "atspi-proxies"
version = "0.9.0"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2eebcb9e7e76f26d0bcfd6f0295e1cd1e6f33bedbc5698a971db8dc43d7751c"
checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc"
dependencies = [
"atspi-common",
"serde",
@@ -2484,12 +2472,10 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]]
name = "kittest"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01fd6dd2cce251a360101038acb9334e3a50cd38cd02fefddbf28aa975f043c8"
source = "git+https://github.com/rerun-io/kittest?branch=main#ce7a2f3b12c36021889b50bdff671cec8016b0fb"
dependencies = [
"accesskit",
"accesskit_consumer",
"parking_lot",
]
[[package]]
@@ -2689,8 +2675,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1706dc14a2e140dec0a7a07109d9a3d5890b81e85bd6c60b906b249a77adf0ca"
dependencies = [
"mime",
"phf",
"phf_shared",
"phf 0.11.3",
"phf_shared 0.11.3",
"unicase",
]
@@ -3269,8 +3255,19 @@ version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
dependencies = [
"phf_macros",
"phf_shared",
"phf_macros 0.11.3",
"phf_shared 0.11.3",
]
[[package]]
name = "phf"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
dependencies = [
"phf_macros 0.13.1",
"phf_shared 0.13.1",
"serde",
]
[[package]]
@@ -3279,8 +3276,8 @@ version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
dependencies = [
"phf_generator",
"phf_shared",
"phf_generator 0.11.3",
"phf_shared 0.11.3",
]
[[package]]
@@ -3289,24 +3286,47 @@ version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [
"phf_shared",
"phf_shared 0.11.3",
"rand 0.8.5",
]
[[package]]
name = "phf_generator"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
dependencies = [
"fastrand",
"phf_shared 0.13.1",
]
[[package]]
name = "phf_macros"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
dependencies = [
"phf_generator",
"phf_shared",
"phf_generator 0.11.3",
"phf_shared 0.11.3",
"proc-macro2",
"quote",
"syn",
"unicase",
]
[[package]]
name = "phf_macros"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef"
dependencies = [
"phf_generator 0.13.1",
"phf_shared 0.13.1",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "phf_shared"
version = "0.11.3"
@@ -3317,6 +3337,15 @@ dependencies = [
"unicase",
]
[[package]]
name = "phf_shared"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
dependencies = [
"siphasher",
]
[[package]]
name = "pico-args"
version = "0.5.0"
@@ -4692,7 +4721,7 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d189085656ca1203291e965444e7f6a2723fbdd1dd9f34f8482e79bafd8338a0"
dependencies = [
"phf",
"phf 0.11.3",
"unicode_names2_generator",
]
@@ -5289,24 +5318,23 @@ dependencies = [
[[package]]
name = "windows"
version = "0.61.1"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419"
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
dependencies = [
"windows-collections",
"windows-core 0.61.0",
"windows-core 0.62.2",
"windows-future",
"windows-link 0.1.3",
"windows-numerics",
]
[[package]]
name = "windows-collections"
version = "0.2.0"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
dependencies = [
"windows-core 0.61.0",
"windows-core 0.62.2",
]
[[package]]
@@ -5328,21 +5356,35 @@ version = "0.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
dependencies = [
"windows-implement 0.60.0",
"windows-interface 0.59.1",
"windows-implement 0.60.2",
"windows-interface 0.59.3",
"windows-link 0.1.3",
"windows-result 0.3.2",
"windows-strings 0.4.0",
]
[[package]]
name = "windows-future"
version = "0.2.0"
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-core 0.61.0",
"windows-link 0.1.3",
"windows-implement 0.60.2",
"windows-interface 0.59.3",
"windows-link 0.2.1",
"windows-result 0.4.1",
"windows-strings 0.5.1",
]
[[package]]
name = "windows-future"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
dependencies = [
"windows-core 0.62.2",
"windows-link 0.2.1",
"windows-threading",
]
[[package]]
@@ -5358,9 +5400,9 @@ dependencies = [
[[package]]
name = "windows-implement"
version = "0.60.0"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
@@ -5380,9 +5422,9 @@ dependencies = [
[[package]]
name = "windows-interface"
version = "0.59.1"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
@@ -5403,12 +5445,12 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-numerics"
version = "0.2.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
dependencies = [
"windows-core 0.61.0",
"windows-link 0.1.3",
"windows-core 0.62.2",
"windows-link 0.2.1",
]
[[package]]
@@ -5429,6 +5471,15 @@ dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "windows-strings"
version = "0.1.0"
@@ -5448,6 +5499,15 @@ dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "windows-sys"
version = "0.45.0"
@@ -5541,6 +5601,15 @@ dependencies = [
"windows_x86_64_msvc 0.53.0",
]
[[package]]
name = "windows-threading"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
@@ -5897,9 +5966,9 @@ dependencies = [
[[package]]
name = "zbus-lockstep"
version = "0.5.0"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a22426b1bc2aca91de97772506f0655fa373448e6010d79d5d5880915c388409"
checksum = "6998de05217a084b7578728a9443d04ea4cd80f2a0839b8d78770b76ccd45863"
dependencies = [
"zbus_xml",
"zvariant",
@@ -5907,9 +5976,9 @@ dependencies = [
[[package]]
name = "zbus-lockstep-macros"
version = "0.5.0"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "100ffec29ed51859052f4563061abe35557acb56ba574510571f8398efc70a29"
checksum = "10da05367f3a7b7553c8cdf8fa91aee6b64afebe32b51c95177957efc47ca3a0"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -68,9 +68,9 @@ egui_glow = { version = "0.33.3", path = "crates/egui_glow", default-features =
egui_kittest = { version = "0.33.3", path = "crates/egui_kittest", default-features = false }
eframe = { version = "0.33.3", path = "crates/eframe", default-features = false }
accesskit = "0.21.1"
accesskit_consumer = "0.30.1"
accesskit_winit = "0.29.1"
accesskit = "0.24.0"
accesskit_consumer = "0.35.0"
accesskit_winit = "0.32.0"
ahash = { version = "0.8.12", default-features = false, features = [
"no-rng", # we don't need DOS-protection, so we let users opt-in to it instead
"std",
@@ -148,6 +148,8 @@ wgpu = { version = "27.0.1", default-features = false, features = ["std"] }
windows-sys = "0.61.2"
winit = { version = "0.30.12", default-features = false }
[patch.crates-io]
kittest = { git = "https://github.com/rerun-io/kittest", branch = "main" }
[workspace.lints.rust]
unsafe_code = "deny"

View File

@@ -102,7 +102,7 @@ pub struct State {
has_sent_ime_enabled: bool,
#[cfg(feature = "accesskit")]
accesskit: Option<accesskit_winit::Adapter>,
pub accesskit: Option<accesskit_winit::Adapter>,
allow_ime: bool,
ime_rect_px: Option<egui::Rect>,

View File

@@ -2616,6 +2616,7 @@ impl ContextImpl {
platform_output.accesskit_update = Some(accesskit::TreeUpdate {
nodes,
tree: Some(accesskit::Tree::new(root_id)),
tree_id: accesskit::TreeId::ROOT,
focus: focus_id,
});
}

View File

@@ -79,7 +79,7 @@ impl Id {
self.0.get()
}
pub(crate) fn accesskit_id(&self) -> accesskit::NodeId {
pub fn accesskit_id(&self) -> accesskit::NodeId {
self.value().into()
}

View File

@@ -871,7 +871,8 @@ impl InputState {
let accesskit_id = id.accesskit_id();
self.events.iter().filter_map(move |event| {
if let Event::AccessKitActionRequest(request) = event
&& request.target == accesskit_id
&& request.target_node == accesskit_id
&& request.target_tree == accesskit::TreeId::ROOT
&& request.action == action
{
return Some(request);
@@ -888,7 +889,8 @@ impl InputState {
let accesskit_id = id.accesskit_id();
self.events.retain(|event| {
if let Event::AccessKitActionRequest(request) = event
&& request.target == accesskit_id
&& request.target_node == accesskit_id
&& request.target_tree == accesskit::TreeId::ROOT
{
return !consume(request);
}

View File

@@ -564,11 +564,13 @@ impl Focus {
if let crate::Event::AccessKitActionRequest(accesskit::ActionRequest {
action: accesskit::Action::Focus,
target,
target_node,
target_tree,
data: None,
}) = event
&& *target_tree == accesskit::TreeId::ROOT
{
self.id_requested_by_accesskit = Some(*target);
self.id_requested_by_accesskit = Some(*target_node);
}
}
}

View File

@@ -4,6 +4,26 @@ use crate::{Context, Galley, Id};
use super::{CCursorRange, text_cursor_state::is_word_char};
/// AccessKit's `word_starts` uses `u8` indices, so text runs cannot exceed this length.
pub(crate) const MAX_CHARS_PER_TEXT_RUN: usize = 255;
/// Convert a (row, column) layout cursor position to a text run node ID and character index,
/// accounting for rows that are split into multiple text runs.
fn text_run_position(parent_id: Id, row: usize, column: usize) -> accesskit::TextPosition {
// When column lands exactly on a chunk boundary (e.g., 255), it refers to
// the end of the previous chunk, not the start of a new one.
let chunk_index = if column > 0 && column.is_multiple_of(MAX_CHARS_PER_TEXT_RUN) {
column / MAX_CHARS_PER_TEXT_RUN - 1
} else {
column / MAX_CHARS_PER_TEXT_RUN
};
let character_index = column - chunk_index * MAX_CHARS_PER_TEXT_RUN;
accesskit::TextPosition {
node: parent_id.with(row).with(chunk_index).accesskit_id(),
character_index,
}
}
/// Update accesskit with the current text state.
pub fn update_accesskit_for_text_widget(
ctx: &Context,
@@ -20,14 +40,8 @@ pub fn update_accesskit_for_text_widget(
let anchor = galley.layout_from_cursor(cursor_range.secondary);
let focus = galley.layout_from_cursor(cursor_range.primary);
builder.set_text_selection(accesskit::TextSelection {
anchor: accesskit::TextPosition {
node: parent_id.with(anchor.row).accesskit_id(),
character_index: anchor.column,
},
focus: accesskit::TextPosition {
node: parent_id.with(focus.row).accesskit_id(),
character_index: focus.column,
},
anchor: text_run_position(parent_id, anchor.row, anchor.column),
focus: text_run_position(parent_id, focus.row, focus.column),
});
}
@@ -40,61 +54,144 @@ pub fn update_accesskit_for_text_widget(
return;
};
let mut prev_row_ended_with_newline = true;
for (row_index, row) in galley.rows.iter().enumerate() {
let row_id = parent_id.with(row_index);
let glyph_count = row.glyphs.len();
let mut value = String::with_capacity(glyph_count);
let mut character_lengths = Vec::<u8>::with_capacity(glyph_count);
let mut character_positions = Vec::<f32>::with_capacity(glyph_count);
let mut character_widths = Vec::<f32>::with_capacity(glyph_count);
let mut word_starts = Vec::<usize>::new();
// For soft-wrapped continuation rows, treat the start as a word
// boundary so the first word character gets a `word_starts` entry.
// Paragraph-starting runs (first row or after a newline) get an
// implicit word start from AccessKit, so they don't need this.
let mut was_at_word_end = !prev_row_ended_with_newline;
ctx.register_accesskit_parent(row_id, parent_id);
for glyph in &row.glyphs {
let is_word_char = is_word_char(glyph.chr);
if is_word_char && was_at_word_end {
word_starts.push(character_lengths.len());
}
was_at_word_end = !is_word_char;
let old_len = value.len();
value.push(glyph.chr);
character_lengths.push((value.len() - old_len) as _);
character_positions.push(glyph.pos.x - row.pos.x);
character_widths.push(glyph.advance_width);
}
ctx.accesskit_node_builder(row_id, |builder| {
builder.set_role(accesskit::Role::TextRun);
let rect = global_from_galley * row.rect_without_leading_space();
builder.set_bounds(accesskit::Rect {
x0: rect.min.x.into(),
y0: rect.min.y.into(),
x1: rect.max.x.into(),
y1: rect.max.y.into(),
});
builder.set_text_direction(accesskit::TextDirection::LeftToRight);
// TODO(mwcampbell): Set more node fields for the row
// once AccessKit adapters expose text formatting info.
if row.ends_with_newline {
value.push('\n');
character_lengths.push(1);
character_positions.push(row.size.x);
character_widths.push(0.0);
}
let glyph_count = row.glyphs.len();
let mut value = String::new();
value.reserve(glyph_count);
let mut character_lengths = Vec::<u8>::with_capacity(glyph_count);
let mut character_positions = Vec::<f32>::with_capacity(glyph_count);
let mut character_widths = Vec::<f32>::with_capacity(glyph_count);
let mut word_lengths = Vec::<u8>::new();
let mut was_at_word_end = false;
let mut last_word_start = 0usize;
let total_chars = character_lengths.len();
for glyph in &row.glyphs {
let is_word_char = is_word_char(glyph.chr);
if is_word_char && was_at_word_end {
word_lengths.push((character_lengths.len() - last_word_start) as _);
last_word_start = character_lengths.len();
if total_chars <= MAX_CHARS_PER_TEXT_RUN {
let run_id = parent_id.with(row_index).with(0usize);
ctx.register_accesskit_parent(run_id, parent_id);
ctx.accesskit_node_builder(run_id, |builder| {
builder.set_role(accesskit::Role::TextRun);
builder.set_text_direction(accesskit::TextDirection::LeftToRight);
// TODO(mwcampbell): Set more node fields for the row
// once AccessKit adapters expose text formatting info.
let rect = global_from_galley * row.rect_without_leading_space();
builder.set_bounds(accesskit::Rect {
x0: rect.min.x.into(),
y0: rect.min.y.into(),
x1: rect.max.x.into(),
y1: rect.max.y.into(),
});
builder.set_value(value);
builder.set_character_lengths(character_lengths);
let pos_offset = character_positions.first().copied().unwrap_or(0.0);
for p in &mut character_positions {
*p -= pos_offset;
}
was_at_word_end = !is_word_char;
let old_len = value.len();
value.push(glyph.chr);
character_lengths.push((value.len() - old_len) as _);
character_positions.push(glyph.pos.x - row.pos.x);
character_widths.push(glyph.advance_width);
}
builder.set_character_positions(character_positions);
builder.set_character_widths(character_widths);
if row.ends_with_newline {
value.push('\n');
character_lengths.push(1);
character_positions.push(row.size.x);
character_widths.push(0.0);
}
word_lengths.push((character_lengths.len() - last_word_start) as _);
let chunk_word_starts: Vec<u8> = word_starts.iter().map(|&ws| ws as u8).collect();
builder.set_word_starts(chunk_word_starts);
});
} else {
let num_chunks = total_chars.div_ceil(MAX_CHARS_PER_TEXT_RUN);
let mut byte_offset = 0usize;
builder.set_value(value);
builder.set_character_lengths(character_lengths);
builder.set_character_positions(character_positions);
builder.set_character_widths(character_widths);
builder.set_word_lengths(word_lengths);
});
for chunk_idx in 0..num_chunks {
let char_start = chunk_idx * MAX_CHARS_PER_TEXT_RUN;
let char_end = (char_start + MAX_CHARS_PER_TEXT_RUN).min(total_chars);
let byte_start = byte_offset;
let chunk_byte_len: usize = character_lengths[char_start..char_end]
.iter()
.map(|&l| l as usize)
.sum();
let byte_end = byte_start + chunk_byte_len;
byte_offset = byte_end;
let run_id = parent_id.with(row_index).with(chunk_idx);
ctx.register_accesskit_parent(run_id, parent_id);
ctx.accesskit_node_builder(run_id, |builder| {
builder.set_role(accesskit::Role::TextRun);
builder.set_text_direction(accesskit::TextDirection::LeftToRight);
// TODO(mwcampbell): Set more node fields for the row
// once AccessKit adapters expose text formatting info.
if chunk_idx > 0 {
let prev_id = parent_id.with(row_index).with(chunk_idx - 1);
builder.set_previous_on_line(prev_id.accesskit_id());
}
if chunk_idx + 1 < num_chunks {
let next_id = parent_id.with(row_index).with(chunk_idx + 1);
builder.set_next_on_line(next_id.accesskit_id());
}
let row_rect = row.rect_without_leading_space();
let chunk_x0 = row.pos.x + character_positions[char_start];
let chunk_x1 = row.pos.x
+ character_positions[char_end - 1]
+ character_widths[char_end - 1];
let chunk_rect = emath::Rect::from_min_max(
emath::pos2(chunk_x0, row_rect.min.y),
emath::pos2(chunk_x1, row_rect.max.y),
);
let rect = global_from_galley * chunk_rect;
builder.set_bounds(accesskit::Rect {
x0: rect.min.x.into(),
y0: rect.min.y.into(),
x1: rect.max.x.into(),
y1: rect.max.y.into(),
});
builder.set_value(value[byte_start..byte_end].to_owned());
builder.set_character_lengths(character_lengths[char_start..char_end].to_vec());
let pos_offset = character_positions[char_start];
let chunk_positions: Vec<f32> = character_positions[char_start..char_end]
.iter()
.map(|&p| p - pos_offset)
.collect();
builder.set_character_positions(chunk_positions);
builder.set_character_widths(character_widths[char_start..char_end].to_vec());
let chunk_word_starts: Vec<u8> = word_starts
.iter()
.filter(|&&ws| ws >= char_start && ws < char_end)
.map(|&ws| (ws - char_start) as u8)
.collect();
builder.set_word_starts(chunk_word_starts);
});
}
}
prev_row_ended_with_newline = row.ends_with_newline;
}
}

View File

@@ -192,10 +192,13 @@ impl CCursorRange {
Event::AccessKitActionRequest(accesskit::ActionRequest {
action: accesskit::Action::SetTextSelection,
target,
target_node,
target_tree,
data: Some(accesskit::ActionData::SetTextSelection(selection)),
}) => {
if _widget_id.accesskit_id() == *target {
if _widget_id.accesskit_id() == *target_node
&& *target_tree == accesskit::TreeId::ROOT
{
let primary =
ccursor_from_accesskit_text_position(_widget_id, galley, &selection.focus);
let secondary =
@@ -224,18 +227,31 @@ fn ccursor_from_accesskit_text_position(
galley: &Galley,
position: &accesskit::TextPosition,
) -> Option<CCursor> {
use super::accesskit_text::MAX_CHARS_PER_TEXT_RUN;
let mut total_length = 0usize;
for (i, row) in galley.rows.iter().enumerate() {
let row_id = id.with(i);
if row_id.accesskit_id() == position.node {
return Some(CCursor {
index: total_length + position.character_index,
prefer_next_row: !(position.character_index == row.glyphs.len()
&& !row.ends_with_newline
&& (i + 1) < galley.rows.len()),
});
let row_chars = row.glyphs.len() + (row.ends_with_newline as usize);
let num_chunks = if row_chars == 0 {
1
} else {
row_chars.div_ceil(MAX_CHARS_PER_TEXT_RUN)
};
for chunk_idx in 0..num_chunks {
let run_id = id.with(i).with(chunk_idx);
if run_id.accesskit_id() == position.node {
let column = chunk_idx * MAX_CHARS_PER_TEXT_RUN + position.character_index;
return Some(CCursor {
index: total_length + column,
prefer_next_row: !(column == row.glyphs.len()
&& !row.ends_with_newline
&& (i + 1) < galley.rows.len()),
});
}
}
total_length += row.glyphs.len() + (row.ends_with_newline as usize);
total_length += row_chars;
}
None
}

View File

@@ -1,7 +1,7 @@
use std::mem;
use accesskit::{Action, ActionRequest, NodeId};
use accesskit_consumer::{FilterResult, Node, Tree, TreeChangeHandler};
use accesskit::{Action, ActionRequest};
use accesskit_consumer::{FilterResult, Node, NodeId, Tree, TreeChangeHandler};
use eframe::epaint::text::TextWrapMode;
use egui::{
@@ -25,7 +25,7 @@ use egui::{
pub struct AccessibilityInspectorPlugin {
pub open: bool,
tree: Option<accesskit_consumer::Tree>,
selected_node: Option<Id>,
selected_node: Option<NodeId>,
queued_action: Option<ActionRequest>,
}
@@ -113,13 +113,17 @@ impl AccessibilityInspectorPlugin {
Id::new("Accessibility Inspector")
}
fn selection_ui(&mut self, ui: &mut Ui, selected_node: Id) {
fn selection_ui(&mut self, ui: &mut Ui, selected_node: NodeId) {
ui.separator();
if let Some(tree) = &self.tree
&& let Some(node) = tree.state().node_by_id(NodeId::from(selected_node.value()))
&& let Some(node) = tree.state().node_by_id(selected_node)
{
let node_response = ui.ctx().read_response(selected_node);
// Safety: This is safe since the `accesskit::NodeId` was created from an `egui::Id`.
#[expect(unsafe_code)]
let egui_node_id = unsafe { Id::from_high_entropy_bits(node.locate().0.0) };
let node_response = ui.ctx().read_response(egui_node_id);
if let Some(widget_response) = node_response {
ui.debug_painter().debug_rect(
@@ -174,8 +178,10 @@ impl AccessibilityInspectorPlugin {
if node.supports_action(action, &|_node| FilterResult::Include)
&& ui.button(format!("{action:?}")).clicked()
{
let (target_node, target_tree) = node.locate();
let action_request = ActionRequest {
target: node.id(),
target_node,
target_tree,
action,
data: None,
};
@@ -188,8 +194,8 @@ impl AccessibilityInspectorPlugin {
}
}
fn node_ui(ui: &mut Ui, node: &Node<'_>, selected_node: &mut Option<Id>) {
if node.id() == Self::id().value().into()
fn node_ui(ui: &mut Ui, node: &Node<'_>, selected_node: &mut Option<NodeId>) {
if node.locate() == (Self::id().value().into(), accesskit::TreeId::ROOT)
|| node
.value()
.as_deref()
@@ -200,12 +206,12 @@ impl AccessibilityInspectorPlugin {
let label = node
.label()
.or_else(|| node.value())
.unwrap_or_else(|| node.id().0.to_string());
.unwrap_or_else(|| node.locate().0.0.to_string());
let label = format!("({:?}) {}", node.role(), label);
// Safety: This is safe since the `accesskit::NodeId` was created from an `egui::Id`.
#[expect(unsafe_code)]
let egui_node_id = unsafe { Id::from_high_entropy_bits(node.id().0) };
let egui_node_id = unsafe { Id::from_high_entropy_bits(node.locate().0.0) };
ui.push_id(node.id(), |ui| {
let child_count = node.children().len();
@@ -228,7 +234,7 @@ impl AccessibilityInspectorPlugin {
collapsing.set_open(!collapsing.is_open());
}
let label_response =
ui.selectable_value(selected_node, Some(egui_node_id), label.clone());
ui.selectable_value(selected_node, Some(node.id()), label.clone());
if label_response.hovered() {
let widget_response = ui.ctx().read_response(egui_node_id);

View File

@@ -98,9 +98,11 @@ impl Node<'_> {
/// This will trigger a [`accesskit::Action::Click`] action.
/// In contrast to `click()`, this can also click widgets that are not currently visible.
pub fn click_accesskit(&self) {
let (target_node, target_tree) = self.accesskit_node.locate();
self.event(egui::Event::AccessKitActionRequest(
accesskit::ActionRequest {
target: self.accesskit_node.id(),
target_node,
target_tree,
action: accesskit::Action::Click,
data: None,
},
@@ -119,9 +121,11 @@ impl Node<'_> {
}
pub fn focus(&self) {
let (target_node, target_tree) = self.accesskit_node.locate();
self.event(egui::Event::AccessKitActionRequest(ActionRequest {
action: accesskit::Action::Focus,
target: self.accesskit_node.id(),
target_node,
target_tree,
data: None,
}));
}
@@ -162,45 +166,55 @@ impl Node<'_> {
/// Scroll the node into view.
pub fn scroll_to_me(&self) {
let (target_node, target_tree) = self.accesskit_node.locate();
self.event(egui::Event::AccessKitActionRequest(ActionRequest {
action: accesskit::Action::ScrollIntoView,
target: self.accesskit_node.id(),
target_node,
target_tree,
data: None,
}));
}
/// Scroll the [`egui::ScrollArea`] containing this node down (100px).
pub fn scroll_down(&self) {
let (target_node, target_tree) = self.accesskit_node.locate();
self.event(egui::Event::AccessKitActionRequest(ActionRequest {
action: accesskit::Action::ScrollDown,
target: self.accesskit_node.id(),
target_node,
target_tree,
data: None,
}));
}
/// Scroll the [`egui::ScrollArea`] containing this node up (100px).
pub fn scroll_up(&self) {
let (target_node, target_tree) = self.accesskit_node.locate();
self.event(egui::Event::AccessKitActionRequest(ActionRequest {
action: accesskit::Action::ScrollUp,
target: self.accesskit_node.id(),
target_node,
target_tree,
data: None,
}));
}
/// Scroll the [`egui::ScrollArea`] containing this node left (100px).
pub fn scroll_left(&self) {
let (target_node, target_tree) = self.accesskit_node.locate();
self.event(egui::Event::AccessKitActionRequest(ActionRequest {
action: accesskit::Action::ScrollLeft,
target: self.accesskit_node.id(),
target_node,
target_tree,
data: None,
}));
}
/// Scroll the [`egui::ScrollArea`] containing this node right (100px).
pub fn scroll_right(&self) {
let (target_node, target_tree) = self.accesskit_node.locate();
self.event(egui::Event::AccessKitActionRequest(ActionRequest {
action: accesskit::Action::ScrollRight,
target: self.accesskit_node.id(),
target_node,
target_tree,
data: None,
}));
}

View File

@@ -64,6 +64,7 @@ skip-tree = [
{ name = "hashbrown" }, # wgpu's naga depends on 0.16, accesskit depends on 0.15
{ name = "rfd" }, # example dependency
{ name = "windows" }, # the ecosystem is currently transitioning from 0.58 to 0.61
{ name = "phf" }, # mime_guess2, unicode_names2 -> 0.11.3; accesskit -> 0.13.1
]