From 2861bc956a01b4aa1f21e03f0525f09931d43fc5 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 5 Aug 2020 13:32:40 +0200 Subject: [PATCH] [menu] click outside a menu to close it --- egui/src/containers.rs | 1 - egui/src/input.rs | 26 +++++++++++ egui/src/lib.rs | 1 + egui/src/memory.rs | 5 +-- egui/src/{containers => }/menu.rs | 73 +++++++++++++++++++++---------- 5 files changed, 79 insertions(+), 27 deletions(-) rename egui/src/{containers => }/menu.rs (77%) diff --git a/egui/src/containers.rs b/egui/src/containers.rs index d42f2994..8ce7ddfb 100644 --- a/egui/src/containers.rs +++ b/egui/src/containers.rs @@ -1,7 +1,6 @@ pub(crate) mod area; pub(crate) mod collapsing_header; pub(crate) mod frame; -pub mod menu; pub(crate) mod popup; pub(crate) mod resize; pub(crate) mod scroll_area; diff --git a/egui/src/input.rs b/egui/src/input.rs index e541251b..060f2c8b 100644 --- a/egui/src/input.rs +++ b/egui/src/input.rs @@ -215,6 +215,32 @@ impl InputState { || self.scroll_delta != Vec2::zero() || !self.events.is_empty() } + + /// Was the given key pressed this frame? + pub fn key_pressed(&self, desired_key: Key) -> bool { + self.events.iter().any(|event| { + matches!( + event, + Event::Key { + key, + pressed: true + } if *key == desired_key + ) + }) + } + + /// Was the given key released this frame? + pub fn key_released(&self, desired_key: Key) -> bool { + self.events.iter().any(|event| { + matches!( + event, + Event::Key { + key, + pressed: false + } if *key == desired_key + ) + }) + } } impl MouseInput { diff --git a/egui/src/lib.rs b/egui/src/lib.rs index fba6d0db..956edc05 100644 --- a/egui/src/lib.rs +++ b/egui/src/lib.rs @@ -33,6 +33,7 @@ mod layers; mod layout; pub mod math; mod memory; +pub mod menu; mod movement_tracker; pub mod paint; mod painter; diff --git a/egui/src/memory.rs b/egui/src/memory.rs index b3f9f142..dd1cb8e5 100644 --- a/egui/src/memory.rs +++ b/egui/src/memory.rs @@ -1,9 +1,8 @@ use std::collections::{HashMap, HashSet}; use crate::{ - containers::{area, collapsing_header, menu, resize, scroll_area, window}, - widgets::text_edit, - Id, Layer, Pos2, Rect, + area, collapsing_header, menu, resize, scroll_area, widgets::text_edit, window, Id, Layer, + Pos2, Rect, }; /// The data that Egui persists between frames. diff --git a/egui/src/containers/menu.rs b/egui/src/menu.rs similarity index 77% rename from egui/src/containers/menu.rs rename to egui/src/menu.rs index a6ec78de..0c0bf919 100644 --- a/egui/src/containers/menu.rs +++ b/egui/src/menu.rs @@ -1,6 +1,21 @@ -use crate::{widgets::*, *}; +//! Menu bar functionality. +//! +//! Usage: +//! ``` rust +//! fn show_menu(ui: &mut egui::Ui) { +//! use egui::{menu, Button}; +//! +//! menu::bar(ui, |ui| { +//! menu::menu(ui, "File", |ui| { +//! if ui.add(Button::new("Open")).clicked { +//! // ... +//! } +//! }); +//! }); +//! } +//! ``` -use super::*; +use crate::{widgets::*, *}; #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "with_serde", derive(serde::Deserialize, serde::Serialize))] @@ -21,6 +36,26 @@ impl Default for BarState { } } +impl BarState { + fn load(ctx: &Context, bar_id: &Id) -> Self { + ctx.memory() + .menu_bar + .get(bar_id) + .cloned() + .unwrap_or_default() + } + + fn save(self, ctx: &Context, bar_id: Id) { + ctx.memory().menu_bar.insert(bar_id, self); + } + + fn close_menus(ctx: &Context, bar_id: Id) { + let mut bar_state = BarState::load(ctx, &bar_id); + bar_state.open_menu = None; + bar_state.save(ctx, bar_id); + } +} + pub fn bar(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Rect) { ui.inner_layout(Layout::horizontal(Align::Center), |ui| { Frame::menu_bar(ui.style()).show(ui, |ui| { @@ -38,7 +73,17 @@ pub fn bar(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Rect) let height = ui.style().menu_bar.height; ui.set_desired_height(height); ui.expand_to_size(vec2(ui.available().width(), height)); - add_contents(ui) + + let ret = add_contents(ui); + + let clicked_outside = !ui.hovered(ui.rect()) && ui.input().mouse.released; + if clicked_outside || ui.input().key_pressed(Key::Escape) { + // TODO: this prevent sub-menus in menus. We should fix that. + let bar_id = ui.id(); + BarState::close_menus(ui.ctx(), bar_id); + } + + ret }) }) } @@ -57,12 +102,7 @@ fn menu_impl<'c>( let bar_id = ui.id(); let menu_id = bar_id.with(&title); - let mut bar_state = ui - .memory() - .menu_bar - .get(&bar_id) - .cloned() - .unwrap_or_default(); + let mut bar_state = BarState::load(ui.ctx(), &bar_id); let mut button = Button::new(title); @@ -105,7 +145,7 @@ fn menu_impl<'c>( } } - ui.memory().menu_bar.insert(bar_id, bar_state); + bar_state.save(ui.ctx(), bar_id); } fn interact_with_menu_button( @@ -138,17 +178,4 @@ fn interact_with_menu_button( if button_interact.hovered && bar_state.open_menu.is_some() { bar_state.open_menu = Some(menu_id); } - - let pressed_escape = input.events.iter().any(|event| { - matches!( - event, - Event::Key { - key: Key::Escape, - pressed: true - } - ) - }); - if pressed_escape { - bar_state.open_menu = None; - } }