Skip to content

Commit

Permalink
Add PopupCloseBehavior (#4636)
Browse files Browse the repository at this point in the history
This PR adds `PopupCloseBehavior` to improve state of the
<#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 <#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 <#4607>
  • Loading branch information
Umatriz authored Jun 27, 2024
1 parent ab86157 commit 5051e94
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 6 deletions.
8 changes: 8 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions crates/egui/src/containers/combo_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
40 changes: 34 additions & 6 deletions crates/egui/src/containers/popup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,18 +253,37 @@ 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(
ui,
popup_id,
widget_response,
AboveOrBelow::Below,
close_behavior,
add_contents,
)
}
Expand All @@ -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("…");
Expand All @@ -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)) {
Expand All @@ -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)
Expand All @@ -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
}
Expand Down
22 changes: 22 additions & 0 deletions examples/popups/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 changes: 5 additions & 0 deletions examples/popups/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Example of how to use menus, popups, context menus and tooltips.

```sh
cargo run -p popups
```
52 changes: 52 additions & 0 deletions examples/popups/src/main.rs
Original file line number Diff line number Diff line change
@@ -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}"));
}
});
});
}
}

0 comments on commit 5051e94

Please sign in to comment.