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

logic changes for selection cancellation #1139

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
39 changes: 32 additions & 7 deletions crates/rnote-engine/src/engine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ use futures::channel::{mpsc, oneshot};
use p2d::bounding_volume::{Aabb, BoundingVolume};
use rnote_compose::eventresult::EventPropagation;
use rnote_compose::ext::AabbExt;
use rnote_compose::penevent::{PenEvent, ShortcutKey};
use rnote_compose::penevent::{KeyboardKey, PenEvent, ShortcutKey};
use rnote_compose::{Color, SplitOrder};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Instant;
Expand Down Expand Up @@ -858,13 +859,37 @@ impl Engine {
| self.update_rendering_current_viewport()
}

pub fn trash_selection(&mut self) -> WidgetFlags {
pub fn cancel_selection_temporary_pen(&self) -> bool {
let selection_keys = self.store.selection_keys_as_rendered();
self.store.set_trashed_keys(&selection_keys, true);
self.current_pen_update_state()
| self.doc_resize_autoexpand()
| self.record(Instant::now())
| self.update_rendering_current_viewport()
!selection_keys.is_empty()
&& self
.penholder
.pen_mode_state()
.take_style_override()
.is_some()
}

pub fn trash_selection(&mut self) -> WidgetFlags {
// check if we have a selector as a temporary tool and need to change the pen
let cancel_selection = self.cancel_selection_temporary_pen();
if cancel_selection {
let (_, widget_flags) = self.handle_pen_event(
rnote_compose::PenEvent::KeyPressed {
keyboard_key: KeyboardKey::Delete,
modifier_keys: HashSet::new(),
},
None,
Instant::now(),
);
widget_flags
} else {
let selection_keys = self.store.selection_keys_as_rendered();
self.store.set_trashed_keys(&selection_keys, true);
self.current_pen_update_state()
| self.doc_resize_autoexpand()
| self.record(Instant::now())
| self.update_rendering_current_viewport()
}
}

pub fn nothing_selected(&self) -> bool {
Expand Down
31 changes: 31 additions & 0 deletions crates/rnote-engine/src/pens/brush.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use rnote_compose::builders::{
use rnote_compose::eventresult::{EventPropagation, EventResult};
use rnote_compose::penevent::{PenEvent, PenProgress};
use rnote_compose::penpath::{Element, Segment};
use rnote_compose::shapes::Shapeable;
use rnote_compose::Constraints;
use std::time::Instant;

Expand All @@ -41,6 +42,15 @@ impl Default for Brush {
}
}

impl Brush {
/// Threshold for the ratio of stroke bounds volume over
/// stroke width**2 over which we consider that strokes
/// or shapes that are left by pressing the pen down when
/// cancelling a selection should be kept. Smaller ratio
/// are deleted
const VOLUME_RATIO_THRESHOLD: f64 = 5.0;
}

impl PenBehaviour for Brush {
fn init(&mut self, _engine_view: &EngineView) -> WidgetFlags {
WidgetFlags::default()
Expand Down Expand Up @@ -229,6 +239,27 @@ impl PenBehaviour for Brush {
);
}

// remove strokes that follow a selection cancellation if they are large
// hence we can write after selecting strokes but we won't leave tiny spots
// behind
let volume = engine_view
.store
.get_stroke_ref(*current_stroke_key)
.unwrap()
.bounds()
.volume();
if engine_view.store.get_cancelled_state()
&& volume
< Self::VOLUME_RATIO_THRESHOLD
* engine_view
.pens_config
.brush_config
.get_stroke_width()
.powi(2)
{
engine_view.store.remove_stroke(*current_stroke_key);
}

// Finish up the last stroke
engine_view
.store
Expand Down
7 changes: 5 additions & 2 deletions crates/rnote-engine/src/pens/eraser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,11 @@ impl PenBehaviour for Eraser {

let event_result = match (&mut self.state, event) {
(EraserState::Up | EraserState::Proximity { .. }, PenEvent::Down { element, .. }) => {
widget_flags |= erase(element, engine_view);
self.state = EraserState::Down(element);
if !engine_view.store.get_cancelled_state() {
widget_flags |= erase(element, engine_view);
self.state = EraserState::Down(element);
// this means we need one more up/down event here to activate the eraser
}
EventResult {
handled: true,
propagate: EventPropagation::Stop,
Expand Down
8 changes: 7 additions & 1 deletion crates/rnote-engine/src/pens/penholder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,11 +248,17 @@ impl PenHolder {
widget_flags |= wf | self.handle_pen_progress(event_result.progress, engine_view);

if !event_result.handled {
let (propagate, wf) = self.handle_pen_event_global(event, now, engine_view);
let (propagate, wf) = self.handle_pen_event_global(event.clone(), now, engine_view);
event_result.propagate |= propagate;
widget_flags |= wf;
}

// reset the state
match event {
PenEvent::Up { .. } => engine_view.store.set_cancelled_state(false),
_ => (),
}

// Always redraw after handling a pen event
widget_flags.redraw = true;

Expand Down
8 changes: 8 additions & 0 deletions crates/rnote-engine/src/pens/pensconfig/brushconfig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,12 @@ impl BrushConfig {
}
}
}

pub(crate) fn get_stroke_width(&self) -> f64 {
match &self.style {
BrushStyle::Marker => self.marker_options.stroke_width,
BrushStyle::Solid => self.solid_options.stroke_width,
BrushStyle::Textured => self.textured_options.stroke_width,
}
}
}
5 changes: 5 additions & 0 deletions crates/rnote-engine/src/pens/selector/penevents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,14 @@ impl Selector {
};
} else {
// when clicking outside the selection bounds, reset
tracing::debug!("cancelling the selection");
engine_view.store.set_selected_keys(selection, false);
self.state = SelectorState::Idle;

// This event is in the same sequence than the one that
// cancelled the selection. We thus set the variable to true here
engine_view.store.set_cancelled_state(true);

progress = PenProgress::Finished;
}
}
Expand Down
44 changes: 25 additions & 19 deletions crates/rnote-engine/src/pens/shaper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,19 @@ impl PenBehaviour for Shaper {

let event_result = match (&mut self.state, event) {
(ShaperState::Idle, PenEvent::Down { element, .. }) => {
engine_view.pens_config.shaper_config.new_style_seeds();
if !engine_view.store.get_cancelled_state() {
// here we need an additional up/down event after a selection
// cancellation
engine_view.pens_config.shaper_config.new_style_seeds();

self.state = ShaperState::BuildShape {
builder: new_builder(
engine_view.pens_config.shaper_config.builder_type,
element,
now,
),
};
self.state = ShaperState::BuildShape {
builder: new_builder(
engine_view.pens_config.shaper_config.builder_type,
element,
now,
),
};
}

EventResult {
handled: true,
Expand Down Expand Up @@ -154,17 +158,19 @@ impl PenBehaviour for Shaper {
.gen_style_for_current_options();

let shapes_emitted = !shapes.is_empty();
for shape in shapes {
let key = engine_view.store.insert_stroke(
Stroke::ShapeStroke(ShapeStroke::new(shape, style.clone())),
None,
);
style.advance_seed();
engine_view.store.regenerate_rendering_for_stroke(
key,
engine_view.camera.viewport(),
engine_view.camera.image_scale(),
);
if shapes_emitted {
for shape in shapes {
let key = engine_view.store.insert_stroke(
Stroke::ShapeStroke(ShapeStroke::new(shape, style.clone())),
None,
);
style.advance_seed();
engine_view.store.regenerate_rendering_for_stroke(
key,
engine_view.camera.viewport(),
engine_view.camera.image_scale(),
);
}
}

self.state = ShaperState::Idle;
Expand Down
16 changes: 15 additions & 1 deletion crates/rnote-engine/src/pens/typewriter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::{AudioPlayer, Camera, DrawableOnDoc, WidgetFlags};
use futures::channel::oneshot;
use p2d::bounding_volume::{Aabb, BoundingVolume};
use piet::RenderContext;
use rnote_compose::eventresult::EventPropagation;
use rnote_compose::ext::{AabbExt, Vector2Ext};
use rnote_compose::penevent::{KeyboardKey, PenEvent, PenProgress, PenState};
use rnote_compose::shapes::Shapeable;
Expand Down Expand Up @@ -360,7 +361,20 @@ impl PenBehaviour for Typewriter {
PenEvent::Down {
element,
modifier_keys,
} => self.handle_pen_event_down(element, modifier_keys, now, engine_view),
} => {
if !engine_view.store.get_cancelled_state() {
self.handle_pen_event_down(element, modifier_keys, now, engine_view)
} else {
(
EventResult {
handled: true,
propagate: EventPropagation::Stop,
progress: PenProgress::InProgress,
},
WidgetFlags::default(),
)
}
}
PenEvent::Up {
element,
modifier_keys,
Expand Down
17 changes: 17 additions & 0 deletions crates/rnote-engine/src/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ pub struct StrokeStore {
/// The index of the current live document in the history stack.
#[serde(skip)]
live_index: usize,
/// Boolean that indicates the pen event is one from the same
/// event sequence than the one that cancelled the selection
/// Events that cancel a selection should set this to true
/// and this should be set back to false on a pen up event
#[serde(skip)]
canceled_state: bool,
/// An rtree backed by the slotmap store, for faster spatial queries.
///
/// Needs to be updated with `update_with_key()` when strokes changed their geometry or position!
Expand All @@ -110,6 +116,7 @@ impl Default for StrokeStore {
// Start off with state in the history
history: VecDeque::from(vec![HistoryEntry::default()]),
live_index: 0,
canceled_state: false,

key_tree: KeyTree::default(),

Expand Down Expand Up @@ -368,4 +375,14 @@ impl StrokeStore {

widget_flags
}

/// set the active state for the cancelled selection
pub fn set_cancelled_state(&mut self, state: bool) {
self.canceled_state = state
}

/// get the active state for the cancelled selection
pub fn get_cancelled_state(&self) -> bool {
self.canceled_state
}
}