Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions crates/egui/src/containers/combo_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -315,7 +315,7 @@ fn combo_box_dyn<'c, R>(
) -> InnerResponse<Option<R>> {
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());

Expand Down
11 changes: 5 additions & 6 deletions crates/egui/src/containers/modal.rs
Original file line number Diff line number Diff line change
@@ -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.
///
Expand Down Expand Up @@ -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,
Expand Down
100 changes: 82 additions & 18 deletions crates/egui/src/containers/popup.rs
Original file line number Diff line number Diff line change
@@ -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?
///
Expand Down Expand Up @@ -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)),
}
}
Expand Down Expand Up @@ -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),
}
}
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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),
}
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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<Pos2> {
ctx.memory(|mem| mem.popup_position(popup_id))
}
}
18 changes: 15 additions & 3 deletions crates/egui/src/memory/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -1025,13 +1025,15 @@ 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()
}

/// 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));
Expand All @@ -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 {
Expand All @@ -1051,27 +1054,32 @@ 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<Option<Pos2>>) {
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<Pos2> {
self.popups
.get(&self.viewport_id)
.and_then(|state| if state.id == id { state.pos } else { None })
}

/// Close any currently open popup.
#[deprecated = "Use Popup::close_all instead"]
pub fn close_all_popups(&mut self) {
self.popups.clear();
}

/// 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);
}
Expand All @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion crates/egui/src/widgets/color_picker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
6 changes: 3 additions & 3 deletions crates/egui_demo_lib/src/demo/modals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand All @@ -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);
}

Expand Down
Loading