Reintroduce stroke eraser, copying code from PR #7
This commit is contained in:
parent
0b74394042
commit
827d8d6a1c
101
src/canvas.rs
101
src/canvas.rs
|
|
@ -14,6 +14,8 @@
|
|||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use std::vec::Vec;
|
||||
|
||||
use im::Vector;
|
||||
|
||||
use serde::de::{Deserializer, SeqAccess, Visitor};
|
||||
|
|
@ -25,12 +27,58 @@ pub struct Path {
|
|||
pub kurbo_path: druid::kurbo::BezPath,
|
||||
}
|
||||
|
||||
pub type Canvas = Vector<CanvasElement>;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(remote = "druid::Color")]
|
||||
enum ColorDef {
|
||||
Rgba32(u32)
|
||||
Rgba32(u32),
|
||||
}
|
||||
|
||||
struct Interval {
|
||||
start_pos: f64,
|
||||
end_pos: f64,
|
||||
}
|
||||
|
||||
impl Interval {
|
||||
fn new(x1: f64, x2: f64) -> Interval {
|
||||
Interval {
|
||||
start_pos: x1.min(x2),
|
||||
end_pos: x1.max(x2),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_ordered(start: f64, end: f64) -> Interval {
|
||||
Interval {
|
||||
start_pos: start,
|
||||
end_pos: end,
|
||||
}
|
||||
}
|
||||
|
||||
fn overlaps(&self, other: &Self) -> bool {
|
||||
self.start_pos < other.end_pos && other.start_pos < self.end_pos
|
||||
}
|
||||
}
|
||||
|
||||
// O(1) complexity
|
||||
// TODO(francesco): Implement this in a way that is not so flamboyant
|
||||
// TODO(francesco): Implement intersection test for bezier curves
|
||||
fn segment_intersects_rect(segment: &druid::kurbo::PathSeg, rect: druid::Rect) -> bool {
|
||||
match segment {
|
||||
druid::kurbo::PathSeg::Line(line) => {
|
||||
// A Segment intersects the rect
|
||||
// if their projections on the x and y line both overlap
|
||||
let line_x_proj = Interval::new(line.p0.x, line.p1.x);
|
||||
let line_y_proj = Interval::new(line.p0.y, line.p1.y);
|
||||
|
||||
let rect_x_proj = Interval::new_ordered(rect.min_x(), rect.max_x());
|
||||
let rect_y_proj = Interval::new_ordered(rect.min_y(), rect.max_y());
|
||||
|
||||
line_x_proj.overlaps(&rect_x_proj) && line_y_proj.overlaps(&rect_y_proj)
|
||||
}
|
||||
|
||||
_ => {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, druid::Data, Serialize, Deserialize)]
|
||||
|
|
@ -46,7 +94,9 @@ pub enum CanvasElement {
|
|||
impl CanvasElement {
|
||||
pub fn bounding_box(&self) -> druid::Rect {
|
||||
match self {
|
||||
CanvasElement::Freehand { path, thickness, .. } => {
|
||||
CanvasElement::Freehand {
|
||||
path, thickness, ..
|
||||
} => {
|
||||
use druid::kurbo::Shape;
|
||||
path.kurbo_path
|
||||
.bounding_box()
|
||||
|
|
@ -54,9 +104,25 @@ impl CanvasElement {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn intersects_rect(&self, rect: druid::Rect) -> bool {
|
||||
match self {
|
||||
CanvasElement::Freehand { path, .. } => {
|
||||
// TODO(enrico) this doesn't handle thickness
|
||||
path.kurbo_path
|
||||
.segments()
|
||||
.any(|segment| segment_intersects_rect(&segment, rect))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&self, ctx: &mut druid::PaintCtx) {
|
||||
match self {
|
||||
CanvasElement::Freehand { path, thickness, stroke_color } => {
|
||||
CanvasElement::Freehand {
|
||||
path,
|
||||
thickness,
|
||||
stroke_color,
|
||||
} => {
|
||||
use druid::RenderContext;
|
||||
ctx.stroke(&path.kurbo_path, &*stroke_color, *thickness);
|
||||
}
|
||||
|
|
@ -70,6 +136,29 @@ impl CanvasElement {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, druid::Data)]
|
||||
pub struct Canvas {
|
||||
pub elements: Vector<CanvasElement>,
|
||||
}
|
||||
|
||||
impl Canvas {
|
||||
/// Find all CanvasElement that intersect with rect
|
||||
pub fn find_intersections(&self, rect: druid::Rect) -> Vec<usize> {
|
||||
let mut found_elements = Vec::<usize>::new();
|
||||
|
||||
for (i, elem) in self.elements.iter().enumerate() {
|
||||
// Check if the element intersects the eraser rect
|
||||
if elem.bounding_box().intersect(rect).area() > 0.0 {
|
||||
if elem.intersects_rect(rect) {
|
||||
found_elements.push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
found_elements
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Path {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
|
|
@ -92,7 +181,7 @@ impl<'de> Deserialize<'de> for Path {
|
|||
{
|
||||
struct PathVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for PathVisitor{
|
||||
impl<'de> Visitor<'de> for PathVisitor {
|
||||
type Value = Path;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ use druid::{
|
|||
|
||||
use im::Vector;
|
||||
|
||||
use stiletto::canvas::Canvas;
|
||||
use stiletto::history::VersionedCanvas;
|
||||
use stiletto::tool::{CanvasToolCtx, CanvasToolParams};
|
||||
use stiletto::widget::{build_simple_tool_widget, CanvasState, CanvasToolIconState, CanvasWidget};
|
||||
|
|
@ -51,7 +52,9 @@ pub fn main() {
|
|||
};
|
||||
let canvas_data = StilettoState {
|
||||
canvas: CanvasState {
|
||||
versioned_canvas: VersionedCanvas::new(vector![]),
|
||||
versioned_canvas: VersionedCanvas::new(Canvas {
|
||||
elements: vector![],
|
||||
}),
|
||||
tool_ctx: CanvasToolCtx::new(default_pen_params.clone()),
|
||||
},
|
||||
tool_icons: vector![
|
||||
|
|
|
|||
40
src/tool.rs
40
src/tool.rs
|
|
@ -36,7 +36,11 @@ pub enum CanvasToolType {
|
|||
#[derive(Clone, Data)]
|
||||
pub enum CanvasToolState {
|
||||
Idle,
|
||||
DrawingFreehand { pen_params: CanvasToolParams, current_path: CanvasElement },
|
||||
DrawingFreehand {
|
||||
pen_params: CanvasToolParams,
|
||||
current_path: CanvasElement,
|
||||
},
|
||||
Erasing,
|
||||
}
|
||||
|
||||
#[derive(Clone, Data)]
|
||||
|
|
@ -71,6 +75,36 @@ impl CanvasToolCtx {
|
|||
) {
|
||||
match self.initial_params.tool_type() {
|
||||
CanvasToolType::Pen => self.handle_pen_event(&ctx, &event, &mut vcanvas, &env),
|
||||
CanvasToolType::Eraser => self.handle_erase_event(&ctx, &event, &mut vcanvas, &env),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_erase_event(
|
||||
&mut self,
|
||||
_ctx: &EventCtx,
|
||||
event: &Event,
|
||||
vcanvas: &mut VersionedCanvas,
|
||||
_env: &Env,
|
||||
) {
|
||||
match (&mut self.state, event) {
|
||||
(CanvasToolState::Idle, Event::MouseDown(_)) => {
|
||||
self.state = CanvasToolState::Erasing;
|
||||
}
|
||||
(CanvasToolState::Erasing, Event::MouseMove(mouse_event)) => {
|
||||
let eraser_rect = druid::Rect::from_center_size(mouse_event.pos, (5.0, 5.0));
|
||||
let elements = vcanvas.get().find_intersections(eraser_rect);
|
||||
|
||||
if !elements.is_empty() {
|
||||
vcanvas.update(|canvas: &mut Canvas| {
|
||||
for i in elements {
|
||||
canvas.elements.remove(i);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
(CanvasToolState::Erasing, Event::MouseUp(_)) => {
|
||||
self.state = CanvasToolState::Idle;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
@ -86,7 +120,7 @@ impl CanvasToolCtx {
|
|||
(CanvasToolState::Idle, Event::MouseDown(mouse_event)) => {
|
||||
let mut kurbo_path = BezPath::new();
|
||||
kurbo_path.move_to((mouse_event.pos.x, mouse_event.pos.y));
|
||||
if let CanvasToolParams::Pen{ thickness, color } = &self.initial_params {
|
||||
if let CanvasToolParams::Pen { thickness, color } = &self.initial_params {
|
||||
self.state = CanvasToolState::DrawingFreehand {
|
||||
pen_params: self.initial_params.clone(),
|
||||
current_path: CanvasElement::Freehand {
|
||||
|
|
@ -114,7 +148,7 @@ impl CanvasToolCtx {
|
|||
vcanvas.update(move |canvas: &mut Canvas| {
|
||||
let current_state = std::mem::replace(&mut self.state, CanvasToolState::Idle);
|
||||
if let CanvasToolState::DrawingFreehand { current_path, .. } = current_state {
|
||||
canvas.push_back(current_path);
|
||||
canvas.elements.push_back(current_path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
use im::Vector;
|
||||
|
||||
use crate::canvas::Canvas;
|
||||
use crate::history::VersionedCanvas;
|
||||
use crate::tool::CanvasToolCtx;
|
||||
use crate::DocumentSnapshot;
|
||||
|
|
@ -44,12 +45,20 @@ impl CanvasState {
|
|||
|
||||
pub fn get_document_snapshot(&self) -> DocumentSnapshot {
|
||||
DocumentSnapshot {
|
||||
canvas_elements: self.versioned_canvas.get().iter().cloned().collect(),
|
||||
canvas_elements: self
|
||||
.versioned_canvas
|
||||
.get()
|
||||
.elements
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_from_snapshot(&mut self, snapshot: DocumentSnapshot) {
|
||||
self.versioned_canvas = VersionedCanvas::new(Vector::from(snapshot.canvas_elements));
|
||||
self.versioned_canvas = VersionedCanvas::new(Canvas {
|
||||
elements: Vector::from(snapshot.canvas_elements),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_tool_ctx(&mut self, ctx: CanvasToolCtx) {
|
||||
|
|
@ -121,7 +130,7 @@ impl Widget<CanvasState> for CanvasWidget {
|
|||
// and we only want to clear this widget's area.
|
||||
ctx.fill(rect, &Color::WHITE);
|
||||
|
||||
for element in data.versioned_canvas.get().iter() {
|
||||
for element in data.versioned_canvas.get().elements.iter() {
|
||||
element.draw(ctx);
|
||||
}
|
||||
if data.tool_ctx.needs_repaint() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue