aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPolesznyák Márk <contact@pml68.dev>2025-10-21 18:09:06 +0200
committerPolesznyák Márk <contact@pml68.dev>2025-10-21 18:09:06 +0200
commit8700f85ff826dd8fc428e2acd3c7c5ae18a4945d (patch)
tree0456dccecaa4641ac9d0037592e584c5fdc7f3e2
parentrefactor: clean up text imports, cursor position code (diff)
downloadiced_selection-8700f85ff826dd8fc428e2acd3c7c5ae18a4945d.tar.gz
feat: implement triple-click + drag by-line selection
-rw-r--r--TODO.md2
-rw-r--r--src/selection.rs25
-rw-r--r--src/text.rs41
-rw-r--r--src/text/rich.rs37
4 files changed, 81 insertions, 24 deletions
diff --git a/TODO.md b/TODO.md
index 867dbb4..fe881aa 100644
--- a/TODO.md
+++ b/TODO.md
@@ -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 =