fix(editable): the UX is closer to design now
This fixes the unresponsive trailing icon and changes the behavior to be closer to the UI/UX design.
This commit is contained in:
parent
22661fd764
commit
aef328238f
1 changed files with 108 additions and 38 deletions
|
|
@ -66,18 +66,20 @@ pub fn editable_input<'a, Message: Clone + 'static>(
|
||||||
editing: bool,
|
editing: bool,
|
||||||
on_toggle_edit: impl Fn(bool) -> Message + 'a,
|
on_toggle_edit: impl Fn(bool) -> Message + 'a,
|
||||||
) -> TextInput<'a, Message> {
|
) -> TextInput<'a, Message> {
|
||||||
let icon = crate::widget::icon::from_name(if editing {
|
// The trailing icon is a placeholder; diff() rebuilds it reactively
|
||||||
"edit-clear-symbolic"
|
// based on the current is_read_only state and value content.
|
||||||
} else {
|
|
||||||
"edit-symbolic"
|
|
||||||
});
|
|
||||||
|
|
||||||
TextInput::new(placeholder, text)
|
TextInput::new(placeholder, text)
|
||||||
.style(crate::theme::TextInput::EditableText)
|
.style(crate::theme::TextInput::EditableText)
|
||||||
.editable()
|
.editable()
|
||||||
.editing(editing)
|
.editing(editing)
|
||||||
.on_toggle_edit(on_toggle_edit)
|
.on_toggle_edit(on_toggle_edit)
|
||||||
.trailing_icon(icon.size(16).into())
|
.trailing_icon(
|
||||||
|
crate::widget::icon::from_name("edit-symbolic")
|
||||||
|
.size(16)
|
||||||
|
.apply(crate::widget::container)
|
||||||
|
.padding(8)
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new search [`TextInput`].
|
/// Creates a new search [`TextInput`].
|
||||||
|
|
@ -666,7 +668,36 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.is_read_only = state.is_read_only;
|
if self.is_editable_variant {
|
||||||
|
if !state.is_focused() {
|
||||||
|
// Not yet interacted, use the widget's value
|
||||||
|
state.is_read_only = self.is_read_only;
|
||||||
|
} else {
|
||||||
|
// Already interacted, use the state
|
||||||
|
self.is_read_only = state.is_read_only;
|
||||||
|
}
|
||||||
|
|
||||||
|
let editing = !self.is_read_only;
|
||||||
|
let icon_name = if editing {
|
||||||
|
if self.value.is_empty() {
|
||||||
|
"window-close-symbolic"
|
||||||
|
} else {
|
||||||
|
"edit-clear-symbolic"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"edit-symbolic"
|
||||||
|
};
|
||||||
|
|
||||||
|
self.trailing_icon = Some(
|
||||||
|
crate::widget::icon::from_name(icon_name)
|
||||||
|
.size(16)
|
||||||
|
.apply(crate::widget::container)
|
||||||
|
.padding(8)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.is_read_only = state.is_read_only;
|
||||||
|
}
|
||||||
|
|
||||||
// Stop pasting if input becomes disabled
|
// Stop pasting if input becomes disabled
|
||||||
if !self.manage_value && self.on_input.is_none() {
|
if !self.manage_value && self.on_input.is_none() {
|
||||||
|
|
@ -855,9 +886,6 @@ where
|
||||||
if !state.is_read_only && state.is_focused.is_some_and(|f| !f.focused) {
|
if !state.is_read_only && state.is_focused.is_some_and(|f| !f.focused) {
|
||||||
state.is_read_only = true;
|
state.is_read_only = true;
|
||||||
shell.publish((on_edit)(false));
|
shell.publish((on_edit)(false));
|
||||||
} else if state.is_focused() && state.is_read_only {
|
|
||||||
state.is_read_only = false;
|
|
||||||
shell.publish((on_edit)(true));
|
|
||||||
} else if let Some(f) = state.is_focused.as_mut().filter(|f| f.needs_update) {
|
} else if let Some(f) = state.is_focused.as_mut().filter(|f| f.needs_update) {
|
||||||
// TODO do we want to just move this to on_focus or on_unfocus for all inputs?
|
// TODO do we want to just move this to on_focus or on_unfocus for all inputs?
|
||||||
f.needs_update = false;
|
f.needs_update = false;
|
||||||
|
|
@ -1018,9 +1046,7 @@ where
|
||||||
index += 1;
|
index += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (Some(trailing_icon), Some(tree)) =
|
if self.trailing_icon.is_some() {
|
||||||
(self.trailing_icon.as_ref(), state.children.get(index))
|
|
||||||
{
|
|
||||||
let mut children = layout.children();
|
let mut children = layout.children();
|
||||||
children.next();
|
children.next();
|
||||||
// skip if there is no leading icon
|
// skip if there is no leading icon
|
||||||
|
|
@ -1030,13 +1056,21 @@ where
|
||||||
let trailing_icon_layout = children.next().unwrap();
|
let trailing_icon_layout = children.next().unwrap();
|
||||||
|
|
||||||
if cursor_position.is_over(trailing_icon_layout.bounds()) {
|
if cursor_position.is_over(trailing_icon_layout.bounds()) {
|
||||||
return trailing_icon.as_widget().mouse_interaction(
|
if self.is_editable_variant {
|
||||||
tree,
|
return mouse::Interaction::Pointer;
|
||||||
layout,
|
}
|
||||||
cursor_position,
|
|
||||||
viewport,
|
if let Some((trailing_icon, tree)) =
|
||||||
renderer,
|
self.trailing_icon.as_ref().zip(state.children.get(index))
|
||||||
);
|
{
|
||||||
|
return trailing_icon.as_widget().mouse_interaction(
|
||||||
|
tree,
|
||||||
|
layout,
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
renderer,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut children = layout.children();
|
let mut children = layout.children();
|
||||||
|
|
@ -1426,21 +1460,54 @@ pub fn update<'a, Message: Clone + 'static>(
|
||||||
&& edit_button_layout.is_some_and(|l| cursor.is_over(l.bounds()))
|
&& edit_button_layout.is_some_and(|l| cursor.is_over(l.bounds()))
|
||||||
{
|
{
|
||||||
if is_editable_variant {
|
if is_editable_variant {
|
||||||
state.is_read_only = !state.is_read_only;
|
let has_content = !unsecured_value.is_empty();
|
||||||
state.move_cursor_to_end();
|
let is_editing = !state.is_read_only;
|
||||||
|
|
||||||
if let Some(on_toggle_edit) = on_toggle_edit {
|
if is_editing && has_content {
|
||||||
shell.publish(on_toggle_edit(!state.is_read_only));
|
if let Some(on_input) = on_input {
|
||||||
|
shell.publish((on_input)(String::new()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if manage_value {
|
||||||
|
*unsecured_value = Value::new("");
|
||||||
|
state.tracked_value = unsecured_value.clone();
|
||||||
|
|
||||||
|
let cleared_value = if is_secure {
|
||||||
|
unsecured_value.secure()
|
||||||
|
} else {
|
||||||
|
unsecured_value.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
update_cache(state, &cleared_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.move_cursor_to_end();
|
||||||
|
} else if is_editing {
|
||||||
|
// Close: toggle back to read-only and unfocus.
|
||||||
|
state.is_read_only = true;
|
||||||
|
state.unfocus();
|
||||||
|
|
||||||
|
if let Some(on_toggle_edit) = on_toggle_edit {
|
||||||
|
shell.publish(on_toggle_edit(false));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Edit: toggle to editing, select all, and focus.
|
||||||
|
state.is_read_only = false;
|
||||||
|
state.cursor.select_range(0, value.len());
|
||||||
|
|
||||||
|
if let Some(on_toggle_edit) = on_toggle_edit {
|
||||||
|
shell.publish(on_toggle_edit(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
let now = Instant::now();
|
||||||
|
LAST_FOCUS_UPDATE.with(|x| x.set(now));
|
||||||
|
state.is_focused = Some(Focus {
|
||||||
|
updated_at: now,
|
||||||
|
now,
|
||||||
|
focused: true,
|
||||||
|
needs_update: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let now = Instant::now();
|
|
||||||
LAST_FOCUS_UPDATE.with(|x| x.set(now));
|
|
||||||
state.is_focused = Some(Focus {
|
|
||||||
updated_at: now,
|
|
||||||
now,
|
|
||||||
focused: true,
|
|
||||||
needs_update: false,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
|
|
@ -1550,15 +1617,18 @@ pub fn update<'a, Message: Clone + 'static>(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Focus on click of the text input, and ensure that the input is writable.
|
// Focus on click of the text input, and ensure that the input is writable.
|
||||||
if !state.is_focused()
|
if matches!(state.dragging_state, None | Some(DraggingState::Selection))
|
||||||
&& matches!(state.dragging_state, None | Some(DraggingState::Selection))
|
&& (!state.is_focused() || (is_editable_variant && state.is_read_only))
|
||||||
{
|
{
|
||||||
if let Some(on_focus) = on_focus {
|
if !state.is_focused() {
|
||||||
shell.publish(on_focus.clone());
|
if let Some(on_focus) = on_focus {
|
||||||
|
shell.publish(on_focus.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.is_read_only {
|
if state.is_read_only {
|
||||||
state.is_read_only = false;
|
state.is_read_only = false;
|
||||||
|
state.cursor.select_range(0, value.len());
|
||||||
if let Some(on_toggle_edit) = on_toggle_edit {
|
if let Some(on_toggle_edit) = on_toggle_edit {
|
||||||
let message = (on_toggle_edit)(true);
|
let message = (on_toggle_edit)(true);
|
||||||
shell.publish(message);
|
shell.publish(message);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue