Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
68e4530
New Plugin trait
lucasmerlin Jul 16, 2025
07bd077
New Plugin trait
lucasmerlin Jul 16, 2025
9441580
Add setup fn, prevent adding popup multiple times and add documentation
lucasmerlin Jul 23, 2025
54f56c4
Lint
lucasmerlin Jul 23, 2025
e5887bb
Add setup fn, prevent adding popup multiple times and add documentation
lucasmerlin Jul 23, 2025
ec40261
Lint
lucasmerlin Jul 23, 2025
4488bec
Add basic accessibility inspector plugin
lucasmerlin Jul 16, 2025
5a320ac
Merge branch 'lucas/plugin-trait' into lucas/accessibility-inspector
lucasmerlin Jul 23, 2025
593efaf
Group accessibility nodes by Ui
lucasmerlin Jul 17, 2025
c1d8d10
Add panel widget type
lucasmerlin Jul 17, 2025
fb99bba
Don't call setup again when adding plugin multiple times
lucasmerlin Jul 23, 2025
a6c4e2f
Make it possible to obtain a typed reference to a plugin
lucasmerlin Jul 23, 2025
b42c3f6
Move to plugins.rs
lucasmerlin Jul 23, 2025
d0115d7
Port LabelSelectionState to new Plugin api
lucasmerlin Jul 23, 2025
4590770
Port remaining plugins
lucasmerlin Jul 23, 2025
8a5317d
Clippy
lucasmerlin Jul 23, 2025
d72323f
Fix deadlock
lucasmerlin Jul 23, 2025
f1449ea
Merge branch 'lucas/plugin-trait' into lucas/accessibility-inspector
lucasmerlin Jul 23, 2025
dcf52b2
Make second side panel a bottom panel
lucasmerlin Jul 24, 2025
8b2104f
Grid
lucasmerlin Jul 24, 2025
bb9ffc8
Small ui improvements
lucasmerlin Jul 24, 2025
c054654
Merge branch 'lucas/accessibility-inspector' into lucas/group-accessk…
lucasmerlin Jul 24, 2025
4b18cc5
Fixes after merge
lucasmerlin Jul 24, 2025
0033e45
Clippy
lucasmerlin Jul 24, 2025
1fc9f1b
Lint
lucasmerlin Jul 24, 2025
821b9ac
Merge branch 'lucas/accessibility-inspector' into lucas/group-accessk…
lucasmerlin Jul 24, 2025
37ec69a
Clippy
lucasmerlin Jul 24, 2025
6eb19b0
Fix features
lucasmerlin Jul 24, 2025
f1facab
Fix features
lucasmerlin Jul 24, 2025
51068ca
Fix space
lucasmerlin Jul 24, 2025
bda05df
More fixes
lucasmerlin Jul 24, 2025
729a590
Fix
lucasmerlin Jul 24, 2025
cb542e7
Fix
lucasmerlin Jul 24, 2025
e895575
Fix doc comment
lucasmerlin Jul 24, 2025
e816265
Use cfg attr and expect
lucasmerlin Jul 24, 2025
a226d8f
Fix doc link
lucasmerlin Jul 24, 2025
5ce5b7f
Fix dnd deadlock
lucasmerlin Jul 28, 2025
c857fb9
Fixes from review
lucasmerlin Aug 7, 2025
cf1f747
Merge branch 'lucas/plugin-trait' into lucas/accessibility-inspector
lucasmerlin Aug 7, 2025
da3d65d
Add `Id::from_high_entropy_bits`
lucasmerlin Aug 7, 2025
dac5798
Merge branch 'main' into lucas/accessibility-inspector
lucasmerlin Oct 7, 2025
2c3ab28
Clippy
lucasmerlin Oct 7, 2025
9348fea
Merge branch 'lucas/accessibility-inspector' into lucas/group-accessk…
lucasmerlin Oct 7, 2025
9291e80
Fmt
lucasmerlin Oct 7, 2025
2d21997
Resolve some review comments
lucasmerlin Oct 7, 2025
bbf6af8
Resolve more review comments
lucasmerlin Oct 7, 2025
c187dff
Pull out selection_ui
lucasmerlin Oct 7, 2025
d42b88b
Clippy / fmt
lucasmerlin Oct 7, 2025
730c59a
Cleanup
lucasmerlin Oct 7, 2025
e905128
Merge branch 'lucas/accessibility-inspector' into lucas/group-accessk…
lucasmerlin Oct 8, 2025
3f45b62
Remove unused code
lucasmerlin Oct 8, 2025
28feaf9
Fix accesskit tests
lucasmerlin Oct 8, 2025
bd29fba
Merge branch 'main' into lucas/group-accesskit-nodes-by-ui
lucasmerlin Oct 8, 2025
edf5973
Fix test
lucasmerlin Oct 8, 2025
86c3cce
Rename fn
lucasmerlin Oct 8, 2025
3809534
Clippy
lucasmerlin Oct 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/egui/src/containers/area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,7 @@ impl Prepared {
.layer_id(self.layer_id)
.max_rect(max_rect)
.layout(self.layout)
.accessibility_parent(self.move_response.id)
.closable();

if !self.enabled {
Expand Down
6 changes: 5 additions & 1 deletion crates/egui/src/containers/panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ use emath::GuiRounding as _;

use crate::{
Align, Context, CursorIcon, Frame, Id, InnerResponse, LayerId, Layout, NumExt as _, Rangef,
Rect, Sense, Stroke, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, lerp, vec2,
Rect, Sense, Stroke, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetInfo, WidgetType, lerp,
vec2,
};

fn animate_expansion(ctx: &Context, id: Id, is_expanded: bool) -> f32 {
Expand Down Expand Up @@ -390,6 +391,9 @@ impl SidePanel {
.max_rect(available_rect),
);
panel_ui.set_clip_rect(ctx.content_rect());
panel_ui
.response()
.widget_info(|| WidgetInfo::new(WidgetType::Panel));

let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);
let rect = inner_response.response.rect;
Expand Down
192 changes: 96 additions & 96 deletions crates/egui/src/containers/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,15 +505,14 @@ impl Window<'_> {

// First check for resize to avoid frame delay:
let last_frame_outer_rect = area.state().rect();
let resize_interaction = ctx.with_accessibility_parent(area.id(), || {
resize_interaction(
ctx,
possible,
area_layer_id,
last_frame_outer_rect,
window_frame,
)
});
let resize_interaction = resize_interaction(
ctx,
possible,
area.id(),
area_layer_id,
last_frame_outer_rect,
window_frame,
);

{
let margins = window_frame.total_margin().sum()
Expand All @@ -538,109 +537,107 @@ impl Window<'_> {
}

let content_inner = {
ctx.with_accessibility_parent(area.id(), || {
// BEGIN FRAME --------------------------------
let mut frame = window_frame.begin(&mut area_content_ui);

let show_close_button = open.is_some();

let where_to_put_header_background = &area_content_ui.painter().add(Shape::Noop);

let title_bar = if with_title_bar {
let title_bar = TitleBar::new(
&frame.content_ui,
title,
show_close_button,
collapsible,
window_frame,
title_bar_height_with_margin,
);
resize.min_size.x = resize.min_size.x.at_least(title_bar.inner_rect.width()); // Prevent making window smaller than title bar width

frame.content_ui.set_min_size(title_bar.inner_rect.size());

// Skip the title bar (and separator):
if is_collapsed {
frame.content_ui.add_space(title_bar.inner_rect.height());
} else {
frame.content_ui.add_space(
title_bar.inner_rect.height()
+ title_content_spacing
+ window_frame.inner_margin.sum().y,
);
}
// BEGIN FRAME --------------------------------
let mut frame = window_frame.begin(&mut area_content_ui);

Some(title_bar)
} else {
None
};

let (content_inner, content_response) = collapsing
.show_body_unindented(&mut frame.content_ui, |ui| {
resize.show(ui, |ui| {
if scroll.is_any_scroll_enabled() {
scroll.show(ui, add_contents).inner
} else {
add_contents(ui)
}
})
})
.map_or((None, None), |ir| (Some(ir.inner), Some(ir.response)));

let outer_rect = frame.end(&mut area_content_ui).rect;
paint_resize_corner(
&area_content_ui,
&possible,
outer_rect,
&window_frame,
resize_interaction,
let show_close_button = open.is_some();

let where_to_put_header_background = &area_content_ui.painter().add(Shape::Noop);

let title_bar = if with_title_bar {
let title_bar = TitleBar::new(
&frame.content_ui,
title,
show_close_button,
collapsible,
window_frame,
title_bar_height_with_margin,
);
resize.min_size.x = resize.min_size.x.at_least(title_bar.inner_rect.width()); // Prevent making window smaller than title bar width

// END FRAME --------------------------------
frame.content_ui.set_min_size(title_bar.inner_rect.size());

if let Some(mut title_bar) = title_bar {
title_bar.inner_rect = outer_rect.shrink(window_frame.stroke.width);
title_bar.inner_rect.max.y =
title_bar.inner_rect.min.y + title_bar_height_with_margin;
// Skip the title bar (and separator):
if is_collapsed {
frame.content_ui.add_space(title_bar.inner_rect.height());
} else {
frame.content_ui.add_space(
title_bar.inner_rect.height()
+ title_content_spacing
+ window_frame.inner_margin.sum().y,
);
}

if on_top && area_content_ui.visuals().window_highlight_topmost {
let mut round =
window_frame.corner_radius - window_frame.stroke.width.round() as u8;
Some(title_bar)
} else {
None
};

if !is_collapsed {
round.se = 0;
round.sw = 0;
let (content_inner, content_response) = collapsing
.show_body_unindented(&mut frame.content_ui, |ui| {
resize.show(ui, |ui| {
if scroll.is_any_scroll_enabled() {
scroll.show(ui, add_contents).inner
} else {
add_contents(ui)
}
})
})
.map_or((None, None), |ir| (Some(ir.inner), Some(ir.response)));

let outer_rect = frame.end(&mut area_content_ui).rect;
paint_resize_corner(
&area_content_ui,
&possible,
outer_rect,
&window_frame,
resize_interaction,
);

area_content_ui.painter().set(
*where_to_put_header_background,
RectShape::filled(title_bar.inner_rect, round, header_color),
);
}
// END FRAME --------------------------------

if let Some(mut title_bar) = title_bar {
title_bar.inner_rect = outer_rect.shrink(window_frame.stroke.width);
title_bar.inner_rect.max.y =
title_bar.inner_rect.min.y + title_bar_height_with_margin;

if false {
ctx.debug_painter().debug_rect(
title_bar.inner_rect,
Color32::LIGHT_BLUE,
"title_bar.rect",
);
if on_top && area_content_ui.visuals().window_highlight_topmost {
let mut round =
window_frame.corner_radius - window_frame.stroke.width.round() as u8;

if !is_collapsed {
round.se = 0;
round.sw = 0;
}

title_bar.ui(
&mut area_content_ui,
&content_response,
open.as_deref_mut(),
&mut collapsing,
collapsible,
area_content_ui.painter().set(
*where_to_put_header_background,
RectShape::filled(title_bar.inner_rect, round, header_color),
);
}

if false {
ctx.debug_painter().debug_rect(
title_bar.inner_rect,
Color32::LIGHT_BLUE,
"title_bar.rect",
);
}

collapsing.store(ctx);
title_bar.ui(
&mut area_content_ui,
&content_response,
open.as_deref_mut(),
&mut collapsing,
collapsible,
);
}

collapsing.store(ctx);

paint_frame_interaction(&area_content_ui, outer_rect, resize_interaction);
paint_frame_interaction(&area_content_ui, outer_rect, resize_interaction);

content_inner
})
content_inner
};

let full_response = area.end(ctx, area_content_ui);
Expand Down Expand Up @@ -882,6 +879,7 @@ fn move_and_resize_window(ctx: &Context, interaction: &ResizeInteraction) -> Opt
fn resize_interaction(
ctx: &Context,
possible: PossibleInteractions,
_accessibility_parent: Id,
layer_id: LayerId,
outer_rect: Rect,
window_frame: Frame,
Expand All @@ -901,6 +899,8 @@ 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 {
layer_id,
Expand Down
72 changes: 34 additions & 38 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ impl ContextImpl {
nodes.insert(id, root_node);
viewport.this_pass.accesskit_state = Some(AccessKitPassState {
nodes,
parent_stack: vec![id],
parent_map: IdMap::default(),
});
}

Expand Down Expand Up @@ -595,8 +595,28 @@ impl ContextImpl {
let builders = &mut state.nodes;
if let std::collections::hash_map::Entry::Vacant(entry) = builders.entry(id) {
entry.insert(Default::default());
let parent_id = state.parent_stack.last().unwrap();
let parent_builder = builders.get_mut(parent_id).unwrap();

/// Find the first ancestor that already has an accesskit node.
fn find_accesskit_parent(
parent_map: &IdMap<Id>,
node_map: &IdMap<accesskit::Node>,
id: Id,
) -> Option<Id> {
if let Some(parent_id) = parent_map.get(&id) {
if node_map.contains_key(parent_id) {
Some(*parent_id)
} else {
find_accesskit_parent(parent_map, node_map, *parent_id)
}
} else {
None
}
}

let parent_id = find_accesskit_parent(&state.parent_map, builders, id)
.unwrap_or(crate::accesskit_root_id());

let parent_builder = builders.get_mut(&parent_id).unwrap();
parent_builder.push_child(id.accesskit_id());
}
builders.get_mut(&id).unwrap()
Expand Down Expand Up @@ -3464,43 +3484,10 @@ impl Context {

/// ## Accessibility
impl Context {
/// Call the provided function with the given ID pushed on the stack of
/// parent IDs for accessibility purposes. If the `accesskit` feature
/// is disabled or if AccessKit support is not active for this frame,
/// the function is still called, but with no other effect.
///
/// No locks are held while the given closure is called.
#[allow(clippy::unused_self, clippy::let_and_return, clippy::allow_attributes)]
#[inline]
pub fn with_accessibility_parent<R>(&self, _id: Id, f: impl FnOnce() -> R) -> R {
// TODO(emilk): this isn't thread-safe - another thread can call this function between the push/pop calls
#[cfg(feature = "accesskit")]
self.pass_state_mut(|fs| {
if let Some(state) = fs.accesskit_state.as_mut() {
state.parent_stack.push(_id);
}
});

let result = f();

#[cfg(feature = "accesskit")]
self.pass_state_mut(|fs| {
if let Some(state) = fs.accesskit_state.as_mut() {
assert_eq!(
state.parent_stack.pop(),
Some(_id),
"Mismatched push/pop in with_accessibility_parent"
);
}
});

result
}

/// If AccessKit support is active for the current frame, get or create
/// a node builder with the specified ID and return a mutable reference to it.
/// For newly created nodes, the parent is the node with the ID at the top
/// of the stack managed by [`Context::with_accessibility_parent`].
/// For newly created nodes, the parent is the parent [`Ui`]s ID.
/// And an [`Ui`]s parent can be set with [`crate::UiBuilder::accessibility_parent`].
///
/// The `Context` lock is held while the given closure is called!
///
Expand All @@ -3522,6 +3509,15 @@ 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() {
state.parent_map.insert(id, parent_id);
}
});
}

/// Enable generation of AccessKit tree updates in all future frames.
#[cfg(feature = "accesskit")]
pub fn enable_accesskit(&self) {
Expand Down
1 change: 1 addition & 0 deletions crates/egui/src/data/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,7 @@ impl WidgetInfo {
WidgetType::ColorButton => "color button",
WidgetType::Image => "image",
WidgetType::CollapsingHeader => "collapsing header",
WidgetType::Panel => "panel",
WidgetType::ProgressIndicator => "progress indicator",
WidgetType::Window => "window",
WidgetType::Label | WidgetType::Other => "",
Expand Down
2 changes: 2 additions & 0 deletions crates/egui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,8 @@ pub enum WidgetType {

CollapsingHeader,

Panel,

ProgressIndicator,

Window,
Expand Down
2 changes: 1 addition & 1 deletion crates/egui/src/pass_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl ScrollTarget {
#[derive(Clone)]
pub struct AccessKitPassState {
pub nodes: IdMap<accesskit::Node>,
pub parent_stack: Vec<Id>,
pub parent_map: IdMap<Id>,
}

#[cfg(debug_assertions)]
Expand Down
1 change: 1 addition & 0 deletions crates/egui/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,7 @@ impl Response {
WidgetType::Slider => Role::Slider,
WidgetType::DragValue => Role::SpinButton,
WidgetType::ColorButton => Role::ColorWell,
WidgetType::Panel => Role::Pane,
WidgetType::ProgressIndicator => Role::ProgressIndicator,
WidgetType::Window => Role::Window,
WidgetType::Other => Role::Unknown,
Expand Down
Loading