From 1aa8aaf8186a21a135a7ba0525b0e01726cb8e4e Mon Sep 17 00:00:00 2001 From: Andrew Baldwin Date: Thu, 27 Mar 2025 10:30:58 -0700 Subject: [PATCH 1/2] Add the ability to expand selection-by-word initiated by double-click in `text_input`. --- widget/src/text_input.rs | 54 ++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index f214fe1d..1d22521b 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -780,11 +780,14 @@ where } else { state.cursor.move_to(position); } - state.is_dragging = true; + + state.is_dragging = Some(Drag::SingleClick); } click::Kind::Double => { if self.is_secure { state.cursor.select_all(&self.value); + + state.is_dragging = None; } else { let position = find_cursor_position( text_layout.bounds(), @@ -798,13 +801,15 @@ where self.value.previous_start_of_word(position), self.value.next_end_of_word(position), ); - } - state.is_dragging = false; + state.is_dragging = Some(Drag::DoubleClick { + click_position: position, + }); + } } click::Kind::Triple => { state.cursor.select_all(&self.value); - state.is_dragging = false; + state.is_dragging = None; } } @@ -820,13 +825,13 @@ where Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) | Event::Touch(touch::Event::FingerLifted { .. }) | Event::Touch(touch::Event::FingerLost { .. }) => { - state::(tree).is_dragging = false; + state::(tree).is_dragging = None; } Event::Mouse(mouse::Event::CursorMoved { position }) | Event::Touch(touch::Event::FingerMoved { position, .. }) => { let state = state::(tree); - if state.is_dragging { + if let Some(is_dragging) = &state.is_dragging { let text_layout = layout.children().next().unwrap(); let target = { @@ -857,9 +862,30 @@ where let selection_before = state.cursor.selection(&value); - state - .cursor - .select_range(state.cursor.start(&value), position); + match is_dragging { + Drag::SingleClick => { + state.cursor.select_range( + state.cursor.start(&value), + position, + ); + } + Drag::DoubleClick { click_position } => { + if position < *click_position { + state.cursor.select_range( + self.value.previous_start_of_word(position), + self.value + .next_end_of_word(*click_position), + ); + } else { + state.cursor.select_range( + self.value.previous_start_of_word( + *click_position, + ), + self.value.next_end_of_word(position), + ); + } + } + } if let Some(focus) = &mut state.is_focused { focus.updated_at = Instant::now(); @@ -1201,7 +1227,7 @@ where } keyboard::Key::Named(key::Named::Escape) => { state.is_focused = None; - state.is_dragging = false; + state.is_dragging = None; state.is_pasting = None; state.keyboard_modifiers = @@ -1430,7 +1456,7 @@ pub struct State { placeholder: paragraph::Plain

, icon: paragraph::Plain

, is_focused: Option, - is_dragging: bool, + is_dragging: Option, is_pasting: Option, preedit: Option, last_click: Option, @@ -1452,6 +1478,12 @@ struct Focus { is_window_focused: bool, } +#[derive(Debug, Clone)] +enum Drag { + SingleClick, + DoubleClick { click_position: usize }, +} + impl State

{ /// Creates a new [`State`], representing an unfocused [`TextInput`]. pub fn new() -> Self { From 5b7bcc7a9cda8283efa0b331ff7432ef06ed16cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 24 Nov 2025 11:46:34 +0100 Subject: [PATCH 2/2] Improve naming of `Drag` interactions in `text_input` --- widget/src/text_input.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 1d22521b..2d6ac701 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -781,7 +781,7 @@ where state.cursor.move_to(position); } - state.is_dragging = Some(Drag::SingleClick); + state.is_dragging = Some(Drag::Select); } click::Kind::Double => { if self.is_secure { @@ -802,8 +802,8 @@ where self.value.next_end_of_word(position), ); - state.is_dragging = Some(Drag::DoubleClick { - click_position: position, + state.is_dragging = Some(Drag::SelectWords { + anchor: position, }); } } @@ -863,24 +863,21 @@ where let selection_before = state.cursor.selection(&value); match is_dragging { - Drag::SingleClick => { + Drag::Select => { state.cursor.select_range( state.cursor.start(&value), position, ); } - Drag::DoubleClick { click_position } => { - if position < *click_position { + Drag::SelectWords { anchor } => { + if position < *anchor { state.cursor.select_range( self.value.previous_start_of_word(position), - self.value - .next_end_of_word(*click_position), + self.value.next_end_of_word(*anchor), ); } else { state.cursor.select_range( - self.value.previous_start_of_word( - *click_position, - ), + self.value.previous_start_of_word(*anchor), self.value.next_end_of_word(position), ); } @@ -1480,8 +1477,8 @@ struct Focus { #[derive(Debug, Clone)] enum Drag { - SingleClick, - DoubleClick { click_position: usize }, + Select, + SelectWords { anchor: usize }, } impl State

{