Browse Source

Add `PopupCloseBehavior` (#4636)

This PR adds `PopupCloseBehavior` to improve state of the
<https://github.com/emilk/egui/issues/4607>

`PopupCloseBehavior` determines when popup will be closed.
- `CloseOnClick` popup will be closed if the click happens anywhere even
in the popup's body
- `CloseOnClickAway` popup will be closed if the click happens somewhere
else but in the popup's body.

It also adds a test in the demo app which contains several popups
examples.

---

My ideas about <https://github.com/emilk/egui/issues/4607> is to make
every tooltip and popup a menu. So it will provide more control over
popups and tooltips (you will be able to close a popup by calling
something similar to the `ui.close_menu` if you need to). You won't need
to manually handle it's opening. And also will allow to have multiple
popups opened. That means you can have a popup inside a popup. And it
will also lead to the easier creation of the popups. (should we create a
tracking issue to track changes because to me it seems like a huge
amount of changes to be done?)

---

- Improvements on <https://github.com/emilk/egui/issues/4607>
pull/4716/head
Umatriz 4 months ago
committed by GitHub
parent
commit
5051e945e4
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 8
      Cargo.lock
  2. 1
      crates/egui/src/containers/combo_box.rs
  3. 40
      crates/egui/src/containers/popup.rs
  4. 22
      examples/popups/Cargo.toml
  5. 5
      examples/popups/README.md
  6. 52
      examples/popups/src/main.rs

8
Cargo.lock

@ -2883,6 +2883,14 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2"
[[package]]
name = "popups"
version = "0.27.2"
dependencies = [
"eframe",
"env_logger",
]
[[package]]
name = "powerfmt"
version = "0.2.0"

1
crates/egui/src/containers/combo_box.rs

@ -380,6 +380,7 @@ fn combo_box_dyn<'c, R>(
popup_id,
&button_response,
above_or_below,
PopupCloseBehavior::CloseOnClick,
|ui| {
ScrollArea::vertical()
.max_height(height)

40
crates/egui/src/containers/popup.rs

@ -253,11 +253,29 @@ pub fn was_tooltip_open_last_frame(ctx: &Context, widget_id: Id) -> bool {
})
}
/// Determines popup's close behavior
#[derive(Clone, Copy)]
pub enum PopupCloseBehavior {
/// Popup will be closed on click anywhere, inside or outside the popup.
///
/// It is used in [`ComboBox`].
CloseOnClick,
/// Popup will be closed if the click happened somewhere else
/// but in the popup's body
CloseOnClickOutside,
/// Clicks will be ignored. Popup might be closed manually by calling [`Memory::close_popup`]
/// or by pressing the escape button
IgnoreClicks,
}
/// Helper for [`popup_above_or_below_widget`].
pub fn popup_below_widget<R>(
ui: &Ui,
popup_id: Id,
widget_response: &Response,
close_behavior: PopupCloseBehavior,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> {
popup_above_or_below_widget(
@ -265,6 +283,7 @@ pub fn popup_below_widget<R>(
popup_id,
widget_response,
AboveOrBelow::Below,
close_behavior,
add_contents,
)
}
@ -287,7 +306,8 @@ pub fn popup_below_widget<R>(
/// ui.memory_mut(|mem| mem.toggle_popup(popup_id));
/// }
/// let below = egui::AboveOrBelow::Below;
/// egui::popup::popup_above_or_below_widget(ui, popup_id, &response, below, |ui| {
/// let close_on_click_outside = egui::popup::PopupCloseBehavior::CloseOnClickOutside;
/// egui::popup::popup_above_or_below_widget(ui, popup_id, &response, below, close_on_click_outside, |ui| {
/// ui.set_min_width(200.0); // if you want to control the size
/// ui.label("Some more info, or things you can select:");
/// ui.label("…");
@ -299,6 +319,7 @@ pub fn popup_above_or_below_widget<R>(
popup_id: Id,
widget_response: &Response,
above_or_below: AboveOrBelow,
close_behavior: PopupCloseBehavior,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> {
if parent_ui.memory(|mem| mem.is_popup_open(popup_id)) {
@ -317,7 +338,7 @@ pub fn popup_above_or_below_widget<R>(
let frame_margin = frame.total_margin();
let inner_width = widget_response.rect.width() - frame_margin.sum().x;
let inner = Area::new(popup_id)
let response = Area::new(popup_id)
.kind(UiKind::Popup)
.order(Order::Foreground)
.fixed_pos(pos)
@ -333,13 +354,20 @@ pub fn popup_above_or_below_widget<R>(
.inner
})
.inner
})
.inner;
});
let should_close = match close_behavior {
PopupCloseBehavior::CloseOnClick => widget_response.clicked_elsewhere(),
PopupCloseBehavior::CloseOnClickOutside => {
widget_response.clicked_elsewhere() && response.response.clicked_elsewhere()
}
PopupCloseBehavior::IgnoreClicks => false,
};
if parent_ui.input(|i| i.key_pressed(Key::Escape)) || widget_response.clicked_elsewhere() {
if parent_ui.input(|i| i.key_pressed(Key::Escape)) || should_close {
parent_ui.memory_mut(|mem| mem.close_popup());
}
Some(inner)
Some(response.inner)
} else {
None
}

22
examples/popups/Cargo.toml

@ -0,0 +1,22 @@
[package]
name = "popups"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
eframe = { workspace = true, features = [
"default",
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
env_logger = { version = "0.10", default-features = false, features = [
"auto-color",
"humantime",
] }
[lints]
workspace = true

5
examples/popups/README.md

@ -0,0 +1,5 @@
Example of how to use menus, popups, context menus and tooltips.
```sh
cargo run -p popups
```

52
examples/popups/src/main.rs

@ -0,0 +1,52 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
#![allow(rustdoc::missing_crate_level_docs)] // it's an example
use eframe::egui::*;
fn main() -> Result<(), eframe::Error> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions::default();
eframe::run_native("Popups", options, Box::new(|_| Ok(Box::<MyApp>::default())))
}
#[derive(Default)]
struct MyApp {
checkbox: bool,
number: u8,
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &eframe::egui::Context, _frame: &mut eframe::Frame) {
CentralPanel::default().show(ctx, |ui| {
ui.label("PopupCloseBehavior::CloseOnClickAway popup");
let response = ui.button("Open");
let popup_id = Id::new("popup_id");
if response.clicked() {
ui.memory_mut(|mem| mem.toggle_popup(popup_id));
}
popup_below_widget(
ui,
popup_id,
&response,
PopupCloseBehavior::CloseOnClickOutside,
|ui| {
ui.set_min_width(300.0);
ui.label("This popup will be open even if you click the checkbox");
ui.checkbox(&mut self.checkbox, "Checkbox");
},
);
ui.label("PopupCloseBehavior::CloseOnClick popup");
ComboBox::from_label("ComboBox")
.selected_text(format!("{}", self.number))
.show_ui(ui, |ui| {
for num in 0..10 {
ui.selectable_value(&mut self.number, num, format!("{num}"));
}
});
});
}
}
Loading…
Cancel
Save