diff options
| author | Polesznyák Márk <contact@pml68.dev> | 2025-10-21 18:09:06 +0200 |
|---|---|---|
| committer | Polesznyák Márk <contact@pml68.dev> | 2025-10-21 18:09:06 +0200 |
| commit | 8700f85ff826dd8fc428e2acd3c7c5ae18a4945d (patch) | |
| tree | 0456dccecaa4641ac9d0037592e584c5fdc7f3e2 | |
| parent | refactor: clean up text imports, cursor position code (diff) | |
| download | iced_selection-8700f85ff826dd8fc428e2acd3c7c5ae18a4945d.tar.gz | |
feat: implement triple-click + drag by-line selection
| -rw-r--r-- | TODO.md | 2 | ||||
| -rw-r--r-- | src/selection.rs | 25 | ||||
| -rw-r--r-- | src/text.rs | 41 | ||||
| -rw-r--r-- | src/text/rich.rs | 37 |
4 files changed, 81 insertions, 24 deletions
@@ -1,4 +1,4 @@ - [ ] allow out-of-bounds selection dragging - [X] custom markdown `Viewer` - [ ] double-click + drag for by-word selection -- [ ] triple-click + drag for by-line selection +- [X] triple-click + drag for by-line selection diff --git a/src/selection.rs b/src/selection.rs index b98a113..2c1e43d 100644 --- a/src/selection.rs +++ b/src/selection.rs @@ -208,6 +208,31 @@ impl Selection { 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( + &mut self, + new_line: usize, + paragraph: &Paragraph, + ) { + if new_line < self.start.line { + self.direction = Direction::Left; + } else if new_line > self.end.line { + self.direction = Direction::Right; + } + + let (start, end) = if self.direction == Direction::Right { + let value = Value::new(paragraph.buffer().lines[new_line].text()); + + (self.start, SelectionEnd::new(new_line, value.len())) + } else { + (SelectionEnd::new(new_line, 0), self.end) + }; + + self.moving_line_index = None; + self.select_range(start, end); + } + /// Selects the word around the given grapheme position. pub fn select_word( &mut self, diff --git a/src/text.rs b/src/text.rs index b407cd5..484456a 100644 --- a/src/text.rs +++ b/src/text.rs @@ -159,11 +159,20 @@ pub struct State { content: String, is_hovered: bool, selection: Selection, - is_dragging: bool, + dragging: Option<Dragging>, last_click: Option<mouse::Click>, keyboard_modifiers: keyboard::Modifiers, } +/// The type of dragging selection. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[allow(missing_docs)] +pub enum Dragging { + Grapheme, + Word, + Line, +} + impl State { fn grapheme_line_and_index(&self, point: Point) -> Option<(usize, usize)> { let cursor = self.paragraph.buffer().hit(point.x, point.y)?; @@ -266,7 +275,7 @@ where if viewport.intersection(&bounds).is_none() && state.selection == Selection::default() - && !state.is_dragging + && state.dragging.is_none() { return; } @@ -299,7 +308,7 @@ where state.selection.select_range(new_end, new_end); } - state.is_dragging = true; + state.dragging = Some(Dragging::Grapheme); } click::Kind::Double => { let (line, index) = state @@ -311,7 +320,8 @@ where index, &state.paragraph, ); - state.is_dragging = false; + + state.dragging = None; } click::Kind::Triple => { let (line, _) = state @@ -319,7 +329,7 @@ where .unwrap_or((0, 0)); state.selection.select_line(line, &state.paragraph); - state.is_dragging = false; + state.dragging = Some(Dragging::Line); } } @@ -333,20 +343,31 @@ where Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) | Event::Touch(touch::Event::FingerLifted { .. }) | Event::Touch(touch::Event::FingerLost { .. }) => { - state.is_dragging = false; + state.dragging = None; } Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(touch::Event::FingerMoved { .. }) => { if let Some(position) = click_position - && state.is_dragging + && let Some(dragging) = state.dragging { let (line, index) = state .grapheme_line_and_index(position) .unwrap_or((0, 0)); - let new_end = SelectionEnd { line, index }; + match dragging { + Dragging::Grapheme => { + let new_end = SelectionEnd { line, index }; - state.selection.change_selection(new_end); + state.selection.change_selection(new_end); + } + Dragging::Word => {} + Dragging::Line => { + state.selection.change_selection_by_line( + line, + &state.paragraph, + ); + } + }; } } Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { @@ -455,7 +476,7 @@ where shell.capture_event(); } keyboard::Key::Named(key::Named::Escape) => { - state.is_dragging = false; + state.dragging = None; state.selection = Selection::default(); state.keyboard_modifiers = diff --git a/src/text/rich.rs b/src/text/rich.rs index ae8bbab..93cefa2 100644 --- a/src/text/rich.rs +++ b/src/text/rich.rs @@ -16,7 +16,7 @@ use crate::core::{ Rectangle, Shell, Size, Vector, Widget, }; use crate::selection::{Selection, SelectionEnd}; -use crate::text::{Catalog, Style, StyleFn}; +use crate::text::{Catalog, Dragging, Style, StyleFn}; /// A bunch of [`Rich`] text. pub struct Rich< @@ -184,7 +184,7 @@ struct State<Link> { paragraph: Paragraph, is_hovered: bool, selection: Selection, - is_dragging: bool, + dragging: Option<Dragging>, last_click: Option<mouse::Click>, keyboard_modifiers: keyboard::Modifiers, } @@ -239,7 +239,7 @@ where paragraph: Paragraph::default(), is_hovered: false, selection: Selection::default(), - is_dragging: false, + dragging: None, last_click: None, keyboard_modifiers: keyboard::Modifiers::default(), }) @@ -475,7 +475,7 @@ where if viewport.intersection(&bounds).is_none() && state.selection == Selection::default() - && !state.is_dragging + && state.dragging.is_none() { return; } @@ -524,10 +524,10 @@ where if state.keyboard_modifiers.shift() { state.selection.change_selection(new_end); - state.is_dragging = true; + state.dragging = Some(Dragging::Grapheme); } else if state.span_pressed.is_none() { state.selection.select_range(new_end, new_end); - state.is_dragging = true; + state.dragging = Some(Dragging::Grapheme); } } mouse::click::Kind::Double => { @@ -536,11 +536,11 @@ where index, &state.paragraph, ); - state.is_dragging = false; + state.dragging = None; } mouse::click::Kind::Triple => { state.selection.select_line(line, &state.paragraph); - state.is_dragging = false; + state.dragging = Some(Dragging::Line); } } } else { @@ -550,7 +550,7 @@ where Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) | Event::Touch(touch::Event::FingerLifted { .. }) | Event::Touch(touch::Event::FingerLost { .. }) => { - state.is_dragging = false; + state.dragging = None; if !matches!( event, Event::Touch(touch::Event::FingerLost { .. }) @@ -578,15 +578,26 @@ where Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(touch::Event::FingerMoved { .. }) => { if let Some(position) = click_position - && state.is_dragging + && let Some(dragging) = state.dragging { let (line, index) = state .grapheme_line_and_index(position) .unwrap_or((0, 0)); - let new_end = SelectionEnd { line, index }; + match dragging { + Dragging::Grapheme => { + let new_end = SelectionEnd { line, index }; - state.selection.change_selection(new_end); + state.selection.change_selection(new_end); + } + Dragging::Word => {} + Dragging::Line => { + state.selection.change_selection_by_line( + line, + &state.paragraph, + ); + } + }; } } Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { @@ -695,7 +706,7 @@ where shell.capture_event(); } keyboard::Key::Named(key::Named::Escape) => { - state.is_dragging = false; + state.dragging = None; state.selection = Selection::default(); state.keyboard_modifiers = |
