Add `SurrenderFocusOn` option (#7471)

* [X] I have followed the instructions in the PR template

On touch devices you don't want the keyboard to disappear when
scrolling, so this PR adds a `SurrenderFocusOn` enum to configure on
what interaction to surrender focus.
This commit is contained in:
Lucas Meurer 2025-08-21 17:45:34 +02:00 committed by GitHub
parent 5453cceded
commit bf981b8d3e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 51 additions and 5 deletions

View File

@ -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);
}
});

View File

@ -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();
});
}
}

View File

@ -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,