diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs index fc5f3390535..6ef92884937 100644 --- a/crates/egui/src/containers/combo_box.rs +++ b/crates/egui/src/containers/combo_box.rs @@ -293,7 +293,7 @@ impl ComboBox { /// Check if the [`ComboBox`] with the given id has its popup menu currently opened. 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. @@ -315,7 +315,7 @@ fn combo_box_dyn<'c, R>( ) -> InnerResponse> { 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()); diff --git a/crates/egui/src/containers/modal.rs b/crates/egui/src/containers/modal.rs index 2edc628e986..e36ad6e1bad 100644 --- a/crates/egui/src/containers/modal.rs +++ b/crates/egui/src/containers/modal.rs @@ -1,7 +1,8 @@ +use emath::{Align2, Vec2}; + use crate::{ Area, Color32, Context, Frame, Id, InnerResponse, Order, Response, Sense, Ui, UiBuilder, UiKind, }; -use emath::{Align2, Vec2}; /// A modal dialog. /// @@ -80,13 +81,11 @@ impl Modal { frame, } = 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.top_modal_layer() == Some(area.layer()), - mem.any_popup_open(), - ) + mem.top_modal_layer() == Some(area.layer()) }); + let any_popup_open = crate::Popup::is_any_open(ctx); let InnerResponse { inner: (inner, backdrop_response), response, diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 13b095d35a8..66cedddae12 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -1,11 +1,15 @@ -use crate::containers::menu::{MenuConfig, MenuState, menu_style}; -use crate::style::StyleModifier; +#![expect(deprecated)] // This is a new, safe wrapper around the old `Memory::popup` API. + +use std::iter::once; + +use emath::{Align, Pos2, Rect, RectAlign, Vec2, vec2}; + use crate::{ Area, AreaState, Context, Frame, Id, InnerResponse, Key, LayerId, Layout, Order, Response, 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? /// @@ -64,9 +68,7 @@ impl PopupAnchor { match self { Self::ParentRect(rect) => Some(rect), Self::Pointer => ctx.pointer_hover_pos().map(Rect::from_pos), - Self::PointerFixed => ctx - .memory(|mem| mem.popup_position(popup_id)) - .map(Rect::from_pos), + Self::PointerFixed => Popup::position_of_id(ctx, popup_id).map(Rect::from_pos), Self::Position(pos) => Some(Rect::from_pos(pos)), } } @@ -122,12 +124,12 @@ enum OpenKind<'a> { impl OpenKind<'_> { /// 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 { OpenKind::Open => true, OpenKind::Closed => false, 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. pub fn from_response(response: &Response) -> Self { let mut popup = Self::new( - response.id.with("popup"), + Self::default_response_id(response), response.ctx.clone(), response, response.layer_id, @@ -455,7 +457,7 @@ impl<'a> Popup<'a> { OpenKind::Open => true, OpenKind::Closed => false, 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; if let OpenKind::Memory { set } = self.open_kind { - self.ctx.memory_mut(|mem| match set { + match set { Some(SetOpenCommand::Bool(open)) => { if open { match self.anchor { 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 { - mem.close_popup(id); + Self::close_id(&self.ctx, id); } } Some(SetOpenCommand::Toggle) => { - mem.toggle_popup(id); + Self::toggle_id(&self.ctx, id); } 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) { @@ -627,3 +629,65 @@ impl<'a> Popup<'a> { 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 { + ctx.memory(|mem| mem.popup_position(popup_id)) + } +} diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index 8f98de305ef..d4912f9d84b 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -1012,11 +1012,11 @@ impl OpenPopup { } } -/// ## Popups -/// Popups are things like combo-boxes, color pickers, menus etc. -/// Only one can be open at a time. +/// ## Deprecated popup API +/// Use [`crate::Popup`] instead. impl Memory { /// Is the given popup open? + #[deprecated = "Use Popup::is_id_open instead"] pub fn is_popup_open(&self, popup_id: Id) -> bool { self.popups .get(&self.viewport_id) @@ -1025,6 +1025,7 @@ impl Memory { } /// Is any popup open? + #[deprecated = "Use Popup::is_any_open instead"] pub fn any_popup_open(&self) -> bool { 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. /// /// 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) { self.popups .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 /// called. For example, when a context menu is open and the underlying widget stops /// being rendered. + #[deprecated = "Use Popup::show instead"] pub fn keep_popup_open(&mut self, popup_id: Id) { if let Some(state) = self.popups.get_mut(&self.viewport_id) { if state.id == popup_id { @@ -1051,12 +1054,14 @@ impl Memory { } /// 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>) { self.popups .insert(self.viewport_id, OpenPopup::new(popup_id, pos.into())); } /// Get the position for this popup. + #[deprecated = "Use Popup::position_of_id instead"] pub fn popup_position(&self, id: Id) -> Option { self.popups .get(&self.viewport_id) @@ -1064,6 +1069,7 @@ impl Memory { } /// Close any currently open popup. + #[deprecated = "Use Popup::close_all instead"] pub fn close_all_popups(&mut self) { self.popups.clear(); } @@ -1071,7 +1077,9 @@ impl Memory { /// Close the given popup, if it is open. /// /// 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) { + #[expect(deprecated)] if self.is_popup_open(popup_id) { self.popups.remove(&self.viewport_id); } @@ -1080,14 +1088,18 @@ impl Memory { /// Toggle the given popup between closed and open. /// /// 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) { + #[expect(deprecated)] if self.is_popup_open(popup_id) { self.close_popup(popup_id); } else { self.open_popup(popup_id); } } +} +impl Memory { /// If true, all windows, menus, tooltips, etc., will be visible at once. /// /// This is useful for testing, benchmarking, pre-caching, etc. diff --git a/crates/egui/src/widgets/color_picker.rs b/crates/egui/src/widgets/color_picker.rs index f8605beafb0..17d6b650be3 100644 --- a/crates/egui/src/widgets/color_picker.rs +++ b/crates/egui/src/widgets/color_picker.rs @@ -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 { 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); if ui.style().explanation_tooltips { button_response = button_response.on_hover_text("Click to edit color"); diff --git a/crates/egui_demo_lib/src/demo/modals.rs b/crates/egui_demo_lib/src/demo/modals.rs index 0aefbce82aa..a916c8bdfba 100644 --- a/crates/egui_demo_lib/src/demo/modals.rs +++ b/crates/egui_demo_lib/src/demo/modals.rs @@ -164,8 +164,8 @@ impl crate::View for Modals { mod tests { use crate::Demo as _; use crate::demo::modals::Modals; - use egui::Key; use egui::accesskit::Role; + use egui::{Key, Popup}; use egui_kittest::kittest::Queryable as _; 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_ok(); - assert!(harness.ctx.memory(|mem| mem.any_popup_open())); + assert!(Popup::is_any_open(&harness.ctx)); assert!(harness.state().user_modal_open); harness.key_press(Key::Escape); 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); }