diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 923c1069..8dbbc2c1 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -38,10 +38,10 @@ use crate::{ viewport::ViewportClass, }; +use self::{hit_test::WidgetHits, interaction::InteractionSnapshot}; #[cfg(feature = "accesskit")] use crate::IdMap; - -use self::{hit_test::WidgetHits, interaction::InteractionSnapshot}; +use crate::input_state::SurrenderFocusOn; /// Information given to the backend about when it is time to repaint the ui. /// @@ -1430,8 +1430,14 @@ impl Context { res.flags.set(Flags::HOVERED, false); } - let pointer_pressed_elsewhere = any_press && !res.hovered(); - if pointer_pressed_elsewhere && memory.has_focus(id) { + let should_surrender_focus = match memory.options.input_options.surrender_focus_on { + SurrenderFocusOn::Presses => any_press, + SurrenderFocusOn::Clicks => input.pointer.any_click(), + SurrenderFocusOn::Never => false, + }; + + let pointer_clicked_elsewhere = should_surrender_focus && !res.hovered(); + if pointer_clicked_elsewhere && memory.has_focus(id) { memory.surrender_focus(id); } }); diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index a3ebf532..bfa20d6c 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -17,6 +17,37 @@ pub use crate::Key; pub use touch_state::MultiTouchInfo; use touch_state::TouchState; +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub enum SurrenderFocusOn { + /// Surrender focus if the user _presses_ somewhere outside the focused widget. + Presses, + + /// Surrender focus if the user _clicks_ somewhere outside the focused widget. + #[default] + Clicks, + + /// Never surrender focus. + Never, +} + +impl SurrenderFocusOn { + pub fn ui(&mut self, ui: &mut crate::Ui) { + ui.horizontal(|ui| { + ui.selectable_value(self, Self::Presses, "Presses") + .on_hover_text( + "Surrender focus if the user presses somewhere outside the focused widget.", + ); + ui.selectable_value(self, Self::Clicks, "Clicks") + .on_hover_text( + "Surrender focus if the user clicks somewhere outside the focused widget.", + ); + ui.selectable_value(self, Self::Never, "Never") + .on_hover_text("Never surrender focus."); + }); + } +} + /// Options for input state handling. #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -58,6 +89,9 @@ pub struct InputOptions { /// and when combined with [`Self::zoom_modifier`] it will result in zooming /// on only the vertical axis. pub vertical_scroll_modifier: Modifiers, + + /// When should we surrender focus from the focused widget? + pub surrender_focus_on: SurrenderFocusOn, } impl Default for InputOptions { @@ -79,6 +113,7 @@ impl Default for InputOptions { zoom_modifier: Modifiers::COMMAND, horizontal_scroll_modifier: Modifiers::SHIFT, vertical_scroll_modifier: Modifiers::ALT, + surrender_focus_on: SurrenderFocusOn::default(), } } } @@ -95,6 +130,7 @@ impl InputOptions { zoom_modifier, horizontal_scroll_modifier, vertical_scroll_modifier, + surrender_focus_on, } = self; crate::Grid::new("InputOptions") .num_columns(2) @@ -155,6 +191,10 @@ impl InputOptions { vertical_scroll_modifier.ui(ui); ui.end_row(); + ui.label("surrender_focus_on"); + surrender_focus_on.ui(ui); + ui.end_row(); + }); } } diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 3739cd8a..1fc5f6ca 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -495,7 +495,7 @@ pub use self::{ epaint::text::TextWrapMode, grid::Grid, id::{Id, IdMap}, - input_state::{InputOptions, InputState, MultiTouchInfo, PointerState}, + input_state::{InputOptions, InputState, MultiTouchInfo, PointerState, SurrenderFocusOn}, layers::{LayerId, Order}, layout::*, load::SizeHint,