From e74ede6b0eb01a0600e6f15f36663f6222de820f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 22 Dec 2025 19:44:49 +0100 Subject: [PATCH 1/2] Allow moving widgets to top --- crates/egui/src/containers/area.rs | 1 + crates/egui/src/containers/window.rs | 6 ++++++ crates/egui/src/context.rs | 9 +++++++-- crates/egui/src/lib.rs | 2 +- crates/egui/src/response.rs | 1 + crates/egui/src/ui.rs | 15 +++++++++++++++ crates/egui/src/widget_rect.rs | 27 +++++++++++++++++++++++++-- 7 files changed, 56 insertions(+), 5 deletions(-) diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index a9ccd53cec7..3b6d4006ea3 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -523,6 +523,7 @@ impl Area { enabled, }, true, + Default::default(), ); // Used to prevent drift diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 4115272727e..4e3103342f9 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -958,6 +958,12 @@ fn resize_interaction( enabled: true, }, true, + InteractOptions { + // We call this multiple times. + // First to read the result (to avoid frame delay) + // and the second time to move it to the top, above the window contents. + move_to_top: true, + }, ); SideResponse { hover: response.hovered(), diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 8691c96e508..f8996b8a337 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1226,7 +1226,12 @@ impl Context { /// /// `allow_focus` should usually be true, unless you call this function multiple times with the /// same widget, then `allow_focus` should only be true once (like in [`Ui::new`] (true) and [`Ui::remember_min_rect`] (false)). - pub(crate) fn create_widget(&self, w: WidgetRect, allow_focus: bool) -> Response { + pub(crate) fn create_widget( + &self, + w: WidgetRect, + allow_focus: bool, + options: crate::InteractOptions, + ) -> Response { let interested_in_focus = w.enabled && w.sense.is_focusable() && self.memory(|mem| mem.allows_interaction(w.layer_id)); @@ -1238,7 +1243,7 @@ impl Context { // We add all widgets here, even non-interactive ones, // because we need this list not only for checking for blocking widgets, // but also to know when we have reached the widget we are checking for cover. - viewport.this_pass.widgets.insert(w.layer_id, w); + viewport.this_pass.widgets.insert(w.layer_id, w, options); if allow_focus && interested_in_focus { ctx.memory.interested_in_focus(w.id, w.layer_id); diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 794411bc83b..9af88a46592 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -494,7 +494,7 @@ pub use self::{ ui_builder::UiBuilder, ui_stack::*, viewport::*, - widget_rect::{WidgetRect, WidgetRects}, + widget_rect::{InteractOptions, WidgetRect, WidgetRects}, widget_text::{RichText, WidgetText}, widgets::*, }; diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 3e3ef73a7d6..3b6c3ff5960 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -738,6 +738,7 @@ impl Response { enabled: self.enabled(), }, true, + Default::default(), ) } diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 8535388a9d8..daabd10b019 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -180,6 +180,7 @@ impl Ui { enabled: ui.enabled, }, true, + Default::default(), ); if disabled { @@ -345,6 +346,7 @@ impl Ui { enabled: child_ui.enabled, }, true, + Default::default(), ); child_ui @@ -1025,6 +1027,17 @@ 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 { + self.interact_opt(rect, id, sense, Default::default()) + } + + /// Check for clicks, drags and/or hover on a specific region of this [`Ui`]. + pub fn interact_opt( + &self, + rect: Rect, + id: Id, + sense: Sense, + options: crate::InteractOptions, + ) -> Response { self.ctx().register_accesskit_parent(id, self.unique_id); self.ctx().create_widget( @@ -1037,6 +1050,7 @@ impl Ui { enabled: self.enabled, }, true, + options, ) } @@ -1105,6 +1119,7 @@ impl Ui { enabled: self.enabled, }, false, + Default::default(), ); if self.should_close() { response.set_close(); diff --git a/crates/egui/src/widget_rect.rs b/crates/egui/src/widget_rect.rs index fa99c95dc07..40fcd3fa6b7 100644 --- a/crates/egui/src/widget_rect.rs +++ b/crates/egui/src/widget_rect.rs @@ -63,6 +63,21 @@ impl WidgetRect { } } +/// How to handle multiple calls to [`Response::interact`]. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct InteractOptions { + /// If we call interact on the same widget multiple times, + /// should we move it to the top on subsequent calls? + pub move_to_top: bool, +} + +#[expect(clippy::derivable_impls)] // Nice to be explicit +impl Default for InteractOptions { + fn default() -> Self { + Self { move_to_top: false } + } +} + /// Stores the [`WidgetRect`]s of all widgets generated during a single egui update/frame. /// /// All [`crate::Ui`]s have a [`WidgetRect`]. It is created in [`crate::Ui::new`] with [`Rect::NOTHING`] @@ -140,13 +155,15 @@ impl WidgetRects { } /// Insert the given widget rect in the given layer. - pub fn insert(&mut self, layer_id: LayerId, widget_rect: WidgetRect) { + pub fn insert(&mut self, layer_id: LayerId, widget_rect: WidgetRect, options: InteractOptions) { let Self { by_layer, by_id, infos: _, } = self; + let InteractOptions { move_to_top } = options; + let layer_widgets = by_layer.entry(layer_id).or_default(); match by_id.entry(widget_rect.id) { @@ -176,7 +193,13 @@ impl WidgetRects { existing.enabled |= widget_rect.enabled; if existing.layer_id == widget_rect.layer_id { - layer_widgets[*idx_in_layer] = *existing; + if move_to_top { + layer_widgets.remove(*idx_in_layer); + *idx_in_layer = layer_widgets.len(); + layer_widgets.push(*existing); + } else { + layer_widgets[*idx_in_layer] = *existing; + } } } } From 6eb37abb1da8281614a186f481e399d5cf47b96f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 22 Dec 2025 21:26:27 +0100 Subject: [PATCH 2/2] Fix doclink --- crates/egui/src/widget_rect.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/widget_rect.rs b/crates/egui/src/widget_rect.rs index 40fcd3fa6b7..c39b28b5021 100644 --- a/crates/egui/src/widget_rect.rs +++ b/crates/egui/src/widget_rect.rs @@ -63,7 +63,7 @@ impl WidgetRect { } } -/// How to handle multiple calls to [`Response::interact`]. +/// How to handle multiple calls to [`crate::Response::interact`] and [`crate::Ui::interact_opt`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct InteractOptions { /// If we call interact on the same widget multiple times,