diff --git a/CHANGELOG.md b/CHANGELOG.md index a08b31c4..3c752164 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w ## Unreleased +### Added ⭐ +* Add context menus: See `Ui::menu_button` and `Response::context_menu` ([#543](https://github.com/emilk/egui/pull/543)). + ## 0.15.0 - 2021-10-24 - Syntax highlighting and hscroll diff --git a/README.md b/README.md index eb72b370..f69be210 100644 --- a/README.md +++ b/README.md @@ -362,6 +362,7 @@ Notable contributions by: * [@EmbersArc](https://github.com/EmbersArc): [Plots](https://github.com/emilk/egui/pulls?q=+is%3Apr+author%3AEmbersArc) * [@AsmPrgmC3](https://github.com/AsmPrgmC3): [Proper sRGBA blending in `egui_web`](https://github.com/emilk/egui/pull/650) * [@AlexApps99](https://github.com/AlexApps99): [`egui_glow`](https://github.com/emilk/egui/pull/685) +* [@mankinskin](https://github.com/mankinskin): [Context menus](https://github.com/emilk/egui/pull/543) * And [many more](https://github.com/emilk/egui/graphs/contributors) egui is licensed under [MIT](LICENSE-MIT) OR [Apache-2.0](LICENSE-APACHE). diff --git a/egui/src/context.rs b/egui/src/context.rs index fb666604..4de2edbf 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -305,6 +305,7 @@ impl CtxRef { Self::layer_painter(self, LayerId::debug()) } + /// Respond to secondary clicks (right-clicks) by showing the given menu. pub(crate) fn show_context_menu( &self, response: &Response, diff --git a/egui/src/menu.rs b/egui/src/menu.rs index 227ca3ba..1c6b37bb 100644 --- a/egui/src/menu.rs +++ b/egui/src/menu.rs @@ -349,6 +349,7 @@ impl MenuRoot { MenuResponse::Stay => {} } } + /// Respond to secondary (right) clicks. pub fn context_click_interaction(response: &Response, root: &mut MenuRootManager, id: Id) { let menu_response = Self::context_interaction(response, root, id); Self::handle_menu_response(root, menu_response); diff --git a/egui/src/response.rs b/egui/src/response.rs index 8d8e0031..99720b2a 100644 --- a/egui/src/response.rs +++ b/egui/src/response.rs @@ -471,6 +471,17 @@ impl Response { } } + /// Response to secondary clicks (right-clicks) by showing the given menu. + /// + /// ``` rust + /// # let mut ui = &mut egui::Ui::__test(); + /// let response = ui.label("Right-click me!"); + /// response.context_menu(|ui|{ + /// if ui.button("Close the menu").clicked() { + /// ui.close_menu(); + /// } + /// }); + /// ``` pub fn context_menu(&self, add_contents: impl FnOnce(&mut Ui)) -> &Self { self.ctx.show_context_menu(self, add_contents); self diff --git a/egui/src/ui.rs b/egui/src/ui.rs index 833785dd..f060698d 100644 --- a/egui/src/ui.rs +++ b/egui/src/ui.rs @@ -1737,6 +1737,7 @@ impl Ui { self.advance_cursor_after_rect(Rect::from_min_size(top_left, size)); result } + /// Close menu (with submenus), if any. pub fn close_menu(&mut self) { if let Some(menu_state) = &mut self.menu_state { @@ -1744,12 +1745,15 @@ impl Ui { } self.menu_state = None; } + pub(crate) fn get_menu_state(&self) -> Option>> { self.menu_state.clone() } + pub(crate) fn set_menu_state(&mut self, menu_state: Option>>) { self.menu_state = menu_state; } + #[inline(always)] /// Create a menu button. Creates a button for a sub-menu when the `Ui` is inside a menu. /// @@ -1757,7 +1761,9 @@ impl Ui { /// # let mut ui = egui::Ui::__test(); /// ui.menu_button("My menu", |ui| { /// ui.menu_button("My sub-menu", |ui| { - /// ui.label("Item"); + /// if ui.button("Close the menu").clicked() { + /// ui.close_menu(); + /// } /// }); /// }); /// ``` diff --git a/egui_demo_lib/src/apps/demo/context_menu.rs b/egui_demo_lib/src/apps/demo/context_menu.rs index d06e6fd3..627ef4e8 100644 --- a/egui_demo_lib/src/apps/demo/context_menu.rs +++ b/egui_demo_lib/src/apps/demo/context_menu.rs @@ -17,7 +17,6 @@ fn sigmoid(x: f64) -> f64 { #[derive(Clone, PartialEq)] #[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] pub struct ContextMenus { - title: String, plot: Plot, show_axes: [bool; 2], allow_drag: bool, @@ -28,6 +27,87 @@ pub struct ContextMenus { height: f32, } +impl Default for ContextMenus { + fn default() -> Self { + Self { + plot: Plot::Sin, + show_axes: [true, true], + allow_drag: true, + allow_zoom: true, + center_x_axis: false, + center_y_axis: false, + width: 400.0, + height: 200.0, + } + } +} + +impl super::Demo for ContextMenus { + fn name(&self) -> &'static str { + "☰ Context Menus" + } + + fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) { + use super::View; + egui::Window::new(self.name()) + .vscroll(false) + .resizable(false) + .open(open) + .show(ctx, |ui| self.ui(ui)); + } +} + +impl super::View for ContextMenus { + fn ui(&mut self, ui: &mut egui::Ui) { + ui.horizontal(|ui| { + ui.menu_button("Click for menu", Self::nested_menus); + ui.button("Right-click for menu") + .context_menu(Self::nested_menus); + }); + + ui.separator(); + + ui.label("Right-click plot to edit it!"); + ui.horizontal(|ui| { + ui.add(self.example_plot()).context_menu(|ui| { + ui.menu_button("Plot", |ui| { + if ui.radio_value(&mut self.plot, Plot::Sin, "Sin").clicked() + || ui + .radio_value(&mut self.plot, Plot::Bell, "Gaussian") + .clicked() + || ui + .radio_value(&mut self.plot, Plot::Sigmoid, "Sigmoid") + .clicked() + { + ui.close_menu(); + } + }); + egui::Grid::new("button_grid").show(ui, |ui| { + ui.add( + egui::DragValue::new(&mut self.width) + .speed(1.0) + .prefix("Width:"), + ); + ui.add( + egui::DragValue::new(&mut self.height) + .speed(1.0) + .prefix("Height:"), + ); + ui.end_row(); + ui.checkbox(&mut self.show_axes[0], "x-Axis"); + ui.checkbox(&mut self.show_axes[1], "y-Axis"); + ui.end_row(); + if ui.checkbox(&mut self.allow_drag, "Drag").changed() + || ui.checkbox(&mut self.allow_zoom, "Zoom").changed() + { + ui.close_menu(); + } + }); + }); + }); + } +} + impl ContextMenus { fn example_plot(&self) -> egui::plot::Plot { use egui::plot::{Line, Value, Values}; @@ -52,6 +132,7 @@ impl ContextMenus { .height(self.height) .data_aspect(1.0) } + fn nested_menus(ui: &mut egui::Ui) { if ui.button("Open...").clicked() { ui.close_menu(); @@ -86,89 +167,3 @@ impl ContextMenus { let _ = ui.button("Very long text for this item"); } } - -const DEFAULT_TITLE: &str = "☰ Context Menus"; - -impl Default for ContextMenus { - fn default() -> Self { - Self { - title: DEFAULT_TITLE.to_owned(), - plot: Plot::Sin, - show_axes: [true, true], - allow_drag: true, - allow_zoom: true, - center_x_axis: false, - center_y_axis: false, - width: 400.0, - height: 200.0, - } - } -} -impl super::Demo for ContextMenus { - fn name(&self) -> &'static str { - DEFAULT_TITLE - } - - fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) { - let Self { title, .. } = self.clone(); - - use super::View; - let window = egui::Window::new(title) - .id(egui::Id::new("demo_context_menus")) // required since we change the title - .vscroll(false) - .open(open); - window.show(ctx, |ui| self.ui(ui)); - } -} - -impl super::View for ContextMenus { - fn ui(&mut self, ui: &mut egui::Ui) { - ui.horizontal(|ui| ui.text_edit_singleline(&mut self.title)); - ui.horizontal(|ui| { - ui.add(self.example_plot()) - .on_hover_text("Right click for options") - .context_menu(|ui| { - ui.menu_button("Plot", |ui| { - if ui.radio_value(&mut self.plot, Plot::Sin, "Sin").clicked() - || ui - .radio_value(&mut self.plot, Plot::Bell, "Gaussian") - .clicked() - || ui - .radio_value(&mut self.plot, Plot::Sigmoid, "Sigmoid") - .clicked() - { - ui.close_menu(); - } - }); - egui::Grid::new("button_grid").show(ui, |ui| { - ui.add( - egui::DragValue::new(&mut self.width) - .speed(1.0) - .prefix("Width:"), - ); - ui.add( - egui::DragValue::new(&mut self.height) - .speed(1.0) - .prefix("Height:"), - ); - ui.end_row(); - ui.checkbox(&mut self.show_axes[0], "x-Axis"); - ui.checkbox(&mut self.show_axes[1], "y-Axis"); - ui.end_row(); - if ui.checkbox(&mut self.allow_drag, "Drag").changed() - || ui.checkbox(&mut self.allow_zoom, "Zoom").changed() - { - ui.close_menu(); - } - }); - }); - }); - ui.label("Right-click plot to edit it!"); - ui.separator(); - ui.horizontal(|ui| { - ui.menu_button("Click for menu", Self::nested_menus); - ui.button("Right-click for menu") - .context_menu(Self::nested_menus); - }); - } -}