From 0ee848553ebad0e9cb1cd8ac6ae46603a40af1d8 Mon Sep 17 00:00:00 2001 From: Doublonmousse <115779707+Doublonmousse@users.noreply.github.com> Date: Thu, 11 Jul 2024 19:37:37 +0200 Subject: [PATCH 01/12] logic changes for selection cancellation --- crates/rnote-engine/src/pens/brush.rs | 24 +++++++++++++++++++ crates/rnote-engine/src/pens/eraser.rs | 7 ++++-- crates/rnote-engine/src/pens/penholder.rs | 9 +++++-- .../src/pens/pensconfig/brushconfig.rs | 8 +++++++ .../src/pens/selector/penevents.rs | 7 ++++++ crates/rnote-engine/src/pens/shaper.rs | 20 +++++++++------- .../rnote-engine/src/pens/typewriter/mod.rs | 16 ++++++++++++- crates/rnote-engine/src/store/mod.rs | 13 ++++++++++ 8 files changed, 91 insertions(+), 13 deletions(-) diff --git a/crates/rnote-engine/src/pens/brush.rs b/crates/rnote-engine/src/pens/brush.rs index e9d3480f39..12e7613b91 100644 --- a/crates/rnote-engine/src/pens/brush.rs +++ b/crates/rnote-engine/src/pens/brush.rs @@ -17,6 +17,8 @@ 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; #[derive(Debug)] @@ -229,6 +231,28 @@ 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 + < 4.0 + * engine_view + .pens_config + .brush_config + .get_stroke_width() + .powi(2) + { + tracing::debug!("VOLUME {volume}"); + engine_view.store.remove_stroke(*current_stroke_key); + } + // Finish up the last stroke engine_view .store diff --git a/crates/rnote-engine/src/pens/eraser.rs b/crates/rnote-engine/src/pens/eraser.rs index e5999309fa..1f9538552e 100644 --- a/crates/rnote-engine/src/pens/eraser.rs +++ b/crates/rnote-engine/src/pens/eraser.rs @@ -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, diff --git a/crates/rnote-engine/src/pens/penholder.rs b/crates/rnote-engine/src/pens/penholder.rs index 1dd04caa61..0886c0be79 100644 --- a/crates/rnote-engine/src/pens/penholder.rs +++ b/crates/rnote-engine/src/pens/penholder.rs @@ -248,11 +248,16 @@ 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 cancelled state as we just handled a pen tool (and not a selection tool) event + match event { + PenEvent::Up { .. } => engine_view.store.set_cancelled_state(false), + _ => (), + } + // Always redraw after handling a pen event. // // This is also needed because pens might have claimed/requested an animation frame. diff --git a/crates/rnote-engine/src/pens/pensconfig/brushconfig.rs b/crates/rnote-engine/src/pens/pensconfig/brushconfig.rs index 514efaddbf..f60c104c1a 100644 --- a/crates/rnote-engine/src/pens/pensconfig/brushconfig.rs +++ b/crates/rnote-engine/src/pens/pensconfig/brushconfig.rs @@ -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, + } + } } diff --git a/crates/rnote-engine/src/pens/selector/penevents.rs b/crates/rnote-engine/src/pens/selector/penevents.rs index 096c962d4c..bda380adef 100644 --- a/crates/rnote-engine/src/pens/selector/penevents.rs +++ b/crates/rnote-engine/src/pens/selector/penevents.rs @@ -198,9 +198,16 @@ 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; + // we create a new engine variable that indicates that the pen event is one from the same + // event sequence that the one that cancelled the selection + // this is done by setting this variable to true here and + // removing it on a pen up event (and doing more things if this variable is true on a pen up) + engine_view.store.set_cancelled_state(true); + progress = PenProgress::Finished; } } diff --git a/crates/rnote-engine/src/pens/shaper.rs b/crates/rnote-engine/src/pens/shaper.rs index 1e3f4163d4..88c8422a3b 100644 --- a/crates/rnote-engine/src/pens/shaper.rs +++ b/crates/rnote-engine/src/pens/shaper.rs @@ -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, diff --git a/crates/rnote-engine/src/pens/typewriter/mod.rs b/crates/rnote-engine/src/pens/typewriter/mod.rs index 24960843e8..44727eef28 100644 --- a/crates/rnote-engine/src/pens/typewriter/mod.rs +++ b/crates/rnote-engine/src/pens/typewriter/mod.rs @@ -14,6 +14,7 @@ use futures::channel::oneshot; use p2d::bounding_volume::{Aabb, BoundingVolume}; use piet::RenderContext; use rnote_compose::EventResult; +use rnote_compose::eventresult::EventPropagation; use rnote_compose::ext::{AabbExt, Vector2Ext}; use rnote_compose::penevent::{KeyboardKey, PenEvent, PenProgress, PenState}; use rnote_compose::shapes::Shapeable; @@ -386,7 +387,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, diff --git a/crates/rnote-engine/src/store/mod.rs b/crates/rnote-engine/src/store/mod.rs index e73fc2f7ef..70b8204114 100644 --- a/crates/rnote-engine/src/store/mod.rs +++ b/crates/rnote-engine/src/store/mod.rs @@ -91,6 +91,8 @@ pub struct StrokeStore { /// The index of the current live document in the history stack. #[serde(skip)] live_index: usize, + #[serde(skip)] + canceled_selection: 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! @@ -110,6 +112,7 @@ impl Default for StrokeStore { // Start off with state in the history history: VecDeque::from(vec![HistoryEntry::default()]), live_index: 0, + canceled_selection: false, key_tree: KeyTree::default(), @@ -368,4 +371,14 @@ impl StrokeStore { widget_flags } + + /// set the active state for the cancelled selection + pub fn set_cancelled_state(&mut self, state: bool) { + self.canceled_selection = state + } + + /// get the active state for the cancelled selection + pub fn get_cancelled_state(&self) -> bool { + self.canceled_selection + } } From 6b296e977e0d31227dc6ae92c62aff302a0a5f6c Mon Sep 17 00:00:00 2001 From: Doublonmousse <115779707+Doublonmousse@users.noreply.github.com> Date: Thu, 11 Jul 2024 21:19:29 +0200 Subject: [PATCH 02/12] prevent shapes that are too small to be visible --- .../src/pens/pensconfig/shaperconfig.rs | 7 ++++ crates/rnote-engine/src/pens/shaper.rs | 41 +++++++++++++------ 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/crates/rnote-engine/src/pens/pensconfig/shaperconfig.rs b/crates/rnote-engine/src/pens/pensconfig/shaperconfig.rs index c8e1e9be94..7d9cf6faaf 100644 --- a/crates/rnote-engine/src/pens/pensconfig/shaperconfig.rs +++ b/crates/rnote-engine/src/pens/pensconfig/shaperconfig.rs @@ -91,4 +91,11 @@ impl ShaperConfig { } } } + + pub(crate) fn get_stroke_width(&self) -> f64 { + match &self.style { + ShaperStyle::Smooth => self.smooth_options.stroke_width, + ShaperStyle::Rough => self.rough_options.stroke_width, + } + } } diff --git a/crates/rnote-engine/src/pens/shaper.rs b/crates/rnote-engine/src/pens/shaper.rs index 88c8422a3b..04594ce01e 100644 --- a/crates/rnote-engine/src/pens/shaper.rs +++ b/crates/rnote-engine/src/pens/shaper.rs @@ -6,6 +6,7 @@ use crate::strokes::ShapeStroke; use crate::strokes::Stroke; use crate::{DrawableOnDoc, WidgetFlags}; use p2d::bounding_volume::Aabb; +use p2d::bounding_volume::BoundingVolume; use piet::RenderContext; use rnote_compose::Shape; use rnote_compose::builders::buildable::{Buildable, BuilderCreator, BuilderProgress}; @@ -17,6 +18,7 @@ use rnote_compose::builders::{ use rnote_compose::eventresult::{EventPropagation, EventResult}; use rnote_compose::penevent::{KeyboardKey, ModifierKey, PenEvent, PenProgress}; use rnote_compose::penpath::Element; +use rnote_compose::shapes::Shapeable; use std::time::Instant; #[derive(Debug)] @@ -157,18 +159,33 @@ impl PenBehaviour for Shaper { .shaper_config .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(), - ); + // calculate the bounds + let bound_condition = shapes + .iter() + .map(|x| x.bounds()) + .reduce(|acc, x| acc.merged(&x)) + .unwrap_or(Aabb::new_invalid()) + .volume() + > engine_view + .pens_config + .shaper_config + .get_stroke_width() + .powi(2); + + let shapes_emitted = !shapes.is_empty() && bound_condition; + 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; From 27d2ea4b0fb119fc3a39d681f9dd6c704c15d592 Mon Sep 17 00:00:00 2001 From: Doublonmousse <115779707+Doublonmousse@users.noreply.github.com> Date: Sat, 13 Jul 2024 16:38:36 +0200 Subject: [PATCH 03/12] close selection tool when deleting in temporary mode --- crates/rnote-engine/src/engine/mod.rs | 10 ++++++++++ crates/rnote-ui/src/appwindow/actions.rs | 11 ++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/rnote-engine/src/engine/mod.rs b/crates/rnote-engine/src/engine/mod.rs index bb7a92bdfc..04afd9db0b 100644 --- a/crates/rnote-engine/src/engine/mod.rs +++ b/crates/rnote-engine/src/engine/mod.rs @@ -833,6 +833,16 @@ impl Engine { | self.update_rendering_current_viewport() } + pub fn cancel_selection_temporary_pen(&self) -> bool { + let selection_keys = self.store.selection_keys_as_rendered(); + !selection_keys.is_empty() + && self + .penholder + .pen_mode_state() + .take_style_override() + .is_some() + } + pub fn trash_selection(&mut self) -> WidgetFlags { let selection_keys = self.store.selection_keys_as_rendered(); self.store.set_trashed_keys(&selection_keys, true); diff --git a/crates/rnote-ui/src/appwindow/actions.rs b/crates/rnote-ui/src/appwindow/actions.rs index 26ccca5690..ae559ee546 100644 --- a/crates/rnote-ui/src/appwindow/actions.rs +++ b/crates/rnote-ui/src/appwindow/actions.rs @@ -8,6 +8,7 @@ use gtk4::{ prelude::*, }; use p2d::bounding_volume::BoundingVolume; +use rnote_compose::penevent::{KeyboardKey, ShortcutKey}; use rnote_compose::SplitOrder; use rnote_compose::penevent::ShortcutKey; use rnote_engine::engine::StrokeContent; @@ -550,7 +551,15 @@ impl RnAppWindow { let Some(canvas) = appwindow.active_tab_canvas() else { return; }; - let widget_flags = canvas.engine_mut().trash_selection(); + // check if we have a selector as a temporary tool and need to change the pen + let cancel_selection = canvas.engine_ref().cancel_selection_temporary_pen(); + let widget_flags = if cancel_selection { + // trigger an event for a KeyboardPress::Delete + let (_, widget_flags) = canvas.engine_mut().handle_pen_event(rnote_compose::PenEvent::KeyPressed { keyboard_key: KeyboardKey::Delete, modifier_keys: vec![] }, None, Instant::now()); + widget_flags + } else { + canvas.engine_mut().trash_selection() + }; appwindow.handle_widget_flags(widget_flags, &canvas); } )); From d07f54376a2efe6d40dd167ba94c01c0f1cd7847 Mon Sep 17 00:00:00 2001 From: Doublonmousse Date: Sun, 6 Apr 2025 17:06:49 +0200 Subject: [PATCH 04/12] fmt --- crates/rnote-engine/src/pens/brush.rs | 2 +- crates/rnote-engine/src/pens/penholder.rs | 2 +- crates/rnote-ui/src/appwindow/actions.rs | 13 ++++++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/crates/rnote-engine/src/pens/brush.rs b/crates/rnote-engine/src/pens/brush.rs index 12e7613b91..d0b7f9f504 100644 --- a/crates/rnote-engine/src/pens/brush.rs +++ b/crates/rnote-engine/src/pens/brush.rs @@ -10,6 +10,7 @@ use crate::{DrawableOnDoc, WidgetFlags}; use p2d::bounding_volume::{Aabb, BoundingVolume}; use piet::RenderContext; use rnote_compose::Constraints; +use rnote_compose::Constraints; use rnote_compose::builders::buildable::{Buildable, BuilderCreator, BuilderProgress}; use rnote_compose::builders::{ PenPathBuilderType, PenPathCurvedBuilder, PenPathModeledBuilder, PenPathSimpleBuilder, @@ -18,7 +19,6 @@ 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; #[derive(Debug)] diff --git a/crates/rnote-engine/src/pens/penholder.rs b/crates/rnote-engine/src/pens/penholder.rs index 0886c0be79..2c9ab9c3af 100644 --- a/crates/rnote-engine/src/pens/penholder.rs +++ b/crates/rnote-engine/src/pens/penholder.rs @@ -257,7 +257,7 @@ impl PenHolder { PenEvent::Up { .. } => engine_view.store.set_cancelled_state(false), _ => (), } - + // Always redraw after handling a pen event. // // This is also needed because pens might have claimed/requested an animation frame. diff --git a/crates/rnote-ui/src/appwindow/actions.rs b/crates/rnote-ui/src/appwindow/actions.rs index ae559ee546..c422ccadde 100644 --- a/crates/rnote-ui/src/appwindow/actions.rs +++ b/crates/rnote-ui/src/appwindow/actions.rs @@ -8,9 +8,9 @@ use gtk4::{ prelude::*, }; use p2d::bounding_volume::BoundingVolume; -use rnote_compose::penevent::{KeyboardKey, ShortcutKey}; use rnote_compose::SplitOrder; use rnote_compose::penevent::ShortcutKey; +use rnote_compose::penevent::{KeyboardKey, ShortcutKey}; use rnote_engine::engine::StrokeContent; use rnote_engine::ext::GraphenePointExt; use rnote_engine::pens::PenStyle; @@ -555,10 +555,17 @@ impl RnAppWindow { let cancel_selection = canvas.engine_ref().cancel_selection_temporary_pen(); let widget_flags = if cancel_selection { // trigger an event for a KeyboardPress::Delete - let (_, widget_flags) = canvas.engine_mut().handle_pen_event(rnote_compose::PenEvent::KeyPressed { keyboard_key: KeyboardKey::Delete, modifier_keys: vec![] }, None, Instant::now()); + let (_, widget_flags) = canvas.engine_mut().handle_pen_event( + rnote_compose::PenEvent::KeyPressed { + keyboard_key: KeyboardKey::Delete, + modifier_keys: vec![], + }, + None, + Instant::now(), + ); widget_flags } else { - canvas.engine_mut().trash_selection() + canvas.engine_mut().trash_selection() }; appwindow.handle_widget_flags(widget_flags, &canvas); } From ed0660a3eba04dbe610ba8ff15fc8e472de12619 Mon Sep 17 00:00:00 2001 From: Doublonmousse <115779707+Doublonmousse@users.noreply.github.com> Date: Sun, 21 Jul 2024 18:41:16 +0200 Subject: [PATCH 05/12] remove unnecessary size check fixes bug where large vertical or horizontal elements would get cancelled --- .../src/pens/pensconfig/shaperconfig.rs | 7 ------- crates/rnote-engine/src/pens/shaper.rs | 16 +--------------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/crates/rnote-engine/src/pens/pensconfig/shaperconfig.rs b/crates/rnote-engine/src/pens/pensconfig/shaperconfig.rs index 7d9cf6faaf..c8e1e9be94 100644 --- a/crates/rnote-engine/src/pens/pensconfig/shaperconfig.rs +++ b/crates/rnote-engine/src/pens/pensconfig/shaperconfig.rs @@ -91,11 +91,4 @@ impl ShaperConfig { } } } - - pub(crate) fn get_stroke_width(&self) -> f64 { - match &self.style { - ShaperStyle::Smooth => self.smooth_options.stroke_width, - ShaperStyle::Rough => self.rough_options.stroke_width, - } - } } diff --git a/crates/rnote-engine/src/pens/shaper.rs b/crates/rnote-engine/src/pens/shaper.rs index 04594ce01e..6e44362f80 100644 --- a/crates/rnote-engine/src/pens/shaper.rs +++ b/crates/rnote-engine/src/pens/shaper.rs @@ -6,7 +6,6 @@ use crate::strokes::ShapeStroke; use crate::strokes::Stroke; use crate::{DrawableOnDoc, WidgetFlags}; use p2d::bounding_volume::Aabb; -use p2d::bounding_volume::BoundingVolume; use piet::RenderContext; use rnote_compose::Shape; use rnote_compose::builders::buildable::{Buildable, BuilderCreator, BuilderProgress}; @@ -159,20 +158,7 @@ impl PenBehaviour for Shaper { .shaper_config .gen_style_for_current_options(); - // calculate the bounds - let bound_condition = shapes - .iter() - .map(|x| x.bounds()) - .reduce(|acc, x| acc.merged(&x)) - .unwrap_or(Aabb::new_invalid()) - .volume() - > engine_view - .pens_config - .shaper_config - .get_stroke_width() - .powi(2); - - let shapes_emitted = !shapes.is_empty() && bound_condition; + let shapes_emitted = !shapes.is_empty(); if shapes_emitted { for shape in shapes { let key = engine_view.store.insert_stroke( From 858fcb2206f11f7ad8035fac8a2fe2f67afecd48 Mon Sep 17 00:00:00 2001 From: Doublonmousse <115779707+Doublonmousse@users.noreply.github.com> Date: Sat, 2 Nov 2024 15:59:44 +0000 Subject: [PATCH 06/12] apply corrections and switch to hashset to fix type error apply corrections - move actions logic into `trash_selection` - small rename of canceled* to have coherent naming between variables and functions getters/settesr - move comment closer to the `canceled_state` variable (variable-level docstring) - constant added as such fmt and hashset --- crates/rnote-engine/src/engine/mod.rs | 29 ++++++++++++++----- crates/rnote-engine/src/pens/brush.rs | 13 +++++++-- .../src/pens/selector/penevents.rs | 6 ++-- crates/rnote-engine/src/pens/shaper.rs | 1 - crates/rnote-engine/src/store/mod.rs | 12 +++++--- crates/rnote-ui/src/appwindow/actions.rs | 4 +-- 6 files changed, 44 insertions(+), 21 deletions(-) diff --git a/crates/rnote-engine/src/engine/mod.rs b/crates/rnote-engine/src/engine/mod.rs index 04afd9db0b..852546b2fc 100644 --- a/crates/rnote-engine/src/engine/mod.rs +++ b/crates/rnote-engine/src/engine/mod.rs @@ -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; @@ -844,12 +845,26 @@ impl Engine { } pub fn trash_selection(&mut self) -> WidgetFlags { - 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() + // 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 { diff --git a/crates/rnote-engine/src/pens/brush.rs b/crates/rnote-engine/src/pens/brush.rs index d0b7f9f504..09692bcf5b 100644 --- a/crates/rnote-engine/src/pens/brush.rs +++ b/crates/rnote-engine/src/pens/brush.rs @@ -10,7 +10,6 @@ use crate::{DrawableOnDoc, WidgetFlags}; use p2d::bounding_volume::{Aabb, BoundingVolume}; use piet::RenderContext; use rnote_compose::Constraints; -use rnote_compose::Constraints; use rnote_compose::builders::buildable::{Buildable, BuilderCreator, BuilderProgress}; use rnote_compose::builders::{ PenPathBuilderType, PenPathCurvedBuilder, PenPathModeledBuilder, PenPathSimpleBuilder, @@ -43,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() @@ -242,14 +250,13 @@ impl PenBehaviour for Brush { .volume(); if engine_view.store.get_cancelled_state() && volume - < 4.0 + < Self::VOLUME_RATIO_THRESHOLD * engine_view .pens_config .brush_config .get_stroke_width() .powi(2) { - tracing::debug!("VOLUME {volume}"); engine_view.store.remove_stroke(*current_stroke_key); } diff --git a/crates/rnote-engine/src/pens/selector/penevents.rs b/crates/rnote-engine/src/pens/selector/penevents.rs index bda380adef..93e0d1a1d8 100644 --- a/crates/rnote-engine/src/pens/selector/penevents.rs +++ b/crates/rnote-engine/src/pens/selector/penevents.rs @@ -202,10 +202,8 @@ impl Selector { engine_view.store.set_selected_keys(selection, false); self.state = SelectorState::Idle; - // we create a new engine variable that indicates that the pen event is one from the same - // event sequence that the one that cancelled the selection - // this is done by setting this variable to true here and - // removing it on a pen up event (and doing more things if this variable is true on a pen up) + // 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; diff --git a/crates/rnote-engine/src/pens/shaper.rs b/crates/rnote-engine/src/pens/shaper.rs index 6e44362f80..61ae78f907 100644 --- a/crates/rnote-engine/src/pens/shaper.rs +++ b/crates/rnote-engine/src/pens/shaper.rs @@ -17,7 +17,6 @@ use rnote_compose::builders::{ use rnote_compose::eventresult::{EventPropagation, EventResult}; use rnote_compose::penevent::{KeyboardKey, ModifierKey, PenEvent, PenProgress}; use rnote_compose::penpath::Element; -use rnote_compose::shapes::Shapeable; use std::time::Instant; #[derive(Debug)] diff --git a/crates/rnote-engine/src/store/mod.rs b/crates/rnote-engine/src/store/mod.rs index 70b8204114..be67c36a97 100644 --- a/crates/rnote-engine/src/store/mod.rs +++ b/crates/rnote-engine/src/store/mod.rs @@ -91,8 +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_selection: bool, + 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! @@ -112,7 +116,7 @@ impl Default for StrokeStore { // Start off with state in the history history: VecDeque::from(vec![HistoryEntry::default()]), live_index: 0, - canceled_selection: false, + canceled_state: false, key_tree: KeyTree::default(), @@ -374,11 +378,11 @@ impl StrokeStore { /// set the active state for the cancelled selection pub fn set_cancelled_state(&mut self, state: bool) { - self.canceled_selection = state + self.canceled_state = state } /// get the active state for the cancelled selection pub fn get_cancelled_state(&self) -> bool { - self.canceled_selection + self.canceled_state } } diff --git a/crates/rnote-ui/src/appwindow/actions.rs b/crates/rnote-ui/src/appwindow/actions.rs index c422ccadde..4e87e5e01f 100644 --- a/crates/rnote-ui/src/appwindow/actions.rs +++ b/crates/rnote-ui/src/appwindow/actions.rs @@ -9,13 +9,13 @@ use gtk4::{ }; use p2d::bounding_volume::BoundingVolume; use rnote_compose::SplitOrder; -use rnote_compose::penevent::ShortcutKey; use rnote_compose::penevent::{KeyboardKey, ShortcutKey}; use rnote_engine::engine::StrokeContent; use rnote_engine::ext::GraphenePointExt; use rnote_engine::pens::PenStyle; use rnote_engine::strokes::resize::{ImageSizeOption, Resize}; use rnote_engine::{Camera, Engine}; +use std::collections::HashSet; use std::path::PathBuf; use std::str::FromStr; use std::time::Instant; @@ -558,7 +558,7 @@ impl RnAppWindow { let (_, widget_flags) = canvas.engine_mut().handle_pen_event( rnote_compose::PenEvent::KeyPressed { keyboard_key: KeyboardKey::Delete, - modifier_keys: vec![], + modifier_keys: HashSet::new(), }, None, Instant::now(), From 52edea2298c39411a4332f36151bd0d5af717ea7 Mon Sep 17 00:00:00 2001 From: Doublonmousse Date: Sun, 6 Apr 2025 17:39:58 +0200 Subject: [PATCH 07/12] fmt, more comments and style changes --- crates/rnote-engine/src/engine/mod.rs | 2 ++ crates/rnote-engine/src/pens/eraser.rs | 2 +- crates/rnote-engine/src/pens/penholder.rs | 5 ++--- crates/rnote-ui/src/appwindow/actions.rs | 3 ++- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/rnote-engine/src/engine/mod.rs b/crates/rnote-engine/src/engine/mod.rs index 852546b2fc..3811461bd5 100644 --- a/crates/rnote-engine/src/engine/mod.rs +++ b/crates/rnote-engine/src/engine/mod.rs @@ -848,6 +848,8 @@ impl Engine { // 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 { + // we trigger a delete press event as this reset the pen back to its + // original mode and deletes the content let (_, widget_flags) = self.handle_pen_event( rnote_compose::PenEvent::KeyPressed { keyboard_key: KeyboardKey::Delete, diff --git a/crates/rnote-engine/src/pens/eraser.rs b/crates/rnote-engine/src/pens/eraser.rs index 1f9538552e..3d3a910ae7 100644 --- a/crates/rnote-engine/src/pens/eraser.rs +++ b/crates/rnote-engine/src/pens/eraser.rs @@ -63,7 +63,7 @@ impl PenBehaviour for Eraser { 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 + // this means we need one more up/down event here to activate the eraser after a selection cancellation } EventResult { handled: true, diff --git a/crates/rnote-engine/src/pens/penholder.rs b/crates/rnote-engine/src/pens/penholder.rs index 2c9ab9c3af..fc7e1dc11b 100644 --- a/crates/rnote-engine/src/pens/penholder.rs +++ b/crates/rnote-engine/src/pens/penholder.rs @@ -253,9 +253,8 @@ impl PenHolder { widget_flags |= wf; } // reset the cancelled state as we just handled a pen tool (and not a selection tool) event - match event { - PenEvent::Up { .. } => engine_view.store.set_cancelled_state(false), - _ => (), + if matches!(event, PenEvent::Up { .. }) { + engine_view.store.set_cancelled_state(false); } // Always redraw after handling a pen event. diff --git a/crates/rnote-ui/src/appwindow/actions.rs b/crates/rnote-ui/src/appwindow/actions.rs index 4e87e5e01f..a82fc4a9ee 100644 --- a/crates/rnote-ui/src/appwindow/actions.rs +++ b/crates/rnote-ui/src/appwindow/actions.rs @@ -554,7 +554,8 @@ impl RnAppWindow { // check if we have a selector as a temporary tool and need to change the pen let cancel_selection = canvas.engine_ref().cancel_selection_temporary_pen(); let widget_flags = if cancel_selection { - // trigger an event for a KeyboardPress::Delete + // trigger an event for a KeyboardPress::Delete : this both deletes the selection + // and resets the pen back to its previous mode let (_, widget_flags) = canvas.engine_mut().handle_pen_event( rnote_compose::PenEvent::KeyPressed { keyboard_key: KeyboardKey::Delete, From e4c3cdd671753dd95159a299afffe5deb853a7cd Mon Sep 17 00:00:00 2001 From: Doublonmousse Date: Mon, 7 Apr 2025 19:46:03 +0200 Subject: [PATCH 08/12] fix #888 : store in the `PenHolder` informations for temporary shortcuts and pass to pens This adds a `temporary_tool` bool to the `PenHolder` which signifies whether the following sequence occured (for now only with the selector) 1. activates the selector 2. Select something then lift the pen (triggering an up or cancel event) 3. go back with the selector activated via a temporary shortcut 4. Select something else Then the temporary style will be `true` as going back will trigger again the `handle_pressed_shortcut_key` function on the pen down event. --- crates/rnote-engine/src/pens/brush.rs | 1 + crates/rnote-engine/src/pens/eraser.rs | 1 + crates/rnote-engine/src/pens/mod.rs | 17 +++++++---- crates/rnote-engine/src/pens/penbehaviour.rs | 1 + crates/rnote-engine/src/pens/penholder.rs | 28 ++++++++++++++++--- crates/rnote-engine/src/pens/selector/mod.rs | 5 +++- .../src/pens/selector/penevents.rs | 22 +++++++++++---- crates/rnote-engine/src/pens/shaper.rs | 1 + crates/rnote-engine/src/pens/tools/mod.rs | 1 + .../rnote-engine/src/pens/typewriter/mod.rs | 1 + 10 files changed, 61 insertions(+), 17 deletions(-) diff --git a/crates/rnote-engine/src/pens/brush.rs b/crates/rnote-engine/src/pens/brush.rs index 09692bcf5b..c3eeb40f9a 100644 --- a/crates/rnote-engine/src/pens/brush.rs +++ b/crates/rnote-engine/src/pens/brush.rs @@ -73,6 +73,7 @@ impl PenBehaviour for Brush { event: PenEvent, now: Instant, engine_view: &mut EngineViewMut, + _temporary_tool: bool, ) -> (EventResult, WidgetFlags) { let mut widget_flags = WidgetFlags::default(); diff --git a/crates/rnote-engine/src/pens/eraser.rs b/crates/rnote-engine/src/pens/eraser.rs index 3d3a910ae7..150b36020e 100644 --- a/crates/rnote-engine/src/pens/eraser.rs +++ b/crates/rnote-engine/src/pens/eraser.rs @@ -55,6 +55,7 @@ impl PenBehaviour for Eraser { event: PenEvent, _now: Instant, engine_view: &mut EngineViewMut, + _temporary_tool: bool, ) -> (EventResult, WidgetFlags) { let mut widget_flags = WidgetFlags::default(); diff --git a/crates/rnote-engine/src/pens/mod.rs b/crates/rnote-engine/src/pens/mod.rs index c6c45c0127..83b6c23423 100644 --- a/crates/rnote-engine/src/pens/mod.rs +++ b/crates/rnote-engine/src/pens/mod.rs @@ -102,14 +102,19 @@ impl PenBehaviour for Pen { event: PenEvent, now: Instant, engine_view: &mut EngineViewMut, + temporary_tool: bool, ) -> (EventResult, WidgetFlags) { match self { - Pen::Brush(brush) => brush.handle_event(event, now, engine_view), - Pen::Shaper(shaper) => shaper.handle_event(event, now, engine_view), - Pen::Typewriter(typewriter) => typewriter.handle_event(event, now, engine_view), - Pen::Eraser(eraser) => eraser.handle_event(event, now, engine_view), - Pen::Selector(selector) => selector.handle_event(event, now, engine_view), - Pen::Tools(tools) => tools.handle_event(event, now, engine_view), + Pen::Brush(brush) => brush.handle_event(event, now, engine_view, temporary_tool), + Pen::Shaper(shaper) => shaper.handle_event(event, now, engine_view, temporary_tool), + Pen::Typewriter(typewriter) => { + typewriter.handle_event(event, now, engine_view, temporary_tool) + } + Pen::Eraser(eraser) => eraser.handle_event(event, now, engine_view, temporary_tool), + Pen::Selector(selector) => { + selector.handle_event(event, now, engine_view, temporary_tool) + } + Pen::Tools(tools) => tools.handle_event(event, now, engine_view, temporary_tool), } } diff --git a/crates/rnote-engine/src/pens/penbehaviour.rs b/crates/rnote-engine/src/pens/penbehaviour.rs index 2983f7a832..b12dd560fa 100644 --- a/crates/rnote-engine/src/pens/penbehaviour.rs +++ b/crates/rnote-engine/src/pens/penbehaviour.rs @@ -30,6 +30,7 @@ pub trait PenBehaviour: DrawableOnDoc { event: PenEvent, now: Instant, engine_view: &mut EngineViewMut, + temporary_tool: bool, ) -> (EventResult, WidgetFlags); /// Handle a requested animation frame. diff --git a/crates/rnote-engine/src/pens/penholder.rs b/crates/rnote-engine/src/pens/penholder.rs index fc7e1dc11b..5c4973c09d 100644 --- a/crates/rnote-engine/src/pens/penholder.rs +++ b/crates/rnote-engine/src/pens/penholder.rs @@ -45,6 +45,15 @@ pub struct PenHolder { toggle_pen_style: Option, #[serde(skip)] prev_shortcut_key: Option, + + /// indicates if we toggled a shortcut key + /// in temporary mode without changing + /// `PenMode` in the current sequence. + /// Used to tweak the behavior of tools + /// on exit so that we don't exit the tool + /// with the shortcut key pressed + #[serde(skip)] + temporary_style: bool, } impl Default for PenHolder { @@ -58,6 +67,7 @@ impl Default for PenHolder { progress: PenProgress::Idle, toggle_pen_style: None, prev_shortcut_key: None, + temporary_style: false, } } } @@ -158,6 +168,7 @@ impl PenHolder { // When the style is changed externally, the toggle mode / internal states are reset self.toggle_pen_style = None; self.prev_shortcut_key = None; + self.temporary_style = false; widget_flags } @@ -195,6 +206,7 @@ impl PenHolder { if self.pen_mode_state.pen_mode() != new_pen_mode { self.pen_mode_state.set_pen_mode(new_pen_mode); + self.temporary_style = false; widget_flags |= self.reinstall_pen_current_style(engine_view); widget_flags.refresh_ui = true; } @@ -211,7 +223,7 @@ impl PenHolder { // first cancel the current pen let (_, mut widget_flags) = self.current_pen - .handle_event(PenEvent::Cancel, Instant::now(), engine_view); + .handle_event(PenEvent::Cancel, Instant::now(), engine_view, false); // then reinstall a new pen instance let mut new_pen = new_pen(self.current_pen_style_w_override()); @@ -241,10 +253,14 @@ impl PenHolder { widget_flags |= self.change_pen_mode(pen_mode, engine_view); } + if matches!(event, PenEvent::Cancel | PenEvent::Up { .. }) { + self.temporary_style = false; + } + // Handle the event with the current pen - let (mut event_result, wf) = self - .current_pen - .handle_event(event.clone(), now, engine_view); + let (mut event_result, wf) = + self.current_pen + .handle_event(event.clone(), now, engine_view, self.temporary_style); widget_flags |= wf | self.handle_pen_progress(event_result.progress, engine_view); if !event_result.handled { @@ -280,6 +296,7 @@ impl PenHolder { _now: Instant, engine_view: &mut EngineViewMut, ) -> (EventPropagation, WidgetFlags) { + println!("handle_pressed_shortcut_key"); let mut widget_flags = WidgetFlags::default(); let mut propagate = EventPropagation::Proceed; @@ -287,6 +304,9 @@ impl PenHolder { match action { ShortcutAction::ChangePenStyle { style, mode } => match mode { ShortcutMode::Temporary => { + if self.pen_mode_state.current_style_w_override() == style { + self.temporary_style = true; + }; widget_flags |= self.change_style_override(Some(style), engine_view); } ShortcutMode::Permanent => { diff --git a/crates/rnote-engine/src/pens/selector/mod.rs b/crates/rnote-engine/src/pens/selector/mod.rs index 906819efcc..9dd9bac126 100644 --- a/crates/rnote-engine/src/pens/selector/mod.rs +++ b/crates/rnote-engine/src/pens/selector/mod.rs @@ -133,12 +133,15 @@ impl PenBehaviour for Selector { event: PenEvent, now: Instant, engine_view: &mut EngineViewMut, + temporary_tool: bool, ) -> (EventResult, WidgetFlags) { match event { PenEvent::Down { element, modifier_keys, - } => self.handle_pen_event_down(element, modifier_keys, now, engine_view), + } => { + self.handle_pen_event_down(element, modifier_keys, now, engine_view, temporary_tool) + } PenEvent::Up { element, modifier_keys, diff --git a/crates/rnote-engine/src/pens/selector/penevents.rs b/crates/rnote-engine/src/pens/selector/penevents.rs index 93e0d1a1d8..8606d1974e 100644 --- a/crates/rnote-engine/src/pens/selector/penevents.rs +++ b/crates/rnote-engine/src/pens/selector/penevents.rs @@ -22,6 +22,7 @@ impl Selector { modifier_keys: HashSet, _now: Instant, engine_view: &mut EngineViewMut, + temporary_tool: bool, ) -> (EventResult, WidgetFlags) { let mut widget_flags = WidgetFlags::default(); self.pos = Some(element.pos); @@ -200,13 +201,22 @@ impl Selector { // 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; + if temporary_tool { + // we had a selection active then for the next sequence + // we activated the temporary mode for the selector and + // clicked outside the selection. We expect to be able + // to do another selection + self.state = SelectorState::Selecting { path: Vec::new() }; + progress = PenProgress::InProgress; + } else { + self.state = SelectorState::Idle; + progress = PenProgress::Finished; + + // 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); + } } } ModifyState::Translate { diff --git a/crates/rnote-engine/src/pens/shaper.rs b/crates/rnote-engine/src/pens/shaper.rs index 61ae78f907..70f7127cce 100644 --- a/crates/rnote-engine/src/pens/shaper.rs +++ b/crates/rnote-engine/src/pens/shaper.rs @@ -62,6 +62,7 @@ impl PenBehaviour for Shaper { event: PenEvent, now: Instant, engine_view: &mut EngineViewMut, + _temporary_tool: bool, ) -> (EventResult, WidgetFlags) { let mut widget_flags = WidgetFlags::default(); diff --git a/crates/rnote-engine/src/pens/tools/mod.rs b/crates/rnote-engine/src/pens/tools/mod.rs index 528fce43b8..56c6c83174 100644 --- a/crates/rnote-engine/src/pens/tools/mod.rs +++ b/crates/rnote-engine/src/pens/tools/mod.rs @@ -64,6 +64,7 @@ impl PenBehaviour for Tools { event: PenEvent, now: Instant, engine_view: &mut EngineViewMut, + _temporary_tool: bool, ) -> (EventResult, WidgetFlags) { match engine_view.pens_config.tools_config.style { ToolStyle::VerticalSpace => { diff --git a/crates/rnote-engine/src/pens/typewriter/mod.rs b/crates/rnote-engine/src/pens/typewriter/mod.rs index 44727eef28..66c05268d6 100644 --- a/crates/rnote-engine/src/pens/typewriter/mod.rs +++ b/crates/rnote-engine/src/pens/typewriter/mod.rs @@ -382,6 +382,7 @@ impl PenBehaviour for Typewriter { event: PenEvent, now: Instant, engine_view: &mut EngineViewMut, + _temporary_tool: bool, ) -> (EventResult, WidgetFlags) { let (event_result, widget_flags) = match event { PenEvent::Down { From 1b31e0f3e73bcee7adfef93f5ca26809d99a4f94 Mon Sep 17 00:00:00 2001 From: Doublonmousse <115779707+Doublonmousse@users.noreply.github.com> Date: Sat, 10 May 2025 17:12:26 +0200 Subject: [PATCH 09/12] remove println --- crates/rnote-engine/src/pens/penholder.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/rnote-engine/src/pens/penholder.rs b/crates/rnote-engine/src/pens/penholder.rs index 2daa9d200a..63305d254d 100644 --- a/crates/rnote-engine/src/pens/penholder.rs +++ b/crates/rnote-engine/src/pens/penholder.rs @@ -248,7 +248,6 @@ impl PenHolder { _now: Instant, engine_view: &mut EngineViewMut, ) -> (EventPropagation, WidgetFlags) { - println!("handle_pressed_shortcut_key"); let mut widget_flags = WidgetFlags::default(); let mut propagate = EventPropagation::Proceed; From 9cd4c99f0ef5a37018cd23c2b9ecd605d7c8c44e Mon Sep 17 00:00:00 2001 From: Doublonmousse <115779707+Doublonmousse@users.noreply.github.com> Date: Sat, 10 May 2025 17:24:10 +0200 Subject: [PATCH 10/12] rebase fixes --- crates/rnote-engine/src/engine/mod.rs | 1 + crates/rnote-engine/src/pens/brush.rs | 1 + crates/rnote-engine/src/pens/penholder.rs | 6 +++++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/rnote-engine/src/engine/mod.rs b/crates/rnote-engine/src/engine/mod.rs index c9d0746f51..3ede0991db 100644 --- a/crates/rnote-engine/src/engine/mod.rs +++ b/crates/rnote-engine/src/engine/mod.rs @@ -37,6 +37,7 @@ use rnote_compose::penevent::{KeyboardKey, PenEvent, ShortcutKey}; use rnote_compose::{Color, SplitOrder}; use serde::{Deserialize, Serialize}; use snapshot::Snapshotable; +use std::collections::HashSet; use std::path::PathBuf; use std::sync::{Arc, RwLock}; use std::time::Instant; diff --git a/crates/rnote-engine/src/pens/brush.rs b/crates/rnote-engine/src/pens/brush.rs index 245ad5dee0..7aab2f96ae 100644 --- a/crates/rnote-engine/src/pens/brush.rs +++ b/crates/rnote-engine/src/pens/brush.rs @@ -259,6 +259,7 @@ impl PenBehaviour for Brush { && volume < Self::VOLUME_RATIO_THRESHOLD * engine_view + .config .pens_config .brush_config .get_stroke_width() diff --git a/crates/rnote-engine/src/pens/penholder.rs b/crates/rnote-engine/src/pens/penholder.rs index 63305d254d..05df6a1210 100644 --- a/crates/rnote-engine/src/pens/penholder.rs +++ b/crates/rnote-engine/src/pens/penholder.rs @@ -259,7 +259,11 @@ impl PenHolder { match action { ShortcutAction::ChangePenStyle { style, mode } => match mode { ShortcutMode::Temporary => { - if self.pen_mode_state.current_style_w_override() == style { + if self + .pen_mode_state + .current_style_w_override(&engine_view.config.pens_config) + == style + { self.temporary_style = true; }; widget_flags |= self.change_style_override(Some(style), engine_view); From 20c8107e78110c6a2b62c0a0e0d4c0d776f264ef Mon Sep 17 00:00:00 2001 From: Doublonmousse Date: Sun, 11 May 2025 15:13:48 +0200 Subject: [PATCH 11/12] switch to perimeter heuristic for cancelling small strokes This is a little more robust that volume as tiny spots tend to be single elements or small lines. The perimeter also increase quite rapidly with non line elements. The threshold is placed quite high (to limit unwanted spots) --- crates/rnote-engine/src/pens/brush.rs | 52 +++++++++++++++------------ 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/crates/rnote-engine/src/pens/brush.rs b/crates/rnote-engine/src/pens/brush.rs index 7aab2f96ae..88b1c269b3 100644 --- a/crates/rnote-engine/src/pens/brush.rs +++ b/crates/rnote-engine/src/pens/brush.rs @@ -7,6 +7,7 @@ use crate::store::StrokeKey; use crate::strokes::BrushStroke; use crate::strokes::Stroke; use crate::{DrawableOnDoc, WidgetFlags}; +use kurbo::Shape; use p2d::bounding_volume::{Aabb, BoundingVolume}; use piet::RenderContext; use rnote_compose::Constraints; @@ -19,6 +20,7 @@ use rnote_compose::penevent::{PenEvent, PenProgress}; use rnote_compose::penpath::{Element, Segment}; use rnote_compose::shapes::Shapeable; use std::time::Instant; +use tracing::debug; #[derive(Debug)] enum BrushState { @@ -43,12 +45,11 @@ 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; + /// Threshold for the stroke length over which we consider + /// that strokes that are left by pressing the pen down + /// when cancelling a selection should be kept. + /// Smaller ratio are deleted. Zoom ratio is taken into account + const LENGTH_PX_THRESHOLD: f64 = 25.0; } impl PenBehaviour for Brush { @@ -249,23 +250,28 @@ 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 - .config - .pens_config - .brush_config - .get_stroke_width() - .powi(2) - { - engine_view.store.remove_stroke(*current_stroke_key); + if engine_view.store.get_cancelled_state() { + let current_stroke_width = engine_view + .config + .pens_config + .brush_config + .get_stroke_width(); + let length_px = engine_view + .store + .get_stroke_ref(*current_stroke_key) + .unwrap() + .outline_path() + .perimeter(current_stroke_width); + let threshold = Self::LENGTH_PX_THRESHOLD / engine_view.camera.zoom(); + debug!( + "perimeter {:?} threshold {:?}, zoom {:?}", + length_px, + threshold, + engine_view.camera.zoom() + ); + if length_px < threshold { + engine_view.store.remove_stroke(*current_stroke_key); + } } // Finish up the last stroke From 6b74db155ecd02f166bee4fd76bfd5935630e74d Mon Sep 17 00:00:00 2001 From: Doublonmousse <115779707+Doublonmousse@users.noreply.github.com> Date: Wed, 20 Aug 2025 18:54:19 +0200 Subject: [PATCH 12/12] fmt --- crates/rnote-engine/src/pens/shaper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rnote-engine/src/pens/shaper.rs b/crates/rnote-engine/src/pens/shaper.rs index d17d84286f..9a98512239 100644 --- a/crates/rnote-engine/src/pens/shaper.rs +++ b/crates/rnote-engine/src/pens/shaper.rs @@ -86,7 +86,7 @@ impl PenBehaviour for Shaper { element, now, ), - }; + }; }; EventResult {