aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPolesznyák Márk <contact@pml68.dev>2025-11-12 16:31:59 +0100
committerPolesznyák Márk <contact@pml68.dev>2025-11-12 16:31:59 +0100
commita01024abcd4d9c7e4ad9f9761bf1adf2bc5e6994 (patch)
treebede84f543871c867d6deb51cd8ed1aea78694fc
parentfeat(name example): replace TextInput with TextEditor for multi-line support,... (diff)
downloadiced_selection-a01024abcd4d9c7e4ad9f9761bf1adf2bc5e6994.tar.gz
feat: implement double-click + drag by-word selection
-rw-r--r--README.md2
-rw-r--r--src/selection.rs73
-rw-r--r--src/text.rs25
-rw-r--r--src/text/rich.rs25
4 files changed, 106 insertions, 19 deletions
diff --git a/README.md b/README.md
index 0321630..d69df0b 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,7 @@ iced_selection = { git = "https://git.sr.ht/~pml68/iced_selection" }
- [ ] allow out-of-bounds selection dragging
- [X] custom markdown `Viewer`
-- [ ] double-click + drag for by-word selection
+- [X] double-click + drag for by-word selection
- [X] triple-click + drag for by-line selection
- [ ] support wrapped lines
diff --git a/src/selection.rs b/src/selection.rs
index 9be01dd..375072c 100644
--- a/src/selection.rs
+++ b/src/selection.rs
@@ -213,6 +213,78 @@ impl Selection {
self.select_range(start, end);
}
+ /// Updates the current selection by setting a new end point, either to the start of the
+ /// previous word, or to the next one's end.
+ pub fn change_selection_by_word(
+ &mut self,
+ new_end: SelectionEnd,
+ paragraph: &Paragraph,
+ ) {
+ let (base_word_start, base_word_end) = {
+ if self.direction == Direction::Right {
+ let value = Value::new(
+ paragraph.buffer().lines[self.start.line].text(),
+ );
+
+ let end = SelectionEnd::new(
+ self.start.line,
+ value.next_end_of_word(self.start.index),
+ );
+
+ (self.start, end)
+ } else {
+ let value =
+ Value::new(paragraph.buffer().lines[self.end.line].text());
+
+ let start = SelectionEnd::new(
+ self.end.line,
+ value.previous_start_of_word(self.end.index),
+ );
+
+ (start, self.end)
+ }
+ };
+
+ let value = Value::new(paragraph.buffer().lines[new_end.line].text());
+
+ let (start, end) = if new_end < self.start {
+ self.direction = Direction::Left;
+
+ let start = SelectionEnd::new(
+ new_end.line,
+ value.previous_start_of_word(new_end.index),
+ );
+
+ (start, base_word_end)
+ } else if new_end > self.end {
+ self.direction = Direction::Right;
+
+ let end = SelectionEnd::new(
+ new_end.line,
+ value.next_end_of_word(new_end.index),
+ );
+
+ (base_word_start, end)
+ } else if self.direction == Direction::Right {
+ let end = SelectionEnd::new(
+ new_end.line,
+ value.next_end_of_word(new_end.index),
+ );
+
+ (base_word_start, end.max(base_word_end))
+ } else {
+ let start = SelectionEnd::new(
+ new_end.line,
+ value.previous_start_of_word(new_end.index),
+ );
+
+ (start.min(base_word_start), base_word_end)
+ };
+
+ self.moving_line_index = None;
+ self.select_range(start, end);
+ }
+
/// Updates the current selection by setting a new end point, either to the end of a following
/// line, or the beginning of a previous one.
pub fn change_selection_by_line(
@@ -246,6 +318,7 @@ impl Selection {
(start, end)
} else {
let start = SelectionEnd::new(new_line, 0);
+
let end = if self.direction == old_direction {
self.end
} else {
diff --git a/src/text.rs b/src/text.rs
index ad5c350..ff5d5a4 100644
--- a/src/text.rs
+++ b/src/text.rs
@@ -388,7 +388,7 @@ where
let click_position = cursor.position_in(bounds);
if viewport.intersection(&bounds).is_none()
- && state.selection == Selection::default()
+ && state.selection.is_empty()
&& state.dragging.is_none()
{
return;
@@ -466,7 +466,14 @@ where
state.selection.change_selection(new_end);
}
- Dragging::Word => {}
+ Dragging::Word => {
+ let new_end = SelectionEnd { line, index };
+
+ state.selection.change_selection_by_word(
+ new_end,
+ &state.paragraph,
+ );
+ }
Dragging::Line => {
state.selection.change_selection_by_line(
line,
@@ -491,7 +498,7 @@ where
}
keyboard::Key::Character("a")
if state.keyboard_modifiers.command()
- && state.selection != Selection::default() =>
+ && !state.selection.is_empty() =>
{
state.selection.select_all(&state.paragraph);
@@ -499,7 +506,7 @@ where
}
keyboard::Key::Named(key::Named::Home)
if state.keyboard_modifiers.shift()
- && state.selection != Selection::default() =>
+ && !state.selection.is_empty() =>
{
if state.keyboard_modifiers.jump() {
state.selection.select_beginning();
@@ -511,7 +518,7 @@ where
}
keyboard::Key::Named(key::Named::End)
if state.keyboard_modifiers.shift()
- && state.selection != Selection::default() =>
+ && !state.selection.is_empty() =>
{
if state.keyboard_modifiers.jump() {
state.selection.select_end(&state.paragraph);
@@ -523,7 +530,7 @@ where
}
keyboard::Key::Named(key::Named::ArrowLeft)
if state.keyboard_modifiers.shift()
- && state.selection != Selection::default() =>
+ && !state.selection.is_empty() =>
{
if state.keyboard_modifiers.macos_command() {
state.selection.select_line_beginning();
@@ -539,7 +546,7 @@ where
}
keyboard::Key::Named(key::Named::ArrowRight)
if state.keyboard_modifiers.shift()
- && state.selection != Selection::default() =>
+ && !state.selection.is_empty() =>
{
if state.keyboard_modifiers.macos_command() {
state.selection.select_line_end(&state.paragraph);
@@ -555,7 +562,7 @@ where
}
keyboard::Key::Named(key::Named::ArrowUp)
if state.keyboard_modifiers.shift()
- && state.selection != Selection::default() =>
+ && !state.selection.is_empty() =>
{
if state.keyboard_modifiers.macos_command() {
state.selection.select_beginning();
@@ -569,7 +576,7 @@ where
}
keyboard::Key::Named(key::Named::ArrowDown)
if state.keyboard_modifiers.shift()
- && state.selection != Selection::default() =>
+ && !state.selection.is_empty() =>
{
if state.keyboard_modifiers.macos_command() {
state.selection.select_end(&state.paragraph);
diff --git a/src/text/rich.rs b/src/text/rich.rs
index a30663b..5ca3051 100644
--- a/src/text/rich.rs
+++ b/src/text/rich.rs
@@ -600,7 +600,7 @@ where
let click_position = cursor.position_in(bounds);
if viewport.intersection(&bounds).is_none()
- && state.selection == Selection::default()
+ && state.selection.is_empty()
&& state.dragging.is_none()
{
return;
@@ -720,7 +720,14 @@ where
state.selection.change_selection(new_end);
}
- Dragging::Word => {}
+ Dragging::Word => {
+ let new_end = SelectionEnd { line, index };
+
+ state.selection.change_selection_by_word(
+ new_end,
+ &state.paragraph,
+ );
+ }
Dragging::Line => {
state.selection.change_selection_by_line(
line,
@@ -745,7 +752,7 @@ where
}
keyboard::Key::Character("a")
if state.keyboard_modifiers.command()
- && state.selection != Selection::default() =>
+ && !state.selection.is_empty() =>
{
state.selection.select_all(&state.paragraph);
@@ -753,7 +760,7 @@ where
}
keyboard::Key::Named(key::Named::Home)
if state.keyboard_modifiers.shift()
- && state.selection != Selection::default() =>
+ && !state.selection.is_empty() =>
{
if state.keyboard_modifiers.jump() {
state.selection.select_beginning();
@@ -765,7 +772,7 @@ where
}
keyboard::Key::Named(key::Named::End)
if state.keyboard_modifiers.shift()
- && state.selection != Selection::default() =>
+ && !state.selection.is_empty() =>
{
if state.keyboard_modifiers.jump() {
state.selection.select_end(&state.paragraph);
@@ -777,7 +784,7 @@ where
}
keyboard::Key::Named(key::Named::ArrowLeft)
if state.keyboard_modifiers.shift()
- && state.selection != Selection::default() =>
+ && !state.selection.is_empty() =>
{
if state.keyboard_modifiers.macos_command() {
state.selection.select_line_beginning();
@@ -793,7 +800,7 @@ where
}
keyboard::Key::Named(key::Named::ArrowRight)
if state.keyboard_modifiers.shift()
- && state.selection != Selection::default() =>
+ && !state.selection.is_empty() =>
{
if state.keyboard_modifiers.macos_command() {
state.selection.select_line_end(&state.paragraph);
@@ -809,7 +816,7 @@ where
}
keyboard::Key::Named(key::Named::ArrowUp)
if state.keyboard_modifiers.shift()
- && state.selection != Selection::default() =>
+ && !state.selection.is_empty() =>
{
if state.keyboard_modifiers.macos_command() {
state.selection.select_beginning();
@@ -823,7 +830,7 @@ where
}
keyboard::Key::Named(key::Named::ArrowDown)
if state.keyboard_modifiers.shift()
- && state.selection != Selection::default() =>
+ && !state.selection.is_empty() =>
{
if state.keyboard_modifiers.macos_command() {
state.selection.select_end(&state.paragraph);