Fix manual `Popup` not closing (#7383)

Fixes manually created popups (via `Popup::new`) not closing, since
widget_clicked_elsewhere was always false.

This example would never close:

```rs
    let mut open = true;

    eframe::run_simple_native("My egui App", options, move |ctx, _frame| {
        egui::CentralPanel::default().show(ctx, |ui| {
            let response = egui::Popup::new(
                Id::new("popup"),
                ctx.clone(),
                PopupAnchor::Position(Pos2::new(10.0, 10.0)),
                LayerId::new(Order::Foreground, Id::new("popup")),
            )
            .open(open)
            .show(|ui| {
                ui.label("This is a popup!");
                ui.label("You can put anything in here.");
            });

            if let Some(response) = response {
                if response.response.should_close() {
                    open = false;
                }
            }
        });
    })
```

I also noticed that the Color submenu in the popups example had a double
arrow (must have been broken in the atoms PR):

<img width="248" height="110" alt="Screenshot 2025-08-07 at 13 42 28"
src="https://github.com/user-attachments/assets/a4e0c267-ae71-4b2c-a1f0-f53f9662d026"
/>

Also fixed this in the PR.
This commit is contained in:
Lucas Meurer 2025-08-07 14:18:04 +02:00 committed by GitHub
parent 36a4981f29
commit e8e99a0bb6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 25 additions and 22 deletions

View File

@ -179,9 +179,6 @@ pub struct Popup<'a> {
/// Gap between the anchor and the popup /// Gap between the anchor and the popup
gap: f32, gap: f32,
/// Used later depending on close behavior
widget_clicked_elsewhere: bool,
/// Default width passed to the Area /// Default width passed to the Area
width: Option<f32>, width: Option<f32>,
sense: Sense, sense: Sense,
@ -205,7 +202,6 @@ impl<'a> Popup<'a> {
rect_align: RectAlign::BOTTOM_START, rect_align: RectAlign::BOTTOM_START,
alternative_aligns: None, alternative_aligns: None,
gap: 0.0, gap: 0.0,
widget_clicked_elsewhere: false,
width: None, width: None,
sense: Sense::click(), sense: Sense::click(),
layout: Layout::default(), layout: Layout::default(),
@ -219,14 +215,12 @@ 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( Self::new(
Self::default_response_id(response), Self::default_response_id(response),
response.ctx.clone(), response.ctx.clone(),
response, response,
response.layer_id, response.layer_id,
); )
popup.widget_clicked_elsewhere = response.clicked_elsewhere();
popup
} }
/// Show a popup relative to some widget, /// Show a popup relative to some widget,
@ -504,9 +498,14 @@ impl<'a> Popup<'a> {
/// Returns `None` if the popup is not open or anchor is `PopupAnchor::Pointer` and there is /// Returns `None` if the popup is not open or anchor is `PopupAnchor::Pointer` and there is
/// no pointer. /// no pointer.
pub fn show<R>(self, content: impl FnOnce(&mut Ui) -> R) -> Option<InnerResponse<R>> { pub fn show<R>(self, content: impl FnOnce(&mut Ui) -> R) -> Option<InnerResponse<R>> {
let hover_pos = self.ctx.pointer_hover_pos();
let id = self.id; let id = self.id;
// When the popup was just opened with a click we don't want to immediately close it based
// on the `PopupCloseBehavior`, so we need to remember if the popup was already open on
// last frame. A convenient way to check this is to see if we have a response for the `Area`
// from last frame:
let was_open_last_frame = self.ctx.read_response(id).is_some();
let hover_pos = self.ctx.pointer_hover_pos();
if let OpenKind::Memory { set } = self.open_kind { if let OpenKind::Memory { set } = self.open_kind {
match set { match set {
Some(SetOpenCommand::Bool(open)) => { Some(SetOpenCommand::Bool(open)) => {
@ -548,7 +547,6 @@ impl<'a> Popup<'a> {
rect_align: _, rect_align: _,
alternative_aligns: _, alternative_aligns: _,
gap, gap,
widget_clicked_elsewhere,
width, width,
sense, sense,
layout, layout,
@ -595,10 +593,13 @@ impl<'a> Popup<'a> {
frame.show(ui, content).inner frame.show(ui, content).inner
}); });
// If the popup was just opened with a click, we don't want to immediately close it again.
let close_click = was_open_last_frame && ctx.input(|i| i.pointer.any_click());
let closed_by_click = match close_behavior { let closed_by_click = match close_behavior {
PopupCloseBehavior::CloseOnClick => widget_clicked_elsewhere, PopupCloseBehavior::CloseOnClick => close_click,
PopupCloseBehavior::CloseOnClickOutside => { PopupCloseBehavior::CloseOnClickOutside => {
widget_clicked_elsewhere && response.response.clicked_elsewhere() close_click && response.response.clicked_elsewhere()
} }
PopupCloseBehavior::IgnoreClicks => false, PopupCloseBehavior::IgnoreClicks => false,
}; };

View File

@ -2,8 +2,8 @@ use crate::rust_view_ui;
use egui::color_picker::{Alpha, color_picker_color32}; use egui::color_picker::{Alpha, color_picker_color32};
use egui::containers::menu::{MenuConfig, SubMenuButton}; use egui::containers::menu::{MenuConfig, SubMenuButton};
use egui::{ use egui::{
Align, Align2, ComboBox, Frame, Id, Layout, Popup, PopupCloseBehavior, RectAlign, RichText, Align, Align2, Atom, Button, ComboBox, Frame, Id, Layout, Popup, PopupCloseBehavior, RectAlign,
Tooltip, Ui, UiBuilder, include_image, RichText, Tooltip, Ui, UiBuilder, include_image,
}; };
/// Showcase [`Popup`]. /// Showcase [`Popup`].
@ -79,13 +79,15 @@ impl PopupsDemo {
} else { } else {
egui::Color32::WHITE egui::Color32::WHITE
}; };
let mut color_button =
SubMenuButton::new(RichText::new("Background").color(text_color)); let button = Button::new((
color_button.button = color_button.button.fill(self.color); RichText::new("Background").color(text_color),
color_button.button = color_button Atom::grow(),
.button RichText::new(SubMenuButton::RIGHT_ARROW).color(text_color),
.right_text(RichText::new(SubMenuButton::RIGHT_ARROW).color(text_color)); ))
color_button.ui(ui, |ui| { .fill(self.color);
SubMenuButton::from_button(button).ui(ui, |ui| {
ui.spacing_mut().slider_width = 200.0; ui.spacing_mut().slider_width = 200.0;
color_picker_color32(ui, &mut self.color, Alpha::Opaque); color_picker_color32(ui, &mut self.color, Alpha::Opaque);
}); });