From efde4d7c83b2fdb8fff48859a51cfbd45ae7203d Mon Sep 17 00:00:00 2001 From: Enrico Lumetti Date: Mon, 1 Mar 2021 23:19:41 +0100 Subject: [PATCH] Add support to stylus eraser --- src/lib.rs | 2 + src/main.rs | 89 +++++++++++++++++++++++++++++++++----------- src/widget/canvas.rs | 80 +++++++++++++++++++++++++++++---------- 3 files changed, 129 insertions(+), 42 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8376545..222ad17 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,8 @@ pub mod commands { use druid::Selector; pub const UPDATE_TOOL: Selector = Selector::new("stiletto_update_tool"); + pub const PUSH_ERASER: Selector<()> = Selector::new("push_eraser_context"); + pub const POP_ERASER: Selector<()> = Selector::new("pop_eraser_context"); } #[derive(Serialize, Deserialize, Debug)] diff --git a/src/main.rs b/src/main.rs index 0d5cbee..5d47476 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,7 @@ use log::{info, warn}; use druid::commands; use druid::im::vector; use druid::widget::prelude::*; -use druid::widget::{Align, Button, CrossAxisAlignment, Flex, List, SizedBox, WidgetExt}; +use druid::widget::{Align, Button, Controller, CrossAxisAlignment, Flex, List, SizedBox, WidgetExt}; use druid::{ AppDelegate, AppLauncher, Color, Command, Data, DelegateCtx, Env, FileDialogOptions, FileSpec, Handled, Lens, Target, WidgetId, WindowDesc, @@ -31,7 +31,7 @@ use im::Vector; use stiletto::tool::{CanvasToolCtx, CanvasToolParams}; use stiletto::widget::{ - build_simple_tool_widget, CanvasState, CanvasToolIconState, CanvasWidget, SCROLL, + build_simple_tool_widget, CanvasState, CanvasToolIconState, CanvasWidget, }; use stiletto::DocumentSnapshot; @@ -72,6 +72,7 @@ pub fn main() { }, ], current_tool: 0, + eraser_tool_id: 2, current_file_path: None, }; AppLauncher::with_window(window) @@ -86,10 +87,24 @@ struct StilettoState { canvas: CanvasState, tool_icons: Vector, current_tool: usize, + eraser_tool_id: usize, #[data(same_fn = "PartialEq::eq")] current_file_path: Option, } +impl StilettoState { + pub fn set_tool(&mut self, new_tool: usize) { + let old_tool = self.current_tool; + info!("Changing active tool to: tool #{}", new_tool); + self.tool_icons.get_mut(old_tool).unwrap().selected = false; + self.tool_icons.get_mut(new_tool).unwrap().selected = true; + self.current_tool = new_tool; + self.canvas.set_tool_ctx(CanvasToolCtx::new( + self.tool_icons.get(new_tool).unwrap().tool_params.clone(), + )); + } +} + fn build_ui() -> impl Widget { let canvas_id = WidgetId::next(); @@ -113,12 +128,12 @@ fn build_ui() -> impl Widget { .cross_axis_alignment(CrossAxisAlignment::Center) .with_child(Button::new("Up").on_click( move |ctx: &mut EventCtx, _data: &mut StilettoState, _env: &Env| { - ctx.submit_command(Command::new(SCROLL, -30.0, canvas_id)); + ctx.submit_command(Command::new(CanvasWidget::SCROLL, -30.0, canvas_id)); }, )) .with_child(Button::new("Down").on_click( move |ctx: &mut EventCtx, _data: &mut StilettoState, _env: &Env| { - ctx.submit_command(Command::new(SCROLL, 30.0, canvas_id)); + ctx.submit_command(Command::new(CanvasWidget::SCROLL, 30.0, canvas_id)); }, )) .with_child(Button::new("Save").on_click(move |ctx, data: &mut StilettoState, _| { @@ -155,7 +170,7 @@ fn build_ui() -> impl Widget { .padding((8.0, 0.0)) .on_click(|ctx, tool_state, _| { tool_state.selected = true; - ctx.submit_command(stiletto::commands::UPDATE_TOOL); + ctx.submit_notification(stiletto::commands::UPDATE_TOOL); }) }) .horizontal() @@ -183,12 +198,58 @@ fn build_ui() -> impl Widget { .with_id(canvas_id), 1.0, ) + .controller(ToolSwitcher::new()) +} + +struct ToolSwitcher { + saved_tool: usize, +} + +impl ToolSwitcher { + pub fn new() -> Self { + ToolSwitcher { + saved_tool: 0, + } + } +} + +impl> Controller for ToolSwitcher { + fn event(&mut self, child: &mut W, ctx: &mut EventCtx, event: &Event, data: &mut StilettoState, env: &Env) { + match event { + Event::Notification(cmd) => { + let new_tool = if cmd.get(stiletto::commands::PUSH_ERASER).is_some() { + self.saved_tool = data.current_tool; + Some(data.eraser_tool_id) + } + else if cmd.get(stiletto::commands::POP_ERASER).is_some() { + Some(self.saved_tool) + } + else if cmd.get(stiletto::commands::UPDATE_TOOL).is_some() { + let old_tool = data.current_tool; + data + .tool_icons + .iter() + .enumerate() + .position(|(pos, x)| pos != old_tool && x.selected) + } else { + None + }; + + if let Some(new_tool) = new_tool + { + data.set_tool(new_tool); + ctx.set_handled(); + } + } + _ => {} + } + child.event(ctx, event, data, env); + } } struct Delegate; impl AppDelegate for Delegate { - fn command( &mut self, _ctx: &mut DelegateCtx, @@ -239,22 +300,6 @@ impl AppDelegate for Delegate { } return Handled::Yes; } - if cmd.get(stiletto::commands::UPDATE_TOOL).is_some() { - let old_tool = data.current_tool; - if let Some(new_tool) = data - .tool_icons - .iter() - .enumerate() - .position(|(pos, x)| pos != old_tool && x.selected) - { - info!("Changing active tool to: tool #{}", new_tool); - data.tool_icons.get_mut(old_tool).unwrap().selected = false; - data.current_tool = new_tool; - data.canvas.set_tool_ctx(CanvasToolCtx::new( - data.tool_icons.get(new_tool).unwrap().tool_params.clone(), - )); - } - } Handled::No } } diff --git a/src/widget/canvas.rs b/src/widget/canvas.rs index 38ee658..9092641 100644 --- a/src/widget/canvas.rs +++ b/src/widget/canvas.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use im::Vector; +use im::{Vector}; use crate::canvas::Canvas; use crate::history::VersionedCanvas; @@ -24,21 +24,21 @@ use crate::DocumentSnapshot; use druid::scroll_component::ScrollComponent; use druid::widget::prelude::*; use druid::widget::Viewport; -use druid::{Affine, Color, Data, Env, Event, Selector}; - -pub const SCROLL: Selector = Selector::new("scroll_canvas"); +use druid::{Affine, Color, Data, Env, Event, PointerType, Selector}; #[derive(Clone, Data)] pub struct CanvasState { - pub versioned_canvas: VersionedCanvas, - pub tool_ctx: CanvasToolCtx, + versioned_canvas: VersionedCanvas, + tool_ctx: CanvasToolCtx, + temporary_erasing: bool, } impl CanvasState { pub fn new(tool_ctx: CanvasToolCtx) -> Self { CanvasState { versioned_canvas: VersionedCanvas::new(Canvas::new()), - tool_ctx, + tool_ctx: tool_ctx, + temporary_erasing: true, } } pub fn perform_undo(&mut self) { @@ -76,6 +76,25 @@ impl CanvasState { pub fn set_tool_ctx(&mut self, ctx: CanvasToolCtx) { self.tool_ctx = ctx; } + + pub fn get_tool_ctx(&self) -> &CanvasToolCtx { + &self.tool_ctx + } + + pub fn get_tool_ctx_mut(&mut self) -> &mut CanvasToolCtx { + &mut self.tool_ctx + } + + pub fn handle_event( + &mut self, + mut ctx: &mut EventCtx, + event: &Event, + transform: Affine, + env: &Env, + ) { + self.tool_ctx + .handle_event(ctx, event, &mut self.versioned_canvas, transform, env); + } } pub struct CanvasWidget { @@ -84,6 +103,8 @@ pub struct CanvasWidget { } impl CanvasWidget { + pub const SCROLL: Selector = Selector::new("scroll_canvas"); + pub fn new() -> Self { CanvasWidget { viewport: Viewport { @@ -102,9 +123,11 @@ impl Widget for CanvasWidget { .event(&mut self.viewport, ctx, event, env); if !ctx.is_handled() { let mut scroll_amount = 0.0; + let mut toggle_eraser_event = false; + let mut enable_temporary_erasing = false; match event { Event::Command(cmd) => { - if let Some(value) = cmd.get(SCROLL) { + if let Some(value) = cmd.get(CanvasWidget::SCROLL) { scroll_amount = *value; } } @@ -115,17 +138,19 @@ impl Widget for CanvasWidget { scroll_amount = -30.0; } } - _ => { - let transform = - Affine::translate((self.viewport.rect.x0, self.viewport.rect.y0)); - data.tool_ctx.handle_event( - ctx, - event, - &mut data.versioned_canvas, - transform, - env, - ); + Event::MouseDown(mouse_event) => { + toggle_eraser_event = true; + enable_temporary_erasing = mouse_event.pointer_type == PointerType::Eraser; } + Event::MouseMove(mouse_event) => { + toggle_eraser_event = true; + enable_temporary_erasing = mouse_event.pointer_type == PointerType::Eraser; + } + Event::MouseUp(mouse_event) => { + toggle_eraser_event = true; + enable_temporary_erasing = mouse_event.pointer_type == PointerType::Eraser; + } + _ => {} } if scroll_amount != 0.0 { self.viewport.rect.y0 = 0.0_f64.max(self.viewport.rect.y0 + scroll_amount); @@ -134,6 +159,21 @@ impl Widget for CanvasWidget { ctx.request_paint(); ctx.set_handled(); } + if !ctx.is_handled() { + // TODO: the first eraser toggle is not handled + if toggle_eraser_event && data.temporary_erasing != enable_temporary_erasing { + if enable_temporary_erasing { + ctx.submit_notification(crate::commands::PUSH_ERASER); + } else { + ctx.submit_notification(crate::commands::POP_ERASER); + } + data.temporary_erasing = enable_temporary_erasing; + } else { + let transform = + Affine::translate((self.viewport.rect.x0, self.viewport.rect.y0)); + data.handle_event(ctx, event, transform, env); + } + } } // TODO: replace this by own handling of Wheel event (must be able to extend content size) self.scroll_component @@ -213,8 +253,8 @@ impl Widget for CanvasWidget { element.draw(ctx); } ctx.restore().unwrap(); - if data.tool_ctx.needs_repaint() { - data.tool_ctx.paint(ctx, env); + if data.get_tool_ctx().needs_repaint() { + data.get_tool_ctx().paint(ctx, env); } self.scroll_component.draw_bars(ctx, &self.viewport, env); }