Implement viewport selection (#178)

* Begin implementing viewport selection

* Implement viewport click and drag selection for ellipse and rectangle

* Begin implementing line selection

* Remove debug prints

* Run cargo format

* Use DVec2 instead of kurbo::Point

* Line and polyline intersection

* Run cargo format

* Add fix for missing layer panel update

* Replace point selection with box selection

* Formatting

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Paul Kupper
2021-07-05 01:05:12 +02:00
committed by Keavon Chambers
parent 94c42ff5d7
commit 751fabb417
13 changed files with 293 additions and 25 deletions

View File

@@ -1,4 +1,4 @@
use glam::DAffine2;
use glam::{DAffine2, DVec2};
use crate::{
layers::{self, style::PathStyle, Folder, Layer, LayerDataTypes, Line, PolyLine, Rect, Shape},
@@ -64,6 +64,19 @@ 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: [DVec2; 4], path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>) {
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: [DVec2; 4]) -> Vec<Vec<LayerId>> {
let mut intersections = Vec::new();
self.intersects_quad(quad, &mut vec![], &mut intersections);
intersections
}
fn is_mounted(&self, mount_path: &[LayerId], path: &[LayerId]) -> bool {
path.starts_with(mount_path) && self.work_mounted
}

View File

@@ -0,0 +1,57 @@
use glam::DVec2;
use kurbo::{BezPath, Line, PathSeg, Point, Shape, Vec2};
fn to_point(vec: DVec2) -> Point {
Point::new(vec.x, vec.y)
}
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])),
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 {
if !path_segment.intersect_line(*line).is_empty() {
return true;
}
}
}
// check if selection is entirely within the shape
if closed && shape.contains(to_point(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;
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 += 1;
}
if pos > 0 && neg > 0 {
return false;
}
}
}
true
}
pub fn get_arbitrary_point_on_path(path: &BezPath) -> Option<Point> {
path.segments().next().map(|seg| match seg {
PathSeg::Line(line) => line.p0,
PathSeg::Quad(quad) => quad.p0,
PathSeg::Cubic(cubic) => cubic.p0,
})
}

View File

@@ -1,5 +1,10 @@
use glam::DAffine2;
use glam::DVec2;
use kurbo::Shape;
use crate::intersection::intersect_quad_bez_path;
use crate::LayerId;
use super::style;
use super::LayerData;
@@ -16,10 +21,17 @@ impl Ellipse {
}
impl LayerData for Ellipse {
fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath {
kurbo::Ellipse::from_affine(kurbo::Affine::new(transform.to_cols_array())).to_path(0.1)
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(0.01)
}
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
let _ = write!(svg, r#"<path d="{}" {} />"#, self.to_kurbo_path(transform, style).to_svg(), style.render());
}
fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, style: style::PathStyle) {
if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style), true) {
intersections.push(path.clone());
}
}
}

View File

@@ -1,3 +1,5 @@
use glam::DVec2;
use crate::{DocumentError, LayerId};
use super::{style, Layer, LayerData, LayerDataTypes};
@@ -13,6 +15,10 @@ pub struct Folder {
}
impl LayerData for Folder {
fn to_kurbo_path(&self, _: glam::DAffine2, _: style::PathStyle) -> kurbo::BezPath {
unimplemented!()
}
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, _style: style::PathStyle) {
let _ = writeln!(svg, r#"<g transform="matrix("#);
transform.to_cols_array().iter().enumerate().for_each(|(i, f)| {
@@ -26,8 +32,12 @@ impl LayerData for Folder {
let _ = writeln!(svg, "</g>");
}
fn to_kurbo_path(&mut self, _: glam::DAffine2, _: style::PathStyle) -> kurbo::BezPath {
unimplemented!()
fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, _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);
path.pop();
}
}
}

View File

@@ -1,6 +1,10 @@
use glam::DAffine2;
use glam::DVec2;
use kurbo::Point;
use crate::intersection::intersect_quad_bez_path;
use crate::LayerId;
use super::style;
use super::LayerData;
@@ -17,7 +21,7 @@ impl Line {
}
impl LayerData for Line {
fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath {
fn to_kurbo_path(&self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath {
fn new_point(a: DVec2) -> Point {
Point::new(a.x, a.y)
}
@@ -26,10 +30,17 @@ impl LayerData for Line {
path.line_to(new_point(transform.transform_point2(DVec2::ONE)));
path
}
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
let [x1, y1] = transform.translation.to_array();
let [x2, y2] = transform.transform_point2(DVec2::ONE).to_array();
let _ = write!(svg, r#"<line x1="{}" y1="{}" x2="{}" y2="{}"{} />"#, x1, y1, x2, y2, style.render(),);
}
fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, style: style::PathStyle) {
if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style), false) {
intersections.push(path.clone());
}
}
}

View File

@@ -19,12 +19,16 @@ pub use shape::Shape;
pub mod folder;
use crate::DocumentError;
use crate::LayerId;
pub use folder::Folder;
use serde::{Deserialize, Serialize};
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(&mut self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath;
fn to_kurbo_path(&self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath;
fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, style: style::PathStyle);
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
@@ -51,6 +55,15 @@ macro_rules! call_kurbo_path {
}
};
}
macro_rules! call_intersects_quad {
($self:ident.intersects_quad($quad:ident, $path:ident, $intersections:ident, $style:ident) { $($variant:ident),* }) => {
match $self {
$(Self::$variant(x) => x.intersects_quad($quad, $path, $intersections, $style)),*
}
};
}
impl LayerDataTypes {
pub fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
call_render! {
@@ -64,7 +77,7 @@ impl LayerDataTypes {
}
}
}
pub fn to_kurbo_path(&mut self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath {
pub fn to_kurbo_path(&self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath {
call_kurbo_path! {
self.to_kurbo_path(transform, style) {
Folder,
@@ -76,6 +89,19 @@ impl LayerDataTypes {
}
}
}
pub fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, style: style::PathStyle) {
call_intersects_quad! {
self.intersects_quad(quad, path, intersections, style) {
Folder,
Ellipse,
Rect,
Line,
PolyLine,
Shape
}
}
}
}
#[derive(Serialize, Deserialize)]
@@ -122,6 +148,20 @@ impl Layer {
self.cache.as_str()
}
pub fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>) {
let inv_transform = self.transform.inverse();
let transformed_quad = [
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;
}
self.data.intersects_quad(transformed_quad, path, intersections, self.style)
}
pub fn render_on(&mut self, svg: &mut String) {
*svg += self.render();
}
@@ -129,12 +169,14 @@ impl Layer {
pub fn to_kurbo_path(&mut self) -> BezPath {
self.data.to_kurbo_path(self.transform, self.style)
}
pub fn as_folder_mut(&mut self) -> Result<&mut Folder, DocumentError> {
match &mut self.data {
LayerDataTypes::Folder(f) => Ok(f),
_ => Err(DocumentError::NotAFolder),
}
}
pub fn as_folder(&self) -> Result<&Folder, DocumentError> {
match &self.data {
LayerDataTypes::Folder(f) => Ok(&f),

View File

@@ -1,3 +1,5 @@
use crate::{intersection::intersect_quad_bez_path, LayerId};
use glam::{DAffine2, DVec2};
use serde::{Deserialize, Serialize};
use std::fmt::Write;
@@ -17,7 +19,7 @@ impl PolyLine {
}
impl LayerData for PolyLine {
fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath {
fn to_kurbo_path(&self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath {
let mut path = kurbo::BezPath::new();
self.points
.iter()
@@ -27,6 +29,7 @@ impl LayerData for PolyLine {
.for_each(|(i, p)| if i == 0 { path.move_to(p) } else { path.line_to(p) });
path
}
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
if self.points.is_empty() {
return;
@@ -40,6 +43,12 @@ impl LayerData for PolyLine {
}
let _ = write!(svg, r#""{} />"#, style.render());
}
fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, style: style::PathStyle) {
if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style), false) {
intersections.push(path.clone());
}
}
}
#[cfg(test)]

View File

@@ -1,6 +1,10 @@
use glam::DAffine2;
use glam::DVec2;
use kurbo::Point;
use crate::intersection::intersect_quad_bez_path;
use crate::LayerId;
use super::style;
use super::LayerData;
@@ -17,7 +21,7 @@ impl Rect {
}
impl LayerData for Rect {
fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath {
fn to_kurbo_path(&self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath {
fn new_point(a: DVec2) -> Point {
Point::new(a.x, a.y)
}
@@ -32,4 +36,10 @@ impl LayerData for Rect {
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
let _ = write!(svg, r#"<path d="{}" {} />"#, self.to_kurbo_path(transform, style).to_svg(), style.render());
}
fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, style: style::PathStyle) {
if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style), true) {
intersections.push(path.clone());
}
}
}

View File

@@ -1,3 +1,8 @@
use glam::DAffine2;
use glam::DVec2;
use crate::intersection::intersect_quad_bez_path;
use crate::LayerId;
use kurbo::BezPath;
use kurbo::Vec2;
@@ -20,7 +25,7 @@ impl Shape {
}
impl LayerData for Shape {
fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> BezPath {
fn to_kurbo_path(&self, transform: glam::DAffine2, _style: style::PathStyle) -> BezPath {
fn unit_rotation(theta: f64) -> Vec2 {
Vec2::new(-theta.sin(), theta.cos())
}
@@ -66,4 +71,10 @@ impl LayerData for Shape {
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
let _ = write!(svg, r#"<path d="{}" {} />"#, self.to_kurbo_path(transform, style).to_svg(), style.render());
}
fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, style: style::PathStyle) {
if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style), true) {
intersections.push(path.clone());
}
}
}

View File

@@ -1,5 +1,6 @@
pub mod color;
pub mod document;
pub mod intersection;
pub mod layers;
pub mod operation;
pub mod response;