A text field keeps the cursor where an edit left it unless it asks to be pinned to the end, which only the time picker does, and a hook for a window that loses the keyboard
A field built with `select_on_focus( true )` kept its cursor at the end of the value after every insertion and deletion. That was meant for the time picker, whose `on_change` normalises what was typed, "2" becoming "02", so that a cursor placed at the end of the typed text would land in the middle of the normalised one. But the flag was also the only way to have a field select its whole value on focus, so every field that wanted that got the pinning too, and in an address bar it is a defect: tapping inside the address to correct a word, the cursor was placed where the finger was, and the first character typed sent it to the end, so the rest of the edit went to the wrong place. The two behaviours are now separate. `select_on_focus` only selects the value when the field takes the focus. The new `TextEdit::pin_cursor_to_end( bool )`, `false` by default, keeps the cursor at the end of the displayed value through the `usize::MAX` sentinel, as before, for fields whose `on_change` rewrites the value. It reaches the runtime in a new `pin_cursor_to_end` field of `WidgetHandlers::TextEdit`, set where the snapshot is built in `Element` and copied in its `Clone`, and the four places in `insert_delete.rs` that anchored the cursor, for an insertion, a delete forward, the deletion of a selection and the move after a replacement, read it instead of `select_on_focus`. The time picker's value fields ask for it, so they behave as they did. A field that uses `select_on_focus` only to select on focus now edits with an ordinary cursor. The one other user known is a dialog in `crate`, which gets the ordinary behaviour; if its `on_change` normalises the text it has to ask for `pin_cursor_to_end( true )` as the time picker does. Documentation of the previous commit and of what building crate showed. `docs/widgets.md` says that a `pressable` loses the tap and the right click to an interactive widget inside it even when that widget has no handler, as a `list_item` without `on_press`, so its content should be built from pieces that take no input; and that the hint of `Button::tooltip` is an xdg-popup beside the button in a window, placed by the compositor and taking no grab, and an overlay spanning the output on a layer surface. `docs/theming.md` gains "Application icons": a desktop entry can point at an installed theme icon, the Figma glass effects resvg does not reproduce, a `backdrop-filter` in a `<foreignObject>` ignored and `plus-lighter` rewritten to `screen` by `decode_svg_bytes`, and the advice to flatten them; the table row of `decode_svg_bytes` mentions the rewrite and that its pixels come back premultiplied. `docs/cookbook.md` says that requesting the focus for the text field that already has it puts the cursor at the end of its value without a second `on_focus`, which is how an application moves the cursor after replacing the value, and that Tab and the vertical arrows reach `App::on_key_with_modifiers` before the field, while Escape reaches it after ltk has closed any transient UI and taken the focus off the field. Losing the keyboard. An application that repeats keys on its own timer, as prompt does for its terminal, never heard that the window lost the keyboard, so a key held while the focus went elsewhere kept repeating until the next key, since its release went to the other window. The new `App::on_keyboard_leave()`, a no-op by default, is called from the keyboard's `leave`, right after ltk stops its own repeat, for the application to stop its repeat and forget the modifiers it kept. Documentation of the previous commit's hooks and widgets. `docs/architecture.md` lists `on_mouse_button` with `MouseButtonEvent` and `MouseButton`, `on_modifiers` and `on_keyboard_leave` among the input hooks. `docs/widgets.md` describes `ListItem::ink( label, secondary )`, which draws a row's label, subtitle, trailing text and hover and press tints in colours of its own for a surface the theme does not know about, and says that `icon_button` keeps a bitmap's proportions, fitting one that is not square into its size and centring it rather than stretching it. Not done. There is no test for the new flag, as `select_on_focus` has one in `text_edit/tests.rs` for its builder and its propagation to the handler, and the documentation of the widget does not mention it.
This commit is contained in:
@@ -336,6 +336,7 @@ impl<Msg: Clone> Element<Msg>
|
||||
font_size: t.font_size,
|
||||
pad_h: t.pad_h,
|
||||
select_on_focus: t.select_on_focus,
|
||||
pin_cursor_to_end: t.pin_cursor_to_end,
|
||||
password_toggle_msg: t.password_toggle.as_ref().map( |( _, m )| m.clone() ),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,8 @@ pub enum WidgetHandlers<Msg: Clone>
|
||||
/// whether the new selection should anchor at `0` (replace
|
||||
/// on next keystroke) or at the cursor (insert).
|
||||
select_on_focus: bool,
|
||||
/// `true` when edits keep the cursor at the end of the displayed value.
|
||||
pin_cursor_to_end: bool,
|
||||
/// Snapshot of the
|
||||
/// [`crate::widget::text_edit::TextEdit::password_toggle`]
|
||||
/// callback. When `Some`, pointer / touch dispatch checks
|
||||
@@ -144,7 +146,7 @@ impl<Msg: Clone> Clone for WidgetHandlers<Msg>
|
||||
WidgetHandlers::Radio { on_select, selected } => WidgetHandlers::Radio { on_select: on_select.clone(), selected: *selected },
|
||||
WidgetHandlers::ListItem { on_press } => WidgetHandlers::ListItem { on_press: on_press.clone() },
|
||||
WidgetHandlers::WindowButton { on_press } => WidgetHandlers::WindowButton { on_press: on_press.clone() },
|
||||
WidgetHandlers::TextEdit { value, on_change, on_submit, on_focus, secure, purpose, multiline, align, font_size, pad_h, select_on_focus, password_toggle_msg } =>
|
||||
WidgetHandlers::TextEdit { value, on_change, on_submit, on_focus, secure, purpose, multiline, align, font_size, pad_h, select_on_focus, pin_cursor_to_end, password_toggle_msg } =>
|
||||
{
|
||||
WidgetHandlers::TextEdit
|
||||
{
|
||||
@@ -159,6 +161,7 @@ impl<Msg: Clone> Clone for WidgetHandlers<Msg>
|
||||
font_size: *font_size,
|
||||
pad_h: *pad_h,
|
||||
select_on_focus: *select_on_focus,
|
||||
pin_cursor_to_end: *pin_cursor_to_end,
|
||||
password_toggle_msg: password_toggle_msg.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +176,10 @@ pub struct TextEdit<Msg: Clone>
|
||||
/// retype rather than edit. Default `false` — long-form fields
|
||||
/// keep the cursor at the end on focus.
|
||||
pub( crate ) select_on_focus: bool,
|
||||
/// When `true`, editing keeps the cursor at the end of the displayed
|
||||
/// value, whatever the app rewrites it to in `on_change`. Default
|
||||
/// `false` — the cursor stays where the edit left it.
|
||||
pub( crate ) pin_cursor_to_end: bool,
|
||||
/// Self-managed "show / hide password" eye affordance — when
|
||||
/// `Some( ( visible, on_toggle ) )` the field renders an
|
||||
/// `actions/visible` ↔ `actions/invisible` icon at its right
|
||||
@@ -220,6 +224,7 @@ impl<Msg: Clone> TextEdit<Msg>
|
||||
font_size: None,
|
||||
height: None,
|
||||
select_on_focus: false,
|
||||
pin_cursor_to_end: false,
|
||||
password_toggle: None,
|
||||
read_only: false,
|
||||
}
|
||||
@@ -296,6 +301,15 @@ impl<Msg: Clone> TextEdit<Msg>
|
||||
self
|
||||
}
|
||||
|
||||
/// Keep the cursor at the end of the displayed value while editing,
|
||||
/// for fields whose `on_change` normalises what was typed ("2" → "02").
|
||||
/// Default `false`.
|
||||
pub fn pin_cursor_to_end( mut self, on: bool ) -> Self
|
||||
{
|
||||
self.pin_cursor_to_end = on;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the horizontal alignment of the displayed text. Default
|
||||
/// [`TextAlign::Left`](super::text::TextAlign::Left).
|
||||
pub fn align( mut self, a: super::text::TextAlign ) -> Self
|
||||
@@ -608,6 +622,7 @@ impl<Msg: Clone> TextEdit<Msg>
|
||||
font_size: self.font_size,
|
||||
height: self.height,
|
||||
select_on_focus: self.select_on_focus,
|
||||
pin_cursor_to_end: self.pin_cursor_to_end,
|
||||
password_toggle: self.password_toggle.clone().map( |( v, m )| ( v, ( *f )( m ) ) ),
|
||||
read_only: self.read_only,
|
||||
}
|
||||
|
||||
@@ -321,6 +321,7 @@ impl<Msg: Clone + 'static> TimePicker<Msg>
|
||||
.font_size_fluid( theme::VAL_FS )
|
||||
.align( TextAlign::Center )
|
||||
.select_on_focus( true )
|
||||
.pin_cursor_to_end( true )
|
||||
.on_change( move |s: String|
|
||||
{
|
||||
let new = parser( &s ).unwrap_or( snapshot );
|
||||
|
||||
Reference in New Issue
Block a user