Skip to content

Commit

Permalink
feat: multi-click selections (#1268)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kneemund authored Feb 16, 2025
1 parent a30213d commit 58087a1
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 6 deletions.
12 changes: 12 additions & 0 deletions crates/rnote-engine/src/engine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,18 @@ impl Engine {
widget_flags
}

pub fn text_select_closest_word(&mut self) {
if let Pen::Typewriter(typewriter) = self.penholder.current_pen_mut() {
typewriter.select_closest_word(&mut engine_view_mut!(self))
}
}

pub fn text_select_closest_line(&mut self) {
if let Pen::Typewriter(typewriter) = self.penholder.current_pen_mut() {
typewriter.select_closest_line(&mut engine_view_mut!(self))
}
}

pub fn text_selection_toggle_attribute(
&mut self,
text_attribute: TextAttribute,
Expand Down
15 changes: 15 additions & 0 deletions crates/rnote-engine/src/pens/typewriter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,26 @@ use std::time::{Duration, Instant};
use tracing::error;
use unicode_segmentation::GraphemeCursor;

#[derive(Debug, Clone)]
pub(super) enum SelectionMode {
/// Select individual characters.
Caret,
/// Select whole words.
///
/// The values represent the start and end of the initially selected word.
Word(usize, usize),
/// Select whole lines.
///
/// The values represent the start and end of the initially selected line.
Line(usize, usize),
}

#[derive(Debug, Clone)]
pub(super) enum ModifyState {
Idle,
Selecting {
selection_cursor: GraphemeCursor,
mode: SelectionMode,
/// Whether selecting is finished.
///
/// If true, the state will get reset on the next click.
Expand Down
120 changes: 117 additions & 3 deletions crates/rnote-engine/src/pens/typewriter/penevents.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Imports
use super::{ModifyState, Typewriter, TypewriterState};
use super::{ModifyState, SelectionMode, Typewriter, TypewriterState};
use crate::engine::EngineViewMut;
use crate::pens::PenBehaviour;
use crate::strokes::{Stroke, TextStroke};
Expand Down Expand Up @@ -151,6 +151,7 @@ impl Typewriter {
self.state = TypewriterState::Modifying {
modify_state: ModifyState::Selecting {
selection_cursor: cursor.clone(),
mode: SelectionMode::Caret,
finished: false,
},
stroke_key: *stroke_key,
Expand All @@ -177,7 +178,11 @@ impl Typewriter {
progress,
}
}
ModifyState::Selecting { finished, .. } => {
ModifyState::Selecting {
selection_cursor,
mode,
finished,
} => {
let mut progress = PenProgress::InProgress;

if let Some(typewriter_bounds) = typewriter_bounds {
Expand Down Expand Up @@ -215,8 +220,48 @@ impl Typewriter {
if let Ok(new_cursor) =
textstroke.get_cursor_for_global_coord(element.pos)
{
let previous_cursor_position = cursor.cur_cursor();
*cursor = new_cursor;
self.reset_blink();

match mode {
SelectionMode::Word(start, end) => {
let mouse_position = cursor.cur_cursor();

if mouse_position <= *start {
selection_cursor.set_cursor(*end);
textstroke
.move_cursor_word_boundary_back(cursor);
} else if mouse_position >= *end {
selection_cursor.set_cursor(*start);
textstroke
.move_cursor_word_boundary_forward(
cursor,
);
} else {
selection_cursor.set_cursor(*start);
cursor.set_cursor(*end);
}
}
SelectionMode::Line(start, end) => {
let mouse_position = cursor.cur_cursor();

if mouse_position < *start {
selection_cursor.set_cursor(*end);
textstroke.move_cursor_line_start(cursor);
} else if mouse_position > *end {
selection_cursor.set_cursor(*start);
textstroke.move_cursor_line_end(cursor);
} else {
selection_cursor.set_cursor(*start);
cursor.set_cursor(*end);
}
}
SelectionMode::Caret => {}
}

if previous_cursor_position != cursor.cur_cursor() {
self.reset_blink();
}
}
}
}
Expand Down Expand Up @@ -579,6 +624,7 @@ impl Typewriter {
textstroke.text.len(),
true,
),
mode: SelectionMode::Caret,
finished: true,
};
} else {
Expand Down Expand Up @@ -654,6 +700,7 @@ impl Typewriter {

*modify_state = ModifyState::Selecting {
selection_cursor: old_cursor,
mode: SelectionMode::Caret,
finished: false,
}
} else {
Expand Down Expand Up @@ -682,6 +729,7 @@ impl Typewriter {

*modify_state = ModifyState::Selecting {
selection_cursor: old_cursor,
mode: SelectionMode::Caret,
finished: false,
};
} else {
Expand All @@ -706,6 +754,7 @@ impl Typewriter {

*modify_state = ModifyState::Selecting {
selection_cursor: old_cursor,
mode: SelectionMode::Caret,
finished: false,
};
} else {
Expand All @@ -725,6 +774,7 @@ impl Typewriter {

*modify_state = ModifyState::Selecting {
selection_cursor: old_cursor,
mode: SelectionMode::Caret,
finished: false,
};
} else {
Expand All @@ -748,6 +798,7 @@ impl Typewriter {

*modify_state = ModifyState::Selecting {
selection_cursor: old_cursor,
mode: SelectionMode::Caret,
finished: false,
};
} else {
Expand Down Expand Up @@ -776,6 +827,7 @@ impl Typewriter {

*modify_state = ModifyState::Selecting {
selection_cursor: old_cursor,
mode: SelectionMode::Caret,
finished: false,
};
} else {
Expand Down Expand Up @@ -810,6 +862,7 @@ impl Typewriter {
ModifyState::Selecting {
selection_cursor,
finished,
..
} => {
super::play_sound(Some(keyboard_key), engine_view.audioplayer);

Expand Down Expand Up @@ -1139,6 +1192,7 @@ impl Typewriter {
ModifyState::Selecting {
selection_cursor,
finished,
..
} => {
super::play_sound(None, engine_view.audioplayer);

Expand Down Expand Up @@ -1219,4 +1273,64 @@ impl Typewriter {

(event_result, widget_flags)
}

pub fn select_closest_word(&mut self, engine_view: &mut EngineViewMut) {
match &mut self.state {
TypewriterState::Modifying {
modify_state,
stroke_key,
cursor,
pen_down: _,
} => {
if let Some(Stroke::TextStroke(ref mut textstroke)) =
engine_view.store.get_stroke_mut(*stroke_key)
{
textstroke.move_cursor_word_boundary_forward(cursor);

let mut selection_cursor = cursor.clone();
textstroke.move_cursor_word_boundary_back(&mut selection_cursor);

*modify_state = ModifyState::Selecting {
mode: SelectionMode::Word(
selection_cursor.cur_cursor(),
cursor.cur_cursor(),
),
selection_cursor,
finished: false,
};
}
}
_ => {}
}
}

pub fn select_closest_line(&mut self, engine_view: &mut EngineViewMut) {
match &mut self.state {
TypewriterState::Modifying {
modify_state,
stroke_key,
cursor,
pen_down: _,
} => {
if let Some(Stroke::TextStroke(ref mut textstroke)) =
engine_view.store.get_stroke_mut(*stroke_key)
{
textstroke.move_cursor_line_end(cursor);

let mut selection_cursor = cursor.clone();
textstroke.move_cursor_line_start(&mut selection_cursor);

*modify_state = ModifyState::Selecting {
mode: SelectionMode::Line(
selection_cursor.cur_cursor(),
cursor.cur_cursor(),
),
selection_cursor,
finished: false,
};
}
}
_ => {}
}
}
}
40 changes: 40 additions & 0 deletions crates/rnote-engine/src/strokes/textstroke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,22 @@ impl TextStroke {
current_char_index
}

fn get_prev_word_boundary_index(&self, current_char_index: usize) -> usize {
for (start_index, word) in self.text.unicode_word_indices().rev() {
let end_index = start_index + word.len();

if end_index < current_char_index {
return end_index;
}

if start_index < current_char_index {
return start_index;
}
}

current_char_index
}

fn get_next_word_end_index(&self, current_char_index: usize) -> usize {
for (start_index, word) in self.text.unicode_word_indices() {
let end_index = start_index + word.len();
Expand All @@ -852,6 +868,22 @@ impl TextStroke {
current_char_index
}

fn get_next_word_boundary_index(&self, current_char_index: usize) -> usize {
for (start_index, word) in self.text.unicode_word_indices() {
if start_index >= current_char_index {
return start_index;
}

let end_index = start_index + word.len();

if end_index >= current_char_index {
return end_index;
}
}

current_char_index
}

pub fn move_cursor_back(&self, cursor: &mut GraphemeCursor) {
// Cant fail, we are providing the entire text
cursor.prev_boundary(&self.text, 0).unwrap();
Expand All @@ -866,10 +898,18 @@ impl TextStroke {
cursor.set_cursor(self.get_prev_word_start_index(cursor.cur_cursor()));
}

pub fn move_cursor_word_boundary_back(&self, cursor: &mut GraphemeCursor) {
cursor.set_cursor(self.get_prev_word_boundary_index(cursor.cur_cursor()));
}

pub fn move_cursor_word_forward(&self, cursor: &mut GraphemeCursor) {
cursor.set_cursor(self.get_next_word_end_index(cursor.cur_cursor()));
}

pub fn move_cursor_word_boundary_forward(&self, cursor: &mut GraphemeCursor) {
cursor.set_cursor(self.get_next_word_boundary_index(cursor.cur_cursor()));
}

pub fn move_cursor_text_start(&self, cursor: &mut GraphemeCursor) {
cursor.set_cursor(0);
}
Expand Down
2 changes: 1 addition & 1 deletion crates/rnote-ui/src/canvas/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ fn trace_gdk_event(event: &gdk::Event) {
}

/// Returns true if input should be rejected
fn reject_pointer_input(event: &gdk::Event, touch_drawing: bool) -> bool {
pub(crate) fn reject_pointer_input(event: &gdk::Event, touch_drawing: bool) -> bool {
if touch_drawing {
if event.device().unwrap().num_touches() > 1 {
return true;
Expand Down
1 change: 1 addition & 0 deletions crates/rnote-ui/src/canvas/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod widgetflagsboxed;

// Re-exports
pub(crate) use canvaslayout::RnCanvasLayout;
pub(crate) use input::reject_pointer_input;
pub(crate) use widgetflagsboxed::WidgetFlagsBoxed;

// Imports
Expand Down
Loading

0 comments on commit 58087a1

Please sign in to comment.