Skip to content

Implement viewport selection #178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jul 4, 2021
15 changes: 14 additions & 1 deletion core/document/src/document.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use glam::DAffine2;
use glam::{DAffine2, DVec2};

use crate::{
layers::{self, style::PathStyle, Folder, Layer, LayerDataTypes, Line, PolyLine, Rect, Shape},
Expand Down Expand Up @@ -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
}
Expand Down
57 changes: 57 additions & 0 deletions core/document/src/intersection.rs
Original file line number Diff line number Diff line change
@@ -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,
})
}
16 changes: 14 additions & 2 deletions core/document/src/layers/ellipse.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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());
}
}
}
14 changes: 12 additions & 2 deletions core/document/src/layers/folder.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use glam::DVec2;

use crate::{DocumentError, LayerId};

use super::{style, Layer, LayerData, LayerDataTypes};
Expand All @@ -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)| {
Expand All @@ -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();
}
}
}

Expand Down
13 changes: 12 additions & 1 deletion core/document/src/layers/line.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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)
}
Expand All @@ -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());
}
}
}
46 changes: 44 additions & 2 deletions core/document/src/layers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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! {
Expand All @@ -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,
Expand All @@ -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)]
Expand Down Expand Up @@ -122,19 +148,35 @@ 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();
}

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),
Expand Down
11 changes: 10 additions & 1 deletion core/document/src/layers/polyline.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::{intersection::intersect_quad_bez_path, LayerId};
use glam::{DAffine2, DVec2};
use serde::{Deserialize, Serialize};
use std::fmt::Write;

Expand All @@ -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()
Expand All @@ -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;
Expand All @@ -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)]
Expand Down
12 changes: 11 additions & 1 deletion core/document/src/layers/rect.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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)
}
Expand All @@ -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());
}
}
}
13 changes: 12 additions & 1 deletion core/document/src/layers/shape.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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())
}
Expand Down Expand Up @@ -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());
}
}
}
1 change: 1 addition & 0 deletions core/document/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod color;
pub mod document;
pub mod intersection;
pub mod layers;
pub mod operation;
pub mod response;
Expand Down
2 changes: 2 additions & 0 deletions core/editor/src/document/document_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,8 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> 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
Expand Down
Loading