Skip to content

Commit ad06460

Browse files
pkupperKeavon
andauthored
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 <[email protected]>
1 parent 20420c1 commit ad06460

File tree

13 files changed

+293
-25
lines changed

13 files changed

+293
-25
lines changed

core/document/src/document.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use glam::DAffine2;
1+
use glam::{DAffine2, DVec2};
22

33
use crate::{
44
layers::{self, style::PathStyle, Folder, Layer, LayerDataTypes, Line, PolyLine, Rect, Shape},
@@ -64,6 +64,19 @@ impl Document {
6464
svg
6565
}
6666

67+
/// Checks whether each layer under `path` intersects with the provided `quad` and adds all intersection layers as paths to `intersections`.
68+
pub fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>) {
69+
self.document_folder(path).unwrap().intersects_quad(quad, path, intersections);
70+
return;
71+
}
72+
73+
/// Checks whether each layer under the root path intersects with the provided `quad` and returns the paths to all intersecting layers.
74+
pub fn intersects_quad_root(&self, quad: [DVec2; 4]) -> Vec<Vec<LayerId>> {
75+
let mut intersections = Vec::new();
76+
self.intersects_quad(quad, &mut vec![], &mut intersections);
77+
intersections
78+
}
79+
6780
fn is_mounted(&self, mount_path: &[LayerId], path: &[LayerId]) -> bool {
6881
path.starts_with(mount_path) && self.work_mounted
6982
}

core/document/src/intersection.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use glam::DVec2;
2+
use kurbo::{BezPath, Line, PathSeg, Point, Shape, Vec2};
3+
4+
fn to_point(vec: DVec2) -> Point {
5+
Point::new(vec.x, vec.y)
6+
}
7+
8+
pub fn intersect_quad_bez_path(quad: [DVec2; 4], shape: &BezPath, closed: bool) -> bool {
9+
let lines = vec![
10+
Line::new(to_point(quad[0]), to_point(quad[1])),
11+
Line::new(to_point(quad[1]), to_point(quad[2])),
12+
Line::new(to_point(quad[2]), to_point(quad[3])),
13+
Line::new(to_point(quad[3]), to_point(quad[0])),
14+
];
15+
// check if outlines intersect
16+
for path_segment in shape.segments() {
17+
for line in &lines {
18+
if !path_segment.intersect_line(*line).is_empty() {
19+
return true;
20+
}
21+
}
22+
}
23+
// check if selection is entirely within the shape
24+
if closed && shape.contains(to_point(quad[0])) {
25+
return true;
26+
}
27+
// check if shape is entirely within the selection
28+
if let Some(shape_point) = get_arbitrary_point_on_path(shape) {
29+
let mut pos = 0;
30+
let mut neg = 0;
31+
for line in lines {
32+
if line.p0 == shape_point {
33+
return true;
34+
};
35+
let line_vec = Vec2::new(line.p1.x - line.p0.x, line.p1.y - line.p0.y);
36+
let point_vec = Vec2::new(line.p1.x - shape_point.x, line.p1.y - shape_point.y);
37+
let cross = line_vec.cross(point_vec);
38+
if cross > 0.0 {
39+
pos += 1;
40+
} else if cross < 0.0 {
41+
neg += 1;
42+
}
43+
if pos > 0 && neg > 0 {
44+
return false;
45+
}
46+
}
47+
}
48+
true
49+
}
50+
51+
pub fn get_arbitrary_point_on_path(path: &BezPath) -> Option<Point> {
52+
path.segments().next().map(|seg| match seg {
53+
PathSeg::Line(line) => line.p0,
54+
PathSeg::Quad(quad) => quad.p0,
55+
PathSeg::Cubic(cubic) => cubic.p0,
56+
})
57+
}

core/document/src/layers/ellipse.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
use glam::DAffine2;
2+
use glam::DVec2;
13
use kurbo::Shape;
24

5+
use crate::intersection::intersect_quad_bez_path;
6+
use crate::LayerId;
7+
38
use super::style;
49
use super::LayerData;
510

@@ -16,10 +21,17 @@ impl Ellipse {
1621
}
1722

1823
impl LayerData for Ellipse {
19-
fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath {
20-
kurbo::Ellipse::from_affine(kurbo::Affine::new(transform.to_cols_array())).to_path(0.1)
24+
fn to_kurbo_path(&self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath {
25+
kurbo::Ellipse::from_affine(kurbo::Affine::new(transform.to_cols_array())).to_path(0.01)
2126
}
27+
2228
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
2329
let _ = write!(svg, r#"<path d="{}" {} />"#, self.to_kurbo_path(transform, style).to_svg(), style.render());
2430
}
31+
32+
fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, style: style::PathStyle) {
33+
if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style), true) {
34+
intersections.push(path.clone());
35+
}
36+
}
2537
}

core/document/src/layers/folder.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use glam::DVec2;
2+
13
use crate::{DocumentError, LayerId};
24

35
use super::{style, Layer, LayerData, LayerDataTypes};
@@ -13,6 +15,10 @@ pub struct Folder {
1315
}
1416

1517
impl LayerData for Folder {
18+
fn to_kurbo_path(&self, _: glam::DAffine2, _: style::PathStyle) -> kurbo::BezPath {
19+
unimplemented!()
20+
}
21+
1622
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, _style: style::PathStyle) {
1723
let _ = writeln!(svg, r#"<g transform="matrix("#);
1824
transform.to_cols_array().iter().enumerate().for_each(|(i, f)| {
@@ -26,8 +32,12 @@ impl LayerData for Folder {
2632
let _ = writeln!(svg, "</g>");
2733
}
2834

29-
fn to_kurbo_path(&mut self, _: glam::DAffine2, _: style::PathStyle) -> kurbo::BezPath {
30-
unimplemented!()
35+
fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, _style: style::PathStyle) {
36+
for (layer, layer_id) in self.layers().iter().zip(&self.layer_ids) {
37+
path.push(*layer_id);
38+
layer.intersects_quad(quad, path, intersections);
39+
path.pop();
40+
}
3141
}
3242
}
3343

core/document/src/layers/line.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
use glam::DAffine2;
12
use glam::DVec2;
23
use kurbo::Point;
34

5+
use crate::intersection::intersect_quad_bez_path;
6+
use crate::LayerId;
7+
48
use super::style;
59
use super::LayerData;
610

@@ -17,7 +21,7 @@ impl Line {
1721
}
1822

1923
impl LayerData for Line {
20-
fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath {
24+
fn to_kurbo_path(&self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath {
2125
fn new_point(a: DVec2) -> Point {
2226
Point::new(a.x, a.y)
2327
}
@@ -26,10 +30,17 @@ impl LayerData for Line {
2630
path.line_to(new_point(transform.transform_point2(DVec2::ONE)));
2731
path
2832
}
33+
2934
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
3035
let [x1, y1] = transform.translation.to_array();
3136
let [x2, y2] = transform.transform_point2(DVec2::ONE).to_array();
3237

3338
let _ = write!(svg, r#"<line x1="{}" y1="{}" x2="{}" y2="{}"{} />"#, x1, y1, x2, y2, style.render(),);
3439
}
40+
41+
fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, style: style::PathStyle) {
42+
if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style), false) {
43+
intersections.push(path.clone());
44+
}
45+
}
3546
}

core/document/src/layers/mod.rs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,16 @@ pub use shape::Shape;
1919

2020
pub mod folder;
2121
use crate::DocumentError;
22+
use crate::LayerId;
2223
pub use folder::Folder;
2324
use serde::{Deserialize, Serialize};
2425

26+
pub const SELECTION_TOLERANCE: f64 = 5.0;
27+
2528
pub trait LayerData {
2629
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle);
27-
fn to_kurbo_path(&mut self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath;
30+
fn to_kurbo_path(&self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath;
31+
fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, style: style::PathStyle);
2832
}
2933

3034
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
@@ -51,6 +55,15 @@ macro_rules! call_kurbo_path {
5155
}
5256
};
5357
}
58+
59+
macro_rules! call_intersects_quad {
60+
($self:ident.intersects_quad($quad:ident, $path:ident, $intersections:ident, $style:ident) { $($variant:ident),* }) => {
61+
match $self {
62+
$(Self::$variant(x) => x.intersects_quad($quad, $path, $intersections, $style)),*
63+
}
64+
};
65+
}
66+
5467
impl LayerDataTypes {
5568
pub fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
5669
call_render! {
@@ -64,7 +77,7 @@ impl LayerDataTypes {
6477
}
6578
}
6679
}
67-
pub fn to_kurbo_path(&mut self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath {
80+
pub fn to_kurbo_path(&self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath {
6881
call_kurbo_path! {
6982
self.to_kurbo_path(transform, style) {
7083
Folder,
@@ -76,6 +89,19 @@ impl LayerDataTypes {
7689
}
7790
}
7891
}
92+
93+
pub fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, style: style::PathStyle) {
94+
call_intersects_quad! {
95+
self.intersects_quad(quad, path, intersections, style) {
96+
Folder,
97+
Ellipse,
98+
Rect,
99+
Line,
100+
PolyLine,
101+
Shape
102+
}
103+
}
104+
}
79105
}
80106

81107
#[derive(Serialize, Deserialize)]
@@ -122,19 +148,35 @@ impl Layer {
122148
self.cache.as_str()
123149
}
124150

151+
pub fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>) {
152+
let inv_transform = self.transform.inverse();
153+
let transformed_quad = [
154+
inv_transform.transform_point2(quad[0]),
155+
inv_transform.transform_point2(quad[1]),
156+
inv_transform.transform_point2(quad[2]),
157+
inv_transform.transform_point2(quad[3]),
158+
];
159+
if !self.visible {
160+
return;
161+
}
162+
self.data.intersects_quad(transformed_quad, path, intersections, self.style)
163+
}
164+
125165
pub fn render_on(&mut self, svg: &mut String) {
126166
*svg += self.render();
127167
}
128168

129169
pub fn to_kurbo_path(&mut self) -> BezPath {
130170
self.data.to_kurbo_path(self.transform, self.style)
131171
}
172+
132173
pub fn as_folder_mut(&mut self) -> Result<&mut Folder, DocumentError> {
133174
match &mut self.data {
134175
LayerDataTypes::Folder(f) => Ok(f),
135176
_ => Err(DocumentError::NotAFolder),
136177
}
137178
}
179+
138180
pub fn as_folder(&self) -> Result<&Folder, DocumentError> {
139181
match &self.data {
140182
LayerDataTypes::Folder(f) => Ok(&f),

core/document/src/layers/polyline.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use crate::{intersection::intersect_quad_bez_path, LayerId};
2+
use glam::{DAffine2, DVec2};
13
use serde::{Deserialize, Serialize};
24
use std::fmt::Write;
35

@@ -17,7 +19,7 @@ impl PolyLine {
1719
}
1820

1921
impl LayerData for PolyLine {
20-
fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath {
22+
fn to_kurbo_path(&self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath {
2123
let mut path = kurbo::BezPath::new();
2224
self.points
2325
.iter()
@@ -27,6 +29,7 @@ impl LayerData for PolyLine {
2729
.for_each(|(i, p)| if i == 0 { path.move_to(p) } else { path.line_to(p) });
2830
path
2931
}
32+
3033
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
3134
if self.points.is_empty() {
3235
return;
@@ -40,6 +43,12 @@ impl LayerData for PolyLine {
4043
}
4144
let _ = write!(svg, r#""{} />"#, style.render());
4245
}
46+
47+
fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, style: style::PathStyle) {
48+
if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style), false) {
49+
intersections.push(path.clone());
50+
}
51+
}
4352
}
4453

4554
#[cfg(test)]

core/document/src/layers/rect.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
use glam::DAffine2;
12
use glam::DVec2;
23
use kurbo::Point;
34

5+
use crate::intersection::intersect_quad_bez_path;
6+
use crate::LayerId;
7+
48
use super::style;
59
use super::LayerData;
610

@@ -17,7 +21,7 @@ impl Rect {
1721
}
1822

1923
impl LayerData for Rect {
20-
fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath {
24+
fn to_kurbo_path(&self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath {
2125
fn new_point(a: DVec2) -> Point {
2226
Point::new(a.x, a.y)
2327
}
@@ -32,4 +36,10 @@ impl LayerData for Rect {
3236
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
3337
let _ = write!(svg, r#"<path d="{}" {} />"#, self.to_kurbo_path(transform, style).to_svg(), style.render());
3438
}
39+
40+
fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, style: style::PathStyle) {
41+
if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style), true) {
42+
intersections.push(path.clone());
43+
}
44+
}
3545
}

core/document/src/layers/shape.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
use glam::DAffine2;
2+
use glam::DVec2;
3+
4+
use crate::intersection::intersect_quad_bez_path;
5+
use crate::LayerId;
16
use kurbo::BezPath;
27
use kurbo::Vec2;
38

@@ -20,7 +25,7 @@ impl Shape {
2025
}
2126

2227
impl LayerData for Shape {
23-
fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> BezPath {
28+
fn to_kurbo_path(&self, transform: glam::DAffine2, _style: style::PathStyle) -> BezPath {
2429
fn unit_rotation(theta: f64) -> Vec2 {
2530
Vec2::new(-theta.sin(), theta.cos())
2631
}
@@ -66,4 +71,10 @@ impl LayerData for Shape {
6671
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
6772
let _ = write!(svg, r#"<path d="{}" {} />"#, self.to_kurbo_path(transform, style).to_svg(), style.render());
6873
}
74+
75+
fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, style: style::PathStyle) {
76+
if intersect_quad_bez_path(quad, &self.to_kurbo_path(DAffine2::IDENTITY, style), true) {
77+
intersections.push(path.clone());
78+
}
79+
}
6980
}

core/document/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod color;
22
pub mod document;
3+
pub mod intersection;
34
pub mod layers;
45
pub mod operation;
56
pub mod response;

core/editor/src/document/document_message_handler.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,8 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
281281
for path in paths {
282282
responses.extend(self.select_layer(&path));
283283
}
284+
// TODO: Correctly update layer panel in clear_selection instead of here
285+
responses.extend(self.handle_folder_changed(Vec::new()));
284286
}
285287
Undo => {
286288
// this is a temporary fix and will be addressed by #123

0 commit comments

Comments
 (0)