fix(text_input): prevent simultaneous input focus
This commit is contained in:
parent
1ad6877cb1
commit
756f4b6ba6
1 changed files with 37 additions and 17 deletions
|
|
@ -6,6 +6,7 @@
|
||||||
//!
|
//!
|
||||||
//! A [`TextInput`] has some local [`State`].
|
//! A [`TextInput`] has some local [`State`].
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::cell::{Cell, LazyCell};
|
||||||
|
|
||||||
use crate::ext::ColorExt;
|
use crate::ext::ColorExt;
|
||||||
use crate::theme::THEME;
|
use crate::theme::THEME;
|
||||||
|
|
@ -48,6 +49,11 @@ use cctk::sctk::reexports::client::protocol::wl_data_device_manager::DndAction;
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
use iced_runtime::command::platform_specific::wayland::data_device::{DataFromMimeType, DndIcon};
|
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<Cell<Instant>> = LazyCell::new(|| Cell::new(Instant::now()));
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new [`TextInput`].
|
/// Creates a new [`TextInput`].
|
||||||
///
|
///
|
||||||
/// [`TextInput`]: widget::TextInput
|
/// [`TextInput`]: widget::TextInput
|
||||||
|
|
@ -572,12 +578,11 @@ where
|
||||||
state.dirty = true;
|
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() {
|
if self.always_active && state.is_focused.is_none() {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
LAST_FOCUS_UPDATE.with(|x| x.set(now));
|
||||||
state.is_focused = Some(Focus {
|
state.is_focused = Some(Focus {
|
||||||
updated_at: now,
|
updated_at: now,
|
||||||
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
|
let mut children: Vec<_> = self
|
||||||
.leading_icon
|
.leading_icon
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
|
|
@ -1181,7 +1192,7 @@ pub fn update<'a, Message>(
|
||||||
event: Event,
|
event: Event,
|
||||||
text_layout: Layout<'_>,
|
text_layout: Layout<'_>,
|
||||||
trailing_icon_layout: Option<Layout<'_>>,
|
trailing_icon_layout: Option<Layout<'_>>,
|
||||||
cursor_position: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
value: &mut Value,
|
value: &mut Value,
|
||||||
|
|
@ -1220,24 +1231,29 @@ where
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||||
let state = state();
|
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(|| {
|
state.is_focused = state.is_focused.or_else(|| {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
LAST_FOCUS_UPDATE.with(|x| x.set(now));
|
||||||
Some(Focus {
|
Some(Focus {
|
||||||
updated_at: now,
|
updated_at: now,
|
||||||
now,
|
now,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let Some(pos) = cursor_position.position() else {
|
if let Some(cursor_position) = click_position {
|
||||||
return event::Status::Ignored;
|
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(cursor_position, state.last_click);
|
||||||
|
|
||||||
let click = mouse::Click::new(pos, state.last_click);
|
|
||||||
|
|
||||||
match (
|
match (
|
||||||
&state.dragging_state,
|
&state.dragging_state,
|
||||||
|
|
@ -1284,7 +1300,7 @@ where
|
||||||
height: text_layout.bounds().height,
|
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
|
// XXX never start a dnd if the input is secure
|
||||||
if is_secure {
|
if is_secure {
|
||||||
return event::Status::Ignored;
|
return event::Status::Ignored;
|
||||||
|
|
@ -1394,6 +1410,7 @@ where
|
||||||
shell.publish(message);
|
shell.publish(message);
|
||||||
|
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
LAST_FOCUS_UPDATE.with(|x| x.set(now));
|
||||||
state.is_focused = Some(Focus {
|
state.is_focused = Some(Focus {
|
||||||
updated_at: now,
|
updated_at: now,
|
||||||
now,
|
now,
|
||||||
|
|
@ -1411,11 +1428,11 @@ where
|
||||||
|
|
||||||
if is_editable {
|
if is_editable {
|
||||||
if let Some(trailing_layout) = trailing_icon_layout {
|
if let Some(trailing_layout) = trailing_icon_layout {
|
||||||
let is_trailing_clicked = cursor_position.is_over(trailing_layout.bounds())
|
let is_trailing_clicked =
|
||||||
&& on_toggle_edit.is_some();
|
cursor.is_over(trailing_layout.bounds()) && on_toggle_edit.is_some();
|
||||||
|
|
||||||
if is_trailing_clicked {
|
if is_trailing_clicked {
|
||||||
let Some(pos) = cursor_position.position() else {
|
let Some(pos) = cursor.position() else {
|
||||||
return event::Status::Ignored;
|
return event::Status::Ignored;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1433,6 +1450,7 @@ where
|
||||||
shell.publish(message);
|
shell.publish(message);
|
||||||
|
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
LAST_FOCUS_UPDATE.with(|x| x.set(now));
|
||||||
state.is_focused = Some(Focus {
|
state.is_focused = Some(Focus {
|
||||||
updated_at: now,
|
updated_at: now,
|
||||||
now,
|
now,
|
||||||
|
|
@ -1488,6 +1506,7 @@ where
|
||||||
|
|
||||||
let modifiers = state.keyboard_modifiers;
|
let modifiers = state.keyboard_modifiers;
|
||||||
focus.updated_at = Instant::now();
|
focus.updated_at = Instant::now();
|
||||||
|
LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));
|
||||||
|
|
||||||
match key {
|
match key {
|
||||||
keyboard::Key::Named(keyboard::key::Named::Enter) => {
|
keyboard::Key::Named(keyboard::key::Named::Enter) => {
|
||||||
|
|
@ -1692,6 +1711,7 @@ where
|
||||||
shell.publish(message);
|
shell.publish(message);
|
||||||
|
|
||||||
focus.updated_at = Instant::now();
|
focus.updated_at = Instant::now();
|
||||||
|
LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));
|
||||||
|
|
||||||
let value = if is_secure {
|
let value = if is_secure {
|
||||||
unsecured_value.secure()
|
unsecured_value.secure()
|
||||||
|
|
@ -2528,7 +2548,7 @@ impl State {
|
||||||
/// Focuses the [`TextInput`].
|
/// Focuses the [`TextInput`].
|
||||||
pub fn focus(&mut self) {
|
pub fn focus(&mut self) {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
LAST_FOCUS_UPDATE.with(|x| x.set(now));
|
||||||
self.is_read_only = false;
|
self.is_read_only = false;
|
||||||
self.is_focused = Some(Focus {
|
self.is_focused = Some(Focus {
|
||||||
updated_at: now,
|
updated_at: now,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue