mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 21:00:03 -04:00
Implement accessibility APIs via AccessKit (#2294)
* squash before rebase * Update AccessKit, introducing support for editable spinners on Windows and an important fix for navigation order on macOS * Restore support for increment and decrement actions in DragValue * Avoid VoiceOver race condition bug * fix clippy lint * Tell AccessKit that the default action for a text edit (equivalent to a click) is to set the focus. This matters to some platform adapters. * Refactor InputState functions for AccessKit actions * Support the AccessKit SetValue for DragValue; this is the only way for a Windows AT to programmatically adjust the value * Same for Slider * Properly associate the slider label with both the slider and the drag value * Lazily activate egui's AccessKit support * fix clippy lint * Update AccessKit * More documentation, particularly around lazy activation * Tweak one of the doc comments * See if I can get AccessKit exempted from the 'missing backticks' lint * Make PlatformOutput::accesskit_update an Option * Refactor lazy activation * Refactor node mutation (again) * Eliminate the need for an explicit is_accesskit_active method, at least for now * Fix doc comment * More refactoring of tree construction; don't depend on Arc::get_mut * Override a clippy lint; I seem to have no other choice * Final planned refactor: a more flexible approach to hierarchy * Last AccessKit update for this PR; includes an important macOS DPI fix * Move and document the optional accesskit dependency * Fix comment typo Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com> * reformat * More elegant code for conditionally creating a node Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com> * Set step to 1.0 for all integer sliders * Add doc example for Response::labelled_by * Clarify a TODO comment I left for myself Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
@@ -3,6 +3,10 @@ use winit::event_loop::EventLoopWindowTarget;
|
||||
#[cfg(target_os = "macos")]
|
||||
use winit::platform::macos::WindowBuilderExtMacOS as _;
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
use egui::accesskit;
|
||||
#[cfg(feature = "accesskit")]
|
||||
use egui_winit::accesskit_winit;
|
||||
use egui_winit::{native_pixels_per_point, EventResponse, WindowSettings};
|
||||
|
||||
use crate::{epi, Theme, WindowInfo};
|
||||
@@ -262,6 +266,25 @@ impl EpiIntegration {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
pub fn init_accesskit<E: From<accesskit_winit::ActionRequestEvent> + Send>(
|
||||
&mut self,
|
||||
window: &winit::window::Window,
|
||||
event_loop_proxy: winit::event_loop::EventLoopProxy<E>,
|
||||
) {
|
||||
let egui_ctx = self.egui_ctx.clone();
|
||||
self.egui_winit
|
||||
.init_accesskit(window, event_loop_proxy, move || {
|
||||
// This function is called when an accessibility client
|
||||
// (e.g. screen reader) makes its first request. If we got here,
|
||||
// we know that an accessibility tree is actually wanted.
|
||||
egui_ctx.enable_accesskit();
|
||||
// Enqueue a repaint so we'll receive a full tree update soon.
|
||||
egui_ctx.request_repaint();
|
||||
egui::accesskit_placeholder_tree_update()
|
||||
});
|
||||
}
|
||||
|
||||
pub fn warm_up(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
|
||||
crate::profile_function!();
|
||||
let saved_memory: egui::Memory = self.egui_ctx.memory().clone();
|
||||
@@ -301,6 +324,11 @@ impl EpiIntegration {
|
||||
self.egui_winit.on_event(&self.egui_ctx, event)
|
||||
}
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
pub fn on_accesskit_action_request(&mut self, request: accesskit::ActionRequest) {
|
||||
self.egui_winit.on_accesskit_action_request(request);
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
app: &mut dyn epi::App,
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
use egui_winit::accesskit_winit;
|
||||
use egui_winit::winit;
|
||||
use winit::event_loop::{
|
||||
ControlFlow, EventLoop, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget,
|
||||
@@ -15,6 +17,15 @@ use crate::epi;
|
||||
#[derive(Debug)]
|
||||
pub enum UserEvent {
|
||||
RequestRepaint,
|
||||
#[cfg(feature = "accesskit")]
|
||||
AccessKitActionRequest(accesskit_winit::ActionRequestEvent),
|
||||
}
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
impl From<accesskit_winit::ActionRequestEvent> for UserEvent {
|
||||
fn from(inner: accesskit_winit::ActionRequestEvent) -> Self {
|
||||
Self::AccessKitActionRequest(inner)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -353,7 +364,9 @@ mod glow_integration {
|
||||
|
||||
let window_builder = epi_integration::window_builder(native_options, &window_settings)
|
||||
.with_title(title)
|
||||
.with_visible(false); // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
|
||||
// Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
|
||||
// We must also keep the window hidden until AccessKit is initialized.
|
||||
.with_visible(false);
|
||||
|
||||
let gl_window = unsafe {
|
||||
glutin::ContextBuilder::new()
|
||||
@@ -400,6 +413,10 @@ mod glow_integration {
|
||||
#[cfg(feature = "wgpu")]
|
||||
None,
|
||||
);
|
||||
#[cfg(feature = "accesskit")]
|
||||
{
|
||||
integration.init_accesskit(gl_window.window(), self.repaint_proxy.lock().clone());
|
||||
}
|
||||
let theme = system_theme.unwrap_or(self.native_options.default_theme);
|
||||
integration.egui_ctx.set_visuals(theme.egui_visuals());
|
||||
|
||||
@@ -671,6 +688,21 @@ mod glow_integration {
|
||||
EventResult::Wait
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "accesskit")]
|
||||
winit::event::Event::UserEvent(UserEvent::AccessKitActionRequest(
|
||||
accesskit_winit::ActionRequestEvent { request, .. },
|
||||
)) => {
|
||||
if let Some(running) = &mut self.running {
|
||||
running
|
||||
.integration
|
||||
.on_accesskit_action_request(request.clone());
|
||||
// As a form of user input, accessibility actions should
|
||||
// lead to a repaint.
|
||||
EventResult::RepaintNext
|
||||
} else {
|
||||
EventResult::Wait
|
||||
}
|
||||
}
|
||||
_ => EventResult::Wait,
|
||||
}
|
||||
}
|
||||
@@ -769,7 +801,9 @@ mod wgpu_integration {
|
||||
let window_settings = epi_integration::load_window_settings(storage);
|
||||
epi_integration::window_builder(native_options, &window_settings)
|
||||
.with_title(title)
|
||||
.with_visible(false) // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
|
||||
// Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
|
||||
// We must also keep the window hidden until AccessKit is initialized.
|
||||
.with_visible(false)
|
||||
.build(event_loop)
|
||||
.unwrap()
|
||||
}
|
||||
@@ -825,6 +859,10 @@ mod wgpu_integration {
|
||||
None,
|
||||
wgpu_render_state.clone(),
|
||||
);
|
||||
#[cfg(feature = "accesskit")]
|
||||
{
|
||||
integration.init_accesskit(&window, self.repaint_proxy.lock().unwrap().clone());
|
||||
}
|
||||
let theme = system_theme.unwrap_or(self.native_options.default_theme);
|
||||
integration.egui_ctx.set_visuals(theme.egui_visuals());
|
||||
|
||||
@@ -1068,6 +1106,21 @@ mod wgpu_integration {
|
||||
EventResult::Wait
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "accesskit")]
|
||||
winit::event::Event::UserEvent(UserEvent::AccessKitActionRequest(
|
||||
accesskit_winit::ActionRequestEvent { request, .. },
|
||||
)) => {
|
||||
if let Some(running) = &mut self.running {
|
||||
running
|
||||
.integration
|
||||
.on_accesskit_action_request(request.clone());
|
||||
// As a form of user input, accessibility actions should
|
||||
// lead to a repaint.
|
||||
EventResult::RepaintNext
|
||||
} else {
|
||||
EventResult::Wait
|
||||
}
|
||||
}
|
||||
_ => EventResult::Wait,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,6 +400,8 @@ impl AppRunner {
|
||||
events: _, // already handled
|
||||
mutable_text_under_cursor,
|
||||
text_cursor_pos,
|
||||
#[cfg(feature = "accesskit")]
|
||||
accesskit_update: _, // not currently implemented
|
||||
} = platform_output;
|
||||
|
||||
set_cursor_icon(cursor_icon);
|
||||
|
||||
Reference in New Issue
Block a user