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

Revert "Remove accesskit feature and always depend on accesskit (#7701)"

This reverts commit 1af5d1d37e.
This commit is contained in:
lucasmerlin
2025-11-18 17:17:25 +01:00
parent d53a4a9c1d
commit 7980401b25
25 changed files with 140 additions and 56 deletions

View File

@@ -36,7 +36,7 @@ default = [
]
## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/).
accesskit = ["egui-winit/accesskit"]
accesskit = ["egui/accesskit", "egui-winit/accesskit"]
# Allow crates to choose an android-activity backend via Winit
# - It's important that most applications should not have to depend on android-activity directly, and can

View File

@@ -358,7 +358,8 @@ impl AppRunner {
events: _, // already handled
mutable_text_under_cursor: _, // TODO(#4569): https://github.com/emilk/egui/issues/4569
ime,
accesskit_update: _, // not currently implemented
#[cfg(feature = "accesskit")]
accesskit_update: _, // not currently implemented
num_completed_passes: _, // handled by `Context::run`
request_discard_reasons: _, // handled by `Context::run`
} = platform_output;

View File

@@ -24,7 +24,7 @@ rustdoc-args = ["--generate-link-to-definition"]
default = ["clipboard", "links", "wayland", "winit/default", "x11"]
## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/).
accesskit = ["dep:accesskit_winit"]
accesskit = ["dep:accesskit_winit", "egui/accesskit"]
# Allow crates to choose an android-activity backend via Winit
# - It's important that most applications should not have to depend on android-activity directly, and can

View File

@@ -888,6 +888,7 @@ impl State {
events: _, // handled elsewhere
mutable_text_under_cursor: _, // only used in eframe web
ime,
#[cfg(feature = "accesskit")]
accesskit_update,
num_completed_passes: _, // `egui::Context::run` handles this
request_discard_reasons: _, // `egui::Context::run` handles this
@@ -946,9 +947,6 @@ impl State {
profiling::scope!("accesskit");
accesskit.update_if_active(|| update);
}
#[cfg(not(feature = "accesskit"))]
let _ = accesskit_update;
}
fn set_cursor_icon(&mut self, window: &Window, cursor_icon: egui::CursorIcon) {

View File

@@ -26,6 +26,10 @@ rustdoc-args = ["--generate-link-to-definition"]
[features]
default = ["default_fonts"]
## Exposes detailed accessibility implementation required by platform
## accessibility APIs. Also requires support in the egui integration.
accesskit = ["dep:accesskit"]
## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast [`epaint::Vertex`], [`emath::Vec2`] etc to `&[u8]`.
bytemuck = ["epaint/bytemuck"]
@@ -57,7 +61,7 @@ persistence = ["serde", "epaint/serde", "ron"]
rayon = ["epaint/rayon"]
## Allow serialization using [`serde`](https://docs.rs/serde).
serde = ["dep:serde", "epaint/serde", "accesskit/serde"]
serde = ["dep:serde", "epaint/serde", "accesskit?/serde"]
## Change Vertex layout to be compatible with unity
unity = ["epaint/unity"]
@@ -71,7 +75,6 @@ _override_unity = ["epaint/_override_unity"]
emath = { workspace = true, default-features = false }
epaint = { workspace = true, default-features = false }
accesskit.workspace = true
ahash.workspace = true
bitflags.workspace = true
log.workspace = true
@@ -81,6 +84,7 @@ smallvec.workspace = true
unicode-segmentation.workspace = true
#! ### Optional dependencies
accesskit = { workspace = true, optional = true }
backtrace = { workspace = true, optional = true }

View File

@@ -910,6 +910,7 @@ fn resize_interaction(
let rect = outer_rect.shrink(window_frame.stroke.width / 2.0);
let side_response = |rect, id| {
#[cfg(feature = "accesskit")]
ctx.register_accesskit_parent(id, _accessibility_parent);
let response = ctx.create_widget(
WidgetRect {

View File

@@ -41,6 +41,7 @@ use crate::{
viewport::ViewportClass,
};
#[cfg(feature = "accesskit")]
use crate::IdMap;
/// Information given to the backend about when it is time to repaint the ui.
@@ -403,6 +404,7 @@ struct ContextImpl {
embed_viewports: bool,
#[cfg(feature = "accesskit")]
is_accesskit_enabled: bool,
loaders: Arc<Loaders>,
@@ -505,6 +507,7 @@ impl ContextImpl {
},
);
#[cfg(feature = "accesskit")]
if self.is_accesskit_enabled {
profiling::scope!("accesskit");
use crate::pass_state::AccessKitPassState;
@@ -586,10 +589,10 @@ impl ContextImpl {
}
}
#[cfg(feature = "accesskit")]
fn accesskit_node_builder(&mut self, id: Id) -> &mut accesskit::Node {
let state = self.viewport().this_pass.accesskit_state.as_mut().unwrap();
let builders = &mut state.nodes;
if let std::collections::hash_map::Entry::Vacant(entry) = builders.entry(id) {
entry.insert(Default::default());
@@ -616,7 +619,6 @@ impl ContextImpl {
let parent_builder = builders.get_mut(&parent_id).unwrap();
parent_builder.push_child(id.accesskit_id());
}
builders.get_mut(&id).unwrap()
}
@@ -1202,6 +1204,7 @@ impl Context {
plugins.on_widget_under_pointer(self, &w);
}
#[cfg(feature = "accesskit")]
if allow_focus && w.sense.is_focusable() {
// Make sure anything that can receive focus has an AccessKit node.
// TODO(mwcampbell): For nodes that are filled from widget info,
@@ -1209,6 +1212,7 @@ impl Context {
self.accesskit_node_builder(w.id, |builder| res.fill_accesskit_node_common(builder));
}
#[cfg(feature = "accesskit")]
self.write(|ctx| {
use crate::{Align, pass_state::ScrollTarget, style::ScrollAnimation};
let viewport = ctx.viewport_for(ctx.viewport_id());
@@ -1216,14 +1220,12 @@ impl Context {
viewport
.input
.consume_accesskit_action_requests(res.id, |request| {
use accesskit::Action;
// TODO(lucasmerlin): Correctly handle the scroll unit:
// https://github.com/AccessKit/accesskit/blob/e639c0e0d8ccbfd9dff302d972fa06f9766d608e/common/src/lib.rs#L2621
const DISTANCE: f32 = 100.0;
match &request.action {
Action::ScrollIntoView => {
accesskit::Action::ScrollIntoView => {
viewport.this_pass.scroll_target = [
Some(ScrollTarget::new(
res.rect.x_range(),
@@ -1237,16 +1239,16 @@ impl Context {
)),
];
}
Action::ScrollDown => {
accesskit::Action::ScrollDown => {
viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::UP;
}
Action::ScrollUp => {
accesskit::Action::ScrollUp => {
viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::DOWN;
}
Action::ScrollLeft => {
accesskit::Action::ScrollLeft => {
viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::LEFT;
}
Action::ScrollRight => {
accesskit::Action::ScrollRight => {
viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::RIGHT;
}
_ => return false,
@@ -1339,6 +1341,7 @@ impl Context {
res.flags.set(Flags::FAKE_PRIMARY_CLICKED, true);
}
#[cfg(feature = "accesskit")]
if enabled
&& sense.senses_click()
&& input.has_accesskit_action_request(id, accesskit::Action::Click)
@@ -2495,6 +2498,7 @@ impl ContextImpl {
let mut platform_output: PlatformOutput = std::mem::take(&mut viewport.output);
#[cfg(feature = "accesskit")]
{
profiling::scope!("accesskit");
let state = viewport.this_pass.accesskit_state.take();
@@ -3493,8 +3497,9 @@ impl Context {
///
/// The `Context` lock is held while the given closure is called!
///
/// Returns `None` if accesskit is off.
/// Returns `None` if acesskit is off.
// TODO(emilk): consider making both read-only and read-write versions
#[cfg(feature = "accesskit")]
pub fn accesskit_node_builder<R>(
&self,
id: Id,
@@ -3510,6 +3515,7 @@ impl Context {
})
}
#[cfg(feature = "accesskit")]
pub(crate) fn register_accesskit_parent(&self, id: Id, parent_id: Id) {
self.write(|ctx| {
if let Some(state) = ctx.viewport().this_pass.accesskit_state.as_mut() {
@@ -3519,11 +3525,13 @@ impl Context {
}
/// Enable generation of AccessKit tree updates in all future frames.
#[cfg(feature = "accesskit")]
pub fn enable_accesskit(&self) {
self.write(|ctx| ctx.is_accesskit_enabled = true);
}
/// Disable generation of AccessKit tree updates in all future frames.
#[cfg(feature = "accesskit")]
pub fn disable_accesskit(&self) {
self.write(|ctx| ctx.is_accesskit_enabled = false);
}

View File

@@ -548,6 +548,7 @@ pub enum Event {
WindowFocused(bool),
/// An assistive technology (e.g. screen reader) requested an action.
#[cfg(feature = "accesskit")]
AccessKitActionRequest(accesskit::ActionRequest),
/// The reply of a screenshot requested with [`crate::ViewportCommand::Screenshot`].

View File

@@ -128,6 +128,7 @@ pub struct PlatformOutput {
/// The difference in the widget tree since last frame.
///
/// NOTE: this needs to be per-viewport.
#[cfg(feature = "accesskit")]
pub accesskit_update: Option<accesskit::TreeUpdate>,
/// How many ui passes is this the sum of?
@@ -174,6 +175,7 @@ impl PlatformOutput {
mut events,
mutable_text_under_cursor,
ime,
#[cfg(feature = "accesskit")]
accesskit_update,
num_completed_passes,
mut request_discard_reasons,
@@ -188,8 +190,12 @@ impl PlatformOutput {
self.request_discard_reasons
.append(&mut request_discard_reasons);
// egui produces a complete AccessKit tree for each frame, so overwrite rather than append:
self.accesskit_update = accesskit_update;
#[cfg(feature = "accesskit")]
{
// egui produces a complete AccessKit tree for each frame,
// so overwrite rather than appending.
self.accesskit_update = accesskit_update;
}
}
/// Take everything ephemeral (everything except `cursor_icon` currently)

View File

@@ -79,6 +79,7 @@ impl Id {
self.0.get()
}
#[cfg(feature = "accesskit")]
pub(crate) fn accesskit_id(&self) -> accesskit::NodeId {
self.value().into()
}

View File

@@ -855,6 +855,7 @@ impl InputState {
}
}
#[cfg(feature = "accesskit")]
pub fn accesskit_action_requests(
&self,
id: crate::Id,
@@ -872,6 +873,7 @@ impl InputState {
})
}
#[cfg(feature = "accesskit")]
pub fn consume_accesskit_action_requests(
&mut self,
id: crate::Id,
@@ -888,10 +890,12 @@ impl InputState {
});
}
#[cfg(feature = "accesskit")]
pub fn has_accesskit_action_request(&self, id: crate::Id, action: accesskit::Action) -> bool {
self.accesskit_action_requests(id, action).next().is_some()
}
#[cfg(feature = "accesskit")]
pub fn num_accesskit_action_requests(&self, id: crate::Id, action: accesskit::Action) -> usize {
self.accesskit_action_requests(id, action).count()
}

View File

@@ -448,6 +448,7 @@ pub mod widgets;
#[cfg(debug_assertions)]
mod callstack;
#[cfg(feature = "accesskit")]
pub use accesskit;
#[deprecated = "Use the ahash crate directly."]
@@ -707,6 +708,7 @@ pub fn __run_test_ui(add_contents: impl Fn(&mut Ui)) {
});
}
#[cfg(feature = "accesskit")]
pub fn accesskit_root_id() -> Id {
Id::new("accesskit_root")
}

View File

@@ -470,6 +470,7 @@ pub(crate) struct Focus {
/// The ID of a widget to give the focus to in the next frame.
id_next_frame: Option<Id>,
#[cfg(feature = "accesskit")]
id_requested_by_accesskit: Option<accesskit::NodeId>,
/// If set, the next widget that is interested in focus will automatically get it.
@@ -528,7 +529,10 @@ impl Focus {
}
let event_filter = self.focused_widget.map(|w| w.filter).unwrap_or_default();
self.id_requested_by_accesskit = None;
#[cfg(feature = "accesskit")]
{
self.id_requested_by_accesskit = None;
}
self.focus_direction = FocusDirection::None;
@@ -563,13 +567,16 @@ impl Focus {
self.focus_direction = cardinality;
}
if let crate::Event::AccessKitActionRequest(accesskit::ActionRequest {
action: accesskit::Action::Focus,
target,
data: None,
}) = event
#[cfg(feature = "accesskit")]
{
self.id_requested_by_accesskit = Some(*target);
if let crate::Event::AccessKitActionRequest(accesskit::ActionRequest {
action: accesskit::Action::Focus,
target,
data: None,
}) = event
{
self.id_requested_by_accesskit = Some(*target);
}
}
}
}
@@ -599,11 +606,14 @@ impl Focus {
}
fn interested_in_focus(&mut self, id: Id) {
if self.id_requested_by_accesskit == Some(id.accesskit_id()) {
self.focused_widget = Some(FocusWidget::new(id));
self.id_requested_by_accesskit = None;
self.give_to_next = false;
self.reset_focus();
#[cfg(feature = "accesskit")]
{
if self.id_requested_by_accesskit == Some(id.accesskit_id()) {
self.focused_widget = Some(FocusWidget::new(id));
self.id_requested_by_accesskit = None;
self.give_to_next = false;
self.reset_focus();
}
}
// The rect is updated at the end of the frame.

View File

@@ -67,6 +67,7 @@ impl ScrollTarget {
}
}
#[cfg(feature = "accesskit")]
#[derive(Clone)]
pub struct AccessKitPassState {
pub nodes: IdMap<accesskit::Node>,
@@ -224,6 +225,7 @@ pub struct PassState {
/// as when swiping down on a touch-screen or track-pad with natural scrolling.
pub scroll_delta: (Vec2, style::ScrollAnimation),
#[cfg(feature = "accesskit")]
pub accesskit_state: Option<AccessKitPassState>,
/// Highlight these widgets the next pass.
@@ -245,6 +247,7 @@ impl Default for PassState {
used_by_panels: Rect::NAN,
scroll_target: [None, None],
scroll_delta: (Vec2::default(), style::ScrollAnimation::none()),
#[cfg(feature = "accesskit")]
accesskit_state: None,
highlight_next_pass: Default::default(),
@@ -267,6 +270,7 @@ impl PassState {
used_by_panels,
scroll_target,
scroll_delta,
#[cfg(feature = "accesskit")]
accesskit_state,
highlight_next_pass,
@@ -289,7 +293,10 @@ impl PassState {
*debug_rect = None;
}
*accesskit_state = None;
#[cfg(feature = "accesskit")]
{
*accesskit_state = None;
}
highlight_next_pass.clear();
}

View File

@@ -807,6 +807,7 @@ impl Response {
if let Some(event) = event {
self.output_event(event);
} else {
#[cfg(feature = "accesskit")]
self.ctx.accesskit_node_builder(self.id, |builder| {
self.fill_accesskit_node_from_widget_info(builder, make_info());
});
@@ -816,6 +817,7 @@ impl Response {
}
pub fn output_event(&self, event: crate::output::OutputEvent) {
#[cfg(feature = "accesskit")]
self.ctx.accesskit_node_builder(self.id, |builder| {
self.fill_accesskit_node_from_widget_info(builder, event.widget_info().clone());
});
@@ -826,6 +828,7 @@ impl Response {
self.ctx.output_mut(|o| o.events.push(event));
}
#[cfg(feature = "accesskit")]
pub(crate) fn fill_accesskit_node_common(&self, builder: &mut accesskit::Node) {
if !self.enabled() {
builder.set_disabled();
@@ -844,6 +847,7 @@ impl Response {
}
}
#[cfg(feature = "accesskit")]
fn fill_accesskit_node_from_widget_info(
&self,
builder: &mut accesskit::Node,
@@ -918,9 +922,14 @@ impl Response {
/// # });
/// ```
pub fn labelled_by(self, id: Id) -> Self {
#[cfg(feature = "accesskit")]
self.ctx.accesskit_node_builder(self.id, |builder| {
builder.push_labelled_by(id.accesskit_id());
});
#[cfg(not(feature = "accesskit"))]
{
let _ = id;
}
self
}

View File

@@ -42,9 +42,8 @@ pub fn update_accesskit_for_text_widget(
for (row_index, row) in galley.rows.iter().enumerate() {
let row_id = parent_id.with(row_index);
#[cfg(feature = "accesskit")]
ctx.register_accesskit_parent(row_id, parent_id);
ctx.accesskit_node_builder(row_id, |builder| {
builder.set_role(accesskit::Role::TextRun);
let rect = global_from_galley * row.rect_without_leading_space();

View File

@@ -190,6 +190,7 @@ impl CCursorRange {
..
} => self.on_key_press(os, galley, modifiers, *key),
#[cfg(feature = "accesskit")]
Event::AccessKitActionRequest(accesskit::ActionRequest {
action: accesskit::Action::SetTextSelection,
target,
@@ -219,6 +220,7 @@ impl CCursorRange {
// ----------------------------------------------------------------------------
#[cfg(feature = "accesskit")]
fn ccursor_from_accesskit_text_position(
id: Id,
galley: &Galley,

View File

@@ -624,6 +624,7 @@ impl LabelSelectionState {
);
}
#[cfg(feature = "accesskit")]
super::accesskit_text::update_accesskit_for_text_widget(
ui.ctx(),
response.id,

View File

@@ -1,5 +1,6 @@
//! Helpers regarding text selection for labels and text edit.
#[cfg(feature = "accesskit")]
pub mod accesskit_text;
mod cursor_range;

View File

@@ -133,6 +133,7 @@ impl Ui {
sizing_pass,
style,
sense,
#[cfg(feature = "accesskit")]
accessibility_parent,
} = ui_builder;
@@ -174,6 +175,7 @@ impl Ui {
min_rect_already_remembered: false,
};
#[cfg(feature = "accesskit")]
if let Some(accessibility_parent) = accessibility_parent {
ui.ctx()
.register_accesskit_parent(ui.unique_id, accessibility_parent);
@@ -200,6 +202,7 @@ impl Ui {
ui.set_invisible();
}
#[cfg(feature = "accesskit")]
ui.ctx().accesskit_node_builder(ui.unique_id, |node| {
node.set_role(accesskit::Role::GenericContainer);
});
@@ -270,6 +273,7 @@ impl Ui {
sizing_pass,
style,
sense,
#[cfg(feature = "accesskit")]
accessibility_parent,
} = ui_builder;
@@ -339,6 +343,7 @@ impl Ui {
child_ui.disable();
}
#[cfg(feature = "accesskit")]
child_ui.ctx().register_accesskit_parent(
child_ui.unique_id,
accessibility_parent.unwrap_or(self.unique_id),
@@ -358,6 +363,7 @@ impl Ui {
true,
);
#[cfg(feature = "accesskit")]
child_ui
.ctx()
.accesskit_node_builder(child_ui.unique_id, |node| {
@@ -1123,6 +1129,7 @@ impl Ui {
impl Ui {
/// Check for clicks, drags and/or hover on a specific region of this [`Ui`].
pub fn interact(&self, rect: Rect, id: Id, sense: Sense) -> Response {
#[cfg(feature = "accesskit")]
self.ctx().register_accesskit_parent(id, self.unique_id);
self.ctx().create_widget(

View File

@@ -24,6 +24,7 @@ pub struct UiBuilder {
pub sizing_pass: bool,
pub style: Option<Arc<Style>>,
pub sense: Option<Sense>,
#[cfg(feature = "accesskit")]
pub accessibility_parent: Option<Id>,
}
@@ -186,9 +187,15 @@ impl UiBuilder {
///
/// This will override the automatic parent assignment for accessibility purposes.
/// If not set, the parent [`Ui`]'s ID will be used as the accessibility parent.
///
/// This does nothing if the `accesskit` feature is not enabled.
#[cfg_attr(not(feature = "accesskit"), expect(unused_mut, unused_variables))]
#[inline]
pub fn accessibility_parent(mut self, parent_id: Id) -> Self {
self.accessibility_parent = Some(parent_id);
#[cfg(feature = "accesskit")]
{
self.accessibility_parent = Some(parent_id);
}
self
}
}

View File

@@ -489,21 +489,27 @@ impl Widget for DragValue<'_> {
- input.count_and_consume_key(Modifiers::NONE, Key::ArrowDown) as f64;
}
use accesskit::Action;
change += input.num_accesskit_action_requests(id, Action::Increment) as f64
- input.num_accesskit_action_requests(id, Action::Decrement) as f64;
#[cfg(feature = "accesskit")]
{
use accesskit::Action;
change += input.num_accesskit_action_requests(id, Action::Increment) as f64
- input.num_accesskit_action_requests(id, Action::Decrement) as f64;
}
change
});
ui.input(|input| {
#[cfg(feature = "accesskit")]
{
use accesskit::{Action, ActionData};
for request in input.accesskit_action_requests(id, Action::SetValue) {
if let Some(ActionData::NumericValue(new_value)) = request.data {
value = new_value;
ui.input(|input| {
for request in input.accesskit_action_requests(id, Action::SetValue) {
if let Some(ActionData::NumericValue(new_value)) = request.data {
value = new_value;
}
}
}
});
});
}
if clamp_existing_to_range {
value = clamp_value_to_range(value, range.clone());
@@ -663,6 +669,7 @@ impl Widget for DragValue<'_> {
response.widget_info(|| WidgetInfo::drag_value(ui.is_enabled(), value));
#[cfg(feature = "accesskit")]
ui.ctx().accesskit_node_builder(response.id, |builder| {
use accesskit::Action;
// If either end of the range is unbounded, it's better

View File

@@ -716,11 +716,14 @@ impl Slider<'_> {
});
}
ui.input(|input| {
#[cfg(feature = "accesskit")]
{
use accesskit::Action;
decrement += input.num_accesskit_action_requests(response.id, Action::Decrement);
increment += input.num_accesskit_action_requests(response.id, Action::Increment);
});
ui.input(|input| {
decrement += input.num_accesskit_action_requests(response.id, Action::Decrement);
increment += input.num_accesskit_action_requests(response.id, Action::Increment);
});
}
let kb_step = increment as f32 - decrement as f32;
@@ -756,14 +759,17 @@ impl Slider<'_> {
self.set_value(new_value);
}
ui.input(|input| {
#[cfg(feature = "accesskit")]
{
use accesskit::{Action, ActionData};
for request in input.accesskit_action_requests(response.id, Action::SetValue) {
if let Some(ActionData::NumericValue(new_value)) = request.data {
self.set_value(new_value);
ui.input(|input| {
for request in input.accesskit_action_requests(response.id, Action::SetValue) {
if let Some(ActionData::NumericValue(new_value)) = request.data {
self.set_value(new_value);
}
}
}
});
});
}
// Paint it:
if ui.is_rect_visible(response.rect) {
@@ -972,6 +978,7 @@ impl Slider<'_> {
}
response.widget_info(|| WidgetInfo::slider(ui.is_enabled(), value, self.text.text()));
#[cfg(feature = "accesskit")]
ui.ctx().accesskit_node_builder(response.id, |builder| {
use accesskit::Action;
builder.set_min_numeric_value(*self.range.start());

View File

@@ -844,6 +844,7 @@ impl TextEdit<'_> {
});
}
#[cfg(feature = "accesskit")]
{
let role = if password {
accesskit::Role::PasswordInput

View File

@@ -35,7 +35,7 @@ x11 = ["eframe?/x11"]
[dependencies]
kittest.workspace = true
egui.workspace = true
egui = { workspace = true, features = ["accesskit"] }
eframe = { workspace = true, optional = true }
# wgpu dependencies