diff --git a/src/handwriting/mod.rs b/src/handwriting/mod.rs index 20f05f5..bbac7da 100644 --- a/src/handwriting/mod.rs +++ b/src/handwriting/mod.rs @@ -1,5 +1,6 @@ mod canvas_rasterizer; mod disk_format; +mod tool; use std::{ fmt::{self, Display}, @@ -21,12 +22,13 @@ use egui::{ use eyre::{Context, bail}; use eyre::{OptionExt, eyre}; use half::f16; -use serde::Serialize; use zerocopy::{FromBytes, IntoBytes}; use crate::{custom_code_block::try_from_custom_code_block, rasterizer}; use crate::{custom_code_block::write_custom_code_block, util::random_id}; +use self::tool::{ToolEvent, Tool}; + const HANDWRITING_MIN_HEIGHT: f32 = 100.0; const HANDWRITING_BOTTOM_PADDING: f32 = 80.0; const HANDWRITING_MARGIN: f32 = 0.05; @@ -79,6 +81,14 @@ struct Ephemeral { canvas_rasterizer: CanvasRasterizer, + tool: Tool, + + /// Tool position in canvas space. + tool_position: Option, + + /// Tool position last frame, in canvas space. + last_tool_position: Option, + /// The stroke that is currently being drawed. current_stroke: Vec, @@ -129,6 +139,9 @@ impl Default for Ephemeral { Self { id: random_id(), canvas_rasterizer: Default::default(), + tool: Tool::Eraser, + tool_position: None, + last_tool_position: None, current_stroke: Default::default(), tessellator: None, mesh: Default::default(), @@ -173,6 +186,14 @@ impl Handwriting { let _ = Clipboard::new().unwrap().set_text(text); } + let (label, switch_to_tool) = match self.e.tool { + Tool::Pencil => ("eraser", Tool::Eraser), + Tool::Eraser => ("pencil", Tool::Pencil), + }; + if ui.button(label).clicked() { + self.e.tool = switch_to_tool; + } + let vertex_count: usize = self.e.mesh.indices.len() / 3; ui.label(format!("vertices: {vertex_count}")); }) @@ -212,6 +233,8 @@ impl Handwriting { emath::RectTransform::from_to(Rect::from_min_size(Pos2::ZERO, size), response.rect); let from_screen = to_screen.inverse(); + self.e.last_tool_position = self.e.tool_position; + // Was the user in the process of drawing a stroke last frame? let was_drawing = !self.e.current_stroke.is_empty(); @@ -271,10 +294,21 @@ impl Handwriting { // Process input events and turn them into strokes for event in events { - let mut last_canvas_pos = self.e.current_stroke.last().copied(); - process_event(&mut last_canvas_pos, from_screen, &event, |tool_event| { - if self.on_tool_event(tool_event) { - hw_response.changed = true; // FIXME: ugly + let mut last_tool_position = self.e.last_tool_position; + process_event(&mut last_tool_position, from_screen, &event, |tool_event| { + self.e.tool_position = tool_event.position(); + match self.e.tool { + Tool::Pencil => { + hw_response.changed |= + tool::pencil::on_tool_event(self, tool_event); + + } + Tool::Eraser => { + if tool::eraser::on_tool_event(self, tool_event) { + self.e.refresh_texture = true; + hw_response.changed = true; + } + } } }); } @@ -333,6 +367,12 @@ impl Handwriting { // Draw the texture self.e.canvas_rasterizer.show(ui.ctx(), &painter, mesh_rect); + if let Some(tool_position) = self.e.tool_position && let Tool::Eraser = self.e.tool { + let pos = to_screen * tool_position; + let shape = Shape::circle_stroke(pos, tool::eraser::RADIUS, style.stroke); + painter.add(shape); + } + response } @@ -512,34 +552,6 @@ impl Handwriting { bytes.into_boxed_slice() } - - /// Handle a [ToolEvent]. Returns true if a stroke was completed. - fn on_tool_event(&mut self, tool_event: ToolEvent) -> bool { - match tool_event { - ToolEvent::Press { at } => { - debug_assert!(self.e.current_stroke.is_empty()); - self.push_to_stroke(at); - false - } - ToolEvent::Move { to } => { - self.push_to_stroke(to); - false - } - ToolEvent::Release => { - debug_assert!(!self.e.current_stroke.is_empty()); - self.strokes.push(mem::take(&mut self.e.current_stroke)); - true - } - } - } -} - -/// A simple event that can defines how a tool (e.g. the pen) is used on a [Handwriting]. -#[derive(Serialize)] -enum ToolEvent { - Press { at: Pos2 }, - Move { to: Pos2 }, - Release, } /// Convert [egui::Event]s to [ToolEvent]s. diff --git a/src/handwriting/tool/eraser.rs b/src/handwriting/tool/eraser.rs new file mode 100644 index 0000000..4f8ea87 --- /dev/null +++ b/src/handwriting/tool/eraser.rs @@ -0,0 +1,32 @@ +use crate::handwriting::{Handwriting, ToolEvent}; +use egui::Pos2; + +pub const RADIUS: f32 = 12.0; + +/// Handle a [ToolEvent]. Returns true if a stroke was completed. +pub fn on_tool_event(handwriting: &mut Handwriting, tool_event: ToolEvent) -> bool { + match tool_event { + ToolEvent::Press { at } => { + erase(handwriting, at, RADIUS) + } + ToolEvent::Move { to } => { + erase(handwriting, to, RADIUS) + } + ToolEvent::Release => { + false + } + } + +} + +fn erase(handwriting: &mut Handwriting, at: Pos2, radius: f32) -> bool { + let strokes = handwriting.strokes.len(); + + handwriting.strokes.retain(|stroke| { + stroke.iter().all(|&point| { + (point - at).length() > radius + }) + }); + + handwriting.strokes.len() < strokes +} diff --git a/src/handwriting/tool/mod.rs b/src/handwriting/tool/mod.rs new file mode 100644 index 0000000..7a98539 --- /dev/null +++ b/src/handwriting/tool/mod.rs @@ -0,0 +1,28 @@ +pub mod pencil; +pub mod eraser; + +use egui::Pos2; +use serde::Serialize; + +pub enum Tool { + Pencil, + Eraser, +} + +/// A simple event that can defines how a tool (e.g. the pen) is used on a [Handwriting]. +#[derive(Debug, Serialize, Copy, Clone)] +pub enum ToolEvent { + Press { at: Pos2 }, + Move { to: Pos2 }, + Release, +} + +impl ToolEvent { + pub const fn position(&self) -> Option { + match self { + &ToolEvent::Press { at } => Some(at), + &ToolEvent::Move { to } => Some(to), + ToolEvent::Release => None, + } + } +} diff --git a/src/handwriting/tool/pencil.rs b/src/handwriting/tool/pencil.rs new file mode 100644 index 0000000..53841cd --- /dev/null +++ b/src/handwriting/tool/pencil.rs @@ -0,0 +1,22 @@ +use crate::handwriting::{Handwriting, ToolEvent}; +use std::mem; + +/// Handle a [ToolEvent]. Returns true if a stroke was completed. +pub fn on_tool_event(handwriting: &mut Handwriting, tool_event: ToolEvent) -> bool { + match tool_event { + ToolEvent::Press { at } => { + debug_assert!(handwriting.e.current_stroke.is_empty()); + handwriting.push_to_stroke(at); + false + } + ToolEvent::Move { to } => { + handwriting.push_to_stroke(to); + false + } + ToolEvent::Release => { + debug_assert!(!handwriting.e.current_stroke.is_empty()); + handwriting.strokes.push(mem::take(&mut handwriting.e.current_stroke)); + true + } + } +}