From 0aa2f3bccb3a0dc945c0c99db0593976310cbb68 Mon Sep 17 00:00:00 2001 From: Doublonmousse <115779707+Doublonmousse@users.noreply.github.com> Date: Mon, 5 Aug 2024 12:36:16 +0200 Subject: [PATCH 1/6] hold fct : change a pen stroke to a line starting from the end result : the action of changing the stroke to a shape with data from the current stroke --- crates/rnote-engine/src/engine/mod.rs | 7 +++ crates/rnote-engine/src/pens/brush.rs | 71 +++++++++++++++++++++++ crates/rnote-engine/src/pens/penholder.rs | 70 ++++++++++++++++++++++ crates/rnote-engine/src/widgetflags.rs | 6 ++ 4 files changed, 154 insertions(+) diff --git a/crates/rnote-engine/src/engine/mod.rs b/crates/rnote-engine/src/engine/mod.rs index 2e7ad19093..3a98bc598a 100644 --- a/crates/rnote-engine/src/engine/mod.rs +++ b/crates/rnote-engine/src/engine/mod.rs @@ -101,6 +101,10 @@ pub enum EngineTask { BlinkTypewriterCursor, /// Change the permanent zoom to the given value Zoom(f64), + /// Task for a pen that's held down at the same position for a long time + /// We do that through a task to take into account the case of a mouse that's + /// held down at the same position without giving any new event + LongPressStatic, /// Indicates that the application is quitting. Sent to quit the handler which receives the tasks. Quit, } @@ -464,6 +468,9 @@ impl Engine { widget_flags |= self.set_active(false); quit = true; } + EngineTask::LongPressStatic => { + todo!() + } } (widget_flags, quit) diff --git a/crates/rnote-engine/src/pens/brush.rs b/crates/rnote-engine/src/pens/brush.rs index d993bb898a..00231b6a91 100644 --- a/crates/rnote-engine/src/pens/brush.rs +++ b/crates/rnote-engine/src/pens/brush.rs @@ -17,6 +17,7 @@ use rnote_compose::eventresult::{EventPropagation, EventResult}; use rnote_compose::penevent::{PenEvent, PenProgress}; use rnote_compose::penpath::{Element, Segment}; use rnote_compose::Constraints; +use rnote_compose::PenPath; use std::time::Instant; #[derive(Debug)] @@ -31,12 +32,42 @@ enum BrushState { #[derive(Debug)] pub struct Brush { state: BrushState, + /// if we have a long hold of the pen, we save the + /// penpath for recognition one level upper + pub pen_path_recognition: Option, + // maybe we can do that as a speed based detection + // if the speed is lower than ... in the timeout + // calculate here the state + // for a pen that's down and not moving + // vecdecque of the last position + distance to the previous element + // for all events inside the timeout + // and a distance on the side + + // then test inside handle event to trigger the long press + + // but we still need the task if we hold the pen down + // So what we need to do is to have a periodic task + // where we send times of relevant pen events + + // when read, it sleeps until the time + timeout is done + // then tests if the channel is empty + // if it is, then calls the task (and the "does the task need to happen" test is done) + // if not, reads the next messages + + // we need to keep the state of whether a long hold has occured or not as well + // to not trigger the same code twice + + // we'll start with the internal state bookkeeping then try the other part after + + // another thing is to not trigger this too soon ? for a pen stuck on the start position + // maybe not good ? } impl Default for Brush { fn default() -> Self { Self { state: BrushState::Idle, + pen_path_recognition: None, } } } @@ -66,6 +97,16 @@ impl PenBehaviour for Brush { ) -> (EventResult, WidgetFlags) { let mut widget_flags = WidgetFlags::default(); + // we should have a special task on a separate thread that sends event + // with the channel the strategy is the following + // - CANCEL + // the state is idle + // pen cancel event + // - if we start with drawing and the pen is down, start signal + // - as long as we stay in drawing mode : + // - send pen down events + // - + let event_result = match (&mut self.state, event) { (BrushState::Idle, PenEvent::Down { element, .. }) => { if !element.filter_by_bounds( @@ -229,6 +270,34 @@ impl PenBehaviour for Brush { ); } + // we have to see if we can cancel the stroke here + // and then change the type to be a line with the start position + // and end position saved + // so with a change of tools + + // this will have to move a little (happens potentially in the other branch) + + // cancel the stroke (if we have a longpress) + + // the normal way this would happen would be at the previous step : for an in progress penprogress + if true { + // we need to save the stroke data before deleting it + println!("saving the current stroke data"); + if let Some(Stroke::BrushStroke(brushstroke)) = + engine_view.store.get_stroke_ref(*current_stroke_key) + { + let path = brushstroke.path.clone(); + println!("the path is {:?}", path); + + // save to a location + self.pen_path_recognition = Some(path); + } + println!("cancelled stroke"); + engine_view.store.remove_stroke(*current_stroke_key); + widget_flags.long_hold = true; + } + // change to a shaper : need to use the widget flags higher up + // Finish up the last stroke engine_view .store @@ -248,6 +317,8 @@ impl PenBehaviour for Brush { widget_flags |= engine_view.store.record(Instant::now()); widget_flags.store_modified = true; + println!("pen : {:?}", self); //brush reset ? + PenProgress::Finished } }; diff --git a/crates/rnote-engine/src/pens/penholder.rs b/crates/rnote-engine/src/pens/penholder.rs index 66952fbe25..7c3e494d5f 100644 --- a/crates/rnote-engine/src/pens/penholder.rs +++ b/crates/rnote-engine/src/pens/penholder.rs @@ -13,9 +13,12 @@ use crate::{CloneConfig, DrawableOnDoc}; use futures::channel::oneshot; use p2d::bounding_volume::Aabb; use piet::RenderContext; +use rnote_compose::builders::ShapeBuilderType; use rnote_compose::eventresult::EventPropagation; use rnote_compose::penevent::{KeyboardKey, ModifierKey, PenEvent, PenProgress, ShortcutKey}; +use rnote_compose::penpath::Element; use serde::{Deserialize, Serialize}; +use std::collections::HashSet; use std::time::{Duration, Instant}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -245,6 +248,26 @@ impl PenHolder { let (mut event_result, wf) = self .current_pen .handle_event(event.clone(), now, engine_view); + // does the pen loose its path here or before ? + // we have to get the path here + let path = match wf.long_hold { + true => { + // need to get back the original position + // but if we have more than this (shape or more) + // we need to retrieve the full shape here + // we thus recuperate the data here, saved inside an optional part of the pen struct + println!("getting the pen path back"); + match &self.current_pen { + Pen::Brush(brush) => brush.pen_path_recognition.clone(), + _ => None, + } + } + false => None, + }; + //normally this wouldn't be the case as we are doing this on a cancel which is not the real thing ! + //if cancel the pen is reinitialized ! + // could be done further done like the rest + widget_flags |= wf | self.handle_pen_progress(event_result.progress, engine_view); if !event_result.handled { @@ -253,6 +276,53 @@ impl PenHolder { widget_flags |= wf; } + // test the wf for the long press + if widget_flags.long_hold { + // need to get back the original position + // but if we have more than this (shape or more) + // we need to retrieve the full shape here + // we thus recuperate the data here, saved inside an optional part of the pen struct + println!("the path is {:?}", path); + println!("long hold for the pen, sending new events"); + // change the type to line first + engine_view.pens_config.shaper_config.builder_type = ShapeBuilderType::Line; + + // switch to the shaper tool but as an override (temporary) + widget_flags |= self.change_style_override(Some(PenStyle::Shaper), engine_view); + + // update the state of the shaper to be in building + match &self.current_pen { + Pen::Shaper(shaper) => { + println!("hello {:?}", shaper); + } + _ => println!("error"), + } + // then add two new events + // or edit directly the shaper ? + // first event is the original position (to get back up from somewhere ...) + let (_, wf) = self.current_pen.handle_event( + PenEvent::Down { + element: path.as_ref().unwrap().start, + modifier_keys: HashSet::new(), + }, + now, + engine_view, + ); + widget_flags |= wf; + // second event is the last position + let (_, wf) = self.current_pen.handle_event( + PenEvent::Down { + element: path.unwrap().segments.last().unwrap().end(), + modifier_keys: HashSet::new(), + }, + now, + engine_view, + ); + widget_flags |= wf; + // maybe try with the other method : modify the thing manually + // also need to be a temporary pen and have it cancelled back to a pen afterward + } + // Always redraw after handling a pen event widget_flags.redraw = true; diff --git a/crates/rnote-engine/src/widgetflags.rs b/crates/rnote-engine/src/widgetflags.rs index d348063a9c..fbcc594fd6 100644 --- a/crates/rnote-engine/src/widgetflags.rs +++ b/crates/rnote-engine/src/widgetflags.rs @@ -26,6 +26,10 @@ pub struct WidgetFlags { /// Meaning, when enabled instead of key events, text events are then emitted /// for regular unicode text. Used when writing text with the typewriter. pub enable_text_preprocessing: Option, + /// long press event to take into account + /// This triggers actions on the engine further up (additional events) + /// to take it into consideration + pub long_hold: bool, } impl Default for WidgetFlags { @@ -42,6 +46,7 @@ impl Default for WidgetFlags { hide_undo: None, hide_redo: None, enable_text_preprocessing: None, + long_hold: false, } } } @@ -74,5 +79,6 @@ impl std::ops::BitOrAssign for WidgetFlags { if rhs.enable_text_preprocessing.is_some() { self.enable_text_preprocessing = rhs.enable_text_preprocessing; } + self.long_hold |= rhs.long_hold; } } From 5d93e11e5825e4f28b9abdf4a9a94f6dbdb559b7 Mon Sep 17 00:00:00 2001 From: Doublonmousse <115779707+Doublonmousse@users.noreply.github.com> Date: Mon, 5 Aug 2024 14:23:14 +0200 Subject: [PATCH 2/6] engine task for static mouse : wip --- crates/rnote-engine/src/engine/mod.rs | 35 ++++++++- crates/rnote-engine/src/pens/brush.rs | 94 +++++++++++++++-------- crates/rnote-engine/src/pens/penholder.rs | 1 - 3 files changed, 93 insertions(+), 37 deletions(-) diff --git a/crates/rnote-engine/src/engine/mod.rs b/crates/rnote-engine/src/engine/mod.rs index 3a98bc598a..99fc8bc280 100644 --- a/crates/rnote-engine/src/engine/mod.rs +++ b/crates/rnote-engine/src/engine/mod.rs @@ -11,12 +11,13 @@ pub use export::ExportPrefs; use futures::channel::mpsc::UnboundedReceiver; use futures::StreamExt; pub use import::ImportPrefs; +use rnote_compose::penpath::Element; pub use snapshot::EngineSnapshot; pub use strokecontent::StrokeContent; // Imports use crate::document::Layout; -use crate::pens::{Pen, PenStyle}; +use crate::pens::{Pen, PenBehaviour, PenStyle}; use crate::pens::{PenMode, PensConfig}; use crate::store::render_comp::{self, RenderCompState}; use crate::store::StrokeKey; @@ -31,6 +32,7 @@ use rnote_compose::ext::AabbExt; use rnote_compose::penevent::{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; @@ -455,6 +457,34 @@ impl Engine { widget_flags.redraw = true; } } + EngineTask::LongPressStatic => { + println!("long press event"); + // for now just detect is good enough + // we should sent an event (what event ?) + // the idea is that we need to send events to this task + // hence we reply with the lastest such event + // with a wf set to long_hold true as well + // hence for a mouse that's not moving, we re send an event at the same location + // not implemented + + // for now we have a + // 3.110231056s ERROR rnote_engine::tasks: Could not quit periodic task while handle is being dropped, Err: Sending `Quit` message to periodic task failed. + // from waiting for the pen event to finish ? + + let (_, wf) = self.handle_pen_event( + PenEvent::Down { + element: Element { + pos: na::Vector2::new(1.0, 1.0), //for now this is a dummy + pressure: 1.0, + }, + modifier_keys: HashSet::new(), + }, + None, + Instant::now(), + ); + widget_flags |= wf; + widget_flags.long_hold = true; + } EngineTask::Zoom(zoom) => { widget_flags |= self.camera.zoom_temporarily_to(1.0) | self.camera.zoom_to(zoom); @@ -468,9 +498,6 @@ impl Engine { widget_flags |= self.set_active(false); quit = true; } - EngineTask::LongPressStatic => { - todo!() - } } (widget_flags, quit) diff --git a/crates/rnote-engine/src/pens/brush.rs b/crates/rnote-engine/src/pens/brush.rs index 00231b6a91..6d51ffd2ca 100644 --- a/crates/rnote-engine/src/pens/brush.rs +++ b/crates/rnote-engine/src/pens/brush.rs @@ -2,6 +2,7 @@ use super::pensconfig::brushconfig::BrushStyle; use super::PenBehaviour; use super::PenStyle; +use crate::engine::EngineTask; use crate::engine::{EngineView, EngineViewMut}; use crate::store::StrokeKey; use crate::strokes::BrushStroke; @@ -18,6 +19,7 @@ use rnote_compose::penevent::{PenEvent, PenProgress}; use rnote_compose::penpath::{Element, Segment}; use rnote_compose::Constraints; use rnote_compose::PenPath; +use std::time::Duration; use std::time::Instant; #[derive(Debug)] @@ -35,32 +37,35 @@ pub struct Brush { /// if we have a long hold of the pen, we save the /// penpath for recognition one level upper pub pen_path_recognition: Option, - // maybe we can do that as a speed based detection - // if the speed is lower than ... in the timeout - // calculate here the state - // for a pen that's down and not moving - // vecdecque of the last position + distance to the previous element - // for all events inside the timeout - // and a distance on the side + longpress_handle: Option, - // then test inside handle event to trigger the long press + // dumb thing : take the start time of the stroke and transform to a line after 1 second + pub time_start: Option, // maybe we can do that as a speed based detection + // if the speed is lower than ... in the timeout + // calculate here the state + // for a pen that's down and not moving + // vecdecque of the last position + distance to the previous element + // for all events inside the timeout + // and a distance on the side - // but we still need the task if we hold the pen down - // So what we need to do is to have a periodic task - // where we send times of relevant pen events + // then test inside handle event to trigger the long press - // when read, it sleeps until the time + timeout is done - // then tests if the channel is empty - // if it is, then calls the task (and the "does the task need to happen" test is done) - // if not, reads the next messages + // but we still need the task if we hold the pen down + // So what we need to do is to have a periodic task + // where we send times of relevant pen events - // we need to keep the state of whether a long hold has occured or not as well - // to not trigger the same code twice + // when read, it sleeps until the time + timeout is done + // then tests if the channel is empty + // if it is, then calls the task (and the "does the task need to happen" test is done) + // if not, reads the next messages - // we'll start with the internal state bookkeeping then try the other part after + // we need to keep the state of whether a long hold has occured or not as well + // to not trigger the same code twice - // another thing is to not trigger this too soon ? for a pen stuck on the start position - // maybe not good ? + // we'll start with the internal state bookkeeping then try the other part after + + // another thing is to not trigger this too soon ? for a pen stuck on the start position + // maybe not good ? } impl Default for Brush { @@ -68,6 +73,8 @@ impl Default for Brush { Self { state: BrushState::Idle, pen_path_recognition: None, + time_start: None, + longpress_handle: None, } } } @@ -154,6 +161,16 @@ impl PenBehaviour for Brush { ), current_stroke_key, }; + self.time_start = Some(now); + let tasks_tx = engine_view.tasks_tx.clone(); + let longpress_reminder = move || -> crate::tasks::PeriodicTaskResult { + tasks_tx.send(EngineTask::LongPressStatic); + crate::tasks::PeriodicTaskResult::Quit + }; + self.longpress_handle = Some(crate::tasks::PeriodicTaskHandle::new( + longpress_reminder, + Duration::from_secs(1), + )); EventResult { handled: true, @@ -194,6 +211,7 @@ impl PenBehaviour for Brush { .resize_autoexpand(engine_view.store, engine_view.camera); self.state = BrushState::Idle; + self.time_start = None; widget_flags |= engine_view.store.record(Instant::now()); widget_flags.store_modified = true; @@ -248,6 +266,29 @@ impl PenBehaviour for Brush { ); } + // then test + let delta = now - self.time_start.unwrap(); + if delta > Duration::from_secs(1) { + //triger the actual change + // we need to save the stroke data before deleting it + println!("saving the current stroke data"); + if let Some(Stroke::BrushStroke(brushstroke)) = + engine_view.store.get_stroke_ref(*current_stroke_key) + { + let path = brushstroke.path.clone(); + println!("the path is {:?}", path); + + // save to a location + self.pen_path_recognition = Some(path); + } + println!("cancelled stroke"); + engine_view.store.remove_stroke(*current_stroke_key); + widget_flags.long_hold = true; + + // we probably need to set to idle our brush stroke then + // self.state = BrushState::Idle; + } + // is inprogress an issue ? PenProgress::InProgress } BuilderProgress::Finished(segments) => { @@ -270,17 +311,8 @@ impl PenBehaviour for Brush { ); } - // we have to see if we can cancel the stroke here - // and then change the type to be a line with the start position - // and end position saved - // so with a change of tools - - // this will have to move a little (happens potentially in the other branch) - - // cancel the stroke (if we have a longpress) - // the normal way this would happen would be at the previous step : for an in progress penprogress - if true { + if false { // we need to save the stroke data before deleting it println!("saving the current stroke data"); if let Some(Stroke::BrushStroke(brushstroke)) = @@ -317,8 +349,6 @@ impl PenBehaviour for Brush { widget_flags |= engine_view.store.record(Instant::now()); widget_flags.store_modified = true; - println!("pen : {:?}", self); //brush reset ? - PenProgress::Finished } }; diff --git a/crates/rnote-engine/src/pens/penholder.rs b/crates/rnote-engine/src/pens/penholder.rs index 7c3e494d5f..83ff5526e7 100644 --- a/crates/rnote-engine/src/pens/penholder.rs +++ b/crates/rnote-engine/src/pens/penholder.rs @@ -16,7 +16,6 @@ use piet::RenderContext; use rnote_compose::builders::ShapeBuilderType; use rnote_compose::eventresult::EventPropagation; use rnote_compose::penevent::{KeyboardKey, ModifierKey, PenEvent, PenProgress, ShortcutKey}; -use rnote_compose::penpath::Element; use serde::{Deserialize, Serialize}; use std::collections::HashSet; use std::time::{Duration, Instant}; From aa80081f1e79377018171f0fb74e6a74eae2daa5 Mon Sep 17 00:00:00 2001 From: Doublonmousse <115779707+Doublonmousse@users.noreply.github.com> Date: Mon, 5 Aug 2024 15:36:01 +0200 Subject: [PATCH 3/6] fix error and API change Note : the task HAS to exist until the pen is changed to another tool. This is the condition for stopping to check the condition. This means that for each long hold, a test is made to see if we can recognize a shape (or more). If not, then we should reset both methods : start again from scratch on the long hold by event and same thing for the task handle --- crates/rnote-engine/src/engine/mod.rs | 2 +- crates/rnote-engine/src/pens/brush.rs | 17 +++-- crates/rnote-engine/src/pens/penholder.rs | 93 ++++++++++++++--------- 3 files changed, 70 insertions(+), 42 deletions(-) diff --git a/crates/rnote-engine/src/engine/mod.rs b/crates/rnote-engine/src/engine/mod.rs index 99fc8bc280..86f62986e3 100644 --- a/crates/rnote-engine/src/engine/mod.rs +++ b/crates/rnote-engine/src/engine/mod.rs @@ -17,7 +17,7 @@ pub use strokecontent::StrokeContent; // Imports use crate::document::Layout; -use crate::pens::{Pen, PenBehaviour, PenStyle}; +use crate::pens::{Pen, PenStyle}; use crate::pens::{PenMode, PensConfig}; use crate::store::render_comp::{self, RenderCompState}; use crate::store::StrokeKey; diff --git a/crates/rnote-engine/src/pens/brush.rs b/crates/rnote-engine/src/pens/brush.rs index 6d51ffd2ca..46af468018 100644 --- a/crates/rnote-engine/src/pens/brush.rs +++ b/crates/rnote-engine/src/pens/brush.rs @@ -38,6 +38,7 @@ pub struct Brush { /// penpath for recognition one level upper pub pen_path_recognition: Option, longpress_handle: Option, + pub current_stroke_key: Option, // dumb thing : take the start time of the stroke and transform to a line after 1 second pub time_start: Option, // maybe we can do that as a speed based detection @@ -74,6 +75,7 @@ impl Default for Brush { state: BrushState::Idle, pen_path_recognition: None, time_start: None, + current_stroke_key: None, longpress_handle: None, } } @@ -85,6 +87,7 @@ impl PenBehaviour for Brush { } fn deinit(&mut self) -> WidgetFlags { + self.longpress_handle = None; WidgetFlags::default() } @@ -165,11 +168,11 @@ impl PenBehaviour for Brush { let tasks_tx = engine_view.tasks_tx.clone(); let longpress_reminder = move || -> crate::tasks::PeriodicTaskResult { tasks_tx.send(EngineTask::LongPressStatic); - crate::tasks::PeriodicTaskResult::Quit + crate::tasks::PeriodicTaskResult::Continue }; self.longpress_handle = Some(crate::tasks::PeriodicTaskHandle::new( longpress_reminder, - Duration::from_secs(1), + Duration::from_secs(2), )); EventResult { @@ -282,13 +285,13 @@ impl PenBehaviour for Brush { self.pen_path_recognition = Some(path); } println!("cancelled stroke"); - engine_view.store.remove_stroke(*current_stroke_key); + // this HAS to happen AFTER the recognition is done and successful + // dummy test : do this half the time + self.current_stroke_key = Some(current_stroke_key.clone()); + // can't do that here : need to do this two steps higher + //engine_view.store.remove_stroke(*current_stroke_key); widget_flags.long_hold = true; - - // we probably need to set to idle our brush stroke then - // self.state = BrushState::Idle; } - // is inprogress an issue ? PenProgress::InProgress } BuilderProgress::Finished(segments) => { diff --git a/crates/rnote-engine/src/pens/penholder.rs b/crates/rnote-engine/src/pens/penholder.rs index 83ff5526e7..60aa070dfd 100644 --- a/crates/rnote-engine/src/pens/penholder.rs +++ b/crates/rnote-engine/src/pens/penholder.rs @@ -13,6 +13,7 @@ use crate::{CloneConfig, DrawableOnDoc}; use futures::channel::oneshot; use p2d::bounding_volume::Aabb; use piet::RenderContext; +use rand::Rng; use rnote_compose::builders::ShapeBuilderType; use rnote_compose::eventresult::EventPropagation; use rnote_compose::penevent::{KeyboardKey, ModifierKey, PenEvent, PenProgress, ShortcutKey}; @@ -267,6 +268,19 @@ impl PenHolder { //if cancel the pen is reinitialized ! // could be done further done like the rest + //let's get our stroke key here as well + // should only trigger if wf.long_hold TODO + let stroke_key = match &self.current_pen { + Pen::Brush(brush) => { + let stroke_key_opt = brush.current_stroke_key; + match stroke_key_opt { + Some(stroke) => Some(stroke.clone()), + _ => None, + } + } + _ => None, + }; + widget_flags |= wf | self.handle_pen_progress(event_result.progress, engine_view); if !event_result.handled { @@ -282,44 +296,55 @@ impl PenHolder { // we need to retrieve the full shape here // we thus recuperate the data here, saved inside an optional part of the pen struct println!("the path is {:?}", path); - println!("long hold for the pen, sending new events"); - // change the type to line first - engine_view.pens_config.shaper_config.builder_type = ShapeBuilderType::Line; - // switch to the shaper tool but as an override (temporary) - widget_flags |= self.change_style_override(Some(PenStyle::Shaper), engine_view); + // random chance between recognition and not (to replace with a real recognizer) + let mut rng = rand::thread_rng(); + let value = rng.gen_bool(0.5); + if value { + println!("recognition successful"); + + // cancel the stroke + engine_view.store.remove_stroke(stroke_key.unwrap()); + + // change the type to line first + engine_view.pens_config.shaper_config.builder_type = ShapeBuilderType::Line; - // update the state of the shaper to be in building - match &self.current_pen { - Pen::Shaper(shaper) => { - println!("hello {:?}", shaper); + // switch to the shaper tool but as an override (temporary) + widget_flags |= self.change_style_override(Some(PenStyle::Shaper), engine_view); + + // update the state of the shaper to be in building + match &self.current_pen { + Pen::Shaper(shaper) => { + println!("hello {:?}", shaper); + } + _ => println!("error"), } - _ => println!("error"), + // then add two new events + // or edit directly the shaper ? + // first event is the original position (to get back up from somewhere ...) + let (_, wf) = self.current_pen.handle_event( + PenEvent::Down { + element: path.as_ref().unwrap().start, + modifier_keys: HashSet::new(), + }, + now, + engine_view, + ); + widget_flags |= wf; + // second event is the last position + let (_, wf) = self.current_pen.handle_event( + PenEvent::Down { + element: path.unwrap().segments.last().unwrap().end(), + modifier_keys: HashSet::new(), + }, + now, + engine_view, + ); + widget_flags |= wf; + // maybe try with the other method : modify the thing manually + } else { + println!("recognition failed"); } - // then add two new events - // or edit directly the shaper ? - // first event is the original position (to get back up from somewhere ...) - let (_, wf) = self.current_pen.handle_event( - PenEvent::Down { - element: path.as_ref().unwrap().start, - modifier_keys: HashSet::new(), - }, - now, - engine_view, - ); - widget_flags |= wf; - // second event is the last position - let (_, wf) = self.current_pen.handle_event( - PenEvent::Down { - element: path.unwrap().segments.last().unwrap().end(), - modifier_keys: HashSet::new(), - }, - now, - engine_view, - ); - widget_flags |= wf; - // maybe try with the other method : modify the thing manually - // also need to be a temporary pen and have it cancelled back to a pen afterward } // Always redraw after handling a pen event From f22dbdc724cea3bfd02532db20ecffc8cf1a4b64 Mon Sep 17 00:00:00 2001 From: Doublonmousse <115779707+Doublonmousse@users.noreply.github.com> Date: Wed, 7 Aug 2024 22:42:41 +0200 Subject: [PATCH 4/6] reset if the recognition isn't successful and notes (wip) --- crates/rnote-engine/src/engine/mod.rs | 32 +++++++++++------------ crates/rnote-engine/src/pens/brush.rs | 6 +++++ crates/rnote-engine/src/pens/mod.rs | 10 +++++++ crates/rnote-engine/src/pens/penholder.rs | 14 +++++++--- 4 files changed, 42 insertions(+), 20 deletions(-) diff --git a/crates/rnote-engine/src/engine/mod.rs b/crates/rnote-engine/src/engine/mod.rs index 86f62986e3..104674c3b0 100644 --- a/crates/rnote-engine/src/engine/mod.rs +++ b/crates/rnote-engine/src/engine/mod.rs @@ -467,23 +467,21 @@ impl Engine { // hence for a mouse that's not moving, we re send an event at the same location // not implemented - // for now we have a - // 3.110231056s ERROR rnote_engine::tasks: Could not quit periodic task while handle is being dropped, Err: Sending `Quit` message to periodic task failed. - // from waiting for the pen event to finish ? - - let (_, wf) = self.handle_pen_event( - PenEvent::Down { - element: Element { - pos: na::Vector2::new(1.0, 1.0), //for now this is a dummy - pressure: 1.0, - }, - modifier_keys: HashSet::new(), - }, - None, - Instant::now(), - ); - widget_flags |= wf; - widget_flags.long_hold = true; + // + + // let (_, wf) = self.handle_pen_event( + // PenEvent::Down { + // element: Element { + // pos: na::Vector2::new(1.0, 1.0), //for now this is a dummy + // pressure: 1.0, + // }, + // modifier_keys: HashSet::new(), + // }, + // None, + // Instant::now(), + // ); + // widget_flags |= wf; + // widget_flags.long_hold = true; } EngineTask::Zoom(zoom) => { widget_flags |= self.camera.zoom_temporarily_to(1.0) | self.camera.zoom_to(zoom); diff --git a/crates/rnote-engine/src/pens/brush.rs b/crates/rnote-engine/src/pens/brush.rs index 46af468018..8c5e5e15a5 100644 --- a/crates/rnote-engine/src/pens/brush.rs +++ b/crates/rnote-engine/src/pens/brush.rs @@ -415,6 +415,12 @@ impl DrawableOnDoc for Brush { impl Brush { const INPUT_OVERSHOOT: f64 = 30.0; + + // reset the long press + pub fn reset_long_press(&mut self) { + self.time_start = Some(Instant::now()); + // this resets the time to the current one (dummy test) + } } fn play_marker_sound(engine_view: &mut EngineViewMut) { diff --git a/crates/rnote-engine/src/pens/mod.rs b/crates/rnote-engine/src/pens/mod.rs index 0c3c677abf..3d5e6aecc3 100644 --- a/crates/rnote-engine/src/pens/mod.rs +++ b/crates/rnote-engine/src/pens/mod.rs @@ -52,6 +52,16 @@ impl Default for Pen { } } +impl Pen { + // need an intermediary + pub fn reset_long_press(&mut self) { + match self { + Pen::Brush(brush) => brush.reset_long_press(), + _ => panic!("can't reset a not brush"), + } + } +} + impl PenBehaviour for Pen { fn init(&mut self, engine_view: &EngineView) -> WidgetFlags { match self { diff --git a/crates/rnote-engine/src/pens/penholder.rs b/crates/rnote-engine/src/pens/penholder.rs index 60aa070dfd..481c9fd6a4 100644 --- a/crates/rnote-engine/src/pens/penholder.rs +++ b/crates/rnote-engine/src/pens/penholder.rs @@ -298,9 +298,7 @@ impl PenHolder { println!("the path is {:?}", path); // random chance between recognition and not (to replace with a real recognizer) - let mut rng = rand::thread_rng(); - let value = rng.gen_bool(0.5); - if value { + if false { println!("recognition successful"); // cancel the stroke @@ -321,6 +319,11 @@ impl PenHolder { } // then add two new events // or edit directly the shaper ? + // we probably will have to do custom methods + // one thing that's lacking here is the stroke width that's not set + // but we shouldn't make the settings for the shape tool change for a stroke + // recognition + // first event is the original position (to get back up from somewhere ...) let (_, wf) = self.current_pen.handle_event( PenEvent::Down { @@ -344,6 +347,11 @@ impl PenHolder { // maybe try with the other method : modify the thing manually } else { println!("recognition failed"); + // here we have to reset both the status for the long press recogniser that is done by event + // and the one on the dedicated thread + self.current_pen.reset_long_press(); + + // ideally we could keep the same vec for the path and extend it with only new elements if this happens multiple times } } From c3cb4e17905a3b2abd3fe9ad511c34ea58f3f9b1 Mon Sep 17 00:00:00 2001 From: Doublonmousse <115779707+Doublonmousse@users.noreply.github.com> Date: Fri, 9 Aug 2024 17:47:55 +0200 Subject: [PATCH 5/6] long press detector (both mouse and pen) Still some rough parts to go over (in particular for error handling and potential unwraps) but the API should be pretty stable --- crates/rnote-engine/src/engine/mod.rs | 46 +-- crates/rnote-engine/src/pens/brush.rs | 287 ++++++++++++------ crates/rnote-engine/src/pens/mod.rs | 10 +- crates/rnote-engine/src/pens/penholder.rs | 13 +- .../src/pens/pensconfig/brushconfig.rs | 8 + 5 files changed, 244 insertions(+), 120 deletions(-) diff --git a/crates/rnote-engine/src/engine/mod.rs b/crates/rnote-engine/src/engine/mod.rs index 104674c3b0..59216abe8d 100644 --- a/crates/rnote-engine/src/engine/mod.rs +++ b/crates/rnote-engine/src/engine/mod.rs @@ -459,29 +459,29 @@ impl Engine { } EngineTask::LongPressStatic => { println!("long press event"); - // for now just detect is good enough - // we should sent an event (what event ?) - // the idea is that we need to send events to this task - // hence we reply with the lastest such event - // with a wf set to long_hold true as well - // hence for a mouse that's not moving, we re send an event at the same location - // not implemented - - // - - // let (_, wf) = self.handle_pen_event( - // PenEvent::Down { - // element: Element { - // pos: na::Vector2::new(1.0, 1.0), //for now this is a dummy - // pressure: 1.0, - // }, - // modifier_keys: HashSet::new(), - // }, - // None, - // Instant::now(), - // ); - // widget_flags |= wf; - // widget_flags.long_hold = true; + + // TODO + // re send the latest event + // this event will be part of the vecdeques of the brush + // this decque can't be empty ! + let position = match self.penholder.current_pen_mut() { + Pen::Brush(brush) => brush.long_press_detector.get_latest_pos(), + _ => na::Vector2::new(1.0, 1.0), + }; + + let (_, wf) = self.handle_pen_event( + PenEvent::Down { + element: Element { + pos: position, + pressure: 1.0, + }, + modifier_keys: HashSet::new(), + }, + None, + Instant::now(), + ); + widget_flags |= wf; + widget_flags.long_hold = true; } EngineTask::Zoom(zoom) => { widget_flags |= self.camera.zoom_temporarily_to(1.0) | self.camera.zoom_to(zoom); diff --git a/crates/rnote-engine/src/pens/brush.rs b/crates/rnote-engine/src/pens/brush.rs index 8c5e5e15a5..08ec2ea853 100644 --- a/crates/rnote-engine/src/pens/brush.rs +++ b/crates/rnote-engine/src/pens/brush.rs @@ -19,9 +19,27 @@ use rnote_compose::penevent::{PenEvent, PenProgress}; use rnote_compose::penpath::{Element, Segment}; use rnote_compose::Constraints; use rnote_compose::PenPath; +use std::collections::VecDeque; use std::time::Duration; use std::time::Instant; +#[derive(Debug, Copy, Clone)] +pub struct PosTimeDict { + pub pos: na::Vector2, + distance_to_previous: f64, + time: Instant, +} + +impl Default for PosTimeDict { + fn default() -> Self { + Self { + pos: na::Vector2::new(0.0, 0.0), + distance_to_previous: 0.0, + time: Instant::now(), + } + } +} + #[derive(Debug)] enum BrushState { Idle, @@ -31,42 +49,87 @@ enum BrushState { }, } -#[derive(Debug)] -pub struct Brush { - state: BrushState, - /// if we have a long hold of the pen, we save the - /// penpath for recognition one level upper - pub pen_path_recognition: Option, - longpress_handle: Option, - pub current_stroke_key: Option, +#[derive(Debug, Default)] +pub struct LongPressDetector { + distance: f64, + total_distance: f64, + pub last_strokes: VecDeque, +} - // dumb thing : take the start time of the stroke and transform to a line after 1 second - pub time_start: Option, // maybe we can do that as a speed based detection - // if the speed is lower than ... in the timeout - // calculate here the state - // for a pen that's down and not moving - // vecdecque of the last position + distance to the previous element - // for all events inside the timeout - // and a distance on the side +impl LongPressDetector { + fn clear(&mut self) { + self.last_strokes.clear(); + } - // then test inside handle event to trigger the long press + fn total_distance(&self) -> f64 { + self.total_distance + } - // but we still need the task if we hold the pen down - // So what we need to do is to have a periodic task - // where we send times of relevant pen events + fn distance(&self) -> f64 { + self.distance + } - // when read, it sleeps until the time + timeout is done - // then tests if the channel is empty - // if it is, then calls the task (and the "does the task need to happen" test is done) - // if not, reads the next messages + fn reset(&mut self, element: Element, now: Instant) { + self.clear(); + self.last_strokes.push_front(PosTimeDict { + pos: element.pos, + distance_to_previous: 0.0, + time: now, + }); + self.distance = 0.0; + self.total_distance = 0.0; + } - // we need to keep the state of whether a long hold has occured or not as well - // to not trigger the same code twice + fn add_event(&mut self, element: Element, now: Instant) { + // add event to the front of the vecdeque + let latest_pos = self.last_strokes.front().unwrap().pos; + let dist_delta = latest_pos.metric_distance(&element.pos); + + self.last_strokes.push_front(PosTimeDict { + pos: element.pos, + distance_to_previous: dist_delta, + time: now, + }); + self.distance += dist_delta; + + println!("adding {:?}", dist_delta); + + self.total_distance += dist_delta; + + while self.last_strokes.back().is_some() + && self.last_strokes.back().unwrap().time + < now - Duration::from_secs_f64(Brush::LONGPRESS_TIMEOUT) + { + // remove the last element + let back_element = self.last_strokes.pop_back().unwrap(); + self.distance -= back_element.distance_to_previous; + println!("removing {:?}", back_element.distance_to_previous); + } + // println!("last stroke vecdeque {:?}", self.last_strokes); + } - // we'll start with the internal state bookkeeping then try the other part after + pub fn get_latest_pos(&self) -> na::Vector2 { + self.last_strokes.front().unwrap().pos + } +} - // another thing is to not trigger this too soon ? for a pen stuck on the start position - // maybe not good ? +#[derive(Debug)] +pub struct Brush { + state: BrushState, + /// save the current path for recognition one level upper + pub pen_path_recognition: Option, + /// handle for the separate task that makes it possible to + /// trigger long press for input with no jitter (where a long press + /// hold wouldn't trigger any new event) + longpress_handle: Option, + /// stroke key in progress when a long press occurs + pub current_stroke_key: Option, + /// save the start position for the current stroke + /// This prevents long press from happening on a point + /// We create a deadzone around the start position + pub start_position: Option, + pub stroke_width: Option, + pub long_press_detector: LongPressDetector, } impl Default for Brush { @@ -74,9 +137,11 @@ impl Default for Brush { Self { state: BrushState::Idle, pen_path_recognition: None, - time_start: None, current_stroke_key: None, longpress_handle: None, + start_position: None, + stroke_width: None, + long_press_detector: LongPressDetector::default(), } } } @@ -107,16 +172,6 @@ impl PenBehaviour for Brush { ) -> (EventResult, WidgetFlags) { let mut widget_flags = WidgetFlags::default(); - // we should have a special task on a separate thread that sends event - // with the channel the strategy is the following - // - CANCEL - // the state is idle - // pen cancel event - // - if we start with drawing and the pen is down, start signal - // - as long as we stay in drawing mode : - // - send pen down events - // - - let event_result = match (&mut self.state, event) { (BrushState::Idle, PenEvent::Down { element, .. }) => { if !element.filter_by_bounds( @@ -150,6 +205,9 @@ impl PenBehaviour for Brush { ), ); + self.stroke_width = + Some(engine_view.pens_config.brush_config.get_stroke_width()); + engine_view.store.regenerate_rendering_for_stroke( current_stroke_key, engine_view.camera.viewport(), @@ -164,16 +222,18 @@ impl PenBehaviour for Brush { ), current_stroke_key, }; - self.time_start = Some(now); + + self.start_position = Some(PosTimeDict { + pos: element.pos, + distance_to_previous: 0.0, + time: now, + }); let tasks_tx = engine_view.tasks_tx.clone(); - let longpress_reminder = move || -> crate::tasks::PeriodicTaskResult { - tasks_tx.send(EngineTask::LongPressStatic); - crate::tasks::PeriodicTaskResult::Continue - }; - self.longpress_handle = Some(crate::tasks::PeriodicTaskHandle::new( - longpress_reminder, - Duration::from_secs(2), + self.longpress_handle = Some(crate::tasks::OneOffTaskHandle::new( + move || tasks_tx.send(EngineTask::LongPressStatic), + Duration::from_secs_f64(Self::LONGPRESS_TIMEOUT), )); + self.long_press_detector.reset(element, now); EventResult { handled: true, @@ -214,7 +274,11 @@ impl PenBehaviour for Brush { .resize_autoexpand(engine_view.store, engine_view.camera); self.state = BrushState::Idle; - self.time_start = None; + self.current_stroke_key = None; + self.pen_path_recognition = None; + self.start_position = None; + self.long_press_detector.clear(); + self.cancel_handle_long_press(); widget_flags |= engine_view.store.record(Instant::now()); widget_flags.store_modified = true; @@ -233,7 +297,7 @@ impl PenBehaviour for Brush { pen_event, ) => { let builder_result = - path_builder.handle_event(pen_event, now, Constraints::default()); + path_builder.handle_event(pen_event.clone(), now, Constraints::default()); let handled = builder_result.handled; let propagate = builder_result.propagate; @@ -269,28 +333,83 @@ impl PenBehaviour for Brush { ); } - // then test - let delta = now - self.time_start.unwrap(); - if delta > Duration::from_secs(1) { - //triger the actual change - // we need to save the stroke data before deleting it + // first send the event: + if let Some(handle) = self.longpress_handle.as_mut() { + let _ = handle.reset_timeout(); + // we may have asusmed wrongly the type of pen event ? + // could be a key press that's ignored ? KeyPressed + // or Text ? + match pen_event { + PenEvent::Down { element, .. } => { + self.long_press_detector.add_event(element, now) + } + _ => (), + } + } else { + // recreate the handle if it was dropped + // this happens when we sent a long_hold event and cancelled the long + // press. + // We have to restart he handle and the long press detector + let tasks_tx = engine_view.tasks_tx.clone(); + self.longpress_handle = Some(crate::tasks::OneOffTaskHandle::new( + move || tasks_tx.send(EngineTask::LongPressStatic), + Duration::from_secs_f64(Self::LONGPRESS_TIMEOUT), + )); + + match pen_event { + PenEvent::Down { element, .. } => { + self.start_position = Some(PosTimeDict { + pos: element.pos, + distance_to_previous: 0.0, + time: now, + }); + self.current_stroke_key = None; + self.pen_path_recognition = None; + + self.long_press_detector.reset(element, now); + } + _ => { + // we are drawing only if the pen is down... + } + } + } + // then test : long press ? + let is_deadzone = self.long_press_detector.total_distance() + > 4.0 * self.stroke_width.unwrap_or(0.5); + let is_static = + self.long_press_detector.distance() < 4.0 * self.stroke_width.unwrap(); + let time_delta = + now - self.start_position.unwrap_or(PosTimeDict::default()).time; + + println!("static distance {:?}", self.long_press_detector.distance()); + println!( + "deadzone : {:?}, static {:?}, {:?}", + is_deadzone, is_static, time_delta + ); + + if time_delta > Duration::from_secs_f64(Self::LONGPRESS_TIMEOUT) + && is_static + && is_deadzone + { + // save the current stroke for recognition println!("saving the current stroke data"); + + //save the key for potentially deleting it and replacing it with a shape + self.current_stroke_key = Some(current_stroke_key.clone()); + + // save the current stroke for recognition if let Some(Stroke::BrushStroke(brushstroke)) = engine_view.store.get_stroke_ref(*current_stroke_key) { let path = brushstroke.path.clone(); - println!("the path is {:?}", path); - - // save to a location self.pen_path_recognition = Some(path); } - println!("cancelled stroke"); - // this HAS to happen AFTER the recognition is done and successful - // dummy test : do this half the time - self.current_stroke_key = Some(current_stroke_key.clone()); - // can't do that here : need to do this two steps higher - //engine_view.store.remove_stroke(*current_stroke_key); + widget_flags.long_hold = true; + + // quit the handle. Either recognition is successful and we are right + // or we aren't and a new handle will be create on the next event + self.cancel_handle_long_press(); } PenProgress::InProgress } @@ -314,25 +433,6 @@ impl PenBehaviour for Brush { ); } - // the normal way this would happen would be at the previous step : for an in progress penprogress - if false { - // we need to save the stroke data before deleting it - println!("saving the current stroke data"); - if let Some(Stroke::BrushStroke(brushstroke)) = - engine_view.store.get_stroke_ref(*current_stroke_key) - { - let path = brushstroke.path.clone(); - println!("the path is {:?}", path); - - // save to a location - self.pen_path_recognition = Some(path); - } - println!("cancelled stroke"); - engine_view.store.remove_stroke(*current_stroke_key); - widget_flags.long_hold = true; - } - // change to a shaper : need to use the widget flags higher up - // Finish up the last stroke engine_view .store @@ -348,6 +448,7 @@ impl PenBehaviour for Brush { .resize_autoexpand(engine_view.store, engine_view.camera); self.state = BrushState::Idle; + self.cancel_handle_long_press(); widget_flags |= engine_view.store.record(Instant::now()); widget_flags.store_modified = true; @@ -415,11 +516,23 @@ impl DrawableOnDoc for Brush { impl Brush { const INPUT_OVERSHOOT: f64 = 30.0; + const LONGPRESS_TIMEOUT: f64 = 0.5; - // reset the long press - pub fn reset_long_press(&mut self) { - self.time_start = Some(Instant::now()); - // this resets the time to the current one (dummy test) + pub fn cancel_handle_long_press(&mut self) { + // cancel the long press handle + if let Some(handle) = self.longpress_handle.as_mut() { + let _ = handle.quit(); + } + self.longpress_handle = None; + } + + pub fn reset_long_press(&mut self, element: Element, now: Instant) { + self.start_position = None; + self.current_stroke_key = None; + self.cancel_handle_long_press(); + self.longpress_handle = None; + self.stroke_width = None; + self.long_press_detector.reset(element, now); } } diff --git a/crates/rnote-engine/src/pens/mod.rs b/crates/rnote-engine/src/pens/mod.rs index 3d5e6aecc3..77b517259c 100644 --- a/crates/rnote-engine/src/pens/mod.rs +++ b/crates/rnote-engine/src/pens/mod.rs @@ -18,6 +18,7 @@ pub use penbehaviour::PenBehaviour; pub use penholder::PenHolder; pub use penmode::PenMode; pub use pensconfig::PensConfig; +use rnote_compose::penpath::Element; pub use selector::Selector; pub use shaper::Shaper; pub use shortcuts::Shortcuts; @@ -53,11 +54,12 @@ impl Default for Pen { } impl Pen { - // need an intermediary - pub fn reset_long_press(&mut self) { + // intermediary function + // result/error propagation ? + pub fn reset_long_press(&mut self, element: Element, now: Instant) { match self { - Pen::Brush(brush) => brush.reset_long_press(), - _ => panic!("can't reset a not brush"), + Pen::Brush(brush) => brush.reset_long_press(element, now), + _ => panic!("can't reset a pen that's not a brush"), } } } diff --git a/crates/rnote-engine/src/pens/penholder.rs b/crates/rnote-engine/src/pens/penholder.rs index 481c9fd6a4..d1ac50273c 100644 --- a/crates/rnote-engine/src/pens/penholder.rs +++ b/crates/rnote-engine/src/pens/penholder.rs @@ -13,7 +13,6 @@ use crate::{CloneConfig, DrawableOnDoc}; use futures::channel::oneshot; use p2d::bounding_volume::Aabb; use piet::RenderContext; -use rand::Rng; use rnote_compose::builders::ShapeBuilderType; use rnote_compose::eventresult::EventPropagation; use rnote_compose::penevent::{KeyboardKey, ModifierKey, PenEvent, PenProgress, ShortcutKey}; @@ -264,9 +263,6 @@ impl PenHolder { } false => None, }; - //normally this wouldn't be the case as we are doing this on a cancel which is not the real thing ! - //if cancel the pen is reinitialized ! - // could be done further done like the rest //let's get our stroke key here as well // should only trigger if wf.long_hold TODO @@ -298,7 +294,7 @@ impl PenHolder { println!("the path is {:?}", path); // random chance between recognition and not (to replace with a real recognizer) - if false { + if true { println!("recognition successful"); // cancel the stroke @@ -349,7 +345,12 @@ impl PenHolder { println!("recognition failed"); // here we have to reset both the status for the long press recogniser that is done by event // and the one on the dedicated thread - self.current_pen.reset_long_press(); + // ISSUE HERE + self.current_pen + .reset_long_press(path.unwrap().segments.last().unwrap().end(), now); + // not too robust is it ? + // could this happen with no element ? + // ?? is this okay now ? // ideally we could keep the same vec for the path and extend it with only new elements if this happens multiple times } diff --git a/crates/rnote-engine/src/pens/pensconfig/brushconfig.rs b/crates/rnote-engine/src/pens/pensconfig/brushconfig.rs index a9e868b737..82e9ac6c45 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, + } + } } From ae940f259c5b037410eb181ad0bc22d23faaa150 Mon Sep 17 00:00:00 2001 From: Doublonmousse <115779707+Doublonmousse@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:35:46 +0200 Subject: [PATCH 6/6] =?UTF-8?q?create=20a=20`handle=5Flong=5Fpress`=C2=A0f?= =?UTF-8?q?unction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/rnote-engine/src/engine/mod.rs | 35 ++--- crates/rnote-engine/src/pens/brush.rs | 65 ++++---- crates/rnote-engine/src/pens/mod.rs | 19 ++- crates/rnote-engine/src/pens/penholder.rs | 172 ++++++++++------------ 4 files changed, 125 insertions(+), 166 deletions(-) diff --git a/crates/rnote-engine/src/engine/mod.rs b/crates/rnote-engine/src/engine/mod.rs index 59216abe8d..da22c5eb6c 100644 --- a/crates/rnote-engine/src/engine/mod.rs +++ b/crates/rnote-engine/src/engine/mod.rs @@ -11,7 +11,6 @@ pub use export::ExportPrefs; use futures::channel::mpsc::UnboundedReceiver; use futures::StreamExt; pub use import::ImportPrefs; -use rnote_compose::penpath::Element; pub use snapshot::EngineSnapshot; pub use strokecontent::StrokeContent; @@ -32,7 +31,6 @@ use rnote_compose::ext::AabbExt; use rnote_compose::penevent::{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; @@ -458,30 +456,19 @@ impl Engine { } } EngineTask::LongPressStatic => { - println!("long press event"); - - // TODO - // re send the latest event - // this event will be part of the vecdeques of the brush - // this decque can't be empty ! - let position = match self.penholder.current_pen_mut() { - Pen::Brush(brush) => brush.long_press_detector.get_latest_pos(), - _ => na::Vector2::new(1.0, 1.0), - }; - - let (_, wf) = self.handle_pen_event( - PenEvent::Down { - element: Element { - pos: position, - pressure: 1.0, - }, - modifier_keys: HashSet::new(), - }, - None, + println!("long press event, engine task"); + // when we are here, we know this is a long press event + widget_flags |= self.penholder.handle_long_press( Instant::now(), + &mut EngineViewMut { + tasks_tx: self.engine_tasks_tx(), + pens_config: &mut self.pens_config, + document: &mut self.document, + store: &mut self.store, + camera: &mut self.camera, + audioplayer: &mut self.audioplayer, + }, ); - widget_flags |= wf; - widget_flags.long_hold = true; } EngineTask::Zoom(zoom) => { widget_flags |= self.camera.zoom_temporarily_to(1.0) | self.camera.zoom_to(zoom); diff --git a/crates/rnote-engine/src/pens/brush.rs b/crates/rnote-engine/src/pens/brush.rs index 08ec2ea853..ed91d6ad0c 100644 --- a/crates/rnote-engine/src/pens/brush.rs +++ b/crates/rnote-engine/src/pens/brush.rs @@ -18,7 +18,6 @@ use rnote_compose::eventresult::{EventPropagation, EventResult}; use rnote_compose::penevent::{PenEvent, PenProgress}; use rnote_compose::penpath::{Element, Segment}; use rnote_compose::Constraints; -use rnote_compose::PenPath; use std::collections::VecDeque; use std::time::Duration; use std::time::Instant; @@ -92,7 +91,7 @@ impl LongPressDetector { }); self.distance += dist_delta; - println!("adding {:?}", dist_delta); + //println!("adding {:?}", dist_delta); self.total_distance += dist_delta; @@ -103,9 +102,8 @@ impl LongPressDetector { // remove the last element let back_element = self.last_strokes.pop_back().unwrap(); self.distance -= back_element.distance_to_previous; - println!("removing {:?}", back_element.distance_to_previous); + //println!("removing {:?}", back_element.distance_to_previous); } - // println!("last stroke vecdeque {:?}", self.last_strokes); } pub fn get_latest_pos(&self) -> na::Vector2 { @@ -116,8 +114,6 @@ impl LongPressDetector { #[derive(Debug)] pub struct Brush { state: BrushState, - /// save the current path for recognition one level upper - pub pen_path_recognition: Option, /// handle for the separate task that makes it possible to /// trigger long press for input with no jitter (where a long press /// hold wouldn't trigger any new event) @@ -128,7 +124,6 @@ pub struct Brush { /// This prevents long press from happening on a point /// We create a deadzone around the start position pub start_position: Option, - pub stroke_width: Option, pub long_press_detector: LongPressDetector, } @@ -136,11 +131,9 @@ impl Default for Brush { fn default() -> Self { Self { state: BrushState::Idle, - pen_path_recognition: None, current_stroke_key: None, longpress_handle: None, start_position: None, - stroke_width: None, long_press_detector: LongPressDetector::default(), } } @@ -205,9 +198,6 @@ impl PenBehaviour for Brush { ), ); - self.stroke_width = - Some(engine_view.pens_config.brush_config.get_stroke_width()); - engine_view.store.regenerate_rendering_for_stroke( current_stroke_key, engine_view.camera.viewport(), @@ -275,7 +265,6 @@ impl PenBehaviour for Brush { self.state = BrushState::Idle; self.current_stroke_key = None; - self.pen_path_recognition = None; self.start_position = None; self.long_press_detector.clear(); self.cancel_handle_long_press(); @@ -333,12 +322,10 @@ impl PenBehaviour for Brush { ); } - // first send the event: + // first send the event if let Some(handle) = self.longpress_handle.as_mut() { let _ = handle.reset_timeout(); - // we may have asusmed wrongly the type of pen event ? - // could be a key press that's ignored ? KeyPressed - // or Text ? + // only send pen down event to the detector match pen_event { PenEvent::Down { element, .. } => { self.long_press_detector.add_event(element, now) @@ -347,6 +334,7 @@ impl PenBehaviour for Brush { } } else { // recreate the handle if it was dropped + // errors from not using a refcell like the other use ? // this happens when we sent a long_hold event and cancelled the long // press. // We have to restart he handle and the long press detector @@ -364,8 +352,6 @@ impl PenBehaviour for Brush { time: now, }); self.current_stroke_key = None; - self.pen_path_recognition = None; - self.long_press_detector.reset(element, now); } _ => { @@ -373,11 +359,11 @@ impl PenBehaviour for Brush { } } } - // then test : long press ? + // then test : do we have a long press ? let is_deadzone = self.long_press_detector.total_distance() - > 4.0 * self.stroke_width.unwrap_or(0.5); - let is_static = - self.long_press_detector.distance() < 4.0 * self.stroke_width.unwrap(); + > 4.0 * engine_view.pens_config.brush_config.get_stroke_width(); + let is_static = self.long_press_detector.distance() + < 0.1 * engine_view.pens_config.brush_config.get_stroke_width(); let time_delta = now - self.start_position.unwrap_or(PosTimeDict::default()).time; @@ -391,24 +377,12 @@ impl PenBehaviour for Brush { && is_static && is_deadzone { - // save the current stroke for recognition - println!("saving the current stroke data"); - //save the key for potentially deleting it and replacing it with a shape self.current_stroke_key = Some(current_stroke_key.clone()); - - // save the current stroke for recognition - if let Some(Stroke::BrushStroke(brushstroke)) = - engine_view.store.get_stroke_ref(*current_stroke_key) - { - let path = brushstroke.path.clone(); - self.pen_path_recognition = Some(path); - } - widget_flags.long_hold = true; // quit the handle. Either recognition is successful and we are right - // or we aren't and a new handle will be create on the next event + // or we aren't and a new handle will be created on the next event self.cancel_handle_long_press(); } PenProgress::InProgress @@ -516,14 +490,13 @@ impl DrawableOnDoc for Brush { impl Brush { const INPUT_OVERSHOOT: f64 = 30.0; - const LONGPRESS_TIMEOUT: f64 = 0.5; + const LONGPRESS_TIMEOUT: f64 = 1.5; pub fn cancel_handle_long_press(&mut self) { // cancel the long press handle if let Some(handle) = self.longpress_handle.as_mut() { let _ = handle.quit(); } - self.longpress_handle = None; } pub fn reset_long_press(&mut self, element: Element, now: Instant) { @@ -531,9 +504,23 @@ impl Brush { self.current_stroke_key = None; self.cancel_handle_long_press(); self.longpress_handle = None; - self.stroke_width = None; self.long_press_detector.reset(element, now); } + + pub fn add_stroke_key(&mut self) -> Result<(), ()> { + // extract the content of the stroke for recognition purposes + match &mut self.state { + BrushState::Drawing { + path_builder: _, + current_stroke_key, + } => { + //save the key + self.current_stroke_key = Some(current_stroke_key.clone()); + Ok(()) + } + _ => Err(()), + } + } } fn play_marker_sound(engine_view: &mut EngineViewMut) { diff --git a/crates/rnote-engine/src/pens/mod.rs b/crates/rnote-engine/src/pens/mod.rs index 77b517259c..29d52b4125 100644 --- a/crates/rnote-engine/src/pens/mod.rs +++ b/crates/rnote-engine/src/pens/mod.rs @@ -54,12 +54,21 @@ impl Default for Pen { } impl Pen { - // intermediary function - // result/error propagation ? - pub fn reset_long_press(&mut self, element: Element, now: Instant) { + // intermediary function for mutability + pub fn reset_long_press(&mut self, now: Instant) -> Result<(), ()> { match self { - Pen::Brush(brush) => brush.reset_long_press(element, now), - _ => panic!("can't reset a pen that's not a brush"), + Pen::Brush(brush) => { + // get the last element stored in the recognizer + brush.reset_long_press( + Element { + pos: brush.long_press_detector.get_latest_pos(), + pressure: 1.0, + }, + now, + ); + Ok(()) + } + _ => Err(()), } } } diff --git a/crates/rnote-engine/src/pens/penholder.rs b/crates/rnote-engine/src/pens/penholder.rs index d1ac50273c..3e8cbc8f2d 100644 --- a/crates/rnote-engine/src/pens/penholder.rs +++ b/crates/rnote-engine/src/pens/penholder.rs @@ -8,6 +8,7 @@ use super::{ use crate::camera::NudgeDirection; use crate::engine::{EngineView, EngineViewMut}; use crate::pens::shortcuts::ShortcutAction; +use crate::strokes::Stroke; use crate::widgetflags::WidgetFlags; use crate::{CloneConfig, DrawableOnDoc}; use futures::channel::oneshot; @@ -229,6 +230,77 @@ impl PenHolder { self.current_pen_mut().deinit() } + /// handle a hold press for the pen + pub fn handle_long_press( + &mut self, + now: Instant, + engine_view: &mut EngineViewMut, + ) -> WidgetFlags { + let mut widget_flags = WidgetFlags::default(); + + match &mut self.current_pen { + Pen::Brush(brush) => { + brush.add_stroke_key().unwrap(); + let stroke_key = brush.current_stroke_key.unwrap(); + + // get the path + let path = if let Some(Stroke::BrushStroke(brushstroke)) = + engine_view.store.get_stroke_ref(stroke_key) + { + Some(brushstroke.path.clone()) + } else { + None + }; + + // recognize ? + if true { + println!("recognition successful"); + + // cancel the stroke + engine_view.store.remove_stroke(stroke_key); + + // change the type to line first + engine_view.pens_config.shaper_config.builder_type = ShapeBuilderType::Line; + // switch to the shaper tool but as an override (temporary) + widget_flags |= self.change_style_override(Some(PenStyle::Shaper), engine_view); + + // need a function to add a shape directly with some stroke width ? + + // first event is the original position + let (_, wf) = self.current_pen.handle_event( + PenEvent::Down { + element: path.as_ref().unwrap().start, + modifier_keys: HashSet::new(), + }, + now, + engine_view, + ); + widget_flags |= wf; + // second event is the last position + let (_, wf) = self.current_pen.handle_event( + PenEvent::Down { + element: path.unwrap().segments.last().unwrap().end(), + modifier_keys: HashSet::new(), + }, + now, + engine_view, + ); + widget_flags |= wf; + } else { + // reset : We get the last element stored in the recognizer + match self.current_pen.reset_long_press(now) { + Ok(()) => (), + Err(()) => { + tracing::debug!("called `reset_long_press` on an incompatible pen mode") + } + } + } + } + _ => {} + } + return widget_flags; + } + /// Handle a pen event. pub fn handle_pen_event( &mut self, @@ -247,113 +319,17 @@ impl PenHolder { let (mut event_result, wf) = self .current_pen .handle_event(event.clone(), now, engine_view); - // does the pen loose its path here or before ? - // we have to get the path here - let path = match wf.long_hold { - true => { - // need to get back the original position - // but if we have more than this (shape or more) - // we need to retrieve the full shape here - // we thus recuperate the data here, saved inside an optional part of the pen struct - println!("getting the pen path back"); - match &self.current_pen { - Pen::Brush(brush) => brush.pen_path_recognition.clone(), - _ => None, - } - } - false => None, - }; - - //let's get our stroke key here as well - // should only trigger if wf.long_hold TODO - let stroke_key = match &self.current_pen { - Pen::Brush(brush) => { - let stroke_key_opt = brush.current_stroke_key; - match stroke_key_opt { - Some(stroke) => Some(stroke.clone()), - _ => None, - } - } - _ => None, - }; 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; } - // test the wf for the long press if widget_flags.long_hold { - // need to get back the original position - // but if we have more than this (shape or more) - // we need to retrieve the full shape here - // we thus recuperate the data here, saved inside an optional part of the pen struct - println!("the path is {:?}", path); - - // random chance between recognition and not (to replace with a real recognizer) - if true { - println!("recognition successful"); - - // cancel the stroke - engine_view.store.remove_stroke(stroke_key.unwrap()); - - // change the type to line first - engine_view.pens_config.shaper_config.builder_type = ShapeBuilderType::Line; - - // switch to the shaper tool but as an override (temporary) - widget_flags |= self.change_style_override(Some(PenStyle::Shaper), engine_view); - - // update the state of the shaper to be in building - match &self.current_pen { - Pen::Shaper(shaper) => { - println!("hello {:?}", shaper); - } - _ => println!("error"), - } - // then add two new events - // or edit directly the shaper ? - // we probably will have to do custom methods - // one thing that's lacking here is the stroke width that's not set - // but we shouldn't make the settings for the shape tool change for a stroke - // recognition - - // first event is the original position (to get back up from somewhere ...) - let (_, wf) = self.current_pen.handle_event( - PenEvent::Down { - element: path.as_ref().unwrap().start, - modifier_keys: HashSet::new(), - }, - now, - engine_view, - ); - widget_flags |= wf; - // second event is the last position - let (_, wf) = self.current_pen.handle_event( - PenEvent::Down { - element: path.unwrap().segments.last().unwrap().end(), - modifier_keys: HashSet::new(), - }, - now, - engine_view, - ); - widget_flags |= wf; - // maybe try with the other method : modify the thing manually - } else { - println!("recognition failed"); - // here we have to reset both the status for the long press recogniser that is done by event - // and the one on the dedicated thread - // ISSUE HERE - self.current_pen - .reset_long_press(path.unwrap().segments.last().unwrap().end(), now); - // not too robust is it ? - // could this happen with no element ? - // ?? is this okay now ? - - // ideally we could keep the same vec for the path and extend it with only new elements if this happens multiple times - } + widget_flags |= self.handle_long_press(now, engine_view); } // Always redraw after handling a pen event