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:
parent
94c42ff5d7
commit
751fabb417
|
|
@ -1,4 +1,4 @@
|
||||||
use glam::DAffine2;
|
use glam::{DAffine2, DVec2};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
layers::{self, style::PathStyle, Folder, Layer, LayerDataTypes, Line, PolyLine, Rect, Shape},
|
layers::{self, style::PathStyle, Folder, Layer, LayerDataTypes, Line, PolyLine, Rect, Shape},
|
||||||
|
|
@ -64,6 +64,19 @@ impl Document {
|
||||||
svg
|
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 {
|
fn is_mounted(&self, mount_path: &[LayerId], path: &[LayerId]) -> bool {
|
||||||
path.starts_with(mount_path) && self.work_mounted
|
path.starts_with(mount_path) && self.work_mounted
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
|
use glam::DAffine2;
|
||||||
|
use glam::DVec2;
|
||||||
use kurbo::Shape;
|
use kurbo::Shape;
|
||||||
|
|
||||||
|
use crate::intersection::intersect_quad_bez_path;
|
||||||
|
use crate::LayerId;
|
||||||
|
|
||||||
use super::style;
|
use super::style;
|
||||||
use super::LayerData;
|
use super::LayerData;
|
||||||
|
|
||||||
|
|
@ -16,10 +21,17 @@ impl Ellipse {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayerData for Ellipse {
|
impl LayerData for Ellipse {
|
||||||
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 {
|
||||||
kurbo::Ellipse::from_affine(kurbo::Affine::new(transform.to_cols_array())).to_path(0.1)
|
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) {
|
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());
|
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,3 +1,5 @@
|
||||||
|
use glam::DVec2;
|
||||||
|
|
||||||
use crate::{DocumentError, LayerId};
|
use crate::{DocumentError, LayerId};
|
||||||
|
|
||||||
use super::{style, Layer, LayerData, LayerDataTypes};
|
use super::{style, Layer, LayerData, LayerDataTypes};
|
||||||
|
|
@ -13,6 +15,10 @@ pub struct Folder {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayerData for 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) {
|
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, _style: style::PathStyle) {
|
||||||
let _ = writeln!(svg, r#"<g transform="matrix("#);
|
let _ = writeln!(svg, r#"<g transform="matrix("#);
|
||||||
transform.to_cols_array().iter().enumerate().for_each(|(i, f)| {
|
transform.to_cols_array().iter().enumerate().for_each(|(i, f)| {
|
||||||
|
|
@ -26,8 +32,12 @@ impl LayerData for Folder {
|
||||||
let _ = writeln!(svg, "</g>");
|
let _ = writeln!(svg, "</g>");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_kurbo_path(&mut self, _: glam::DAffine2, _: style::PathStyle) -> kurbo::BezPath {
|
fn intersects_quad(&self, quad: [DVec2; 4], path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, _style: style::PathStyle) {
|
||||||
unimplemented!()
|
for (layer, layer_id) in self.layers().iter().zip(&self.layer_ids) {
|
||||||
|
path.push(*layer_id);
|
||||||
|
layer.intersects_quad(quad, path, intersections);
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
|
use glam::DAffine2;
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use kurbo::Point;
|
use kurbo::Point;
|
||||||
|
|
||||||
|
use crate::intersection::intersect_quad_bez_path;
|
||||||
|
use crate::LayerId;
|
||||||
|
|
||||||
use super::style;
|
use super::style;
|
||||||
use super::LayerData;
|
use super::LayerData;
|
||||||
|
|
||||||
|
|
@ -17,7 +21,7 @@ impl Line {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayerData for 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 {
|
fn new_point(a: DVec2) -> Point {
|
||||||
Point::new(a.x, a.y)
|
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.line_to(new_point(transform.transform_point2(DVec2::ONE)));
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
|
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
|
||||||
let [x1, y1] = transform.translation.to_array();
|
let [x1, y1] = transform.translation.to_array();
|
||||||
let [x2, y2] = transform.transform_point2(DVec2::ONE).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(),);
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,16 @@ pub use shape::Shape;
|
||||||
|
|
||||||
pub mod folder;
|
pub mod folder;
|
||||||
use crate::DocumentError;
|
use crate::DocumentError;
|
||||||
|
use crate::LayerId;
|
||||||
pub use folder::Folder;
|
pub use folder::Folder;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub const SELECTION_TOLERANCE: f64 = 5.0;
|
||||||
|
|
||||||
pub trait LayerData {
|
pub trait LayerData {
|
||||||
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle);
|
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)]
|
#[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 {
|
impl LayerDataTypes {
|
||||||
pub fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
|
pub fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
|
||||||
call_render! {
|
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! {
|
call_kurbo_path! {
|
||||||
self.to_kurbo_path(transform, style) {
|
self.to_kurbo_path(transform, style) {
|
||||||
Folder,
|
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)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
|
@ -122,6 +148,20 @@ impl Layer {
|
||||||
self.cache.as_str()
|
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) {
|
pub fn render_on(&mut self, svg: &mut String) {
|
||||||
*svg += self.render();
|
*svg += self.render();
|
||||||
}
|
}
|
||||||
|
|
@ -129,12 +169,14 @@ impl Layer {
|
||||||
pub fn to_kurbo_path(&mut self) -> BezPath {
|
pub fn to_kurbo_path(&mut self) -> BezPath {
|
||||||
self.data.to_kurbo_path(self.transform, self.style)
|
self.data.to_kurbo_path(self.transform, self.style)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_folder_mut(&mut self) -> Result<&mut Folder, DocumentError> {
|
pub fn as_folder_mut(&mut self) -> Result<&mut Folder, DocumentError> {
|
||||||
match &mut self.data {
|
match &mut self.data {
|
||||||
LayerDataTypes::Folder(f) => Ok(f),
|
LayerDataTypes::Folder(f) => Ok(f),
|
||||||
_ => Err(DocumentError::NotAFolder),
|
_ => Err(DocumentError::NotAFolder),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_folder(&self) -> Result<&Folder, DocumentError> {
|
pub fn as_folder(&self) -> Result<&Folder, DocumentError> {
|
||||||
match &self.data {
|
match &self.data {
|
||||||
LayerDataTypes::Folder(f) => Ok(&f),
|
LayerDataTypes::Folder(f) => Ok(&f),
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use crate::{intersection::intersect_quad_bez_path, LayerId};
|
||||||
|
use glam::{DAffine2, DVec2};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
|
@ -17,7 +19,7 @@ impl PolyLine {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayerData for 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();
|
let mut path = kurbo::BezPath::new();
|
||||||
self.points
|
self.points
|
||||||
.iter()
|
.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) });
|
.for_each(|(i, p)| if i == 0 { path.move_to(p) } else { path.line_to(p) });
|
||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
|
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
|
||||||
if self.points.is_empty() {
|
if self.points.is_empty() {
|
||||||
return;
|
return;
|
||||||
|
|
@ -40,6 +43,12 @@ impl LayerData for PolyLine {
|
||||||
}
|
}
|
||||||
let _ = write!(svg, r#""{} />"#, style.render());
|
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)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
|
use glam::DAffine2;
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use kurbo::Point;
|
use kurbo::Point;
|
||||||
|
|
||||||
|
use crate::intersection::intersect_quad_bez_path;
|
||||||
|
use crate::LayerId;
|
||||||
|
|
||||||
use super::style;
|
use super::style;
|
||||||
use super::LayerData;
|
use super::LayerData;
|
||||||
|
|
||||||
|
|
@ -17,7 +21,7 @@ impl Rect {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayerData for 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 {
|
fn new_point(a: DVec2) -> Point {
|
||||||
Point::new(a.x, a.y)
|
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) {
|
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());
|
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,3 +1,8 @@
|
||||||
|
use glam::DAffine2;
|
||||||
|
use glam::DVec2;
|
||||||
|
|
||||||
|
use crate::intersection::intersect_quad_bez_path;
|
||||||
|
use crate::LayerId;
|
||||||
use kurbo::BezPath;
|
use kurbo::BezPath;
|
||||||
use kurbo::Vec2;
|
use kurbo::Vec2;
|
||||||
|
|
||||||
|
|
@ -20,7 +25,7 @@ impl Shape {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayerData for 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 {
|
fn unit_rotation(theta: f64) -> Vec2 {
|
||||||
Vec2::new(-theta.sin(), theta.cos())
|
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) {
|
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());
|
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,5 +1,6 @@
|
||||||
pub mod color;
|
pub mod color;
|
||||||
pub mod document;
|
pub mod document;
|
||||||
|
pub mod intersection;
|
||||||
pub mod layers;
|
pub mod layers;
|
||||||
pub mod operation;
|
pub mod operation;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
|
|
|
||||||
|
|
@ -281,6 +281,8 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||||
for path in paths {
|
for path in paths {
|
||||||
responses.extend(self.select_layer(&path));
|
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 => {
|
Undo => {
|
||||||
// this is a temporary fix and will be addressed by #123
|
// this is a temporary fix and will be addressed by #123
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,12 @@ impl Default for Mapping {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let (up, down, pointer_move) = mapping![
|
let (up, down, pointer_move) = mapping![
|
||||||
entry! {action=DocumentMessage::PasteLayers, key_down=KeyV, modifiers=[KeyControl]},
|
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
|
// Rectangle
|
||||||
entry! {action=RectangleMessage::Center, key_down=KeyAlt},
|
entry! {action=RectangleMessage::Center, key_down=KeyAlt},
|
||||||
entry! {action=RectangleMessage::UnCenter, key_up=KeyAlt},
|
entry! {action=RectangleMessage::UnCenter, key_up=KeyAlt},
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,11 @@
|
||||||
use crate::input::InputPreprocessor;
|
use document_core::color::Color;
|
||||||
|
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};
|
||||||
|
|
||||||
|
use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
||||||
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||||
use crate::{message_prelude::*, SvgDocument};
|
use crate::{message_prelude::*, SvgDocument};
|
||||||
|
|
||||||
|
|
@ -11,19 +18,29 @@ pub struct Select {
|
||||||
#[impl_message(Message, ToolMessage, Select)]
|
#[impl_message(Message, ToolMessage, Select)]
|
||||||
#[derive(PartialEq, Clone, Debug)]
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
pub enum SelectMessage {
|
pub enum SelectMessage {
|
||||||
|
DragStart,
|
||||||
|
DragStop,
|
||||||
MouseMove,
|
MouseMove,
|
||||||
|
Abort,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Select {
|
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Select {
|
||||||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||||
self.fsm_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses);
|
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)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
enum SelectToolFsmState {
|
enum SelectToolFsmState {
|
||||||
Ready,
|
Ready,
|
||||||
|
Dragging,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SelectToolFsmState {
|
impl Default for SelectToolFsmState {
|
||||||
|
|
@ -32,29 +49,96 @@ impl Default for SelectToolFsmState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
struct SelectToolData;
|
struct SelectToolData {
|
||||||
|
drag_start: ViewportPosition,
|
||||||
|
drag_current: ViewportPosition,
|
||||||
|
}
|
||||||
|
|
||||||
impl Fsm for SelectToolFsmState {
|
impl Fsm for SelectToolFsmState {
|
||||||
type ToolData = SelectToolData;
|
type ToolData = SelectToolData;
|
||||||
|
|
||||||
fn transition(
|
fn transition(self, event: ToolMessage, document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque<Message>) -> Self {
|
||||||
self,
|
let transform = document.root.transform;
|
||||||
event: ToolMessage,
|
|
||||||
_document: &SvgDocument,
|
|
||||||
_tool_data: &DocumentToolData,
|
|
||||||
_data: &mut Self::ToolData,
|
|
||||||
_input: &InputPreprocessor,
|
|
||||||
_responses: &mut VecDeque<Message>,
|
|
||||||
) -> Self {
|
|
||||||
use SelectMessage::*;
|
use SelectMessage::*;
|
||||||
use SelectToolFsmState::*;
|
use SelectToolFsmState::*;
|
||||||
if let ToolMessage::Select(event) = event {
|
if let ToolMessage::Select(event) = event {
|
||||||
match (self, 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, transform));
|
||||||
|
|
||||||
|
Dragging
|
||||||
|
}
|
||||||
|
(Dragging, DragStop) => {
|
||||||
|
data.drag_current = input.mouse.position;
|
||||||
|
|
||||||
|
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
responses.push_back(DocumentMessage::SelectLayers(document.intersects_quad_root(quad)).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ready
|
||||||
|
}
|
||||||
|
(Dragging, Abort) => {
|
||||||
|
responses.push_back(Operation::DiscardWorkingFolder.into());
|
||||||
|
|
||||||
|
Ready
|
||||||
|
}
|
||||||
|
_ => self,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn make_operation(data: &SelectToolData, _tool_data: &DocumentToolData, transform: DAffine2) -> Message {
|
||||||
|
let x0 = data.drag_start.x as f64;
|
||||||
|
let y0 = data.drag_start.y as f64;
|
||||||
|
let x1 = data.drag_current.x as f64;
|
||||||
|
let y1 = data.drag_current.y as f64;
|
||||||
|
|
||||||
|
Operation::AddRect {
|
||||||
|
path: vec![],
|
||||||
|
insert_index: -1,
|
||||||
|
transform: (transform.inverse() * glam::DAffine2::from_scale_angle_translation(DVec2::new(x1 - x0, y1 - y0), 0., DVec2::new(x0, y0))).to_cols_array(),
|
||||||
|
style: style::PathStyle::new(Some(Stroke::new(Color::from_rgb8(0x31, 0x94, 0xD6), 2.0)), Some(Fill::none())),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue