Compare commits
3 Commits
47b7feeab8
...
shrink-han
| Author | SHA1 | Date | |
|---|---|---|---|
| fdb92fbd0e | |||
| fac896161e | |||
| 945bb4d9fe |
@ -1,5 +1,6 @@
|
|||||||
mod canvas_rasterizer;
|
mod canvas_rasterizer;
|
||||||
mod disk_format;
|
mod disk_format;
|
||||||
|
mod tool;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{self, Display},
|
fmt::{self, Display},
|
||||||
@ -21,12 +22,13 @@ use egui::{
|
|||||||
use eyre::{Context, bail};
|
use eyre::{Context, bail};
|
||||||
use eyre::{OptionExt, eyre};
|
use eyre::{OptionExt, eyre};
|
||||||
use half::f16;
|
use half::f16;
|
||||||
use serde::Serialize;
|
|
||||||
use zerocopy::{FromBytes, IntoBytes};
|
use zerocopy::{FromBytes, IntoBytes};
|
||||||
|
|
||||||
use crate::{custom_code_block::try_from_custom_code_block, rasterizer};
|
use crate::{custom_code_block::try_from_custom_code_block, rasterizer};
|
||||||
use crate::{custom_code_block::write_custom_code_block, util::random_id};
|
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_MIN_HEIGHT: f32 = 100.0;
|
||||||
const HANDWRITING_BOTTOM_PADDING: f32 = 80.0;
|
const HANDWRITING_BOTTOM_PADDING: f32 = 80.0;
|
||||||
const HANDWRITING_MARGIN: f32 = 0.05;
|
const HANDWRITING_MARGIN: f32 = 0.05;
|
||||||
@ -79,6 +81,17 @@ struct Ephemeral {
|
|||||||
|
|
||||||
canvas_rasterizer: CanvasRasterizer,
|
canvas_rasterizer: CanvasRasterizer,
|
||||||
|
|
||||||
|
tool: Tool,
|
||||||
|
|
||||||
|
/// Tool position in canvas space.
|
||||||
|
tool_position: Option<Pos2>,
|
||||||
|
|
||||||
|
/// Tool position last frame, in canvas space.
|
||||||
|
last_tool_position: Option<Pos2>,
|
||||||
|
|
||||||
|
/// Whether the handwriting element is being interacted with.
|
||||||
|
is_focused: bool,
|
||||||
|
|
||||||
/// The stroke that is currently being drawed.
|
/// The stroke that is currently being drawed.
|
||||||
current_stroke: Vec<Pos2>,
|
current_stroke: Vec<Pos2>,
|
||||||
|
|
||||||
@ -129,6 +142,10 @@ impl Default for Ephemeral {
|
|||||||
Self {
|
Self {
|
||||||
id: random_id(),
|
id: random_id(),
|
||||||
canvas_rasterizer: Default::default(),
|
canvas_rasterizer: Default::default(),
|
||||||
|
tool: Tool::Eraser,
|
||||||
|
tool_position: None,
|
||||||
|
last_tool_position: None,
|
||||||
|
is_focused: false,
|
||||||
current_stroke: Default::default(),
|
current_stroke: Default::default(),
|
||||||
tessellator: None,
|
tessellator: None,
|
||||||
mesh: Default::default(),
|
mesh: Default::default(),
|
||||||
@ -173,6 +190,14 @@ impl Handwriting {
|
|||||||
let _ = Clipboard::new().unwrap().set_text(text);
|
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;
|
let vertex_count: usize = self.e.mesh.indices.len() / 3;
|
||||||
ui.label(format!("vertices: {vertex_count}"));
|
ui.label(format!("vertices: {vertex_count}"));
|
||||||
})
|
})
|
||||||
@ -212,12 +237,21 @@ impl Handwriting {
|
|||||||
emath::RectTransform::from_to(Rect::from_min_size(Pos2::ZERO, size), response.rect);
|
emath::RectTransform::from_to(Rect::from_min_size(Pos2::ZERO, size), response.rect);
|
||||||
let from_screen = to_screen.inverse();
|
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?
|
// Was the user in the process of drawing a stroke last frame?
|
||||||
let was_drawing = !self.e.current_stroke.is_empty();
|
let was_drawing = !self.e.current_stroke.is_empty();
|
||||||
|
|
||||||
// Is the user in the process of drawing a stroke now?
|
// Is the user in the process of drawing a stroke now?;
|
||||||
let is_drawing = response.interact_pointer_pos().is_some();
|
let is_drawing = response.interact_pointer_pos().is_some();
|
||||||
|
|
||||||
|
if is_drawing {
|
||||||
|
self.e.is_focused = true;
|
||||||
|
} else if ui.ctx().input(|i| i.pointer.any_down()) {
|
||||||
|
// if the user starts interactive with something else, unfocus the handwriting area.
|
||||||
|
self.e.is_focused = false;
|
||||||
|
}
|
||||||
|
|
||||||
if !is_drawing {
|
if !is_drawing {
|
||||||
if was_drawing {
|
if was_drawing {
|
||||||
// commit current line
|
// commit current line
|
||||||
@ -225,12 +259,18 @@ impl Handwriting {
|
|||||||
response.mark_changed();
|
response.mark_changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let padding = if self.e.is_focused {
|
||||||
|
HANDWRITING_BOTTOM_PADDING
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
|
||||||
// recalculate how tall the widget should be
|
// recalculate how tall the widget should be
|
||||||
let lines_max_y = self
|
let lines_max_y = self
|
||||||
.strokes
|
.strokes
|
||||||
.iter()
|
.iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.map(|p| p.y + HANDWRITING_BOTTOM_PADDING)
|
.map(|p| p.y + padding)
|
||||||
.fold(HANDWRITING_MIN_HEIGHT, |max, y| max.max(y));
|
.fold(HANDWRITING_MIN_HEIGHT, |max, y| max.max(y));
|
||||||
|
|
||||||
// Change the height of the handwriting item.
|
// Change the height of the handwriting item.
|
||||||
@ -271,10 +311,21 @@ impl Handwriting {
|
|||||||
|
|
||||||
// Process input events and turn them into strokes
|
// Process input events and turn them into strokes
|
||||||
for event in events {
|
for event in events {
|
||||||
let mut last_canvas_pos = self.e.current_stroke.last().copied();
|
let mut last_tool_position = self.e.last_tool_position;
|
||||||
process_event(&mut last_canvas_pos, from_screen, &event, |tool_event| {
|
process_event(&mut last_tool_position, from_screen, &event, |tool_event| {
|
||||||
if self.on_tool_event(tool_event) {
|
self.e.tool_position = tool_event.position();
|
||||||
hw_response.changed = true; // FIXME: ugly
|
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 +384,12 @@ impl Handwriting {
|
|||||||
// Draw the texture
|
// Draw the texture
|
||||||
self.e.canvas_rasterizer.show(ui.ctx(), &painter, mesh_rect);
|
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
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -512,34 +569,6 @@ impl Handwriting {
|
|||||||
|
|
||||||
bytes.into_boxed_slice()
|
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.
|
/// Convert [egui::Event]s to [ToolEvent]s.
|
||||||
@ -553,8 +582,6 @@ fn process_event(
|
|||||||
&Event::PointerMoved(new_position) => {
|
&Event::PointerMoved(new_position) => {
|
||||||
let new_canvas_pos = from_screen * new_position;
|
let new_canvas_pos = from_screen * new_position;
|
||||||
if last_canvas_pos.is_some() && *last_canvas_pos != Some(new_canvas_pos) {
|
if last_canvas_pos.is_some() && *last_canvas_pos != Some(new_canvas_pos) {
|
||||||
//self.push_to_stroke(new_canvas_pos);
|
|
||||||
//response.mark_changed();
|
|
||||||
*last_canvas_pos = Some(new_canvas_pos);
|
*last_canvas_pos = Some(new_canvas_pos);
|
||||||
on_tool_event(ToolEvent::Move { to: new_canvas_pos });
|
on_tool_event(ToolEvent::Move { to: new_canvas_pos });
|
||||||
}
|
}
|
||||||
@ -576,8 +603,6 @@ fn process_event(
|
|||||||
if let Some(pos) = last_canvas_pos {
|
if let Some(pos) = last_canvas_pos {
|
||||||
*pos += delta;
|
*pos += delta;
|
||||||
on_tool_event(ToolEvent::Move { to: *pos });
|
on_tool_event(ToolEvent::Move { to: *pos });
|
||||||
//self.push_to_stroke(last_canvas_pos + delta);
|
|
||||||
//response.mark_changed();
|
|
||||||
} else {
|
} else {
|
||||||
println!("Got `MouseMoved`, but have no previous pos");
|
println!("Got `MouseMoved`, but have no previous pos");
|
||||||
}
|
}
|
||||||
@ -593,8 +618,6 @@ fn process_event(
|
|||||||
if last_canvas_pos.is_none() {
|
if last_canvas_pos.is_none() {
|
||||||
let pos = from_screen * pos;
|
let pos = from_screen * pos;
|
||||||
*last_canvas_pos = Some(pos);
|
*last_canvas_pos = Some(pos);
|
||||||
//self.e.current_stroke.push(from_screen * pos);
|
|
||||||
|
|
||||||
on_tool_event(ToolEvent::Press { at: pos });
|
on_tool_event(ToolEvent::Press { at: pos });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -603,28 +626,15 @@ fn process_event(
|
|||||||
let pos = from_screen * pos;
|
let pos = from_screen * pos;
|
||||||
on_tool_event(ToolEvent::Move { to: pos });
|
on_tool_event(ToolEvent::Move { to: pos });
|
||||||
on_tool_event(ToolEvent::Release {});
|
on_tool_event(ToolEvent::Release {});
|
||||||
//self.push_to_stroke(from_screen * pos);
|
|
||||||
//self.commit_current_line(hw_response);
|
|
||||||
//response.mark_changed();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop reading events.
|
|
||||||
// TODO: In theory, we can get multiple press->draw->release series
|
|
||||||
// in the same frame. Should handle this.
|
|
||||||
//break;
|
|
||||||
}
|
}
|
||||||
(_, _) => {}
|
(_, _) => {}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Stop drawing after pointer disappears or the window is unfocused
|
// Stop drawing after pointer disappears or the window is unfocused
|
||||||
// TODO: In theory, we can get multiple press->draw->release series
|
|
||||||
// in the same frame. Should handle this.
|
|
||||||
Event::PointerGone | Event::WindowFocused(false) => {
|
Event::PointerGone | Event::WindowFocused(false) => {
|
||||||
//if !self.e.current_stroke.is_empty() {
|
|
||||||
if last_canvas_pos.take().is_some() {
|
if last_canvas_pos.take().is_some() {
|
||||||
on_tool_event(ToolEvent::Release {});
|
on_tool_event(ToolEvent::Release {});
|
||||||
//self.commit_current_line(hw_response);
|
|
||||||
//break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
32
src/handwriting/tool/eraser.rs
Normal file
32
src/handwriting/tool/eraser.rs
Normal file
@ -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
|
||||||
|
}
|
||||||
28
src/handwriting/tool/mod.rs
Normal file
28
src/handwriting/tool/mod.rs
Normal file
@ -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<Pos2> {
|
||||||
|
match self {
|
||||||
|
&ToolEvent::Press { at } => Some(at),
|
||||||
|
&ToolEvent::Move { to } => Some(to),
|
||||||
|
ToolEvent::Release => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/handwriting/tool/pencil.rs
Normal file
22
src/handwriting/tool/pencil.rs
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user