From ec048abe8224bca7365c2b9b8970f9a55c1fb5d7 Mon Sep 17 00:00:00 2001 From: Colin Rofls Date: Mon, 5 Oct 2020 14:20:58 -0400 Subject: [PATCH] Improve selection behaviour on focus change On windows, we persist the existing selection when focus changes. On mac, the behaviour depends on whether or not there was a click. If there was a click, we use the click to determine the selection; otherwise we select the whole buffer. In addition, on mac, we no longer draw the cursor if the selection is not a caret. - fixes #1225 (again) Update druid/src/widget/textbox.rs Co-authored-by: Leopold Luley --- CHANGELOG.md | 2 ++ druid/src/text/editor.rs | 5 +++++ druid/src/text/selection.rs | 7 ------- druid/src/widget/textbox.rs | 27 +++++++++++++++++++++++++-- 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 480b37986b..33de0e0167 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,7 @@ You can find its changes [documented below](#060---2020-06-01). - `LocalizedString` and `LabelText` use `ArcStr` instead of String ([#1245] by [@cmyr]) - `LensWrap` widget moved into widget module ([#1251] by [@cmyr]) - `Delegate::command` now returns `Handled`, not `bool` ([#1298] by [@jneem]) +- `TextBox` selects all contents when tabbed to on macOS ([#1283] by [@cmyr]) ### Deprecated @@ -498,6 +499,7 @@ Last release without a changelog :( [#1276]: https://github.com/linebender/druid/pull/1276 [#1278]: https://github.com/linebender/druid/pull/1278 [#1280]: https://github.com/linebender/druid/pull/1280 +[#1283]: https://github.com/linebender/druid/pull/1283 [#1295]: https://github.com/linebender/druid/pull/1280 [#1298]: https://github.com/linebender/druid/pull/1298 [#1299]: https://github.com/linebender/druid/pull/1299 diff --git a/druid/src/text/editor.rs b/druid/src/text/editor.rs index abd1b7acb1..fab5017c6c 100644 --- a/druid/src/text/editor.rs +++ b/druid/src/text/editor.rs @@ -137,6 +137,11 @@ impl Editor { self.do_edit(EditAction::Paste(t), data) } + /// Set the selection to the entire buffer. + pub fn select_all(&mut self, data: &T) { + self.selection = Selection::new(0, data.len()); + } + fn mouse_action_for_event(&self, event: &MouseEvent) -> MouseAction { let pos = self.layout.text_position_for_point(event.pos); MouseAction { diff --git a/druid/src/text/selection.rs b/druid/src/text/selection.rs index d0030bac3c..632260c926 100644 --- a/druid/src/text/selection.rs +++ b/druid/src/text/selection.rs @@ -53,13 +53,6 @@ impl Selection { self } - /// Create a selection that starts at the beginning and ends at text length. - /// TODO: can text length be at a non-codepoint or a non-grapheme? - pub fn all(&mut self, text: &impl EditableText) { - self.start = 0; - self.end = text.len(); - } - /// Create a caret, which is just a selection with the same and start and end. pub fn caret(pos: usize) -> Self { Selection { diff --git a/druid/src/widget/textbox.rs b/druid/src/widget/textbox.rs index 53fd7c4957..72b8ccfb2b 100644 --- a/druid/src/widget/textbox.rs +++ b/druid/src/widget/textbox.rs @@ -44,6 +44,12 @@ pub struct TextBox { cursor_timer: TimerToken, cursor_on: bool, multiline: bool, + /// true if a click event caused us to gain focus. + /// + /// On macOS, if focus happens via click then we set the selection based + /// on the click position; if focus happens automatically (e.g. on tab) + /// then we select our entire contents. + was_focused_from_click: bool, } impl TextBox<()> { @@ -66,6 +72,7 @@ impl TextBox { cursor_on: false, placeholder, multiline: false, + was_focused_from_click: false, } } @@ -196,6 +203,17 @@ impl TextBox { self.cursor_on = true; self.cursor_timer = token; } + + // on macos we only draw the cursor if the selection is non-caret + #[cfg(target_os = "macos")] + fn should_draw_cursor(&self) -> bool { + self.cursor_on && self.editor.selection().is_caret() + } + + #[cfg(not(target_os = "macos"))] + fn should_draw_cursor(&self) -> bool { + self.cursor_on + } } impl Widget for TextBox { @@ -209,6 +227,7 @@ impl Widget for TextBox { mouse.pos += Vec2::new(self.hscroll_offset, 0.0); if !mouse.focus { + self.was_focused_from_click = true; self.reset_cursor_blink(ctx.request_timer(CURSOR_BLINK_DURATION)); self.editor.click(&mouse, data); } @@ -284,7 +303,11 @@ impl Widget for TextBox { self.editor.set_text(data.to_owned()); self.editor.rebuild_if_needed(ctx.text(), env); } - LifeCycle::FocusChanged(_) => { + LifeCycle::FocusChanged(is_focused) => { + if cfg!(target_os = "macos") && *is_focused && !self.was_focused_from_click { + self.editor.select_all(data); + } + self.was_focused_from_click = false; self.reset_cursor_blink(ctx.request_timer(CURSOR_BLINK_DURATION)); ctx.request_paint(); } @@ -370,7 +393,7 @@ impl Widget for TextBox { } // Paint the cursor if focused and there's no selection - if is_focused && self.cursor_on { + if is_focused && self.should_draw_cursor() { // the cursor position can extend past the edge of the layout // (commonly when there is trailing whitespace) so we clamp it // to the right edge.