diff --git a/Cargo.toml b/Cargo.toml index dc9203e62..b2f129153 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,8 @@ name = "render_egui_to_image" required-features = ["picking", "render", "bevy/bevy_gizmos"] [dependencies] -egui = { version = "0.31", default-features = false } +egui = { version = "0.31", default-features = false, features = ["accesskit"] } +bevy_a11y = "0.15.0" bevy_app = "0.15.0" bevy_derive = "0.15.0" bevy_ecs = "0.15.0" @@ -105,6 +106,7 @@ thread_local = { version = "1.1.0", optional = true } [dev-dependencies] version-sync = "0.9.4" bevy = { version = "0.15.0", default-features = false, features = [ + "accesskit_unix", "bevy_asset", "bevy_core_pipeline", "bevy_pbr", diff --git a/src/lib.rs b/src/lib.rs index 5525b5899..555f57b42 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -154,6 +154,7 @@ pub mod text_agent; #[cfg(all(feature = "manage_clipboard", target_arch = "wasm32",))] pub mod web_clipboard; +use bevy_a11y::{AccessibilityRequested, AccessibilitySystem, ManageAccessibilityUpdates}; pub use egui; use crate::input::*; @@ -204,7 +205,7 @@ use bevy_render::{ }; use bevy_utils::HashSet; use bevy_window::{PrimaryWindow, Window}; -use bevy_winit::cursor::CursorIcon; +use bevy_winit::{accessibility::AccessKitAdapters, cursor::CursorIcon}; use output::process_output_system; #[cfg(all( feature = "manage_clipboard", @@ -1007,7 +1008,7 @@ impl Plugin for EguiPlugin { ( EguiPostUpdateSet::EndPass, EguiPostUpdateSet::ProcessOutput, - EguiPostUpdateSet::PostProcessOutput, + EguiPostUpdateSet::PostProcessOutput.before(AccessibilitySystem::Update), ) .chain(), ); @@ -1197,6 +1198,11 @@ impl Plugin for EguiPlugin { "egui.wgsl", bevy_render::render_resource::Shader::from_wgsl ); + + app.add_systems( + PostUpdate, + update_accessibility_system.in_set(EguiPostUpdateSet::PostProcessOutput), + ); } #[cfg(feature = "render")] @@ -1260,13 +1266,22 @@ pub struct EguiManagedTexture { /// Adds bevy_egui components to newly created windows. pub fn setup_new_windows_system( mut commands: Commands, - enable_multipass_for_primary_context: Option>, new_windows: Query<(Entity, Option<&PrimaryWindow>), (Added, Without)>, + adapters: Option>, + mut manage_accessibility_updates: ResMut, + enable_multipass_for_primary_context: Option>, ) { for (window, primary) in new_windows.iter() { + let context = EguiContext::default(); + if let Some(adapters) = &adapters { + if adapters.get(&window).is_some() { + context.ctx.enable_accesskit(); + **manage_accessibility_updates = false; + } + } // See the list of required components to check the full list of components we add. let mut window_commands = commands.entity(window); - window_commands.insert(EguiContext::default()); + window_commands.insert(context); if enable_multipass_for_primary_context.is_some() && primary.is_some() { window_commands.insert(EguiMultipassSchedule::new(EguiContextPass)); } @@ -1648,6 +1663,27 @@ pub fn end_pass_system( } } +/// Updates the states of [`ManageAccessibilityUpdates`] and [`AccessKitAdapters`]. +pub fn update_accessibility_system( + requested: Res, + mut manage_accessibility_updates: ResMut, + outputs: Query<(Entity, &EguiOutput)>, + mut adapters: NonSendMut, +) { + if requested.get() { + for (entity, output) in &outputs { + if let Some(adapter) = adapters.get_mut(&entity) { + if let Some(update) = &output.platform_output.accesskit_update { + **manage_accessibility_updates = false; + adapter.update_if_active(|| update.clone()); + } else if !**manage_accessibility_updates { + **manage_accessibility_updates = true; + } + } + } + } +} + #[derive(QueryData)] #[query_data(mutable)] #[allow(missing_docs)]