Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: multi-click selections #1268

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions crates/rnote-engine/src/engine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,32 @@ 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 EngineViewMut {
tasks_tx: self.tasks_tx.clone(),
pens_config: &mut self.pens_config,
document: &mut self.document,
store: &mut self.store,
camera: &mut self.camera,
audioplayer: &mut self.audioplayer,
})
}
}

pub fn text_select_closest_line(&mut self) {
if let Pen::Typewriter(typewriter) = self.penholder.current_pen_mut() {
typewriter.select_closest_line(&mut EngineViewMut {
tasks_tx: self.tasks_tx.clone(),
pens_config: &mut self.pens_config,
document: &mut self.document,
store: &mut self.store,
camera: &mut self.camera,
audioplayer: &mut self.audioplayer,
})
}
}

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,12 +24,27 @@ 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 {
Up,
Hover(na::Vector2<f64>),
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 @@ -150,6 +150,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 @@ -176,7 +177,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 @@ -214,8 +219,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 @@ -590,6 +635,7 @@ impl Typewriter {
textstroke.text.len(),
true,
),
mode: SelectionMode::Caret,
finished: true,
};
} else {
Expand Down Expand Up @@ -665,6 +711,7 @@ impl Typewriter {

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

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

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

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

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

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

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

Expand Down Expand Up @@ -1229,4 +1283,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