diff --git a/CHANGELOG.md b/CHANGELOG.md index aa0090fe..9ae94813 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased +### Added ⭐ + +* You can now check if a `TextEdit` lost keyboard focus with `response.lost_kb_focus`. + ### Changed 🔧 * Pressing enter in a single-line `TextEdit` will now surrender keyboard focus for it diff --git a/egui/src/context.rs b/egui/src/context.rs index efdd35c7..afa2055d 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -430,6 +430,12 @@ impl Context { let hovered = self.contains_mouse(layer_id, clip_rect, interact_rect); let has_kb_focus = id.map(|id| self.memory().has_kb_focus(id)).unwrap_or(false); + // If the the focus is lost after the call to interact, + // this will be `false`, so `TextEdit` also sets this manually. + let lost_kb_focus = id + .map(|id| self.memory().lost_kb_focus(id)) + .unwrap_or(false); + if id.is_none() || sense == Sense::nothing() || !layer_id.allow_interaction() { // Not interested or allowed input: return Response { @@ -441,6 +447,7 @@ impl Context { double_clicked: false, active: false, has_kb_focus, + lost_kb_focus, }; } let id = id.unwrap(); @@ -466,6 +473,7 @@ impl Context { double_clicked: false, active: false, has_kb_focus, + lost_kb_focus, }; if sense.click && memory.interaction.click_id.is_none() { @@ -496,6 +504,7 @@ impl Context { double_clicked: false, active: false, has_kb_focus, + lost_kb_focus, } } } else if self.input.mouse.released { @@ -509,6 +518,7 @@ impl Context { double_clicked: clicked && self.input.mouse.double_click, active, has_kb_focus, + lost_kb_focus, } } else if self.input.mouse.down { Response { @@ -520,6 +530,7 @@ impl Context { double_clicked: false, active, has_kb_focus, + lost_kb_focus, } } else { Response { @@ -531,6 +542,7 @@ impl Context { double_clicked: false, active, has_kb_focus, + lost_kb_focus, } } } diff --git a/egui/src/demos/widgets.rs b/egui/src/demos/widgets.rs index 4fd512f1..2e99f50c 100644 --- a/egui/src/demos/widgets.rs +++ b/egui/src/demos/widgets.rs @@ -133,12 +133,16 @@ impl Widgets { ui.horizontal(|ui| { ui.label("Single line text input:"); - ui.add( + let response = ui.add( TextEdit::new(&mut self.single_line_text_input) .multiline(false) .id_source("single line"), ); - }); // TODO: .on_hover_text("Enter text to edit me") + + if response.lost_kb_focus { + // The user pressed enter. + } + }); ui.label("Multiline text input:"); ui.add(TextEdit::new(&mut self.multiline_text_input).id_source("multiline")); diff --git a/egui/src/memory.rs b/egui/src/memory.rs index c4177c6f..d1a21cc5 100644 --- a/egui/src/memory.rs +++ b/egui/src/memory.rs @@ -71,7 +71,7 @@ pub struct Memory { /// If the user releases the button without moving the mouse we register it as a click on `click_id`. /// If the cursor moves too much we clear the `click_id` and start passing move events to `drag_id`. #[derive(Clone, Debug, Default)] -pub struct Interaction { +pub(crate) struct Interaction { /// A widget interested in clicks that has a mouse press on it. pub click_id: Option, @@ -81,6 +81,9 @@ pub struct Interaction { /// The widget with keyboard focus (i.e. a text input field). pub kb_focus_id: Option, + /// What had keyboard focus previous frame? + pub kb_focus_id_previous_frame: Option, + /// HACK: windows have low priority on dragging. /// This is so that if you drag a slider in a window, /// the slider will steal the drag away from the window. @@ -103,6 +106,7 @@ impl Interaction { } fn begin_frame(&mut self, prev_input: &crate::input::InputState) { + self.kb_focus_id_previous_frame = self.kb_focus_id; self.click_interest = false; self.drag_interest = false; @@ -143,6 +147,11 @@ impl Memory { self.areas.layer_id_at(pos, resize_interact_radius_side) } + /// True if the given widget had keyboard focus last frame, but not this one. + pub fn lost_kb_focus(&self, id: Id) -> bool { + self.interaction.kb_focus_id_previous_frame == Some(id) && !self.has_kb_focus(id) + } + pub fn has_kb_focus(&self, id: Id) -> bool { self.interaction.kb_focus_id == Some(id) } diff --git a/egui/src/types.rs b/egui/src/types.rs index 47390aab..46fd3f9e 100644 --- a/egui/src/types.rs +++ b/egui/src/types.rs @@ -81,18 +81,36 @@ pub struct Response { /// This widget has the keyboard focus (i.e. is receiving key pressed) pub has_kb_focus: bool, + + /// The widget had keyboard focus and lost it, + /// perhaps because the user pressed enter. + /// This is often a signal to the user to the application + /// to make use of the contents of the text field. + pub lost_kb_focus: bool, } impl std::fmt::Debug for Response { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { + ctx: _, + rect, + sense, + hovered, + clicked, + double_clicked, + active, + has_kb_focus, + lost_kb_focus, + } = self; f.debug_struct("Response") - .field("rect", &self.rect) - .field("sense", &self.sense) - .field("hovered", &self.hovered) - .field("clicked", &self.clicked) - .field("double_clicked", &self.double_clicked) - .field("active", &self.active) - .field("has_kb_focus", &self.has_kb_focus) + .field("rect", rect) + .field("sense", sense) + .field("hovered", hovered) + .field("clicked", clicked) + .field("double_clicked", double_clicked) + .field("active", active) + .field("has_kb_focus", has_kb_focus) + .field("lost_kb_focus", lost_kb_focus) .finish() } } @@ -133,6 +151,7 @@ impl Response { double_clicked: self.double_clicked || other.double_clicked, active: self.active || other.active, has_kb_focus: self.has_kb_focus || other.has_kb_focus, + lost_kb_focus: self.lost_kb_focus || other.lost_kb_focus, } } } diff --git a/egui/src/widgets/text_edit.rs b/egui/src/widgets/text_edit.rs index 79edcc28..62bbf6f7 100644 --- a/egui/src/widgets/text_edit.rs +++ b/egui/src/widgets/text_edit.rs @@ -16,8 +16,7 @@ pub(crate) struct State { /// # let mut ui = egui::Ui::__test(); /// # let mut my_string = String::new(); /// let response = ui.add(egui::TextEdit::new(&mut my_string).multiline(false)); -/// if response.has_kb_focus && ui.input().key_pressed(egui::Key::Enter) { -/// ui.memory().stop_text_input(); +/// if response.lost_kb_focus { /// // use my_string /// } /// ``` @@ -240,7 +239,11 @@ impl<'t> Widget for TextEdit<'t> { .unwrap_or_else(|| visuals.text_color()); painter.galley(response.rect.min, galley, text_style, text_color); ui.memory().text_edit.insert(id, state); - response + + Response { + lost_kb_focus: ui.memory().lost_kb_focus(id), // we may have lost it during the course of this function + ..response + } } }