1
0
mirror of https://github.com/emilk/egui.git synced 2026-09-02 14:50:03 -04:00

Allow moving existing widgets to the top of interaction stack (#7805)

This adds `Ui::interact_opt` which is a version of `Ui::interact` that
lets you specify that if this widget already exists, it should be moved
to the top of the interaction stack.

This lets you easily more easily "read a response from the future", by
simply calling interact twice, instead of calling
`Context::read_response`.
This commit is contained in:
Emil Ernerfeldt
2025-12-22 21:34:03 +01:00
committed by GitHub
parent 24ac9e193d
commit 1fdc741ebb
7 changed files with 56 additions and 5 deletions

View File

@@ -523,6 +523,7 @@ impl Area {
enabled, enabled,
}, },
true, true,
Default::default(),
); );
// Used to prevent drift // Used to prevent drift

View File

@@ -958,6 +958,12 @@ fn resize_interaction(
enabled: true, enabled: true,
}, },
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,
},
); );
response.widget_info(|| WidgetInfo::new(crate::WidgetType::ResizeHandle)); response.widget_info(|| WidgetInfo::new(crate::WidgetType::ResizeHandle));

View File

@@ -1226,7 +1226,12 @@ impl Context {
/// ///
/// `allow_focus` should usually be true, unless you call this function multiple times with the /// `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)). /// 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 let interested_in_focus = w.enabled
&& w.sense.is_focusable() && w.sense.is_focusable()
&& self.memory(|mem| mem.allows_interaction(w.layer_id)); && self.memory(|mem| mem.allows_interaction(w.layer_id));
@@ -1238,7 +1243,7 @@ impl Context {
// We add all widgets here, even non-interactive ones, // We add all widgets here, even non-interactive ones,
// because we need this list not only for checking for blocking widgets, // 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. // 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 { if allow_focus && interested_in_focus {
ctx.memory.interested_in_focus(w.id, w.layer_id); ctx.memory.interested_in_focus(w.id, w.layer_id);

View File

@@ -494,7 +494,7 @@ pub use self::{
ui_builder::UiBuilder, ui_builder::UiBuilder,
ui_stack::*, ui_stack::*,
viewport::*, viewport::*,
widget_rect::{WidgetRect, WidgetRects}, widget_rect::{InteractOptions, WidgetRect, WidgetRects},
widget_text::{RichText, WidgetText}, widget_text::{RichText, WidgetText},
widgets::*, widgets::*,
}; };

View File

@@ -738,6 +738,7 @@ impl Response {
enabled: self.enabled(), enabled: self.enabled(),
}, },
true, true,
Default::default(),
) )
} }

View File

@@ -180,6 +180,7 @@ impl Ui {
enabled: ui.enabled, enabled: ui.enabled,
}, },
true, true,
Default::default(),
); );
if disabled { if disabled {
@@ -345,6 +346,7 @@ impl Ui {
enabled: child_ui.enabled, enabled: child_ui.enabled,
}, },
true, true,
Default::default(),
); );
child_ui child_ui
@@ -1025,6 +1027,17 @@ impl Ui {
impl Ui { impl Ui {
/// Check for clicks, drags and/or hover on a specific region of this [`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 { 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().register_accesskit_parent(id, self.unique_id);
self.ctx().create_widget( self.ctx().create_widget(
@@ -1037,6 +1050,7 @@ impl Ui {
enabled: self.enabled, enabled: self.enabled,
}, },
true, true,
options,
) )
} }
@@ -1105,6 +1119,7 @@ impl Ui {
enabled: self.enabled, enabled: self.enabled,
}, },
false, false,
Default::default(),
); );
if self.should_close() { if self.should_close() {
response.set_close(); response.set_close();

View File

@@ -63,6 +63,21 @@ impl WidgetRect {
} }
} }
/// 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,
/// 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. /// 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`] /// 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. /// 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 { let Self {
by_layer, by_layer,
by_id, by_id,
infos: _, infos: _,
} = self; } = self;
let InteractOptions { move_to_top } = options;
let layer_widgets = by_layer.entry(layer_id).or_default(); let layer_widgets = by_layer.entry(layer_id).or_default();
match by_id.entry(widget_rect.id) { match by_id.entry(widget_rect.id) {
@@ -176,7 +193,13 @@ impl WidgetRects {
existing.enabled |= widget_rect.enabled; existing.enabled |= widget_rect.enabled;
if existing.layer_id == widget_rect.layer_id { 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;
}
} }
} }
} }