diff --git a/Cargo.lock b/Cargo.lock index 125af9adedf..dad7a238385 100644 --- a/Cargo.lock +++ b/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" diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs index f4c18b51b92..ab14e38ccf3 100644 --- a/crates/egui/src/containers/combo_box.rs +++ b/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) diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index b0aad3636b2..6e59b440bfc 100644 --- a/crates/egui/src/containers/popup.rs +++ b/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( ui: &Ui, popup_id: Id, widget_response: &Response, + close_behavior: PopupCloseBehavior, add_contents: impl FnOnce(&mut Ui) -> R, ) -> Option { popup_above_or_below_widget( @@ -265,6 +283,7 @@ pub fn popup_below_widget( popup_id, widget_response, AboveOrBelow::Below, + close_behavior, add_contents, ) } @@ -287,7 +306,8 @@ pub fn popup_below_widget( /// 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( popup_id: Id, widget_response: &Response, above_or_below: AboveOrBelow, + close_behavior: PopupCloseBehavior, add_contents: impl FnOnce(&mut Ui) -> R, ) -> Option { if parent_ui.memory(|mem| mem.is_popup_open(popup_id)) { @@ -317,7 +338,7 @@ pub fn popup_above_or_below_widget( 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( .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 } diff --git a/examples/popups/Cargo.toml b/examples/popups/Cargo.toml new file mode 100644 index 00000000000..2f9d6b09263 --- /dev/null +++ b/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 diff --git a/examples/popups/README.md b/examples/popups/README.md new file mode 100644 index 00000000000..ce32e895ac2 --- /dev/null +++ b/examples/popups/README.md @@ -0,0 +1,5 @@ +Example of how to use menus, popups, context menus and tooltips. + +```sh +cargo run -p popups +``` diff --git a/examples/popups/src/main.rs b/examples/popups/src/main.rs new file mode 100644 index 00000000000..761f6406b8f --- /dev/null +++ b/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::::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}")); + } + }); + }); + } +}