From 1605a607d375913a302c3d00819464155ff3aa9a Mon Sep 17 00:00:00 2001 From: Paul Kupper Date: Fri, 11 Jun 2021 00:04:05 +0200 Subject: [PATCH 01/11] Begin implementing viewport selection --- core/document/src/intersection.rs | 41 +++++++++++++ core/document/src/layers/circle.rs | 13 ++++ core/document/src/layers/ellipse.rs | 16 ++++- core/document/src/layers/folder.rs | 10 ++++ core/document/src/layers/line.rs | 12 +++- core/document/src/layers/mod.rs | 3 + core/document/src/layers/polyline.rs | 16 ++++- core/document/src/layers/rect.rs | 15 ++++- core/document/src/layers/shape.rs | 14 ++++- core/document/src/lib.rs | 1 + core/editor/src/input/input_mapper.rs | 7 +++ core/editor/src/tool/tools/select.rs | 85 ++++++++++++++++++++++----- 12 files changed, 211 insertions(+), 22 deletions(-) create mode 100644 core/document/src/intersection.rs diff --git a/core/document/src/intersection.rs b/core/document/src/intersection.rs new file mode 100644 index 0000000000..d92e92fda8 --- /dev/null +++ b/core/document/src/intersection.rs @@ -0,0 +1,41 @@ +use kurbo::{BezPath, Line, PathSeg, Point, Vec2}; + +pub fn intersect_quad_bez_path(quad: [Point; 4], shape: &BezPath) -> bool { + let lines = vec![Line::new(quad[0], quad[1]), Line::new(quad[1], quad[2]), Line::new(quad[2], quad[3]), Line::new(quad[3], quad[0])]; + for path_segment in shape.segments() { + for line in &lines { + if !path_segment.intersect_line(*line).is_empty() { + return true; + } + } + } + if let Some(shape_point) = get_arbitrary_point_on_path(shape) { + let mut pos = 0; + let mut neg = 0; + for line in lines { + if line.p0 == shape_point { + return true; + }; + let line_vec = Vec2::new(line.p1.x - line.p0.x, line.p1.y - line.p0.y); + let point_vec = Vec2::new(line.p1.x - shape_point.x, line.p1.y - shape_point.y); + let cross = line_vec.cross(point_vec); + if cross > 0.0 { + pos += 1; + } else if cross < 0.0 { + neg += 0; + } + if pos > 0 && neg > 0 { + return false; + } + } + } + false +} + +pub fn get_arbitrary_point_on_path(path: &BezPath) -> Option { + path.segments().next().map(|seg| match seg { + PathSeg::Line(line) => line.p0, + PathSeg::Quad(quad) => quad.p0, + PathSeg::Cubic(cubic) => cubic.p0, + }) +} diff --git a/core/document/src/layers/circle.rs b/core/document/src/layers/circle.rs index ad9f066f2a..3b62049ded 100644 --- a/core/document/src/layers/circle.rs +++ b/core/document/src/layers/circle.rs @@ -1,3 +1,8 @@ +use kurbo::Point; +use kurbo::Shape; + +use crate::intersection::intersect_quad_bez_path; + use super::style; use super::LayerData; @@ -29,4 +34,12 @@ impl LayerData for Circle { self.style.render(), ); } + + fn contains(&self, point: Point) -> bool { + self.shape.contains(point) + } + + fn intersects_quad(&self, quad: [Point; 4]) -> bool { + intersect_quad_bez_path(quad, &self.shape.to_path(self.shape.radius * 0.0001)) + } } diff --git a/core/document/src/layers/ellipse.rs b/core/document/src/layers/ellipse.rs index fe22124f50..c863adedd8 100644 --- a/core/document/src/layers/ellipse.rs +++ b/core/document/src/layers/ellipse.rs @@ -1,3 +1,9 @@ +use kurbo::Point; +use kurbo::Shape; +use kurbo::Vec2; + +use crate::intersection::intersect_quad_bez_path; + use super::style; use super::LayerData; @@ -10,7 +16,7 @@ pub struct Ellipse { } impl Ellipse { - pub fn new(center: impl Into, radii: impl Into, rotation: f64, style: style::PathStyle) -> Ellipse { + pub fn new(center: impl Into, radii: impl Into, rotation: f64, style: style::PathStyle) -> Ellipse { Ellipse { shape: kurbo::Ellipse::new(center, radii, rotation), style, @@ -34,4 +40,12 @@ impl LayerData for Ellipse { self.style.render(), ); } + + fn contains(&self, point: Point) -> bool { + self.shape.contains(point) + } + + fn intersects_quad(&self, quad: [Point; 4]) -> bool { + intersect_quad_bez_path(quad, &self.shape.to_path(0.0001)) + } } diff --git a/core/document/src/layers/folder.rs b/core/document/src/layers/folder.rs index 71896e2ca4..9f99634249 100644 --- a/core/document/src/layers/folder.rs +++ b/core/document/src/layers/folder.rs @@ -1,3 +1,5 @@ +use kurbo::Point; + use crate::{DocumentError, LayerId}; use super::{Layer, LayerData, LayerDataTypes}; @@ -17,6 +19,14 @@ impl LayerData for Folder { let _ = writeln!(svg, "{}", layer.render()); } } + + fn contains(&self, _point: Point) -> bool { + false + } + + fn intersects_quad(&self, _quad: [Point; 4]) -> bool { + false + } } impl Folder { diff --git a/core/document/src/layers/line.rs b/core/document/src/layers/line.rs index e4b62aeabf..120f135cf6 100644 --- a/core/document/src/layers/line.rs +++ b/core/document/src/layers/line.rs @@ -1,3 +1,5 @@ +use kurbo::Point; + use super::style; use super::LayerData; @@ -10,7 +12,7 @@ pub struct Line { } impl Line { - pub fn new(p0: impl Into, p1: impl Into, style: style::PathStyle) -> Line { + pub fn new(p0: impl Into, p1: impl Into, style: style::PathStyle) -> Line { Line { shape: kurbo::Line::new(p0, p1), style, @@ -25,4 +27,12 @@ impl LayerData for Line { let _ = write!(svg, r#""#, x1, y1, x2, y2, self.style.render(),); } + + fn contains(&self, _point: Point) -> bool { + false + } + + fn intersects_quad(&self, _quad: [Point; 4]) -> bool { + false + } } diff --git a/core/document/src/layers/mod.rs b/core/document/src/layers/mod.rs index e107249da1..fd280da28e 100644 --- a/core/document/src/layers/mod.rs +++ b/core/document/src/layers/mod.rs @@ -7,6 +7,7 @@ pub mod ellipse; pub use ellipse::Ellipse; pub mod line; +use kurbo::Point; pub use line::Line; pub mod rect; @@ -23,6 +24,8 @@ pub use folder::Folder; pub trait LayerData { fn render(&mut self, svg: &mut String); + fn contains(&self, point: Point) -> bool; + fn intersects_quad(&self, quad: [Point; 4]) -> bool; } #[derive(Debug, Clone, PartialEq)] diff --git a/core/document/src/layers/polyline.rs b/core/document/src/layers/polyline.rs index 38e4364581..3997f78d04 100644 --- a/core/document/src/layers/polyline.rs +++ b/core/document/src/layers/polyline.rs @@ -1,3 +1,5 @@ +use kurbo::Point; + use super::style; use super::LayerData; @@ -5,12 +7,12 @@ use std::fmt::Write; #[derive(Debug, Clone, PartialEq)] pub struct PolyLine { - points: Vec, + points: Vec, style: style::PathStyle, } impl PolyLine { - pub fn new(points: Vec>, style: style::PathStyle) -> PolyLine { + pub fn new(points: Vec>, style: style::PathStyle) -> PolyLine { PolyLine { points: points.into_iter().map(|it| it.into()).collect(), style, @@ -32,13 +34,21 @@ impl LayerData for PolyLine { } let _ = write!(svg, r#""{} />"#, self.style.render()); } + + fn contains(&self, point: Point) -> bool { + false + } + + fn intersects_quad(&self, quad: [Point; 4]) -> bool { + false + } } #[cfg(test)] #[test] fn polyline_should_render() { let mut polyline = PolyLine { - points: vec![kurbo::Point::new(3.0, 4.12354), kurbo::Point::new(1.0, 5.54)], + points: vec![Point::new(3.0, 4.12354), Point::new(1.0, 5.54)], style: style::PathStyle::new(Some(style::Stroke::new(crate::color::Color::GREEN, 0.4)), None), }; diff --git a/core/document/src/layers/rect.rs b/core/document/src/layers/rect.rs index e38bcd1294..617f10069f 100644 --- a/core/document/src/layers/rect.rs +++ b/core/document/src/layers/rect.rs @@ -1,3 +1,8 @@ +use kurbo::Point; +use kurbo::Shape; + +use crate::intersection::intersect_quad_bez_path; + use super::style; use super::LayerData; @@ -10,7 +15,7 @@ pub struct Rect { } impl Rect { - pub fn new(p0: impl Into, p1: impl Into, style: style::PathStyle) -> Rect { + pub fn new(p0: impl Into, p1: impl Into, style: style::PathStyle) -> Rect { Rect { shape: kurbo::Rect::from_points(p0, p1), style, @@ -30,4 +35,12 @@ impl LayerData for Rect { self.style.render(), ); } + + fn contains(&self, point: Point) -> bool { + self.shape.contains(point) + } + + fn intersects_quad(&self, quad: [Point; 4]) -> bool { + intersect_quad_bez_path(quad, &self.shape.to_path(0.0001)) + } } diff --git a/core/document/src/layers/shape.rs b/core/document/src/layers/shape.rs index c48ad514f4..4634da87d3 100644 --- a/core/document/src/layers/shape.rs +++ b/core/document/src/layers/shape.rs @@ -1,3 +1,5 @@ +use kurbo::Point; + use crate::shape_points; use super::style; @@ -13,10 +15,10 @@ pub struct Shape { } impl Shape { - pub fn new(p0: impl Into, p1: impl Into, sides: u8, style: style::PathStyle) -> Shape { + pub fn new(p0: impl Into, p1: impl Into, sides: u8, style: style::PathStyle) -> Shape { Shape { bounding_rect: kurbo::Rect::from_points(p0, p1), - shape: shape_points::ShapePoints::new(kurbo::Point::new(0.5, 0.5), kurbo::Vec2::new(0.5, 0.0), sides), + shape: shape_points::ShapePoints::new(Point::new(0.5, 0.5), kurbo::Vec2::new(0.5, 0.0), sides), style, } } @@ -35,4 +37,12 @@ impl LayerData for Shape { self.style.render(), ); } + + fn contains(&self, _point: Point) -> bool { + false + } + + fn intersects_quad(&self, _quad: [Point; 4]) -> bool { + false + } } diff --git a/core/document/src/lib.rs b/core/document/src/lib.rs index 171983d053..18b13e95c9 100644 --- a/core/document/src/lib.rs +++ b/core/document/src/lib.rs @@ -1,5 +1,6 @@ pub mod color; pub mod document; +pub mod intersection; pub mod layers; pub mod operation; pub mod response; diff --git a/core/editor/src/input/input_mapper.rs b/core/editor/src/input/input_mapper.rs index 8b1fa345cb..cd4a30162b 100644 --- a/core/editor/src/input/input_mapper.rs +++ b/core/editor/src/input/input_mapper.rs @@ -1,4 +1,5 @@ use crate::message_prelude::*; +use crate::tool::tools::select::Select; use crate::tool::ToolType; use super::{ @@ -96,6 +97,12 @@ macro_rules! mapping { impl Default for Mapping { fn default() -> Self { let (up, down, pointer_move) = mapping![ + // Select + entry! {action=SelectMessage::MouseMove, message=InputMapperMessage::PointerMove}, + entry! {action=SelectMessage::DragStart, key_down=Lmb}, + entry! {action=SelectMessage::DragStop, key_up=Lmb}, + entry! {action=SelectMessage::Abort, key_down=Rmb}, + entry! {action=SelectMessage::Abort, key_down=KeyEscape}, // Rectangle entry! {action=RectangleMessage::Center, key_down=KeyAlt}, entry! {action=RectangleMessage::UnCenter, key_up=KeyAlt}, diff --git a/core/editor/src/tool/tools/select.rs b/core/editor/src/tool/tools/select.rs index 1df1166c72..d7b9eab261 100644 --- a/core/editor/src/tool/tools/select.rs +++ b/core/editor/src/tool/tools/select.rs @@ -1,4 +1,10 @@ -use crate::input::InputPreprocessor; +use document_core::color::Color; +use document_core::layers::style; +use document_core::layers::style::Fill; +use document_core::layers::style::Stroke; +use document_core::Operation; + +use crate::input::{mouse::ViewportPosition, InputPreprocessor}; use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; use crate::{message_prelude::*, SvgDocument}; @@ -11,19 +17,29 @@ pub struct Select { #[impl_message(Message, ToolMessage, Select)] #[derive(PartialEq, Clone, Debug)] pub enum SelectMessage { + DragStart, + DragStop, MouseMove, + Abort, } impl<'a> MessageHandler> for Select { fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { self.fsm_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses); } - advertise_actions!(); + fn actions(&self) -> ActionList { + use SelectToolFsmState::*; + match self.fsm_state { + Ready => actions!(SelectMessageDiscriminant; DragStart), + Dragging => actions!(SelectMessageDiscriminant; DragStop, MouseMove, Abort), + } + } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum SelectToolFsmState { Ready, + Dragging, } impl Default for SelectToolFsmState { @@ -32,29 +48,70 @@ impl Default for SelectToolFsmState { } } -#[derive(Default)] -struct SelectToolData; +#[derive(Clone, Debug, Default)] +struct SelectToolData { + drag_start: ViewportPosition, + drag_current: ViewportPosition, +} impl Fsm for SelectToolFsmState { type ToolData = SelectToolData; - fn transition( - self, - event: ToolMessage, - _document: &SvgDocument, - _tool_data: &DocumentToolData, - _data: &mut Self::ToolData, - _input: &InputPreprocessor, - _responses: &mut VecDeque, - ) -> Self { + fn transition(self, event: ToolMessage, document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque) -> Self { use SelectMessage::*; use SelectToolFsmState::*; if let ToolMessage::Select(event) = event { match (self, event) { - (Ready, MouseMove) => self, + (Ready, DragStart) => { + data.drag_start = input.mouse.position; + data.drag_current = input.mouse.position; + responses.push_back(Operation::MountWorkingFolder { path: vec![] }.into()); + Dragging + } + (Dragging, MouseMove) => { + data.drag_current = input.mouse.position; + + responses.push_back(Operation::ClearWorkingFolder.into()); + responses.push_back(make_operation(data, tool_data)); + + Dragging + } + (Dragging, DragStop) => { + data.drag_current = input.mouse.position; + + responses.push_back(Operation::ClearWorkingFolder.into()); + // TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100) + if data.drag_start == data.drag_current { + // do point selection + } else { + // do rect selection + } + + Ready + } + // TODO - simplify with or_patterns when rust 1.53.0 is stable (https://github.com/rust-lang/rust/issues/54883) + (Dragging, Abort) => { + responses.push_back(Operation::DiscardWorkingFolder.into()); + + Ready + } + _ => self, } } else { self } } } + +fn make_operation(data: &SelectToolData, _tool_data: &DocumentToolData) -> Message { + Operation::AddRect { + path: vec![], + insert_index: -1, + x0: data.drag_start.x as f64, + y0: data.drag_start.y as f64, + x1: data.drag_current.x as f64, + y1: data.drag_current.y as f64, + style: style::PathStyle::new(Some(Stroke::new(Color::from_rgb8(0x31, 0x94, 0xD6), 2.0)), Some(Fill::none())), + } + .into() +} From 1d78322fcde759387fc2b614d1445426bebad088 Mon Sep 17 00:00:00 2001 From: Paul Kupper Date: Sun, 13 Jun 2021 17:14:54 +0200 Subject: [PATCH 02/11] Implement viewport click and drag selection for ellipse and rectangle --- Cargo.lock | 1 + core/document/src/document.rs | 29 ++++++++++++ core/document/src/intersection.rs | 12 +++-- core/document/src/layers/circle.rs | 14 ++++-- core/document/src/layers/ellipse.rs | 14 ++++-- core/document/src/layers/folder.rs | 16 +++++-- core/document/src/layers/line.rs | 10 ++-- core/document/src/layers/mod.rs | 68 ++++++++++++++++++++++++++- core/document/src/layers/polyline.rs | 10 ++-- core/document/src/layers/rect.rs | 14 ++++-- core/document/src/layers/shape.rs | 9 ++-- core/editor/Cargo.toml | 1 + core/editor/src/input/input_mapper.rs | 1 - core/editor/src/tool/tools/select.rs | 14 +++++- 14 files changed, 171 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6a513ba01..0c807f8e5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,6 +62,7 @@ dependencies = [ "bitflags", "graphite-document-core", "graphite-proc-macros", + "kurbo", "log", "serde", "thiserror", diff --git a/core/document/src/document.rs b/core/document/src/document.rs index 63e9e0978f..b5ea2fa25e 100644 --- a/core/document/src/document.rs +++ b/core/document/src/document.rs @@ -1,3 +1,5 @@ +use kurbo::Point; + use crate::{ layers::{self, Folder, Layer, LayerData, LayerDataTypes, Line, PolyLine, Rect, Shape}, DocumentError, DocumentResponse, LayerId, Operation, @@ -59,6 +61,33 @@ impl Document { svg } + /// Checks whether each layer under `path` intersects with the provided `quad` and adds all intersection layers as paths to `intersections`. + pub fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>) { + log::debug!("Intersects Quad {:?}", quad); + self.document_folder(path).unwrap().intersects_quad(quad, path, intersections); + return; + } + + /// Checks whether each layer under the root path intersects with the provided `quad` and returns the paths to all intersecting layers. + pub fn intersects_quad_root(&self, quad: [Point; 4]) -> Vec> { + let mut intersections = Vec::new(); + self.intersects_quad(quad, &mut vec![], &mut intersections); + intersections + } + + /// Checks whether each layer under `path` intersects with the provided `point` and adds all intersection layers as paths to `intersections`. + pub fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>) { + self.document_folder(path).unwrap().intersects_point(point, path, intersections); + return; + } + + /// Checks whether each layer intersects with the given point and returns the paths to intersecting layers. + pub fn intersects_point_root(&self, point: Point) -> Vec> { + let mut intersections = Vec::new(); + self.intersects_point(point, &mut vec![], &mut intersections); + intersections + } + fn is_mounted(&self, mount_path: &[LayerId], path: &[LayerId]) -> bool { path.starts_with(mount_path) && self.work_mounted } diff --git a/core/document/src/intersection.rs b/core/document/src/intersection.rs index d92e92fda8..9f278d9b9c 100644 --- a/core/document/src/intersection.rs +++ b/core/document/src/intersection.rs @@ -1,7 +1,8 @@ -use kurbo::{BezPath, Line, PathSeg, Point, Vec2}; +use kurbo::{BezPath, Line, PathSeg, Point, Shape, Vec2}; pub fn intersect_quad_bez_path(quad: [Point; 4], shape: &BezPath) -> bool { let lines = vec![Line::new(quad[0], quad[1]), Line::new(quad[1], quad[2]), Line::new(quad[2], quad[3]), Line::new(quad[3], quad[0])]; + // check if outlines intersect for path_segment in shape.segments() { for line in &lines { if !path_segment.intersect_line(*line).is_empty() { @@ -9,6 +10,11 @@ pub fn intersect_quad_bez_path(quad: [Point; 4], shape: &BezPath) -> bool { } } } + // check if selection is entirely within the shape + if shape.contains(quad[0]) { + return true; + } + // check if shape is entirely within the selection if let Some(shape_point) = get_arbitrary_point_on_path(shape) { let mut pos = 0; let mut neg = 0; @@ -22,14 +28,14 @@ pub fn intersect_quad_bez_path(quad: [Point; 4], shape: &BezPath) -> bool { if cross > 0.0 { pos += 1; } else if cross < 0.0 { - neg += 0; + neg += 1; } if pos > 0 && neg > 0 { return false; } } } - false + true } pub fn get_arbitrary_point_on_path(path: &BezPath) -> Option { diff --git a/core/document/src/layers/circle.rs b/core/document/src/layers/circle.rs index 3b62049ded..6f80af15b9 100644 --- a/core/document/src/layers/circle.rs +++ b/core/document/src/layers/circle.rs @@ -2,9 +2,11 @@ use kurbo::Point; use kurbo::Shape; use crate::intersection::intersect_quad_bez_path; +use crate::LayerId; use super::style; use super::LayerData; +use super::KURBO_TOLERANCE; use std::fmt::Write; @@ -35,11 +37,15 @@ impl LayerData for Circle { ); } - fn contains(&self, point: Point) -> bool { - self.shape.contains(point) + fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>) { + if intersect_quad_bez_path(quad, &self.shape.to_path(KURBO_TOLERANCE)) { + intersections.push(path.clone()); + } } - fn intersects_quad(&self, quad: [Point; 4]) -> bool { - intersect_quad_bez_path(quad, &self.shape.to_path(self.shape.radius * 0.0001)) + fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>) { + if self.shape.contains(point) { + intersections.push(path.clone()); + } } } diff --git a/core/document/src/layers/ellipse.rs b/core/document/src/layers/ellipse.rs index c863adedd8..1b07d605da 100644 --- a/core/document/src/layers/ellipse.rs +++ b/core/document/src/layers/ellipse.rs @@ -3,9 +3,11 @@ use kurbo::Shape; use kurbo::Vec2; use crate::intersection::intersect_quad_bez_path; +use crate::LayerId; use super::style; use super::LayerData; +use super::KURBO_TOLERANCE; use std::fmt::Write; @@ -41,11 +43,15 @@ impl LayerData for Ellipse { ); } - fn contains(&self, point: Point) -> bool { - self.shape.contains(point) + fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>) { + if intersect_quad_bez_path(quad, &self.shape.to_path(KURBO_TOLERANCE)) { + intersections.push(path.clone()); + } } - fn intersects_quad(&self, quad: [Point; 4]) -> bool { - intersect_quad_bez_path(quad, &self.shape.to_path(0.0001)) + fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>) { + if self.shape.contains(point) { + intersections.push(path.clone()); + } } } diff --git a/core/document/src/layers/folder.rs b/core/document/src/layers/folder.rs index 9f99634249..711f435875 100644 --- a/core/document/src/layers/folder.rs +++ b/core/document/src/layers/folder.rs @@ -20,12 +20,20 @@ impl LayerData for Folder { } } - fn contains(&self, _point: Point) -> bool { - false + fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>) { + for (layer, layer_id) in self.layers().iter().zip(&self.layer_ids) { + path.push(*layer_id); + layer.data.intersects_quad(quad, path, intersections); + path.pop(); + } } - fn intersects_quad(&self, _quad: [Point; 4]) -> bool { - false + fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>) { + for (layer, layer_id) in self.layers().iter().zip(&self.layer_ids) { + path.push(*layer_id); + layer.data.intersects_point(point, path, intersections); + path.pop(); + } } } diff --git a/core/document/src/layers/line.rs b/core/document/src/layers/line.rs index 120f135cf6..1e8ff62546 100644 --- a/core/document/src/layers/line.rs +++ b/core/document/src/layers/line.rs @@ -1,5 +1,7 @@ use kurbo::Point; +use crate::LayerId; + use super::style; use super::LayerData; @@ -28,11 +30,7 @@ impl LayerData for Line { let _ = write!(svg, r#""#, x1, y1, x2, y2, self.style.render(),); } - fn contains(&self, _point: Point) -> bool { - false - } + fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>) {} - fn intersects_quad(&self, _quad: [Point; 4]) -> bool { - false - } + fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>) {} } diff --git a/core/document/src/layers/mod.rs b/core/document/src/layers/mod.rs index fd280da28e..98c9b18dd3 100644 --- a/core/document/src/layers/mod.rs +++ b/core/document/src/layers/mod.rs @@ -22,10 +22,14 @@ pub use shape::Shape; pub mod folder; pub use folder::Folder; +use crate::LayerId; + +pub const KURBO_TOLERANCE: f64 = 0.0001; + pub trait LayerData { fn render(&mut self, svg: &mut String); - fn contains(&self, point: Point) -> bool; - fn intersects_quad(&self, quad: [Point; 4]) -> bool; + fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>); + fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>); } #[derive(Debug, Clone, PartialEq)] @@ -47,6 +51,22 @@ macro_rules! call_render { }; } +macro_rules! call_intersects_quad { + ($self:ident.intersects_quad($quad:ident, $path:ident, $intersections:ident) { $($variant:ident),* }) => { + match $self { + $(Self::$variant(x) => x.intersects_quad($quad, $path, $intersections)),* + } + }; +} + +macro_rules! call_intersects_point { + ($self:ident.intersects_point($point:ident, $path:ident, $intersections:ident) { $($variant:ident),* }) => { + match $self { + $(Self::$variant(x) => x.intersects_point($point, $path, $intersections)),* + } + }; +} + impl LayerDataTypes { pub fn render(&mut self, svg: &mut String) { call_render! { @@ -61,6 +81,34 @@ impl LayerDataTypes { } } } + + pub fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>) { + call_intersects_quad! { + self.intersects_quad(quad, path, intersections) { + Folder, + Circle, + Ellipse, + Rect, + Line, + PolyLine, + Shape + } + } + } + + pub fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>) { + call_intersects_point! { + self.intersects_point(point, path, intersections) { + Folder, + Circle, + Ellipse, + Rect, + Line, + PolyLine, + Shape + } + } + } } #[derive(Debug, Clone, PartialEq)] @@ -94,4 +142,20 @@ impl Layer { } self.cache.as_str() } + + pub fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>) { + // TODO: apply transform to quad + if !self.visible { + return; + } + self.data.intersects_quad(quad, path, intersections) + } + + pub fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>) { + // TODO: apply transform to point + if !self.visible { + return; + } + self.data.intersects_point(point, path, intersections) + } } diff --git a/core/document/src/layers/polyline.rs b/core/document/src/layers/polyline.rs index 3997f78d04..387a8ef8cb 100644 --- a/core/document/src/layers/polyline.rs +++ b/core/document/src/layers/polyline.rs @@ -1,5 +1,7 @@ use kurbo::Point; +use crate::LayerId; + use super::style; use super::LayerData; @@ -35,13 +37,9 @@ impl LayerData for PolyLine { let _ = write!(svg, r#""{} />"#, self.style.render()); } - fn contains(&self, point: Point) -> bool { - false - } + fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>) {} - fn intersects_quad(&self, quad: [Point; 4]) -> bool { - false - } + fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>) {} } #[cfg(test)] diff --git a/core/document/src/layers/rect.rs b/core/document/src/layers/rect.rs index 617f10069f..530391a775 100644 --- a/core/document/src/layers/rect.rs +++ b/core/document/src/layers/rect.rs @@ -2,9 +2,11 @@ use kurbo::Point; use kurbo::Shape; use crate::intersection::intersect_quad_bez_path; +use crate::LayerId; use super::style; use super::LayerData; +use super::KURBO_TOLERANCE; use std::fmt::Write; @@ -36,11 +38,15 @@ impl LayerData for Rect { ); } - fn contains(&self, point: Point) -> bool { - self.shape.contains(point) + fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>) { + if intersect_quad_bez_path(quad, &self.shape.to_path(KURBO_TOLERANCE)) { + intersections.push(path.clone()); + } } - fn intersects_quad(&self, quad: [Point; 4]) -> bool { - intersect_quad_bez_path(quad, &self.shape.to_path(0.0001)) + fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>) { + if self.shape.contains(point) { + intersections.push(path.clone()); + } } } diff --git a/core/document/src/layers/shape.rs b/core/document/src/layers/shape.rs index 4634da87d3..fd9effd00b 100644 --- a/core/document/src/layers/shape.rs +++ b/core/document/src/layers/shape.rs @@ -1,6 +1,7 @@ use kurbo::Point; use crate::shape_points; +use crate::LayerId; use super::style; use super::LayerData; @@ -38,11 +39,7 @@ impl LayerData for Shape { ); } - fn contains(&self, _point: Point) -> bool { - false - } + fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>) {} - fn intersects_quad(&self, _quad: [Point; 4]) -> bool { - false - } + fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>) {} } diff --git a/core/editor/Cargo.toml b/core/editor/Cargo.toml index fe90d2043d..a0890dce7e 100644 --- a/core/editor/Cargo.toml +++ b/core/editor/Cargo.toml @@ -9,6 +9,7 @@ repository = "https://github.com/GraphiteEditor/Graphite" license = "Apache-2.0" [dependencies] +kurbo = "0.8.0" log = "0.4" bitflags = "1.2.1" thiserror = "1.0.24" diff --git a/core/editor/src/input/input_mapper.rs b/core/editor/src/input/input_mapper.rs index cd4a30162b..db7e7332b9 100644 --- a/core/editor/src/input/input_mapper.rs +++ b/core/editor/src/input/input_mapper.rs @@ -1,5 +1,4 @@ use crate::message_prelude::*; -use crate::tool::tools::select::Select; use crate::tool::ToolType; use super::{ diff --git a/core/editor/src/tool/tools/select.rs b/core/editor/src/tool/tools/select.rs index d7b9eab261..d4d3d90c32 100644 --- a/core/editor/src/tool/tools/select.rs +++ b/core/editor/src/tool/tools/select.rs @@ -3,6 +3,7 @@ use document_core::layers::style; use document_core::layers::style::Fill; use document_core::layers::style::Stroke; use document_core::Operation; +use kurbo::Point; use crate::input::{mouse::ViewportPosition, InputPreprocessor}; use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; @@ -82,9 +83,18 @@ impl Fsm for SelectToolFsmState { responses.push_back(Operation::ClearWorkingFolder.into()); // TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100) if data.drag_start == data.drag_current { - // do point selection + let point = Point::new(data.drag_current.x as f64, data.drag_current.y as f64); + if let Some(intersection) = document.intersects_point_root(point).last() { + responses.push_back(DocumentMessage::SelectLayers(vec![intersection.clone()]).into()); + } } else { - // do rect selection + let quad = [ + Point::new(data.drag_start.x as f64, data.drag_start.y as f64), + Point::new(data.drag_current.x as f64, data.drag_start.y as f64), + Point::new(data.drag_current.x as f64, data.drag_current.y as f64), + Point::new(data.drag_start.x as f64, data.drag_current.y as f64), + ]; + responses.push_back(DocumentMessage::SelectLayers(document.intersects_quad_root(quad)).into()); } Ready From 7681b3c5a17913be5e4d74542b930cf0ce7d205a Mon Sep 17 00:00:00 2001 From: Paul Kupper Date: Sun, 27 Jun 2021 10:28:39 +0200 Subject: [PATCH 03/11] Begin implementing line selection --- Cargo.lock | 7 +++++++ core/document/Cargo.toml | 1 + core/document/src/intersection.rs | 10 ++++++++++ core/document/src/layers/line.rs | 12 ++++++++++-- core/document/src/layers/mod.rs | 3 ++- core/editor/src/tool/tools/select.rs | 2 ++ 6 files changed, 32 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c807f8e5c..1dfb653059 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,6 +42,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "glam" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4126c0479ccf7e8664c36a2d719f5f2c140fbb4f9090008098d2c291fa5b3f16" + [[package]] name = "graphite-cli" version = "0.1.0" @@ -50,6 +56,7 @@ version = "0.1.0" name = "graphite-document-core" version = "0.1.0" dependencies = [ + "glam", "kurbo", "log", "serde", diff --git a/core/document/Cargo.toml b/core/document/Cargo.toml index 74f5e1ecd5..95b325c12d 100644 --- a/core/document/Cargo.toml +++ b/core/document/Cargo.toml @@ -11,4 +11,5 @@ license = "Apache-2.0" [dependencies] log = "0.4" kurbo = "0.8.0" +glam = "0.16.0" serde = { version = "1.0", features = ["derive"] } diff --git a/core/document/src/intersection.rs b/core/document/src/intersection.rs index 9f278d9b9c..b8dd56ad0f 100644 --- a/core/document/src/intersection.rs +++ b/core/document/src/intersection.rs @@ -1,3 +1,4 @@ +use glam::DMat3; use kurbo::{BezPath, Line, PathSeg, Point, Shape, Vec2}; pub fn intersect_quad_bez_path(quad: [Point; 4], shape: &BezPath) -> bool { @@ -45,3 +46,12 @@ pub fn get_arbitrary_point_on_path(path: &BezPath) -> Option { PathSeg::Cubic(cubic) => cubic.p0, }) } + +pub fn point_line_segment_dist(x: Point, a: Point, b: Point) -> f64 { + if (a - b).dot(x - b) * (b - a).dot(x - a) >= 0.0 { + let mat = DMat3::from_cols_array(&[a.x, a.y, 1.0, b.x, b.y, 1.0, x.x, x.y, 1.0]); + (mat.determinant() / (b - a).hypot()).abs() + } else { + f64::sqrt(f64::min((a - x).hypot2(), (b - x).hypot2())) + } +} diff --git a/core/document/src/layers/line.rs b/core/document/src/layers/line.rs index 1e8ff62546..171ea955cc 100644 --- a/core/document/src/layers/line.rs +++ b/core/document/src/layers/line.rs @@ -1,7 +1,9 @@ use kurbo::Point; use crate::LayerId; +use crate::intersection::point_line_segment_dist; +use super::POINT_SELECTION_TOLERANCE; use super::style; use super::LayerData; @@ -30,7 +32,13 @@ impl LayerData for Line { let _ = write!(svg, r#""#, x1, y1, x2, y2, self.style.render(),); } - fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>) {} + fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>) { - fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>) {} + } + + fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>) { + if point_line_segment_dist(point, self.shape.p0, self.shape.p1) < POINT_SELECTION_TOLERANCE { + intersections.push(path.clone()); + } + } } diff --git a/core/document/src/layers/mod.rs b/core/document/src/layers/mod.rs index 98c9b18dd3..a800481761 100644 --- a/core/document/src/layers/mod.rs +++ b/core/document/src/layers/mod.rs @@ -24,7 +24,8 @@ pub use folder::Folder; use crate::LayerId; -pub const KURBO_TOLERANCE: f64 = 0.0001; +pub const KURBO_TOLERANCE: f64 = 0.0001; // TODO: should depend on zoom level +pub const POINT_SELECTION_TOLERANCE: f64 = 10.0; // TODO: should depend on zoom level pub trait LayerData { fn render(&mut self, svg: &mut String); diff --git a/core/editor/src/tool/tools/select.rs b/core/editor/src/tool/tools/select.rs index d4d3d90c32..4709261bb6 100644 --- a/core/editor/src/tool/tools/select.rs +++ b/core/editor/src/tool/tools/select.rs @@ -86,6 +86,8 @@ impl Fsm for SelectToolFsmState { let point = Point::new(data.drag_current.x as f64, data.drag_current.y as f64); if let Some(intersection) = document.intersects_point_root(point).last() { responses.push_back(DocumentMessage::SelectLayers(vec![intersection.clone()]).into()); + } else { + responses.push_back(DocumentMessage::SelectLayers(vec![]).into()); } } else { let quad = [ From b386dbf16e220d7327d12dd62c42ed31b24d1eb3 Mon Sep 17 00:00:00 2001 From: Paul Kupper Date: Sun, 27 Jun 2021 14:33:43 +0200 Subject: [PATCH 04/11] Remove debug prints --- core/document/src/document.rs | 1 - core/document/src/layers/mod.rs | 1 - core/document/src/layers/rect.rs | 1 - 3 files changed, 3 deletions(-) diff --git a/core/document/src/document.rs b/core/document/src/document.rs index c529e244f0..a0c017b449 100644 --- a/core/document/src/document.rs +++ b/core/document/src/document.rs @@ -67,7 +67,6 @@ impl Document { /// Checks whether each layer under `path` intersects with the provided `quad` and adds all intersection layers as paths to `intersections`. pub fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>) { - log::debug!("Intersects Quad {:?}", quad); self.document_folder(path).unwrap().intersects_quad(quad, path, intersections); return; } diff --git a/core/document/src/layers/mod.rs b/core/document/src/layers/mod.rs index 96551c1e46..205c437fdb 100644 --- a/core/document/src/layers/mod.rs +++ b/core/document/src/layers/mod.rs @@ -180,7 +180,6 @@ impl Layer { pub fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>) { let transformed_point = transform_kurbo_point(&point, &self.transform.inverse()); - log::debug!("Point: {:?}, Transformed: {:?}", point, transformed_point); if !self.visible { return; } diff --git a/core/document/src/layers/rect.rs b/core/document/src/layers/rect.rs index eed4d946bb..9c83c55194 100644 --- a/core/document/src/layers/rect.rs +++ b/core/document/src/layers/rect.rs @@ -45,7 +45,6 @@ impl LayerData for Rect { } fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { - log::debug!("Intersects point"); if self.to_kurbo_path(DAffine2::IDENTITY, style).contains(point) { intersections.push(path.clone()); } From 6462f8f94052e4824e8c53b4f2cf85906f16146d Mon Sep 17 00:00:00 2001 From: Paul Kupper Date: Sun, 27 Jun 2021 14:39:16 +0200 Subject: [PATCH 05/11] Run cargo format --- core/document/src/layers/mod.rs | 2 +- core/document/src/layers/rect.rs | 2 +- core/document/src/layers/shape.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/document/src/layers/mod.rs b/core/document/src/layers/mod.rs index 205c437fdb..04fd1c7443 100644 --- a/core/document/src/layers/mod.rs +++ b/core/document/src/layers/mod.rs @@ -20,9 +20,9 @@ pub use shape::Shape; pub mod folder; pub use folder::Folder; +use crate::intersection::transform_kurbo_point; use crate::DocumentError; use crate::LayerId; -use crate::intersection::transform_kurbo_point; pub const KURBO_TOLERANCE: f64 = 0.0001; // TODO: should depend on zoom level pub const POINT_SELECTION_TOLERANCE: f64 = 10.0; // TODO: should depend on zoom level diff --git a/core/document/src/layers/rect.rs b/core/document/src/layers/rect.rs index 9c83c55194..ec3a100449 100644 --- a/core/document/src/layers/rect.rs +++ b/core/document/src/layers/rect.rs @@ -4,8 +4,8 @@ use kurbo::Point; use kurbo::Shape; use crate::intersection::intersect_quad_bez_path; -use crate::LayerId; use crate::intersection::transform_kurbo_point; +use crate::LayerId; use super::style; use super::LayerData; diff --git a/core/document/src/layers/shape.rs b/core/document/src/layers/shape.rs index 61e58ef1a3..e7a3026338 100644 --- a/core/document/src/layers/shape.rs +++ b/core/document/src/layers/shape.rs @@ -2,8 +2,8 @@ use glam::DAffine2; use kurbo::Point; use crate::intersection::intersect_quad_bez_path; -use crate::LayerId; use crate::intersection::transform_kurbo_point; +use crate::LayerId; use kurbo::BezPath; use kurbo::Vec2; From 61300fff412ba5b13d2930501b0125e2a5e21f0f Mon Sep 17 00:00:00 2001 From: Paul Kupper Date: Sun, 27 Jun 2021 17:56:22 +0200 Subject: [PATCH 06/11] Use DVec2 instead of kurbo::Point --- Cargo.lock | 1 - core/document/src/document.rs | 11 ++++--- core/document/src/intersection.rs | 29 ++++++++++--------- core/document/src/layers/ellipse.rs | 10 +++---- core/document/src/layers/folder.rs | 6 ++-- core/document/src/layers/line.rs | 10 +++---- core/document/src/layers/mod.rs | 25 ++++++++-------- core/document/src/layers/polyline.rs | 6 ++-- core/document/src/layers/rect.rs | 7 ++--- core/document/src/layers/shape.rs | 9 +++--- core/editor/Cargo.toml | 3 -- .../src/document/document_message_handler.rs | 1 - core/editor/src/tool/tools/select.rs | 11 ++++--- 13 files changed, 60 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6831c7a13d..74e2147412 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,7 +70,6 @@ dependencies = [ "glam", "graphite-document-core", "graphite-proc-macros", - "kurbo", "log", "serde", "thiserror", diff --git a/core/document/src/document.rs b/core/document/src/document.rs index a0c017b449..7ce44efdd6 100644 --- a/core/document/src/document.rs +++ b/core/document/src/document.rs @@ -1,5 +1,4 @@ -use glam::DAffine2; -use kurbo::Point; +use glam::{DAffine2, DVec2}; use crate::{ layers::{self, style::PathStyle, Folder, Layer, LayerDataTypes, Line, PolyLine, Rect, Shape}, @@ -66,26 +65,26 @@ impl Document { } /// Checks whether each layer under `path` intersects with the provided `quad` and adds all intersection layers as paths to `intersections`. - pub fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>) { + pub fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>) { self.document_folder(path).unwrap().intersects_quad(quad, path, intersections); return; } /// Checks whether each layer under the root path intersects with the provided `quad` and returns the paths to all intersecting layers. - pub fn intersects_quad_root(&self, quad: [Point; 4]) -> Vec> { + pub fn intersects_quad_root(&self, quad: [DVec2; 4]) -> Vec> { let mut intersections = Vec::new(); self.intersects_quad(quad, &mut vec![], &mut intersections); intersections } /// Checks whether each layer under `path` intersects with the provided `point` and adds all intersection layers as paths to `intersections`. - pub fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>) { + pub fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>) { self.document_folder(path).unwrap().intersects_point(point, path, intersections); return; } /// Checks whether each layer intersects with the given point and returns the paths to intersecting layers. - pub fn intersects_point_root(&self, point: Point) -> Vec> { + pub fn intersects_point_root(&self, point: DVec2) -> Vec> { let mut intersections = Vec::new(); self.intersects_point(point, &mut vec![], &mut intersections); intersections diff --git a/core/document/src/intersection.rs b/core/document/src/intersection.rs index eb0bd88d61..8f75c47370 100644 --- a/core/document/src/intersection.rs +++ b/core/document/src/intersection.rs @@ -1,8 +1,17 @@ -use glam::{DAffine2, DMat3, DVec2}; +use glam::{DMat3, DVec2}; use kurbo::{BezPath, Line, PathSeg, Point, Shape, Vec2}; -pub fn intersect_quad_bez_path(quad: [Point; 4], shape: &BezPath) -> bool { - let lines = vec![Line::new(quad[0], quad[1]), Line::new(quad[1], quad[2]), Line::new(quad[2], quad[3]), Line::new(quad[3], quad[0])]; +fn to_point(vec: DVec2) -> Point { + Point::new(vec.x, vec.y) +} + +pub fn intersect_quad_bez_path(quad: [DVec2; 4], shape: &BezPath) -> bool { + let lines = vec![ + Line::new(to_point(quad[0]), to_point(quad[1])), + Line::new(to_point(quad[1]), to_point(quad[2])), + Line::new(to_point(quad[2]), to_point(quad[3])), + Line::new(to_point(quad[3]), to_point(quad[0])), + ]; // check if outlines intersect for path_segment in shape.segments() { for line in &lines { @@ -12,7 +21,7 @@ pub fn intersect_quad_bez_path(quad: [Point; 4], shape: &BezPath) -> bool { } } // check if selection is entirely within the shape - if shape.contains(quad[0]) { + if shape.contains(to_point(quad[0])) { return true; } // check if shape is entirely within the selection @@ -47,17 +56,11 @@ pub fn get_arbitrary_point_on_path(path: &BezPath) -> Option { }) } -pub fn point_line_segment_dist(x: Point, a: Point, b: Point) -> f64 { +pub fn point_line_segment_dist(x: DVec2, a: DVec2, b: DVec2) -> f64 { if (a - b).dot(x - b) * (b - a).dot(x - a) >= 0.0 { let mat = DMat3::from_cols_array(&[a.x, a.y, 1.0, b.x, b.y, 1.0, x.x, x.y, 1.0]); - (mat.determinant() / (b - a).hypot()).abs() + (mat.determinant() / (b - a).length()).abs() } else { - f64::sqrt(f64::min((a - x).hypot2(), (b - x).hypot2())) + f64::sqrt(f64::min((a - x).length_squared(), (b - x).length_squared())) } } - -pub fn transform_kurbo_point(point: &Point, transform: &DAffine2) -> Point { - let original = DVec2::new(point.x, point.y); - let transformed = transform.transform_point2(original); - Point::new(transformed.x, transformed.y) -} diff --git a/core/document/src/layers/ellipse.rs b/core/document/src/layers/ellipse.rs index 19ffd81fbc..a2c764426e 100644 --- a/core/document/src/layers/ellipse.rs +++ b/core/document/src/layers/ellipse.rs @@ -1,10 +1,8 @@ use glam::DAffine2; -use kurbo::Point; +use glam::DVec2; use kurbo::Shape; -use kurbo::Vec2; use crate::intersection::intersect_quad_bez_path; -use crate::intersection::transform_kurbo_point; use crate::LayerId; use super::style; @@ -31,14 +29,14 @@ impl LayerData for Ellipse { let _ = write!(svg, r#""#, self.to_kurbo_path(transform, style).to_svg(), style.render()); } - fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { + fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style)) { intersections.push(path.clone()); } } - fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { - if self.to_kurbo_path(DAffine2::IDENTITY, style).contains(point) { + fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { + if self.to_kurbo_path(DAffine2::IDENTITY, style).contains(kurbo::Point::new(point.x, point.y)) { intersections.push(path.clone()); } } diff --git a/core/document/src/layers/folder.rs b/core/document/src/layers/folder.rs index eff15daaa0..28e1d129ff 100644 --- a/core/document/src/layers/folder.rs +++ b/core/document/src/layers/folder.rs @@ -1,4 +1,4 @@ -use kurbo::Point; +use glam::DVec2; use crate::{DocumentError, LayerId}; @@ -31,7 +31,7 @@ impl LayerData for Folder { let _ = writeln!(svg, ""); } - fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>, _style: style::PathStyle) { + fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, _style: style::PathStyle) { for (layer, layer_id) in self.layers().iter().zip(&self.layer_ids) { path.push(*layer_id); layer.intersects_quad(quad, path, intersections); @@ -39,7 +39,7 @@ impl LayerData for Folder { } } - fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>, _style: style::PathStyle) { + fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>, _style: style::PathStyle) { log::debug!("Folder"); for (layer, layer_id) in self.layers().iter().zip(&self.layer_ids) { log::debug!("Layer: {}", layer_id); diff --git a/core/document/src/layers/line.rs b/core/document/src/layers/line.rs index e4b5a3a5ff..9199002af1 100644 --- a/core/document/src/layers/line.rs +++ b/core/document/src/layers/line.rs @@ -1,11 +1,8 @@ use glam::DVec2; use kurbo::Point; -use crate::intersection::point_line_segment_dist; use crate::LayerId; -use super::POINT_SELECTION_TOLERANCE; - use super::style; use super::LayerData; @@ -38,9 +35,12 @@ impl LayerData for Line { let _ = write!(svg, r#""#, x1, y1, x2, y2, style.render(),); } - fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) {} + fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { + } - fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { + fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { + // let [x1, y1] = transform.translation.to_array(); + // let [x2, y2] = transform.transform_point2(DVec2::ONE).to_array(); // if point_line_segment_dist(point, self.shape.p0, self.shape.p1) < POINT_SELECTION_TOLERANCE { // intersections.push(path.clone()); // } diff --git a/core/document/src/layers/mod.rs b/core/document/src/layers/mod.rs index 04fd1c7443..56c69549f2 100644 --- a/core/document/src/layers/mod.rs +++ b/core/document/src/layers/mod.rs @@ -4,8 +4,8 @@ pub mod ellipse; pub use ellipse::Ellipse; pub mod line; +use glam::DVec2; use kurbo::BezPath; -use kurbo::Point; pub use line::Line; pub mod rect; @@ -20,7 +20,6 @@ pub use shape::Shape; pub mod folder; pub use folder::Folder; -use crate::intersection::transform_kurbo_point; use crate::DocumentError; use crate::LayerId; @@ -30,8 +29,8 @@ pub const POINT_SELECTION_TOLERANCE: f64 = 10.0; // TODO: should depend on zoom pub trait LayerData { fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle); fn to_kurbo_path(&self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath; - fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle); - fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle); + fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle); + fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle); } #[derive(Debug, Clone, PartialEq)] @@ -101,7 +100,7 @@ impl LayerDataTypes { } } - pub fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { + pub fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { call_intersects_quad! { self.intersects_quad(quad, path, intersections, style) { Folder, @@ -114,7 +113,7 @@ impl LayerDataTypes { } } - pub fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { + pub fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { call_intersects_point! { self.intersects_point(point, path, intersections, style) { Folder, @@ -164,13 +163,13 @@ impl Layer { self.cache.as_str() } - pub fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>) { + pub fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>) { let inv_transform = self.transform.inverse(); let transformed_quad = [ - transform_kurbo_point(&quad[0], &inv_transform), - transform_kurbo_point(&quad[1], &inv_transform), - transform_kurbo_point(&quad[2], &inv_transform), - transform_kurbo_point(&quad[3], &inv_transform), + inv_transform.transform_point2(quad[0]), + inv_transform.transform_point2(quad[1]), + inv_transform.transform_point2(quad[2]), + inv_transform.transform_point2(quad[3]), ]; if !self.visible { return; @@ -178,8 +177,8 @@ impl Layer { self.data.intersects_quad(transformed_quad, path, intersections, self.style) } - pub fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>) { - let transformed_point = transform_kurbo_point(&point, &self.transform.inverse()); + pub fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>) { + let transformed_point = self.transform.inverse().transform_point2(point); if !self.visible { return; } diff --git a/core/document/src/layers/polyline.rs b/core/document/src/layers/polyline.rs index da11f44919..279701901a 100644 --- a/core/document/src/layers/polyline.rs +++ b/core/document/src/layers/polyline.rs @@ -1,4 +1,4 @@ -use kurbo::Point; +use glam::DVec2; use crate::LayerId; @@ -45,9 +45,9 @@ impl LayerData for PolyLine { let _ = write!(svg, r#""{} />"#, style.render()); } - fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) {} + fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) {} - fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) {} + fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) {} } #[cfg(test)] diff --git a/core/document/src/layers/rect.rs b/core/document/src/layers/rect.rs index ec3a100449..1677e80d52 100644 --- a/core/document/src/layers/rect.rs +++ b/core/document/src/layers/rect.rs @@ -4,7 +4,6 @@ use kurbo::Point; use kurbo::Shape; use crate::intersection::intersect_quad_bez_path; -use crate::intersection::transform_kurbo_point; use crate::LayerId; use super::style; @@ -38,14 +37,14 @@ impl LayerData for Rect { let _ = write!(svg, r#""#, self.to_kurbo_path(transform, style).to_svg(), style.render()); } - fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { + fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style)) { intersections.push(path.clone()); } } - fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { - if self.to_kurbo_path(DAffine2::IDENTITY, style).contains(point) { + fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { + if self.to_kurbo_path(DAffine2::IDENTITY, style).contains(kurbo::Point::new(point.x, point.y)) { intersections.push(path.clone()); } } diff --git a/core/document/src/layers/shape.rs b/core/document/src/layers/shape.rs index e7a3026338..25b5099557 100644 --- a/core/document/src/layers/shape.rs +++ b/core/document/src/layers/shape.rs @@ -1,8 +1,7 @@ use glam::DAffine2; -use kurbo::Point; +use glam::DVec2; use crate::intersection::intersect_quad_bez_path; -use crate::intersection::transform_kurbo_point; use crate::LayerId; use kurbo::BezPath; use kurbo::Vec2; @@ -72,14 +71,14 @@ impl LayerData for Shape { let _ = write!(svg, r#""#, self.to_kurbo_path(transform, style).to_svg(), style.render()); } - fn intersects_quad(&self, quad: [Point; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { + fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style)) { intersections.push(path.clone()); } } - fn intersects_point(&self, point: Point, path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { - if kurbo::Shape::contains(&self.to_kurbo_path(DAffine2::IDENTITY, style), point) { + fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { + if kurbo::Shape::contains(&self.to_kurbo_path(DAffine2::IDENTITY, style), kurbo::Point::new(point.x, point.y)) { intersections.push(path.clone()); } } diff --git a/core/editor/Cargo.toml b/core/editor/Cargo.toml index 12e527026e..aa701dc168 100644 --- a/core/editor/Cargo.toml +++ b/core/editor/Cargo.toml @@ -9,9 +9,6 @@ repository = "https://github.com/GraphiteEditor/Graphite" license = "Apache-2.0" [dependencies] -# TODO: Swich to kurbo release when derive `PartialEq` is available for BezPath -#kurbo = "0.8.0" -kurbo = {git="https://github.com/GraphiteEditor/kurbo.git", branch="bezpath-partial-eq"} log = "0.4" bitflags = "1.2.1" thiserror = "1.0.24" diff --git a/core/editor/src/document/document_message_handler.rs b/core/editor/src/document/document_message_handler.rs index b449ca6509..571dc57f02 100644 --- a/core/editor/src/document/document_message_handler.rs +++ b/core/editor/src/document/document_message_handler.rs @@ -4,7 +4,6 @@ use crate::{ }; use document_core::{DocumentResponse, LayerId, Operation as DocumentOperation}; use glam::{DAffine2, DVec2}; -use log::info; use crate::document::Document; use std::collections::VecDeque; diff --git a/core/editor/src/tool/tools/select.rs b/core/editor/src/tool/tools/select.rs index 0203f43fec..0af4bcd6f5 100644 --- a/core/editor/src/tool/tools/select.rs +++ b/core/editor/src/tool/tools/select.rs @@ -4,7 +4,6 @@ use document_core::layers::style::Fill; use document_core::layers::style::Stroke; use document_core::Operation; use glam::{DAffine2, DVec2}; -use kurbo::Point; use crate::input::{mouse::ViewportPosition, InputPreprocessor}; use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; @@ -85,7 +84,7 @@ impl Fsm for SelectToolFsmState { responses.push_back(Operation::ClearWorkingFolder.into()); // TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100) if data.drag_start == data.drag_current { - let point = Point::new(data.drag_current.x as f64, data.drag_current.y as f64); + let point = DVec2::new(data.drag_current.x as f64, data.drag_current.y as f64); if let Some(intersection) = document.intersects_point_root(point).last() { responses.push_back(DocumentMessage::SelectLayers(vec![intersection.clone()]).into()); } else { @@ -93,10 +92,10 @@ impl Fsm for SelectToolFsmState { } } else { let quad = [ - Point::new(data.drag_start.x as f64, data.drag_start.y as f64), - Point::new(data.drag_current.x as f64, data.drag_start.y as f64), - Point::new(data.drag_current.x as f64, data.drag_current.y as f64), - Point::new(data.drag_start.x as f64, data.drag_current.y as f64), + DVec2::new(data.drag_start.x as f64, data.drag_start.y as f64), + DVec2::new(data.drag_current.x as f64, data.drag_start.y as f64), + DVec2::new(data.drag_current.x as f64, data.drag_current.y as f64), + DVec2::new(data.drag_start.x as f64, data.drag_current.y as f64), ]; responses.push_back(DocumentMessage::SelectLayers(document.intersects_quad_root(quad)).into()); } From 3fc53c9c1d5b709d2c9841794b008510b5265b01 Mon Sep 17 00:00:00 2001 From: Paul Kupper Date: Sun, 27 Jun 2021 18:44:38 +0200 Subject: [PATCH 07/11] Line and polyline intersection --- core/document/src/intersection.rs | 4 ++-- core/document/src/layers/ellipse.rs | 2 +- core/document/src/layers/line.rs | 17 +++++++++++------ core/document/src/layers/mod.rs | 2 +- core/document/src/layers/polyline.rs | 21 ++++++++++++++++----- core/document/src/layers/rect.rs | 2 +- core/document/src/layers/shape.rs | 2 +- 7 files changed, 33 insertions(+), 17 deletions(-) diff --git a/core/document/src/intersection.rs b/core/document/src/intersection.rs index 8f75c47370..974339d0b3 100644 --- a/core/document/src/intersection.rs +++ b/core/document/src/intersection.rs @@ -5,7 +5,7 @@ fn to_point(vec: DVec2) -> Point { Point::new(vec.x, vec.y) } -pub fn intersect_quad_bez_path(quad: [DVec2; 4], shape: &BezPath) -> bool { +pub fn intersect_quad_bez_path(quad: [DVec2; 4], shape: &BezPath, closed: bool) -> bool { let lines = vec![ Line::new(to_point(quad[0]), to_point(quad[1])), Line::new(to_point(quad[1]), to_point(quad[2])), @@ -21,7 +21,7 @@ pub fn intersect_quad_bez_path(quad: [DVec2; 4], shape: &BezPath) -> bool { } } // check if selection is entirely within the shape - if shape.contains(to_point(quad[0])) { + if closed && shape.contains(to_point(quad[0])) { return true; } // check if shape is entirely within the selection diff --git a/core/document/src/layers/ellipse.rs b/core/document/src/layers/ellipse.rs index a2c764426e..42f7a16652 100644 --- a/core/document/src/layers/ellipse.rs +++ b/core/document/src/layers/ellipse.rs @@ -30,7 +30,7 @@ impl LayerData for Ellipse { } fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { - if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style)) { + if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style), true) { intersections.push(path.clone()); } } diff --git a/core/document/src/layers/line.rs b/core/document/src/layers/line.rs index 9199002af1..58df9489f5 100644 --- a/core/document/src/layers/line.rs +++ b/core/document/src/layers/line.rs @@ -1,8 +1,12 @@ +use glam::DAffine2; use glam::DVec2; use kurbo::Point; use crate::LayerId; +use crate::intersection::intersect_quad_bez_path; +use crate::intersection::point_line_segment_dist; +use super::POINT_SELECTION_TOLERANCE; use super::style; use super::LayerData; @@ -36,13 +40,14 @@ impl LayerData for Line { } fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { + if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style), false) { + intersections.push(path.clone()); + } } - fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { - // let [x1, y1] = transform.translation.to_array(); - // let [x2, y2] = transform.transform_point2(DVec2::ONE).to_array(); - // if point_line_segment_dist(point, self.shape.p0, self.shape.p1) < POINT_SELECTION_TOLERANCE { - // intersections.push(path.clone()); - // } + fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>, _style: style::PathStyle) { + if point_line_segment_dist(point, DVec2::ZERO, DVec2::ONE) < POINT_SELECTION_TOLERANCE { + intersections.push(path.clone()); + } } } diff --git a/core/document/src/layers/mod.rs b/core/document/src/layers/mod.rs index 56c69549f2..5effab0b92 100644 --- a/core/document/src/layers/mod.rs +++ b/core/document/src/layers/mod.rs @@ -24,7 +24,7 @@ use crate::DocumentError; use crate::LayerId; pub const KURBO_TOLERANCE: f64 = 0.0001; // TODO: should depend on zoom level -pub const POINT_SELECTION_TOLERANCE: f64 = 10.0; // TODO: should depend on zoom level +pub const POINT_SELECTION_TOLERANCE: f64 = 0.1; // TODO: should depend on zoom level pub trait LayerData { fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle); diff --git a/core/document/src/layers/polyline.rs b/core/document/src/layers/polyline.rs index 279701901a..7913d408f7 100644 --- a/core/document/src/layers/polyline.rs +++ b/core/document/src/layers/polyline.rs @@ -1,10 +1,10 @@ -use glam::DVec2; +use glam::{DAffine2, DVec2}; -use crate::LayerId; +use crate::{LayerId, intersection::{intersect_quad_bez_path, point_line_segment_dist}}; use std::fmt::Write; -use super::{style, LayerData}; +use super::{LayerData, POINT_SELECTION_TOLERANCE, style}; #[derive(Debug, Clone, PartialEq)] pub struct PolyLine { @@ -45,9 +45,20 @@ impl LayerData for PolyLine { let _ = write!(svg, r#""{} />"#, style.render()); } - fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) {} + fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { + if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style), false) { + intersections.push(path.clone()); + } + } - fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) {} + fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>, _style: style::PathStyle) { + for pair in self.points.windows(2) { + if point_line_segment_dist(point, pair[0], pair[1]) < POINT_SELECTION_TOLERANCE { + intersections.push(path.clone()); + return; + } + } + } } #[cfg(test)] diff --git a/core/document/src/layers/rect.rs b/core/document/src/layers/rect.rs index 1677e80d52..0b3ca9a3ba 100644 --- a/core/document/src/layers/rect.rs +++ b/core/document/src/layers/rect.rs @@ -38,7 +38,7 @@ impl LayerData for Rect { } fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { - if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style)) { + if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style), true) { intersections.push(path.clone()); } } diff --git a/core/document/src/layers/shape.rs b/core/document/src/layers/shape.rs index 25b5099557..c31cec90ab 100644 --- a/core/document/src/layers/shape.rs +++ b/core/document/src/layers/shape.rs @@ -72,7 +72,7 @@ impl LayerData for Shape { } fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { - if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style)) { + if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style), true) { intersections.push(path.clone()); } } From fc0ba99561e146f9f8acee338f6cfc9705ae24da Mon Sep 17 00:00:00 2001 From: Paul Kupper Date: Sun, 27 Jun 2021 18:57:33 +0200 Subject: [PATCH 08/11] Run cargo format --- core/document/src/layers/line.rs | 4 ++-- core/document/src/layers/polyline.rs | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/core/document/src/layers/line.rs b/core/document/src/layers/line.rs index 58df9489f5..e56473e0ca 100644 --- a/core/document/src/layers/line.rs +++ b/core/document/src/layers/line.rs @@ -2,13 +2,13 @@ use glam::DAffine2; use glam::DVec2; use kurbo::Point; -use crate::LayerId; use crate::intersection::intersect_quad_bez_path; use crate::intersection::point_line_segment_dist; +use crate::LayerId; -use super::POINT_SELECTION_TOLERANCE; use super::style; use super::LayerData; +use super::POINT_SELECTION_TOLERANCE; use std::fmt::Write; diff --git a/core/document/src/layers/polyline.rs b/core/document/src/layers/polyline.rs index 7913d408f7..dc78b67711 100644 --- a/core/document/src/layers/polyline.rs +++ b/core/document/src/layers/polyline.rs @@ -1,10 +1,13 @@ use glam::{DAffine2, DVec2}; -use crate::{LayerId, intersection::{intersect_quad_bez_path, point_line_segment_dist}}; +use crate::{ + intersection::{intersect_quad_bez_path, point_line_segment_dist}, + LayerId, +}; use std::fmt::Write; -use super::{LayerData, POINT_SELECTION_TOLERANCE, style}; +use super::{style, LayerData, POINT_SELECTION_TOLERANCE}; #[derive(Debug, Clone, PartialEq)] pub struct PolyLine { From 9e390bac8eb71d9fcc1cc1214a82c898b7d0a8e4 Mon Sep 17 00:00:00 2001 From: Paul Kupper Date: Sun, 4 Jul 2021 20:32:43 +0200 Subject: [PATCH 09/11] Add fix for missing layer panel update --- core/editor/src/document/document_message_handler.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/editor/src/document/document_message_handler.rs b/core/editor/src/document/document_message_handler.rs index 571dc57f02..90b30db2d9 100644 --- a/core/editor/src/document/document_message_handler.rs +++ b/core/editor/src/document/document_message_handler.rs @@ -232,6 +232,8 @@ impl MessageHandler for DocumentMessageHand for path in paths { responses.extend(self.select_layer(&path)); } + // TODO: Correctly update layer panel in clear_selection instead of here + responses.extend(self.handle_folder_changed(Vec::new())); } Undo => { // this is a temporary fix and will be addressed by #123 From 2c759adfd299f9be7f7a17e684f217751fb941b6 Mon Sep 17 00:00:00 2001 From: Paul Kupper Date: Sun, 4 Jul 2021 21:38:04 +0200 Subject: [PATCH 10/11] Replace point selection with box selection --- core/document/src/document.rs | 13 ----------- core/document/src/intersection.rs | 11 +-------- core/document/src/layers/ellipse.rs | 9 +------- core/document/src/layers/folder.rs | 10 -------- core/document/src/layers/line.rs | 8 ------- core/document/src/layers/mod.rs | 33 +-------------------------- core/document/src/layers/polyline.rs | 16 ++----------- core/document/src/layers/rect.rs | 7 ------ core/document/src/layers/shape.rs | 6 ----- core/editor/src/tool/tools/select.rs | 34 +++++++++++++++++++--------- 10 files changed, 28 insertions(+), 119 deletions(-) diff --git a/core/document/src/document.rs b/core/document/src/document.rs index 70989d26a8..36a4aac5ae 100644 --- a/core/document/src/document.rs +++ b/core/document/src/document.rs @@ -77,19 +77,6 @@ impl Document { intersections } - /// Checks whether each layer under `path` intersects with the provided `point` and adds all intersection layers as paths to `intersections`. - pub fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>) { - self.document_folder(path).unwrap().intersects_point(point, path, intersections); - return; - } - - /// Checks whether each layer intersects with the given point and returns the paths to intersecting layers. - pub fn intersects_point_root(&self, point: DVec2) -> Vec> { - let mut intersections = Vec::new(); - self.intersects_point(point, &mut vec![], &mut intersections); - intersections - } - fn is_mounted(&self, mount_path: &[LayerId], path: &[LayerId]) -> bool { path.starts_with(mount_path) && self.work_mounted } diff --git a/core/document/src/intersection.rs b/core/document/src/intersection.rs index 974339d0b3..9e749e07ff 100644 --- a/core/document/src/intersection.rs +++ b/core/document/src/intersection.rs @@ -1,4 +1,4 @@ -use glam::{DMat3, DVec2}; +use glam::DVec2; use kurbo::{BezPath, Line, PathSeg, Point, Shape, Vec2}; fn to_point(vec: DVec2) -> Point { @@ -55,12 +55,3 @@ pub fn get_arbitrary_point_on_path(path: &BezPath) -> Option { PathSeg::Cubic(cubic) => cubic.p0, }) } - -pub fn point_line_segment_dist(x: DVec2, a: DVec2, b: DVec2) -> f64 { - if (a - b).dot(x - b) * (b - a).dot(x - a) >= 0.0 { - let mat = DMat3::from_cols_array(&[a.x, a.y, 1.0, b.x, b.y, 1.0, x.x, x.y, 1.0]); - (mat.determinant() / (b - a).length()).abs() - } else { - f64::sqrt(f64::min((a - x).length_squared(), (b - x).length_squared())) - } -} diff --git a/core/document/src/layers/ellipse.rs b/core/document/src/layers/ellipse.rs index 9f6a7bc576..54a0a046b1 100644 --- a/core/document/src/layers/ellipse.rs +++ b/core/document/src/layers/ellipse.rs @@ -7,7 +7,6 @@ use crate::LayerId; use super::style; use super::LayerData; -use super::KURBO_TOLERANCE; use std::fmt::Write; @@ -22,7 +21,7 @@ impl Ellipse { impl LayerData for Ellipse { fn to_kurbo_path(&self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath { - kurbo::Ellipse::from_affine(kurbo::Affine::new(transform.to_cols_array())).to_path(KURBO_TOLERANCE) + kurbo::Ellipse::from_affine(kurbo::Affine::new(transform.to_cols_array())).to_path(0.01) } fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) { @@ -34,10 +33,4 @@ impl LayerData for Ellipse { intersections.push(path.clone()); } } - - fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { - if self.to_kurbo_path(DAffine2::IDENTITY, style).contains(kurbo::Point::new(point.x, point.y)) { - intersections.push(path.clone()); - } - } } diff --git a/core/document/src/layers/folder.rs b/core/document/src/layers/folder.rs index a5bade891c..4cac39b828 100644 --- a/core/document/src/layers/folder.rs +++ b/core/document/src/layers/folder.rs @@ -38,16 +38,6 @@ impl LayerData for Folder { path.pop(); } } - - fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>, _style: style::PathStyle) { - log::debug!("Folder"); - for (layer, layer_id) in self.layers().iter().zip(&self.layer_ids) { - log::debug!("Layer: {}", layer_id); - path.push(*layer_id); - layer.intersects_point(point, path, intersections); - path.pop(); - } - } } impl Folder { diff --git a/core/document/src/layers/line.rs b/core/document/src/layers/line.rs index a8b814eebe..0323d8573c 100644 --- a/core/document/src/layers/line.rs +++ b/core/document/src/layers/line.rs @@ -3,12 +3,10 @@ use glam::DVec2; use kurbo::Point; use crate::intersection::intersect_quad_bez_path; -use crate::intersection::point_line_segment_dist; use crate::LayerId; use super::style; use super::LayerData; -use super::POINT_SELECTION_TOLERANCE; use std::fmt::Write; @@ -44,10 +42,4 @@ impl LayerData for Line { intersections.push(path.clone()); } } - - fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>, _style: style::PathStyle) { - if point_line_segment_dist(point, DVec2::ZERO, DVec2::ONE) < POINT_SELECTION_TOLERANCE { - intersections.push(path.clone()); - } - } } diff --git a/core/document/src/layers/mod.rs b/core/document/src/layers/mod.rs index ad2364c9d3..e5933711e3 100644 --- a/core/document/src/layers/mod.rs +++ b/core/document/src/layers/mod.rs @@ -23,14 +23,12 @@ pub use folder::Folder; use crate::DocumentError; use crate::LayerId; -pub const KURBO_TOLERANCE: f64 = 0.0001; // TODO: should depend on zoom level -pub const POINT_SELECTION_TOLERANCE: f64 = 0.1; // TODO: should depend on zoom level +pub const SELECTION_TOLERANCE: f64 = 5.0; pub trait LayerData { fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle); fn to_kurbo_path(&self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath; fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle); - fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle); } #[derive(Debug, Clone)] @@ -66,14 +64,6 @@ macro_rules! call_intersects_quad { }; } -macro_rules! call_intersects_point { - ($self:ident.intersects_point($point:ident, $path:ident, $intersections:ident, $style:ident) { $($variant:ident),* }) => { - match $self { - $(Self::$variant(x) => x.intersects_point($point, $path, $intersections, $style)),* - } - }; -} - impl LayerDataTypes { pub fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) { call_render! { @@ -112,19 +102,6 @@ impl LayerDataTypes { } } } - - pub fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { - call_intersects_point! { - self.intersects_point(point, path, intersections, style) { - Folder, - Ellipse, - Rect, - Line, - PolyLine, - Shape - } - } - } } #[derive(Debug, Clone)] @@ -177,14 +154,6 @@ impl Layer { self.data.intersects_quad(transformed_quad, path, intersections, self.style) } - pub fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>) { - let transformed_point = self.transform.inverse().transform_point2(point); - if !self.visible { - return; - } - self.data.intersects_point(transformed_point, path, intersections, self.style) - } - pub fn render_on(&mut self, svg: &mut String) { *svg += self.render(); } diff --git a/core/document/src/layers/polyline.rs b/core/document/src/layers/polyline.rs index 40fa69d0c6..fcda910966 100644 --- a/core/document/src/layers/polyline.rs +++ b/core/document/src/layers/polyline.rs @@ -1,13 +1,10 @@ use glam::{DAffine2, DVec2}; -use crate::{ - intersection::{intersect_quad_bez_path, point_line_segment_dist}, - LayerId, -}; +use crate::{intersection::intersect_quad_bez_path, LayerId}; use std::fmt::Write; -use super::{style, LayerData, POINT_SELECTION_TOLERANCE}; +use super::{style, LayerData}; #[derive(Debug, Clone)] pub struct PolyLine { @@ -53,15 +50,6 @@ impl LayerData for PolyLine { intersections.push(path.clone()); } } - - fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>, _style: style::PathStyle) { - for pair in self.points.windows(2) { - if point_line_segment_dist(point, pair[0], pair[1]) < POINT_SELECTION_TOLERANCE { - intersections.push(path.clone()); - return; - } - } - } } #[cfg(test)] diff --git a/core/document/src/layers/rect.rs b/core/document/src/layers/rect.rs index cf266ec61d..d02e8850e2 100644 --- a/core/document/src/layers/rect.rs +++ b/core/document/src/layers/rect.rs @@ -1,7 +1,6 @@ use glam::DAffine2; use glam::DVec2; use kurbo::Point; -use kurbo::Shape; use crate::intersection::intersect_quad_bez_path; use crate::LayerId; @@ -42,10 +41,4 @@ impl LayerData for Rect { intersections.push(path.clone()); } } - - fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { - if self.to_kurbo_path(DAffine2::IDENTITY, style).contains(kurbo::Point::new(point.x, point.y)) { - intersections.push(path.clone()); - } - } } diff --git a/core/document/src/layers/shape.rs b/core/document/src/layers/shape.rs index 87b934313d..6ca52e224b 100644 --- a/core/document/src/layers/shape.rs +++ b/core/document/src/layers/shape.rs @@ -76,10 +76,4 @@ impl LayerData for Shape { intersections.push(path.clone()); } } - - fn intersects_point(&self, point: DVec2, path: &mut Vec, intersections: &mut Vec>, style: style::PathStyle) { - if kurbo::Shape::contains(&self.to_kurbo_path(DAffine2::IDENTITY, style), kurbo::Point::new(point.x, point.y)) { - intersections.push(path.clone()); - } - } } diff --git a/core/editor/src/tool/tools/select.rs b/core/editor/src/tool/tools/select.rs index 0af4bcd6f5..832824c8cd 100644 --- a/core/editor/src/tool/tools/select.rs +++ b/core/editor/src/tool/tools/select.rs @@ -1,7 +1,7 @@ use document_core::color::Color; -use document_core::layers::style; use document_core::layers::style::Fill; use document_core::layers::style::Stroke; +use document_core::layers::{style, SELECTION_TOLERANCE}; use document_core::Operation; use glam::{DAffine2, DVec2}; @@ -82,27 +82,39 @@ impl Fsm for SelectToolFsmState { data.drag_current = input.mouse.position; responses.push_back(Operation::ClearWorkingFolder.into()); - // TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100) + + let (point_1, point_2) = if data.drag_start == data.drag_current { + let (x, y) = (data.drag_current.x as f64, data.drag_current.y as f64); + ( + DVec2::new(x - SELECTION_TOLERANCE, y - SELECTION_TOLERANCE), + DVec2::new(x + SELECTION_TOLERANCE, y + SELECTION_TOLERANCE), + ) + } else { + ( + DVec2::new(data.drag_start.x as f64, data.drag_start.y as f64), + DVec2::new(data.drag_current.x as f64, data.drag_current.y as f64), + ) + }; + + let quad = [ + DVec2::new(point_1.x, point_1.y), + DVec2::new(point_2.x, point_1.y), + DVec2::new(point_2.x, point_2.y), + DVec2::new(point_1.x, point_2.y), + ]; + if data.drag_start == data.drag_current { - let point = DVec2::new(data.drag_current.x as f64, data.drag_current.y as f64); - if let Some(intersection) = document.intersects_point_root(point).last() { + if let Some(intersection) = document.intersects_quad_root(quad).last() { responses.push_back(DocumentMessage::SelectLayers(vec![intersection.clone()]).into()); } else { responses.push_back(DocumentMessage::SelectLayers(vec![]).into()); } } else { - let quad = [ - DVec2::new(data.drag_start.x as f64, data.drag_start.y as f64), - DVec2::new(data.drag_current.x as f64, data.drag_start.y as f64), - DVec2::new(data.drag_current.x as f64, data.drag_current.y as f64), - DVec2::new(data.drag_start.x as f64, data.drag_current.y as f64), - ]; responses.push_back(DocumentMessage::SelectLayers(document.intersects_quad_root(quad)).into()); } Ready } - // TODO - simplify with or_patterns when rust 1.53.0 is stable (https://github.com/rust-lang/rust/issues/54883) (Dragging, Abort) => { responses.push_back(Operation::DiscardWorkingFolder.into()); From 5f0eb5ff0303ad2652ed0c643221674c4097cff9 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sun, 4 Jul 2021 15:54:28 -0700 Subject: [PATCH 11/11] Formatting --- core/document/src/layers/polyline.rs | 2 +- core/editor/src/input/input_mapper.rs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/core/document/src/layers/polyline.rs b/core/document/src/layers/polyline.rs index 2cf911b8b5..068b7585ab 100644 --- a/core/document/src/layers/polyline.rs +++ b/core/document/src/layers/polyline.rs @@ -1,5 +1,5 @@ -use glam::{DAffine2, DVec2}; use crate::{intersection::intersect_quad_bez_path, LayerId}; +use glam::{DAffine2, DVec2}; use serde::{Deserialize, Serialize}; use std::fmt::Write; diff --git a/core/editor/src/input/input_mapper.rs b/core/editor/src/input/input_mapper.rs index 9d93803493..d8fb62afb1 100644 --- a/core/editor/src/input/input_mapper.rs +++ b/core/editor/src/input/input_mapper.rs @@ -104,14 +104,12 @@ impl Default for Mapping { fn default() -> Self { let (up, down, pointer_move) = mapping![ entry! {action=DocumentMessage::PasteLayers, key_down=KeyV, modifiers=[KeyControl]}, - // Select entry! {action=SelectMessage::MouseMove, message=InputMapperMessage::PointerMove}, entry! {action=SelectMessage::DragStart, key_down=Lmb}, entry! {action=SelectMessage::DragStop, key_up=Lmb}, entry! {action=SelectMessage::Abort, key_down=Rmb}, entry! {action=SelectMessage::Abort, key_down=KeyEscape}, - // Rectangle entry! {action=RectangleMessage::Center, key_down=KeyAlt}, entry! {action=RectangleMessage::UnCenter, key_up=KeyAlt},