refactor(text_input): make on_input optional
this prevents a redraw of every surface in an application whenever the input changes.
This commit is contained in:
parent
ab438b87f9
commit
306995febf
3 changed files with 91 additions and 37 deletions
|
|
@ -469,7 +469,7 @@ where
|
||||||
text_input("", self.input_color)
|
text_input("", self.input_color)
|
||||||
.on_input(move |s| on_update(ColorPickerUpdate::Input(s)))
|
.on_input(move |s| on_update(ColorPickerUpdate::Input(s)))
|
||||||
.on_paste(move |s| on_update(ColorPickerUpdate::Input(s)))
|
.on_paste(move |s| on_update(ColorPickerUpdate::Input(s)))
|
||||||
.on_submit(on_update(ColorPickerUpdate::AppliedColor))
|
.on_submit(move |_| on_update(ColorPickerUpdate::AppliedColor))
|
||||||
.leading_icon(
|
.leading_icon(
|
||||||
color_button(
|
color_button(
|
||||||
None,
|
None,
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@ pub struct TextInput<'a, Message> {
|
||||||
error: Option<Cow<'a, str>>,
|
error: Option<Cow<'a, str>>,
|
||||||
on_input: Option<Box<dyn Fn(String) -> Message + 'a>>,
|
on_input: Option<Box<dyn Fn(String) -> Message + 'a>>,
|
||||||
on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>,
|
on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>,
|
||||||
on_submit: Option<Message>,
|
on_submit: Option<Box<dyn Fn(String) -> Message + 'a>>,
|
||||||
on_toggle_edit: Option<Box<dyn Fn(bool) -> Message + 'a>>,
|
on_toggle_edit: Option<Box<dyn Fn(bool) -> Message + 'a>>,
|
||||||
leading_icon: Option<Element<'a, Message, crate::Theme, crate::Renderer>>,
|
leading_icon: Option<Element<'a, Message, crate::Theme, crate::Renderer>>,
|
||||||
trailing_icon: Option<Element<'a, Message, crate::Theme, crate::Renderer>>,
|
trailing_icon: Option<Element<'a, Message, crate::Theme, crate::Renderer>>,
|
||||||
|
|
@ -211,6 +211,8 @@ pub struct TextInput<'a, Message> {
|
||||||
line_height: text::LineHeight,
|
line_height: text::LineHeight,
|
||||||
helper_line_height: text::LineHeight,
|
helper_line_height: text::LineHeight,
|
||||||
always_active: bool,
|
always_active: bool,
|
||||||
|
/// The text input tracks and manages the input value in its state.
|
||||||
|
manage_value: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message> TextInput<'a, Message>
|
impl<'a, Message> TextInput<'a, Message>
|
||||||
|
|
@ -255,6 +257,7 @@ where
|
||||||
label: None,
|
label: None,
|
||||||
helper_text: None,
|
helper_text: None,
|
||||||
always_active: false,
|
always_active: false,
|
||||||
|
manage_value: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -340,14 +343,24 @@ where
|
||||||
|
|
||||||
/// Sets the message that should be produced when the [`TextInput`] is
|
/// Sets the message that should be produced when the [`TextInput`] is
|
||||||
/// focused and the enter key is pressed.
|
/// focused and the enter key is pressed.
|
||||||
pub fn on_submit(self, message: Message) -> Self {
|
pub fn on_submit<F>(mut self, callback: F) -> Self
|
||||||
self.on_submit_maybe(Some(message))
|
where
|
||||||
|
F: 'a + Fn(String) -> Message,
|
||||||
|
{
|
||||||
|
self.on_submit_maybe(Some(Box::new(callback)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maybe sets the message that should be produced when the [`TextInput`] is
|
/// Maybe sets the message that should be produced when the [`TextInput`] is
|
||||||
/// focused and the enter key is pressed.
|
/// focused and the enter key is pressed.
|
||||||
pub fn on_submit_maybe(mut self, message: Option<Message>) -> Self {
|
pub fn on_submit_maybe<F>(mut self, callback: Option<F>) -> Self
|
||||||
self.on_submit = message;
|
where
|
||||||
|
F: 'a + Fn(String) -> Message,
|
||||||
|
{
|
||||||
|
if let Some(callback) = callback {
|
||||||
|
self.on_submit = Some(Box::new(callback));
|
||||||
|
} else {
|
||||||
|
self.on_submit = None;
|
||||||
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -416,6 +429,12 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the text input to manage its input value or not
|
||||||
|
pub fn manage_value(mut self, manage_value: bool) -> Self {
|
||||||
|
self.manage_value = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
|
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
|
||||||
/// [`Value`] if provided.
|
/// [`Value`] if provided.
|
||||||
///
|
///
|
||||||
|
|
@ -526,9 +545,14 @@ where
|
||||||
|
|
||||||
fn diff(&mut self, tree: &mut Tree) {
|
fn diff(&mut self, tree: &mut Tree) {
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
if !self.manage_value || !self.value.is_empty() && state.tracked_value != self.value {
|
||||||
|
state.tracked_value = self.value.clone();
|
||||||
|
} else if self.value.is_empty() {
|
||||||
|
self.value = state.tracked_value.clone();
|
||||||
|
// std::mem::swap(&mut state.tracked_value, &mut self.value);
|
||||||
|
}
|
||||||
// Unfocus text input if it becomes disabled
|
// Unfocus text input if it becomes disabled
|
||||||
if self.on_input.is_none() {
|
if self.on_input.is_none() && !self.manage_value {
|
||||||
state.last_click = None;
|
state.last_click = None;
|
||||||
state.is_focused = None;
|
state.is_focused = None;
|
||||||
state.is_pasting = None;
|
state.is_pasting = None;
|
||||||
|
|
@ -597,6 +621,11 @@ where
|
||||||
state.is_focused = None;
|
state.is_focused = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop pasting if input becomes disabled
|
||||||
|
if !self.manage_value && self.on_input.is_none() {
|
||||||
|
state.is_pasting = None;
|
||||||
|
}
|
||||||
|
|
||||||
let mut children: Vec<_> = self
|
let mut children: Vec<_> = self
|
||||||
.leading_icon
|
.leading_icon
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
|
|
@ -824,13 +853,14 @@ where
|
||||||
self.is_editable,
|
self.is_editable,
|
||||||
self.on_input.as_deref(),
|
self.on_input.as_deref(),
|
||||||
self.on_paste.as_deref(),
|
self.on_paste.as_deref(),
|
||||||
&self.on_submit,
|
self.on_submit.as_deref(),
|
||||||
self.on_toggle_edit.as_deref(),
|
self.on_toggle_edit.as_deref(),
|
||||||
|| tree.state.downcast_mut::<State>(),
|
|| tree.state.downcast_mut::<State>(),
|
||||||
self.on_create_dnd_source.as_deref(),
|
self.on_create_dnd_source.as_deref(),
|
||||||
dnd_id,
|
dnd_id,
|
||||||
line_height,
|
line_height,
|
||||||
layout,
|
layout,
|
||||||
|
self.manage_value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -856,7 +886,7 @@ where
|
||||||
&self.placeholder,
|
&self.placeholder,
|
||||||
self.size,
|
self.size,
|
||||||
self.font,
|
self.font,
|
||||||
self.on_input.is_none(),
|
self.on_input.is_none() && !self.manage_value,
|
||||||
self.is_secure,
|
self.is_secure,
|
||||||
self.leading_icon.as_ref(),
|
self.leading_icon.as_ref(),
|
||||||
self.trailing_icon.as_ref(),
|
self.trailing_icon.as_ref(),
|
||||||
|
|
@ -925,7 +955,11 @@ where
|
||||||
}
|
}
|
||||||
let mut children = layout.children();
|
let mut children = layout.children();
|
||||||
let layout = children.next().unwrap();
|
let layout = children.next().unwrap();
|
||||||
mouse_interaction(layout, cursor_position, self.on_input.is_none())
|
mouse_interaction(
|
||||||
|
layout,
|
||||||
|
cursor_position,
|
||||||
|
self.on_input.is_none() && !self.manage_value,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn id(&self) -> Option<Id> {
|
fn id(&self) -> Option<Id> {
|
||||||
|
|
@ -1236,13 +1270,14 @@ pub fn update<'a, Message: 'static>(
|
||||||
is_editable: bool,
|
is_editable: bool,
|
||||||
on_input: Option<&dyn Fn(String) -> Message>,
|
on_input: Option<&dyn Fn(String) -> Message>,
|
||||||
on_paste: Option<&dyn Fn(String) -> Message>,
|
on_paste: Option<&dyn Fn(String) -> Message>,
|
||||||
on_submit: &Option<Message>,
|
on_submit: Option<&dyn Fn(String) -> Message>,
|
||||||
on_toggle_edit: Option<&dyn Fn(bool) -> Message>,
|
on_toggle_edit: Option<&dyn Fn(bool) -> Message>,
|
||||||
state: impl FnOnce() -> &'a mut State,
|
state: impl FnOnce() -> &'a mut State,
|
||||||
#[allow(unused_variables)] on_start_dnd_source: Option<&dyn Fn(State) -> Message>,
|
#[allow(unused_variables)] on_start_dnd_source: Option<&dyn Fn(State) -> Message>,
|
||||||
#[allow(unused_variables)] dnd_id: u128,
|
#[allow(unused_variables)] dnd_id: u128,
|
||||||
line_height: text::LineHeight,
|
line_height: text::LineHeight,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
|
manage_value: bool,
|
||||||
) -> event::Status
|
) -> event::Status
|
||||||
where
|
where
|
||||||
Message: Clone,
|
Message: Clone,
|
||||||
|
|
@ -1264,7 +1299,7 @@ where
|
||||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||||
let state = state();
|
let state = state();
|
||||||
|
|
||||||
let click_position = if on_input.is_some() {
|
let click_position = if on_input.is_some() || manage_value {
|
||||||
cursor.position_over(layout.bounds())
|
cursor.position_over(layout.bounds())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -1299,7 +1334,7 @@ where
|
||||||
// single click that is on top of the selected text
|
// single click that is on top of the selected text
|
||||||
// is the click on selected text?
|
// is the click on selected text?
|
||||||
|
|
||||||
if let Some(on_input) = on_input {
|
if manage_value || on_input.is_some() {
|
||||||
let left = start.min(end);
|
let left = start.min(end);
|
||||||
let right = end.max(start);
|
let right = end.max(start);
|
||||||
|
|
||||||
|
|
@ -1339,8 +1374,11 @@ where
|
||||||
|
|
||||||
let contents = editor.contents();
|
let contents = editor.contents();
|
||||||
let unsecured_value = Value::new(&contents);
|
let unsecured_value = Value::new(&contents);
|
||||||
let message = (on_input)(contents);
|
state.tracked_value = unsecured_value.clone();
|
||||||
shell.publish(message);
|
if let Some(on_input) = on_input {
|
||||||
|
let message = (on_input)(contents);
|
||||||
|
shell.publish(message);
|
||||||
|
}
|
||||||
if let Some(on_start_dnd) = on_start_dnd_source {
|
if let Some(on_start_dnd) = on_start_dnd_source {
|
||||||
shell.publish(on_start_dnd(state.clone()));
|
shell.publish(on_start_dnd(state.clone()));
|
||||||
}
|
}
|
||||||
|
|
@ -1531,7 +1569,7 @@ where
|
||||||
let state = state();
|
let state = state();
|
||||||
|
|
||||||
if let Some(focus) = &mut state.is_focused {
|
if let Some(focus) = &mut state.is_focused {
|
||||||
let Some(on_input) = on_input else {
|
if !manage_value && on_input.is_none() {
|
||||||
return event::Status::Ignored;
|
return event::Status::Ignored;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1546,7 +1584,7 @@ where
|
||||||
match key {
|
match key {
|
||||||
keyboard::Key::Named(keyboard::key::Named::Enter) => {
|
keyboard::Key::Named(keyboard::key::Named::Enter) => {
|
||||||
if let Some(on_submit) = on_submit.clone() {
|
if let Some(on_submit) = on_submit.clone() {
|
||||||
shell.publish(on_submit);
|
shell.publish((on_submit)(unsecured_value.to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
keyboard::Key::Named(keyboard::key::Named::Backspace) => {
|
keyboard::Key::Named(keyboard::key::Named::Backspace) => {
|
||||||
|
|
@ -1566,9 +1604,11 @@ where
|
||||||
|
|
||||||
let contents = editor.contents();
|
let contents = editor.contents();
|
||||||
let unsecured_value = Value::new(&contents);
|
let unsecured_value = Value::new(&contents);
|
||||||
let message = (on_input)(editor.contents());
|
state.tracked_value = unsecured_value.clone();
|
||||||
shell.publish(message);
|
if let Some(on_input) = on_input {
|
||||||
|
let message = (on_input)(editor.contents());
|
||||||
|
shell.publish(message);
|
||||||
|
}
|
||||||
let value = if is_secure {
|
let value = if is_secure {
|
||||||
unsecured_value.secure()
|
unsecured_value.secure()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1592,8 +1632,12 @@ where
|
||||||
editor.delete();
|
editor.delete();
|
||||||
let contents = editor.contents();
|
let contents = editor.contents();
|
||||||
let unsecured_value = Value::new(&contents);
|
let unsecured_value = Value::new(&contents);
|
||||||
let message = (on_input)(contents);
|
if let Some(on_input) = on_input {
|
||||||
shell.publish(message);
|
let message = (on_input)(contents);
|
||||||
|
state.tracked_value = unsecured_value.clone();
|
||||||
|
shell.publish(message);
|
||||||
|
}
|
||||||
|
|
||||||
let value = if is_secure {
|
let value = if is_secure {
|
||||||
unsecured_value.secure()
|
unsecured_value.secure()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1671,10 +1715,12 @@ where
|
||||||
|
|
||||||
let mut editor = Editor::new(value, &mut state.cursor);
|
let mut editor = Editor::new(value, &mut state.cursor);
|
||||||
editor.delete();
|
editor.delete();
|
||||||
|
let content = editor.contents();
|
||||||
let message = (on_input)(editor.contents());
|
state.tracked_value = Value::new(&content);
|
||||||
|
if let Some(on_input) = on_input {
|
||||||
shell.publish(message);
|
let message = (on_input)(content);
|
||||||
|
shell.publish(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
keyboard::Key::Character(c)
|
keyboard::Key::Character(c)
|
||||||
|
|
@ -1699,13 +1745,16 @@ where
|
||||||
|
|
||||||
let contents = editor.contents();
|
let contents = editor.contents();
|
||||||
let unsecured_value = Value::new(&contents);
|
let unsecured_value = Value::new(&contents);
|
||||||
let message = if let Some(paste) = &on_paste {
|
state.tracked_value = unsecured_value.clone();
|
||||||
(paste)(contents)
|
if let Some(on_input) = on_input {
|
||||||
} else {
|
let message = if let Some(paste) = &on_paste {
|
||||||
(on_input)(contents)
|
(paste)(contents)
|
||||||
};
|
} else {
|
||||||
shell.publish(message);
|
(on_input)(contents)
|
||||||
|
};
|
||||||
|
|
||||||
|
shell.publish(message);
|
||||||
|
}
|
||||||
state.is_pasting = Some(content);
|
state.is_pasting = Some(content);
|
||||||
|
|
||||||
let value = if is_secure {
|
let value = if is_secure {
|
||||||
|
|
@ -1750,8 +1799,11 @@ where
|
||||||
}
|
}
|
||||||
let contents = editor.contents();
|
let contents = editor.contents();
|
||||||
let unsecured_value = Value::new(&contents);
|
let unsecured_value = Value::new(&contents);
|
||||||
let message = (on_input)(contents);
|
state.tracked_value = unsecured_value.clone();
|
||||||
shell.publish(message);
|
if let Some(on_input) = on_input {
|
||||||
|
let message = (on_input)(contents);
|
||||||
|
shell.publish(message);
|
||||||
|
}
|
||||||
|
|
||||||
focus.updated_at = Instant::now();
|
focus.updated_at = Instant::now();
|
||||||
LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));
|
LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));
|
||||||
|
|
@ -1926,7 +1978,7 @@ where
|
||||||
editor.paste(Value::new(content.as_str()));
|
editor.paste(Value::new(content.as_str()));
|
||||||
let contents = editor.contents();
|
let contents = editor.contents();
|
||||||
let unsecured_value = Value::new(&contents);
|
let unsecured_value = Value::new(&contents);
|
||||||
|
state.tracked_value = unsecured_value.clone();
|
||||||
if let Some(on_paste) = on_paste.as_ref() {
|
if let Some(on_paste) = on_paste.as_ref() {
|
||||||
let message = (on_paste)(contents);
|
let message = (on_paste)(contents);
|
||||||
shell.publish(message);
|
shell.publish(message);
|
||||||
|
|
@ -2408,6 +2460,7 @@ pub(crate) struct DndOfferState;
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
|
pub tracked_value: Value,
|
||||||
pub value: crate::Plain,
|
pub value: crate::Plain,
|
||||||
pub placeholder: crate::Plain,
|
pub placeholder: crate::Plain,
|
||||||
pub label: crate::Plain,
|
pub label: crate::Plain,
|
||||||
|
|
@ -2482,6 +2535,7 @@ impl State {
|
||||||
/// Creates a new [`State`], representing a focused [`TextInput`].
|
/// Creates a new [`State`], representing a focused [`TextInput`].
|
||||||
pub fn focused(is_secure: bool, is_read_only: bool) -> Self {
|
pub fn focused(is_secure: bool, is_read_only: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
tracked_value: Value::default(),
|
||||||
is_secure,
|
is_secure,
|
||||||
value: crate::Plain::default(),
|
value: crate::Plain::default(),
|
||||||
placeholder: crate::Plain::default(),
|
placeholder: crate::Plain::default(),
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use unicode_segmentation::UnicodeSegmentation;
|
||||||
///
|
///
|
||||||
/// [`TextInput`]: crate::widget::TextInput
|
/// [`TextInput`]: crate::widget::TextInput
|
||||||
// TODO: Reduce allocations, cache results (?)
|
// TODO: Reduce allocations, cache results (?)
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Default, Debug, Clone, PartialEq)]
|
||||||
pub struct Value {
|
pub struct Value {
|
||||||
graphemes: Vec<String>,
|
graphemes: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue