From 756f4b6ba69d11075eb0c34ea0fc837bd27ad63a Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Sat, 3 Aug 2024 12:25:14 +0200 Subject: [PATCH] fix(text_input): prevent simultaneous input focus --- src/widget/text_input/input.rs | 54 +++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index da49e07f..d3b67054 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -6,6 +6,7 @@ //! //! A [`TextInput`] has some local [`State`]. use std::borrow::Cow; +use std::cell::{Cell, LazyCell}; use crate::ext::ColorExt; use crate::theme::THEME; @@ -48,6 +49,11 @@ use cctk::sctk::reexports::client::protocol::wl_data_device_manager::DndAction; #[cfg(feature = "wayland")] use iced_runtime::command::platform_specific::wayland::data_device::{DataFromMimeType, DndIcon}; +thread_local! { + // Prevents two inputs from being focused at the same time. + static LAST_FOCUS_UPDATE: LazyCell> = LazyCell::new(|| Cell::new(Instant::now())); +} + /// Creates a new [`TextInput`]. /// /// [`TextInput`]: widget::TextInput @@ -572,12 +578,11 @@ where state.dirty = true; } - if state.is_read_only != self.is_read_only { - self.is_read_only = state.is_read_only; - } + self.is_read_only = state.is_read_only; if self.always_active && state.is_focused.is_none() { let now = Instant::now(); + LAST_FOCUS_UPDATE.with(|x| x.set(now)); state.is_focused = Some(Focus { updated_at: now, now, @@ -597,6 +602,12 @@ where }; } + if !state.is_focused.as_ref().map_or(false, |f| { + f.updated_at == LAST_FOCUS_UPDATE.with(|f| f.get()) + }) { + state.is_focused = None; + } + let mut children: Vec<_> = self .leading_icon .iter_mut() @@ -1181,7 +1192,7 @@ pub fn update<'a, Message>( event: Event, text_layout: Layout<'_>, trailing_icon_layout: Option>, - cursor_position: mouse::Cursor, + cursor: mouse::Cursor, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, value: &mut Value, @@ -1220,24 +1231,29 @@ where Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { let state = state(); - let is_clicked = cursor_position.is_over(text_layout.bounds()) && on_input.is_some(); - if is_clicked { + let click_position = if on_input.is_some() { + cursor.position_over(layout.bounds()) + } else { + None + }; + + if click_position.is_some() { state.is_focused = state.is_focused.or_else(|| { let now = Instant::now(); + LAST_FOCUS_UPDATE.with(|x| x.set(now)); Some(Focus { updated_at: now, now, }) }); + } - let Some(pos) = cursor_position.position() else { - return event::Status::Ignored; - }; + if let Some(cursor_position) = click_position { + let text_layout = layout.children().next().unwrap(); + let target = cursor_position.x - text_layout.bounds().x; - let target = pos.x - text_layout.bounds().x; - - let click = mouse::Click::new(pos, state.last_click); + let click = mouse::Click::new(cursor_position, state.last_click); match ( &state.dragging_state, @@ -1284,7 +1300,7 @@ where height: text_layout.bounds().height, }; - if cursor_position.is_over(selection_bounds) { + if cursor.is_over(selection_bounds) { // XXX never start a dnd if the input is secure if is_secure { return event::Status::Ignored; @@ -1394,6 +1410,7 @@ where shell.publish(message); let now = Instant::now(); + LAST_FOCUS_UPDATE.with(|x| x.set(now)); state.is_focused = Some(Focus { updated_at: now, now, @@ -1411,11 +1428,11 @@ where if is_editable { if let Some(trailing_layout) = trailing_icon_layout { - let is_trailing_clicked = cursor_position.is_over(trailing_layout.bounds()) - && on_toggle_edit.is_some(); + let is_trailing_clicked = + cursor.is_over(trailing_layout.bounds()) && on_toggle_edit.is_some(); if is_trailing_clicked { - let Some(pos) = cursor_position.position() else { + let Some(pos) = cursor.position() else { return event::Status::Ignored; }; @@ -1433,6 +1450,7 @@ where shell.publish(message); let now = Instant::now(); + LAST_FOCUS_UPDATE.with(|x| x.set(now)); state.is_focused = Some(Focus { updated_at: now, now, @@ -1488,6 +1506,7 @@ where let modifiers = state.keyboard_modifiers; focus.updated_at = Instant::now(); + LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at)); match key { keyboard::Key::Named(keyboard::key::Named::Enter) => { @@ -1692,6 +1711,7 @@ where shell.publish(message); focus.updated_at = Instant::now(); + LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at)); let value = if is_secure { unsecured_value.secure() @@ -2528,7 +2548,7 @@ impl State { /// Focuses the [`TextInput`]. pub fn focus(&mut self) { let now = Instant::now(); - + LAST_FOCUS_UPDATE.with(|x| x.set(now)); self.is_read_only = false; self.is_focused = Some(Focus { updated_at: now,