Deprecate `Memory::popup` API in favor of new `Popup` API (#7317)

* Closes  #7037
* Closes #7297

This deprecates all popup-related function in `Memory`, replacing them
with the new `egui::Popup`.

The new API is nicer in all ways, so we should encourage people to use
it.
This commit is contained in:
Emil Ernerfeldt 2025-07-09 12:55:06 +02:00 committed by GitHub
parent fbe0aadf63
commit a7f14ca176
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 108 additions and 33 deletions

View File

@ -293,7 +293,7 @@ impl ComboBox {
/// Check if the [`ComboBox`] with the given id has its popup menu currently opened. /// Check if the [`ComboBox`] with the given id has its popup menu currently opened.
pub fn is_open(ctx: &Context, id: Id) -> bool { pub fn is_open(ctx: &Context, id: Id) -> bool {
ctx.memory(|m| m.is_popup_open(Self::widget_to_popup_id(id))) Popup::is_id_open(ctx, Self::widget_to_popup_id(id))
} }
/// Convert a [`ComboBox`] id to the id used to store it's popup state. /// Convert a [`ComboBox`] id to the id used to store it's popup state.
@ -315,7 +315,7 @@ fn combo_box_dyn<'c, R>(
) -> InnerResponse<Option<R>> { ) -> InnerResponse<Option<R>> {
let popup_id = ComboBox::widget_to_popup_id(button_id); let popup_id = ComboBox::widget_to_popup_id(button_id);
let is_popup_open = ui.memory(|m| m.is_popup_open(popup_id)); let is_popup_open = Popup::is_id_open(ui.ctx(), popup_id);
let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode()); let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode());

View File

@ -1,7 +1,8 @@
use emath::{Align2, Vec2};
use crate::{ use crate::{
Area, Color32, Context, Frame, Id, InnerResponse, Order, Response, Sense, Ui, UiBuilder, UiKind, Area, Color32, Context, Frame, Id, InnerResponse, Order, Response, Sense, Ui, UiBuilder, UiKind,
}; };
use emath::{Align2, Vec2};
/// A modal dialog. /// A modal dialog.
/// ///
@ -80,13 +81,11 @@ impl Modal {
frame, frame,
} = self; } = self;
let (is_top_modal, any_popup_open) = ctx.memory_mut(|mem| { let is_top_modal = ctx.memory_mut(|mem| {
mem.set_modal_layer(area.layer()); mem.set_modal_layer(area.layer());
( mem.top_modal_layer() == Some(area.layer())
mem.top_modal_layer() == Some(area.layer()),
mem.any_popup_open(),
)
}); });
let any_popup_open = crate::Popup::is_any_open(ctx);
let InnerResponse { let InnerResponse {
inner: (inner, backdrop_response), inner: (inner, backdrop_response),
response, response,

View File

@ -1,11 +1,15 @@
use crate::containers::menu::{MenuConfig, MenuState, menu_style}; #![expect(deprecated)] // This is a new, safe wrapper around the old `Memory::popup` API.
use crate::style::StyleModifier;
use std::iter::once;
use emath::{Align, Pos2, Rect, RectAlign, Vec2, vec2};
use crate::{ use crate::{
Area, AreaState, Context, Frame, Id, InnerResponse, Key, LayerId, Layout, Order, Response, Area, AreaState, Context, Frame, Id, InnerResponse, Key, LayerId, Layout, Order, Response,
Sense, Ui, UiKind, UiStackInfo, Sense, Ui, UiKind, UiStackInfo,
containers::menu::{MenuConfig, MenuState, menu_style},
style::StyleModifier,
}; };
use emath::{Align, Pos2, Rect, RectAlign, Vec2, vec2};
use std::iter::once;
/// What should we anchor the popup to? /// What should we anchor the popup to?
/// ///
@ -64,9 +68,7 @@ impl PopupAnchor {
match self { match self {
Self::ParentRect(rect) => Some(rect), Self::ParentRect(rect) => Some(rect),
Self::Pointer => ctx.pointer_hover_pos().map(Rect::from_pos), Self::Pointer => ctx.pointer_hover_pos().map(Rect::from_pos),
Self::PointerFixed => ctx Self::PointerFixed => Popup::position_of_id(ctx, popup_id).map(Rect::from_pos),
.memory(|mem| mem.popup_position(popup_id))
.map(Rect::from_pos),
Self::Position(pos) => Some(Rect::from_pos(pos)), Self::Position(pos) => Some(Rect::from_pos(pos)),
} }
} }
@ -122,12 +124,12 @@ enum OpenKind<'a> {
impl OpenKind<'_> { impl OpenKind<'_> {
/// Returns `true` if the popup should be open /// Returns `true` if the popup should be open
fn is_open(&self, id: Id, ctx: &Context) -> bool { fn is_open(&self, popup_id: Id, ctx: &Context) -> bool {
match self { match self {
OpenKind::Open => true, OpenKind::Open => true,
OpenKind::Closed => false, OpenKind::Closed => false,
OpenKind::Bool(open) => **open, OpenKind::Bool(open) => **open,
OpenKind::Memory { .. } => ctx.memory(|mem| mem.is_popup_open(id)), OpenKind::Memory { .. } => Popup::is_id_open(ctx, popup_id),
} }
} }
} }
@ -217,7 +219,7 @@ impl<'a> Popup<'a> {
/// See [`Self::menu`] and [`Self::context_menu`] for common use cases. /// See [`Self::menu`] and [`Self::context_menu`] for common use cases.
pub fn from_response(response: &Response) -> Self { pub fn from_response(response: &Response) -> Self {
let mut popup = Self::new( let mut popup = Self::new(
response.id.with("popup"), Self::default_response_id(response),
response.ctx.clone(), response.ctx.clone(),
response, response,
response.layer_id, response.layer_id,
@ -455,7 +457,7 @@ impl<'a> Popup<'a> {
OpenKind::Open => true, OpenKind::Open => true,
OpenKind::Closed => false, OpenKind::Closed => false,
OpenKind::Bool(open) => **open, OpenKind::Bool(open) => **open,
OpenKind::Memory { .. } => self.ctx.memory(|mem| mem.is_popup_open(self.id)), OpenKind::Memory { .. } => Self::is_id_open(&self.ctx, self.id),
} }
} }
@ -504,26 +506,26 @@ impl<'a> Popup<'a> {
let id = self.id; let id = self.id;
if let OpenKind::Memory { set } = self.open_kind { if let OpenKind::Memory { set } = self.open_kind {
self.ctx.memory_mut(|mem| match set { match set {
Some(SetOpenCommand::Bool(open)) => { Some(SetOpenCommand::Bool(open)) => {
if open { if open {
match self.anchor { match self.anchor {
PopupAnchor::PointerFixed => { PopupAnchor::PointerFixed => {
mem.open_popup_at(id, hover_pos); self.ctx.memory_mut(|mem| mem.open_popup_at(id, hover_pos));
} }
_ => mem.open_popup(id), _ => Popup::open_id(&self.ctx, id),
} }
} else { } else {
mem.close_popup(id); Self::close_id(&self.ctx, id);
} }
} }
Some(SetOpenCommand::Toggle) => { Some(SetOpenCommand::Toggle) => {
mem.toggle_popup(id); Self::toggle_id(&self.ctx, id);
} }
None => { None => {
mem.keep_popup_open(id); self.ctx.memory_mut(|mem| mem.keep_popup_open(id));
} }
}); }
} }
if !self.open_kind.is_open(self.id, &self.ctx) { if !self.open_kind.is_open(self.id, &self.ctx) {
@ -627,3 +629,65 @@ impl<'a> Popup<'a> {
Some(response) Some(response)
} }
} }
/// ## Static methods
impl Popup<'_> {
/// The default ID when constructing a popup from the [`Response`] of e.g. a button.
pub fn default_response_id(response: &Response) -> Id {
response.id.with("popup")
}
/// Is the given popup open?
///
/// This assumes the use of either:
/// * [`Self::open_memory`]
/// * [`Self::from_toggle_button_response`]
/// * [`Self::menu`]
/// * [`Self::context_menu`]
///
/// The popup id should be the same as either you set with [`Self::id`] or the
/// default one from [`Self::default_response_id`].
pub fn is_id_open(ctx: &Context, popup_id: Id) -> bool {
ctx.memory(|mem| mem.is_popup_open(popup_id))
}
/// Is any popup open?
///
/// This assumes the egui memory is being used to track the open state of popups.
pub fn is_any_open(ctx: &Context) -> bool {
ctx.memory(|mem| mem.any_popup_open())
}
/// Open the given popup and close all others.
///
/// If you are NOT using [`Popup::show`], you must
/// also call [`crate::Memory::keep_popup_open`] as long as
/// you're showing the popup.
pub fn open_id(ctx: &Context, popup_id: Id) {
ctx.memory_mut(|mem| mem.open_popup(popup_id));
}
/// Toggle the given popup between closed and open.
///
/// Note: At most, only one popup can be open at a time.
pub fn toggle_id(ctx: &Context, popup_id: Id) {
ctx.memory_mut(|mem| mem.toggle_popup(popup_id));
}
/// Close all currently open popups.
pub fn close_all(ctx: &Context) {
ctx.memory_mut(|mem| mem.close_all_popups());
}
/// Close the given popup, if it is open.
///
/// See also [`Self::close_all`] if you want to close any / all currently open popups.
pub fn close_id(ctx: &Context, popup_id: Id) {
ctx.memory_mut(|mem| mem.close_popup(popup_id));
}
/// Get the position for this popup, if it is open.
pub fn position_of_id(ctx: &Context, popup_id: Id) -> Option<Pos2> {
ctx.memory(|mem| mem.popup_position(popup_id))
}
}

View File

@ -1012,11 +1012,11 @@ impl OpenPopup {
} }
} }
/// ## Popups /// ## Deprecated popup API
/// Popups are things like combo-boxes, color pickers, menus etc. /// Use [`crate::Popup`] instead.
/// Only one can be open at a time.
impl Memory { impl Memory {
/// Is the given popup open? /// Is the given popup open?
#[deprecated = "Use Popup::is_id_open instead"]
pub fn is_popup_open(&self, popup_id: Id) -> bool { pub fn is_popup_open(&self, popup_id: Id) -> bool {
self.popups self.popups
.get(&self.viewport_id) .get(&self.viewport_id)
@ -1025,6 +1025,7 @@ impl Memory {
} }
/// Is any popup open? /// Is any popup open?
#[deprecated = "Use Popup::is_any_open instead"]
pub fn any_popup_open(&self) -> bool { pub fn any_popup_open(&self) -> bool {
self.popups.contains_key(&self.viewport_id) || self.everything_is_visible() self.popups.contains_key(&self.viewport_id) || self.everything_is_visible()
} }
@ -1032,6 +1033,7 @@ impl Memory {
/// Open the given popup and close all others. /// Open the given popup and close all others.
/// ///
/// Note that you must call `keep_popup_open` on subsequent frames as long as the popup is open. /// Note that you must call `keep_popup_open` on subsequent frames as long as the popup is open.
#[deprecated = "Use Popup::open_id instead"]
pub fn open_popup(&mut self, popup_id: Id) { pub fn open_popup(&mut self, popup_id: Id) {
self.popups self.popups
.insert(self.viewport_id, OpenPopup::new(popup_id, None)); .insert(self.viewport_id, OpenPopup::new(popup_id, None));
@ -1042,6 +1044,7 @@ impl Memory {
/// This is needed because in some cases popups can go away without `close_popup` being /// This is needed because in some cases popups can go away without `close_popup` being
/// called. For example, when a context menu is open and the underlying widget stops /// called. For example, when a context menu is open and the underlying widget stops
/// being rendered. /// being rendered.
#[deprecated = "Use Popup::show instead"]
pub fn keep_popup_open(&mut self, popup_id: Id) { pub fn keep_popup_open(&mut self, popup_id: Id) {
if let Some(state) = self.popups.get_mut(&self.viewport_id) { if let Some(state) = self.popups.get_mut(&self.viewport_id) {
if state.id == popup_id { if state.id == popup_id {
@ -1051,12 +1054,14 @@ impl Memory {
} }
/// Open the popup and remember its position. /// Open the popup and remember its position.
#[deprecated = "Use Popup with PopupAnchor::Position instead"]
pub fn open_popup_at(&mut self, popup_id: Id, pos: impl Into<Option<Pos2>>) { pub fn open_popup_at(&mut self, popup_id: Id, pos: impl Into<Option<Pos2>>) {
self.popups self.popups
.insert(self.viewport_id, OpenPopup::new(popup_id, pos.into())); .insert(self.viewport_id, OpenPopup::new(popup_id, pos.into()));
} }
/// Get the position for this popup. /// Get the position for this popup.
#[deprecated = "Use Popup::position_of_id instead"]
pub fn popup_position(&self, id: Id) -> Option<Pos2> { pub fn popup_position(&self, id: Id) -> Option<Pos2> {
self.popups self.popups
.get(&self.viewport_id) .get(&self.viewport_id)
@ -1064,6 +1069,7 @@ impl Memory {
} }
/// Close any currently open popup. /// Close any currently open popup.
#[deprecated = "Use Popup::close_all instead"]
pub fn close_all_popups(&mut self) { pub fn close_all_popups(&mut self) {
self.popups.clear(); self.popups.clear();
} }
@ -1071,7 +1077,9 @@ impl Memory {
/// Close the given popup, if it is open. /// Close the given popup, if it is open.
/// ///
/// See also [`Self::close_all_popups`] if you want to close any / all currently open popups. /// See also [`Self::close_all_popups`] if you want to close any / all currently open popups.
#[deprecated = "Use Popup::close_id instead"]
pub fn close_popup(&mut self, popup_id: Id) { pub fn close_popup(&mut self, popup_id: Id) {
#[expect(deprecated)]
if self.is_popup_open(popup_id) { if self.is_popup_open(popup_id) {
self.popups.remove(&self.viewport_id); self.popups.remove(&self.viewport_id);
} }
@ -1080,14 +1088,18 @@ impl Memory {
/// Toggle the given popup between closed and open. /// Toggle the given popup between closed and open.
/// ///
/// Note: At most, only one popup can be open at a time. /// Note: At most, only one popup can be open at a time.
#[deprecated = "Use Popup::toggle_id instead"]
pub fn toggle_popup(&mut self, popup_id: Id) { pub fn toggle_popup(&mut self, popup_id: Id) {
#[expect(deprecated)]
if self.is_popup_open(popup_id) { if self.is_popup_open(popup_id) {
self.close_popup(popup_id); self.close_popup(popup_id);
} else { } else {
self.open_popup(popup_id); self.open_popup(popup_id);
} }
} }
}
impl Memory {
/// If true, all windows, menus, tooltips, etc., will be visible at once. /// If true, all windows, menus, tooltips, etc., will be visible at once.
/// ///
/// This is useful for testing, benchmarking, pre-caching, etc. /// This is useful for testing, benchmarking, pre-caching, etc.

View File

@ -491,7 +491,7 @@ pub fn color_picker_color32(ui: &mut Ui, srgba: &mut Color32, alpha: Alpha) -> b
pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> Response { pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> Response {
let popup_id = ui.auto_id_with("popup"); let popup_id = ui.auto_id_with("popup");
let open = ui.memory(|mem| mem.is_popup_open(popup_id)); let open = Popup::is_id_open(ui.ctx(), popup_id);
let mut button_response = color_button(ui, (*hsva).into(), open); let mut button_response = color_button(ui, (*hsva).into(), open);
if ui.style().explanation_tooltips { if ui.style().explanation_tooltips {
button_response = button_response.on_hover_text("Click to edit color"); button_response = button_response.on_hover_text("Click to edit color");

View File

@ -164,8 +164,8 @@ impl crate::View for Modals {
mod tests { mod tests {
use crate::Demo as _; use crate::Demo as _;
use crate::demo::modals::Modals; use crate::demo::modals::Modals;
use egui::Key;
use egui::accesskit::Role; use egui::accesskit::Role;
use egui::{Key, Popup};
use egui_kittest::kittest::Queryable as _; use egui_kittest::kittest::Queryable as _;
use egui_kittest::{Harness, SnapshotResults}; use egui_kittest::{Harness, SnapshotResults};
@ -187,12 +187,12 @@ mod tests {
// Harness::run would fail because we keep requesting repaints to simulate progress. // Harness::run would fail because we keep requesting repaints to simulate progress.
harness.run_ok(); harness.run_ok();
assert!(harness.ctx.memory(|mem| mem.any_popup_open())); assert!(Popup::is_any_open(&harness.ctx));
assert!(harness.state().user_modal_open); assert!(harness.state().user_modal_open);
harness.key_press(Key::Escape); harness.key_press(Key::Escape);
harness.run_ok(); harness.run_ok();
assert!(!harness.ctx.memory(|mem| mem.any_popup_open())); assert!(!Popup::is_any_open(&harness.ctx));
assert!(harness.state().user_modal_open); assert!(harness.state().user_modal_open);
} }