Rename vector components to match new terminology (#719)

* Renamed VectorAnchor, VectorShape and VectorControlPoint. Also fixed other naming inconsistencies.

* Renamed messages relating to vector and updated naming in several tools

* Renamed comments + caught a few areas I had missed.

* Caught a few more incorrect names

* Code review pass

* Review changes

* Fixed warning

* Additional review feedback

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Oliver Davies
2022-07-12 17:59:06 -07:00
committed by Keavon Chambers
co-authored by Keavon Chambers
parent 5d1d93917d
commit 03633bf313
39 changed files with 1125 additions and 1095 deletions
+1 -1
View File
@@ -535,7 +535,7 @@ pub fn composite_boolean_operation(mut select: BooleanOperation, shapes: &mut Ve
// TODO: check if shapes are filled
// TODO: Bug: shape with at least two subpaths and comprised of many unions sometimes has erroneous movetos embedded in edges
pub fn boolean_operation(mut select: BooleanOperation, alpha: &mut ShapeLayer, beta: &mut ShapeLayer) -> Result<Vec<ShapeLayer>, BooleanOperationError> {
if alpha.shape.anchors().is_empty() || beta.shape.anchors().is_empty() {
if alpha.shape.manipulator_groups().is_empty() || beta.shape.manipulator_groups().is_empty() {
return Err(BooleanOperationError::InvalidSelection);
}
if select == BooleanOperation::SubtractBack {
+66 -58
View File
@@ -6,7 +6,7 @@ use crate::layers::layer_info::{Layer, LayerData, LayerDataType, LayerDataTypeDi
use crate::layers::shape_layer::ShapeLayer;
use crate::layers::style::RenderData;
use crate::layers::text_layer::{Font, FontCache, TextLayer};
use crate::layers::vector::vector_shape::VectorShape;
use crate::layers::vector::subpath::Subpath;
use crate::{DocumentError, DocumentResponse, Operation};
use glam::{DAffine2, DVec2};
@@ -126,28 +126,28 @@ impl Document {
Ok(shapes)
}
/// Return a copy of all VectorShapes currently in the document.
pub fn all_vector_shapes(&self) -> Vec<VectorShape> {
self.root.iter().flat_map(|layer| layer.as_vector_shape_copy()).collect::<Vec<VectorShape>>()
/// Return a copy of all [Subpath]s currently in the document.
pub fn all_subpaths(&self) -> Vec<Subpath> {
self.root.iter().flat_map(|layer| layer.as_subpath_copy()).collect::<Vec<Subpath>>()
}
/// Returns references to all VectorShapes currently in the document.
pub fn all_vector_shapes_ref(&self) -> Vec<&VectorShape> {
self.root.iter().flat_map(|layer| layer.as_vector_shape()).collect::<Vec<&VectorShape>>()
/// Returns references to all [Subpath]s currently in the document.
pub fn all_subpaths_ref(&self) -> Vec<&Subpath> {
self.root.iter().flat_map(|layer| layer.as_subpath()).collect::<Vec<&Subpath>>()
}
/// Returns a reference to the requested VectorShape by providing a path to its owner layer.
pub fn vector_shape_ref<'a>(&'a self, path: &[LayerId]) -> Option<&'a VectorShape> {
self.layer(path).ok()?.as_vector_shape()
/// Returns a reference to the requested [Subpath] by providing a path to its owner layer.
pub fn subpath_ref<'a>(&'a self, path: &[LayerId]) -> Option<&'a Subpath> {
self.layer(path).ok()?.as_subpath()
}
/// Returns a mutable reference of the requested VectorShape by providing a path to its owner layer.
pub fn vector_shape_mut<'a>(&'a mut self, path: &'a [LayerId]) -> Option<&'a mut VectorShape> {
self.layer_mut(path).ok()?.as_vector_shape_mut()
/// Returns a mutable reference of the requested [Subpath] by providing a path to its owner layer.
pub fn subpath_mut<'a>(&'a mut self, path: &'a [LayerId]) -> Option<&'a mut Subpath> {
self.layer_mut(path).ok()?.as_subpath_mut()
}
/// Set a VectorShape at the specified path.
pub fn set_vector_shape(&mut self, path: &[LayerId], shape: VectorShape) {
/// Set a [Subpath] at the specified path.
pub fn set_subpath(&mut self, path: &[LayerId], shape: Subpath) {
let layer = self.layer_mut(path);
if let Ok(layer) = layer {
if let LayerDataType::Shape(shape_layer) = &mut layer.data {
@@ -158,9 +158,9 @@ impl Document {
}
}
/// Set VectorShapes for multiple paths at once.
pub fn set_vector_shapes<'a>(&'a mut self, paths: impl Iterator<Item = &'a [LayerId]>, shapes: Vec<VectorShape>) {
paths.zip(shapes).for_each(|(path, shape)| self.set_vector_shape(path, shape));
/// Set [Subpath]s for multiple paths at once.
pub fn set_subpaths<'a>(&'a mut self, paths: impl Iterator<Item = &'a [LayerId]>, shapes: Vec<Subpath>) {
paths.zip(shapes).for_each(|(path, shape)| self.set_subpath(path, shape));
}
pub fn common_layer_path_prefix<'a>(&self, layers: impl Iterator<Item = &'a [LayerId]>) -> &'a [LayerId] {
@@ -591,9 +591,9 @@ impl Document {
transform,
insert_index,
style,
vector_path,
subpath,
} => {
let shape = ShapeLayer::new(vector_path, style);
let shape = ShapeLayer::new(subpath, style);
self.set_layer(&path, Layer::new(LayerDataType::Shape(shape), transform), insert_index)?;
Some([vec![DocumentChanged, CreatedLayer { path }]].concat())
}
@@ -758,53 +758,61 @@ impl Document {
self.mark_as_dirty(&path)?;
Some([vec![DocumentChanged], update_thumbnails_upstream(&path)].concat())
}
Operation::SetShapePath { path, vector_path } => {
Operation::SetShapePath { path, subpath } => {
self.mark_as_dirty(&path)?;
if let LayerDataType::Shape(shape) = &mut self.layer_mut(&path)?.data {
shape.shape = vector_path;
shape.shape = subpath;
}
Some(vec![DocumentChanged, LayerChanged { path }])
}
Operation::InsertVectorAnchor { layer_path, anchor, after_id } => {
if let Ok(Some(shape)) = self.layer_mut(&layer_path).map(|layer| layer.as_vector_shape_mut()) {
shape.anchors_mut().insert(anchor, after_id);
Operation::InsertManipulatorGroup {
layer_path,
manipulator_group,
after_id,
} => {
if let Ok(Some(shape)) = self.layer_mut(&layer_path).map(|layer| layer.as_subpath_mut()) {
shape.manipulator_groups_mut().insert(manipulator_group, after_id);
self.mark_as_dirty(&layer_path)?;
}
Some([update_thumbnails_upstream(&layer_path), vec![DocumentChanged, LayerChanged { path: layer_path }]].concat())
}
Operation::PushVectorAnchor { layer_path, anchor } => {
if let Ok(Some(shape)) = self.layer_mut(&layer_path).map(|layer| layer.as_vector_shape_mut()) {
shape.anchors_mut().push(anchor);
Operation::PushManipulatorGroup { layer_path, manipulator_group } => {
if let Ok(Some(shape)) = self.layer_mut(&layer_path).map(|layer| layer.as_subpath_mut()) {
shape.manipulator_groups_mut().push(manipulator_group);
self.mark_as_dirty(&layer_path)?;
}
Some([update_thumbnails_upstream(&layer_path), vec![DocumentChanged, LayerChanged { path: layer_path }]].concat())
}
Operation::RemoveVectorAnchor { layer_path, id } => {
if let Ok(Some(shape)) = self.layer_mut(&layer_path).map(|layer| layer.as_vector_shape_mut()) {
shape.anchors_mut().remove(id);
Operation::RemoveManipulatorGroup { layer_path, id } => {
if let Ok(Some(shape)) = self.layer_mut(&layer_path).map(|layer| layer.as_subpath_mut()) {
shape.manipulator_groups_mut().remove(id);
self.mark_as_dirty(&layer_path)?;
}
Some([update_thumbnails_upstream(&layer_path), vec![DocumentChanged, LayerChanged { path: layer_path }]].concat())
}
Operation::MoveVectorPoint {
Operation::MoveManipulatorPoint {
layer_path,
id,
control_type,
manipulator_type: control_type,
position,
} => {
if let Ok(Some(shape)) = self.layer_mut(&layer_path).map(|layer| layer.as_vector_shape_mut()) {
if let Some(anchor) = shape.anchors_mut().by_id_mut(id) {
anchor.set_point_position(control_type as usize, position.into());
if let Ok(Some(shape)) = self.layer_mut(&layer_path).map(|layer| layer.as_subpath_mut()) {
if let Some(manipulator_group) = shape.manipulator_groups_mut().by_id_mut(id) {
manipulator_group.set_point_position(control_type as usize, position.into());
self.mark_as_dirty(&layer_path)?;
}
}
Some([update_thumbnails_upstream(&layer_path), vec![DocumentChanged, LayerChanged { path: layer_path }]].concat())
}
Operation::RemoveVectorPoint { layer_path, id, control_type } => {
if let Ok(Some(shape)) = self.layer_mut(&layer_path).map(|layer| layer.as_vector_shape_mut()) {
if let Some(anchor) = shape.anchors_mut().by_id_mut(id) {
anchor.points[control_type as usize] = None;
Operation::RemoveManipulatorPoint {
layer_path,
id,
manipulator_type: control_type,
} => {
if let Ok(Some(shape)) = self.layer_mut(&layer_path).map(|layer| layer.as_subpath_mut()) {
if let Some(manipulator_group) = shape.manipulator_groups_mut().by_id_mut(id) {
manipulator_group.points[control_type as usize] = None;
self.mark_as_dirty(&layer_path)?;
}
}
@@ -886,47 +894,47 @@ impl Document {
}
// We may not want the concept of selection here. For now leaving though.
Operation::SelectVectorPoints { layer_path, point_ids, add } => {
Operation::SelectManipulatorPoints { layer_path, point_ids, add } => {
let layer = self.layer_mut(&layer_path)?;
if let Some(shape) = layer.as_vector_shape_mut() {
if let Some(shape) = layer.as_subpath_mut() {
if !add {
shape.clear_selected_anchors();
shape.clear_selected_manipulator_groups();
}
shape.select_points(&point_ids, true);
}
Some(vec![LayerChanged { path: layer_path.clone() }])
}
Operation::DeselectVectorPoints { layer_path, point_ids } => {
Operation::DeselectManipulatorPoints { layer_path, point_ids } => {
let layer = self.layer_mut(&layer_path)?;
if let Some(shape) = layer.as_vector_shape_mut() {
if let Some(shape) = layer.as_subpath_mut() {
shape.select_points(&point_ids, false);
}
Some(vec![LayerChanged { path: layer_path.clone() }])
}
Operation::DeselectAllVectorPoints { layer_path } => {
Operation::DeselectAllManipulatorPoints { layer_path } => {
let layer = self.layer_mut(&layer_path)?;
if let Some(shape) = layer.as_vector_shape_mut() {
shape.clear_selected_anchors();
if let Some(shape) = layer.as_subpath_mut() {
shape.clear_selected_manipulator_groups();
}
Some(vec![LayerChanged { path: layer_path.clone() }])
}
Operation::DeleteSelectedVectorPoints { layer_paths } => {
Operation::DeleteSelectedManipulatorPoints { layer_paths } => {
let mut responses = vec![];
for layer_path in layer_paths {
let layer = self.layer_mut(&layer_path)?;
if let Some(shape) = layer.as_vector_shape_mut() {
if let Some(shape) = layer.as_subpath_mut() {
// Delete the selected points.
shape.delete_selected();
// Delete the layer if there are no longer any anchors
if (shape.anchors().len() - 1) == 0 {
// Delete the layer if there are no longer any manipulator groups
if (shape.manipulator_groups().len() - 1) == 0 {
self.delete(&layer_path)?;
responses.push(DocumentChanged);
responses.push(DocumentResponse::DeletedLayer { path: layer_path });
return Ok(Some(responses));
}
// If we still have anchors, update the layer and thumbnails
// If we still have manipulator groups, update the layer and thumbnails
self.mark_as_dirty(&layer_path)?;
responses.push(DocumentChanged);
responses.push(LayerChanged { path: layer_path.clone() });
@@ -935,13 +943,13 @@ impl Document {
}
Some(responses)
}
Operation::MoveSelectedVectorPoints { layer_path, delta, absolute_position } => {
Operation::MoveSelectedManipulatorPoints { layer_path, delta, absolute_position } => {
if let Ok(viewspace) = self.generate_transform_relative_to_viewport(&layer_path) {
let objectspace = &viewspace.inverse();
let delta = objectspace.transform_vector2(DVec2::new(delta.0, delta.1));
let absolute_position = objectspace.transform_point2(DVec2::new(absolute_position.0, absolute_position.1));
let layer = self.layer_mut(&layer_path)?;
if let Some(shape) = layer.as_vector_shape_mut() {
if let Some(shape) = layer.as_subpath_mut() {
shape.move_selected(delta, absolute_position, &viewspace);
}
}
@@ -954,9 +962,9 @@ impl Document {
toggle_angle,
} => {
let layer = self.layer_mut(&layer_path)?;
if let Some(shape) = layer.as_vector_shape_mut() {
for anchor in shape.selected_anchors_any_points_mut() {
anchor.toggle_mirroring(toggle_distance, toggle_angle);
if let Some(shape) = layer.as_subpath_mut() {
for manipulator_group in shape.selected_manipulator_groups_any_points_mut() {
manipulator_group.toggle_mirroring(toggle_distance, toggle_angle);
}
}
// This does nothing visually so we don't need to send any messages
+4 -4
View File
@@ -1,6 +1,6 @@
use crate::boolean_ops::{split_path_seg, subdivide_path_seg};
use crate::consts::{F64LOOSE, F64PRECISE};
use crate::layers::vector::vector_shape::VectorShape;
use crate::layers::vector::subpath::Subpath;
use glam::{DAffine2, DMat2, DVec2};
use kurbo::{BezPath, CubicBez, Line, ParamCurve, ParamCurveDeriv, ParamCurveExtrema, PathSeg, Point, QuadBez, Rect, Shape, Vec2};
@@ -39,9 +39,9 @@ impl Quad {
path
}
/// Generates a [VectorShape] of the quad
pub fn vector_shape(&self) -> VectorShape {
VectorShape::from_points(self.0.into_iter(), true)
/// Generates a [subpath] of the quad
pub fn subpath(&self) -> Subpath {
Subpath::from_points(self.0.into_iter(), true)
}
/// Generates the axis aligned bounding box of the quad
+2 -2
View File
@@ -10,7 +10,7 @@ use std::ops::{Deref, DerefMut};
/// - Access data by providing Unique ID.
/// - Maintain ordering among the elements.
/// - Remove elements without changing Unique IDs.
/// This data structure is somewhat similar to a linked list in terms of invarients.
/// This data structure is somewhat similar to a linked list in terms of invariants.
/// The downside is that currently it requires a lot of iteration.
type ElementId = u64;
@@ -54,7 +54,7 @@ impl<T> IdBackedVec<T> {
}
/// Push an element to the end of the vector
/// Overriden from Vec, so adding values without creating an id cannot occur
/// Overridden from Vec, so adding values without creating an id cannot occur
pub fn push(&mut self, element: T) -> Option<ElementId> {
self.push_end(element)
}
+10 -10
View File
@@ -4,7 +4,7 @@ use super::image_layer::ImageLayer;
use super::shape_layer::ShapeLayer;
use super::style::{PathStyle, RenderData};
use super::text_layer::TextLayer;
use super::vector::vector_shape::VectorShape;
use super::vector::subpath::Subpath;
use crate::intersection::Quad;
use crate::layers::text_layer::FontCache;
use crate::DocumentError;
@@ -333,26 +333,26 @@ impl Layer {
///
/// // Apply the Identity transform, which leaves the points unchanged
/// assert_eq!(
/// layer.aabounding_box_for_transform(DAffine2::IDENTITY, &Default::default()),
/// layer.aabb_for_transform(DAffine2::IDENTITY, &Default::default()),
/// Some([DVec2::ZERO, DVec2::ONE]),
/// );
///
/// // Apply a transform that scales every point by a factor of two
/// let transform = DAffine2::from_scale(DVec2::ONE * 2.);
/// assert_eq!(
/// layer.aabounding_box_for_transform(transform, &Default::default()),
/// layer.aabb_for_transform(transform, &Default::default()),
/// Some([DVec2::ZERO, DVec2::ONE * 2.]),
/// );
pub fn aabounding_box_for_transform(&self, transform: DAffine2, font_cache: &FontCache) -> Option<[DVec2; 2]> {
pub fn aabb_for_transform(&self, transform: DAffine2, font_cache: &FontCache) -> Option<[DVec2; 2]> {
self.data.bounding_box(transform, font_cache)
}
pub fn aabounding_box(&self, font_cache: &FontCache) -> Option<[DVec2; 2]> {
self.aabounding_box_for_transform(self.transform, font_cache)
pub fn aabb(&self, font_cache: &FontCache) -> Option<[DVec2; 2]> {
self.aabb_for_transform(self.transform, font_cache)
}
pub fn bounding_transform(&self, font_cache: &FontCache) -> DAffine2 {
let scale = match self.aabounding_box_for_transform(DAffine2::IDENTITY, font_cache) {
let scale = match self.aabb_for_transform(DAffine2::IDENTITY, font_cache) {
Some([a, b]) => {
let dimensions = b - a;
DAffine2::from_scale(dimensions)
@@ -372,21 +372,21 @@ impl Layer {
}
}
pub fn as_vector_shape(&self) -> Option<&VectorShape> {
pub fn as_subpath(&self) -> Option<&Subpath> {
match &self.data {
LayerDataType::Shape(s) => Some(&s.shape),
_ => None,
}
}
pub fn as_vector_shape_copy(&self) -> Option<VectorShape> {
pub fn as_subpath_copy(&self) -> Option<Subpath> {
match &self.data {
LayerDataType::Shape(s) => Some(s.shape.clone()),
_ => None,
}
}
pub fn as_vector_shape_mut(&mut self) -> Option<&mut VectorShape> {
pub fn as_subpath_mut(&mut self) -> Option<&mut Subpath> {
match &mut self.data {
LayerDataType::Shape(s) => Some(&mut s.shape),
_ => None,
+19 -19
View File
@@ -1,6 +1,6 @@
use super::layer_info::LayerData;
use super::style::{self, PathStyle, RenderData, ViewMode};
use super::vector::vector_shape::VectorShape;
use super::vector::subpath::Subpath;
use crate::intersection::{intersect_quad_bez_path, Quad};
use crate::layers::text_layer::FontCache;
use crate::LayerId;
@@ -18,7 +18,7 @@ use std::fmt::Write;
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ShapeLayer {
/// The geometry of the layer.
pub shape: VectorShape,
pub shape: Subpath,
/// The visual style of the shape.
pub style: style::PathStyle,
// TODO: We might be able to remove this in a future refactor
@@ -27,9 +27,9 @@ pub struct ShapeLayer {
impl LayerData for ShapeLayer {
fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<DAffine2>, render_data: RenderData) {
let mut vector_shape = self.shape.clone();
let mut subpath = self.shape.clone();
let kurbo::Rect { x0, y0, x1, y1 } = vector_shape.bounding_box();
let kurbo::Rect { x0, y0, x1, y1 } = subpath.bounding_box();
let layer_bounds = [(x0, y0).into(), (x1, y1).into()];
let transform = self.transform(transforms, render_data.view_mode);
@@ -38,9 +38,9 @@ impl LayerData for ShapeLayer {
let _ = write!(svg, "<!-- SVG shape has an invalid transform -->");
return;
}
vector_shape.apply_affine(transform);
subpath.apply_affine(transform);
let kurbo::Rect { x0, y0, x1, y1 } = vector_shape.bounding_box();
let kurbo::Rect { x0, y0, x1, y1 } = subpath.bounding_box();
let transformed_bounds = [(x0, y0).into(), (x1, y1).into()];
let _ = writeln!(svg, r#"<g transform="matrix("#);
@@ -51,25 +51,25 @@ impl LayerData for ShapeLayer {
let _ = write!(
svg,
r#"<path d="{}" {} />"#,
vector_shape.to_svg(),
subpath.to_svg(),
self.style.render(render_data.view_mode, svg_defs, transform, layer_bounds, transformed_bounds)
);
let _ = svg.write_str("</g>");
}
fn bounding_box(&self, transform: glam::DAffine2, _font_cache: &FontCache) -> Option<[DVec2; 2]> {
let mut vector_shape = self.shape.clone();
let mut subpath = self.shape.clone();
if transform.matrix2 == DMat2::ZERO {
return None;
}
vector_shape.apply_affine(transform);
subpath.apply_affine(transform);
let kurbo::Rect { x0, y0, x1, y1 } = vector_shape.bounding_box();
let kurbo::Rect { x0, y0, x1, y1 } = subpath.bounding_box();
Some([(x0, y0).into(), (x1, y1).into()])
}
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, _font_cache: &FontCache) {
let filled = self.style.fill().is_some() || self.shape.anchors().last().filter(|anchor| anchor.is_close()).is_some();
let filled = self.style.fill().is_some() || self.shape.manipulator_groups().last().filter(|manipulator_group| manipulator_group.is_close()).is_some();
if intersect_quad_bez_path(quad, &(&self.shape).into(), filled) {
intersections.push(path.clone());
}
@@ -77,8 +77,8 @@ impl LayerData for ShapeLayer {
}
impl ShapeLayer {
/// Construct a new [ShapeLayer] with the specified [VectorShape] and [PathStyle]
pub fn new(shape: VectorShape, style: PathStyle) -> Self {
/// Construct a new [ShapeLayer] with the specified [Subpath] and [PathStyle]
pub fn new(shape: Subpath, style: PathStyle) -> Self {
Self { shape, style, render_index: 1 }
}
@@ -122,7 +122,7 @@ impl ShapeLayer {
path.close_path();
Self {
shape: VectorShape::new_ngon(DVec2::new(0., 0.), sides.into(), 1.),
shape: Subpath::new_ngon(DVec2::new(0., 0.), sides.into(), 1.),
style,
render_index: 1,
}
@@ -131,7 +131,7 @@ impl ShapeLayer {
/// Create a rectangular shape.
pub fn rectangle(style: PathStyle) -> Self {
Self {
shape: VectorShape::new_rect(DVec2::new(0., 0.), DVec2::new(1., 1.)),
shape: Subpath::new_rect(DVec2::new(0., 0.), DVec2::new(1., 1.)),
style,
render_index: 1,
}
@@ -140,7 +140,7 @@ impl ShapeLayer {
/// Create an elliptical shape.
pub fn ellipse(style: PathStyle) -> Self {
Self {
shape: VectorShape::new_ellipse(DVec2::new(0., 0.), DVec2::new(1., 1.)),
shape: Subpath::new_ellipse(DVec2::new(0., 0.), DVec2::new(1., 1.)),
style,
render_index: 1,
}
@@ -149,7 +149,7 @@ impl ShapeLayer {
/// Create a straight line from (0, 0) to (1, 0).
pub fn line(style: PathStyle) -> Self {
Self {
shape: VectorShape::new_line(DVec2::new(0., 0.), DVec2::new(1., 0.)),
shape: Subpath::new_line(DVec2::new(0., 0.), DVec2::new(1., 0.)),
style,
render_index: 1,
}
@@ -158,7 +158,7 @@ impl ShapeLayer {
/// Create a polygonal line that visits each provided point.
pub fn poly_line(points: Vec<impl Into<glam::DVec2>>, style: PathStyle) -> Self {
Self {
shape: VectorShape::new_poly_line(points),
shape: Subpath::new_poly_line(points),
style,
render_index: 0,
}
@@ -168,7 +168,7 @@ impl ShapeLayer {
/// The algorithm used in this implementation is described here: <https://www.particleincell.com/2012/bezier-splines/>
pub fn spline(points: Vec<impl Into<glam::DVec2>>, style: PathStyle) -> Self {
Self {
shape: VectorShape::new_spline(points),
shape: Subpath::new_spline(points),
style,
render_index: 0,
}
+13 -10
View File
@@ -1,6 +1,6 @@
use super::layer_info::LayerData;
use super::style::{PathStyle, RenderData, ViewMode};
use super::vector::vector_shape::VectorShape;
use super::vector::subpath::Subpath;
use crate::intersection::{intersect_quad_bez_path, Quad};
use crate::LayerId;
pub use font_cache::{Font, FontCache};
@@ -29,7 +29,7 @@ pub struct TextLayer {
#[serde(skip)]
pub editable: bool,
#[serde(skip)]
pub cached_path: Option<VectorShape>,
pub cached_path: Option<Subpath>,
}
impl LayerData for TextLayer {
@@ -68,7 +68,7 @@ impl LayerData for TextLayer {
} else {
let buzz_face = self.load_face(render_data.font_cache);
let mut path = self.to_vector_path(buzz_face);
let mut path = self.to_subpath(buzz_face);
let kurbo::Rect { x0, y0, x1, y1 } = path.bounding_box();
let bounds = [(x0, y0).into(), (x1, y1).into()];
@@ -136,10 +136,10 @@ impl TextLayer {
new
}
/// Converts to a [VectorShape], populating the cache if necessary.
/// Converts to a [Subpath], populating the cache if necessary.
#[inline]
pub fn to_vector_path(&mut self, buzz_face: Option<Face>) -> VectorShape {
if self.cached_path.as_ref().filter(|x| !x.anchors().is_empty()).is_none() {
pub fn to_subpath(&mut self, buzz_face: Option<Face>) -> Subpath {
if self.cached_path.as_ref().filter(|subpath| !subpath.manipulator_groups().is_empty()).is_none() {
let path = self.generate_path(buzz_face);
self.cached_path = Some(path.clone());
return path;
@@ -147,16 +147,19 @@ impl TextLayer {
self.cached_path.clone().unwrap()
}
/// Converts to a [VectorShape], without populating the cache.
/// Converts to a [Subpath], without populating the cache.
#[inline]
pub fn to_vector_path_nonmut(&self, font_cache: &FontCache) -> VectorShape {
pub fn to_subpath_nonmut(&self, font_cache: &FontCache) -> Subpath {
let buzz_face = self.load_face(font_cache);
self.cached_path.clone().filter(|x| !x.anchors().is_empty()).unwrap_or_else(|| self.generate_path(buzz_face))
self.cached_path
.clone()
.filter(|subpath| !subpath.manipulator_groups().is_empty())
.unwrap_or_else(|| self.generate_path(buzz_face))
}
#[inline]
pub fn generate_path(&self, buzz_face: Option<Face>) -> VectorShape {
pub fn generate_path(&self, buzz_face: Option<Face>) -> Subpath {
to_path::to_path(&self.text, buzz_face, self.size, self.line_width)
}
+18 -18
View File
@@ -1,14 +1,14 @@
use crate::layers::vector::constants::ControlPointType;
use crate::layers::vector::vector_anchor::VectorAnchor;
use crate::layers::vector::vector_control_point::VectorControlPoint;
use crate::layers::vector::vector_shape::VectorShape;
use crate::layers::vector::constants::ManipulatorType;
use crate::layers::vector::manipulator_group::ManipulatorGroup;
use crate::layers::vector::manipulator_point::ManipulatorPoint;
use crate::layers::vector::subpath::Subpath;
use glam::DVec2;
use rustybuzz::{GlyphBuffer, UnicodeBuffer};
use ttf_parser::{GlyphId, OutlineBuilder};
struct Builder {
path: VectorShape,
path: Subpath,
pos: DVec2,
offset: DVec2,
ascender: f64,
@@ -24,32 +24,32 @@ impl Builder {
impl OutlineBuilder for Builder {
fn move_to(&mut self, x: f32, y: f32) {
let anchor = self.point(x, y);
if self.path.anchors().last().filter(|el| el.points.iter().any(Option::is_some)).is_some() {
self.path.anchors_mut().push_end(VectorAnchor::closed());
if self.path.manipulator_groups().last().filter(|el| el.points.iter().any(Option::is_some)).is_some() {
self.path.manipulator_groups_mut().push_end(ManipulatorGroup::closed());
}
self.path.anchors_mut().push_end(VectorAnchor::new(anchor));
self.path.manipulator_groups_mut().push_end(ManipulatorGroup::new_with_anchor(anchor));
}
fn line_to(&mut self, x: f32, y: f32) {
let anchor = self.point(x, y);
self.path.anchors_mut().push_end(VectorAnchor::new(anchor));
self.path.manipulator_groups_mut().push_end(ManipulatorGroup::new_with_anchor(anchor));
}
fn quad_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32) {
let [handle, anchor] = [self.point(x1, y1), self.point(x2, y2)];
self.path.anchors_mut().last_mut().unwrap().points[ControlPointType::OutHandle] = Some(VectorControlPoint::new(handle, ControlPointType::OutHandle));
self.path.anchors_mut().push_end(VectorAnchor::new(anchor));
self.path.manipulator_groups_mut().last_mut().unwrap().points[ManipulatorType::OutHandle] = Some(ManipulatorPoint::new(handle, ManipulatorType::OutHandle));
self.path.manipulator_groups_mut().push_end(ManipulatorGroup::new_with_anchor(anchor));
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) {
let [handle1, handle2, anchor] = [self.point(x1, y1), self.point(x2, y2), self.point(x3, y3)];
self.path.anchors_mut().last_mut().unwrap().points[ControlPointType::OutHandle] = Some(VectorControlPoint::new(handle1, ControlPointType::OutHandle));
self.path.anchors_mut().push_end(VectorAnchor::new(anchor));
self.path.anchors_mut().last_mut().unwrap().points[ControlPointType::InHandle] = Some(VectorControlPoint::new(handle2, ControlPointType::InHandle));
self.path.manipulator_groups_mut().last_mut().unwrap().points[ManipulatorType::OutHandle] = Some(ManipulatorPoint::new(handle1, ManipulatorType::OutHandle));
self.path.manipulator_groups_mut().push_end(ManipulatorGroup::new_with_anchor(anchor));
self.path.manipulator_groups_mut().last_mut().unwrap().points[ManipulatorType::InHandle] = Some(ManipulatorPoint::new(handle2, ManipulatorType::InHandle));
}
fn close(&mut self) {
self.path.anchors_mut().push_end(VectorAnchor::closed());
self.path.manipulator_groups_mut().push_end(ManipulatorGroup::closed());
}
}
@@ -80,17 +80,17 @@ fn wrap_word(line_width: Option<f64>, glyph_buffer: &GlyphBuffer, scale: f64, x_
false
}
pub fn to_path(str: &str, buzz_face: Option<rustybuzz::Face>, font_size: f64, line_width: Option<f64>) -> VectorShape {
pub fn to_path(str: &str, buzz_face: Option<rustybuzz::Face>, font_size: f64, line_width: Option<f64>) -> Subpath {
let buzz_face = match buzz_face {
Some(face) => face,
// Show blank layer if font has not loaded
None => return VectorShape::default(),
None => return Subpath::default(),
};
let (scale, line_height, mut buffer) = font_properties(&buzz_face, font_size);
let mut builder = Builder {
path: VectorShape::new(),
path: Subpath::new(),
pos: DVec2::ZERO,
offset: DVec2::ZERO,
ascender: (buzz_face.ascender() as f64 / buzz_face.height() as f64) * font_size / scale,
+20 -23
View File
@@ -1,47 +1,44 @@
use std::ops::{Index, IndexMut, Not};
use std::ops::{Index, IndexMut};
use serde::{Deserialize, Serialize};
#[repr(usize)]
#[derive(PartialEq, Eq, Clone, Debug, Copy, Serialize, Deserialize)]
pub enum ControlPointType {
Anchor = 0,
InHandle = 1,
OutHandle = 2,
pub enum ManipulatorType {
Anchor,
InHandle,
OutHandle,
}
impl ControlPointType {
pub fn from_index(index: usize) -> ControlPointType {
impl ManipulatorType {
pub fn from_index(index: usize) -> ManipulatorType {
match index {
0 => ControlPointType::Anchor,
1 => ControlPointType::InHandle,
2 => ControlPointType::OutHandle,
_ => ControlPointType::Anchor,
0 => ManipulatorType::Anchor,
1 => ManipulatorType::InHandle,
2 => ManipulatorType::OutHandle,
_ => ManipulatorType::Anchor,
}
}
}
impl Not for ControlPointType {
type Output = Self;
fn not(self) -> Self::Output {
pub fn opposite_handle(self) -> ManipulatorType {
match self {
ControlPointType::InHandle => ControlPointType::OutHandle,
ControlPointType::OutHandle => ControlPointType::InHandle,
_ => ControlPointType::Anchor,
ManipulatorType::Anchor => ManipulatorType::Anchor,
ManipulatorType::InHandle => ManipulatorType::OutHandle,
ManipulatorType::OutHandle => ManipulatorType::InHandle,
}
}
}
// Allows us to use ManipulatorType for indexing
impl<T> Index<ControlPointType> for [T; 3] {
impl<T> Index<ManipulatorType> for [T; 3] {
type Output = T;
fn index(&self, mt: ControlPointType) -> &T {
fn index(&self, mt: ManipulatorType) -> &T {
&self[mt as usize]
}
}
// Allows us to use ControlPointType for indexing, mutably
impl<T> IndexMut<ControlPointType> for [T; 3] {
fn index_mut(&mut self, mt: ControlPointType) -> &mut T {
// Allows us to use ManipulatorType for indexing, mutably
impl<T> IndexMut<ManipulatorType> for [T; 3] {
fn index_mut(&mut self, mt: ManipulatorType) -> &mut T {
&mut self[mt as usize]
}
}
@@ -1,79 +1,84 @@
use super::{
constants::{ControlPointType, SELECTION_THRESHOLD},
vector_control_point::VectorControlPoint,
constants::{ManipulatorType, SELECTION_THRESHOLD},
manipulator_point::ManipulatorPoint,
};
use glam::{DAffine2, DVec2};
use serde::{Deserialize, Serialize};
/// Brief overview of VectorAnchor
/// VectorAnchor <- Container for the anchor metadata and optional VectorControlPoints
/// /
/// [Option<VectorControlPoint>; 3] <- [0] is the anchor's draggable point (but not metadata), [1] is the InHandle's draggable point, [2] is the OutHandle's draggable point
/// / | \
/// "Anchor" "InHandle" "OutHandle" <- These are VectorControlPoints and the only editable "primitive"
/// VectorAnchor is used to represent an anchor point + handles on the path that can be moved.
/// [ManipulatorGroup] is used to represent an anchor point + handles on the path that can be moved.
/// It contains 0-2 handles that are optionally available.
///
/// Overview:
/// ```text
/// ManipulatorGroup <- Container for the anchor metadata and optional ManipulatorPoint
/// |
/// [Option<ManipulatorPoint>; 3] <- [0] is the anchor's draggable point (but not metadata), [1] is the
/// / | \ InHandle's draggable point, [2] is the OutHandle's draggable point
/// / | \
/// "Anchor" "InHandle" "OutHandle" <- These are ManipulatorPoints and the only editable "primitive"
/// ```
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Default)]
pub struct VectorAnchor {
// Editable points for the anchor & handles
pub points: [Option<VectorControlPoint>; 3],
pub struct ManipulatorGroup {
/// Editable points for the anchor and handles.
pub points: [Option<ManipulatorPoint>; 3],
#[serde(skip)]
// The editor state of the anchor and handles
pub editor_state: VectorAnchorState,
// TODO: Remove this from Graphene, editor state should be stored in the frontend if possible.
/// The editor state of the anchor and handles.
pub editor_state: ManipulatorGroupEditorState,
}
impl VectorAnchor {
/// Create a new anchor with the given position
pub fn new(anchor_pos: DVec2) -> Self {
impl ManipulatorGroup {
/// Create a new anchor with the given position.
pub fn new_with_anchor(anchor_pos: DVec2) -> Self {
Self {
// An anchor and 2x None's which represent non-existent handles
points: [Some(VectorControlPoint::new(anchor_pos, ControlPointType::Anchor)), None, None],
editor_state: VectorAnchorState::default(),
points: [Some(ManipulatorPoint::new(anchor_pos, ManipulatorType::Anchor)), None, None],
editor_state: ManipulatorGroupEditorState::default(),
}
}
/// Create a new anchor with the given anchor position and handles
/// Create a new anchor with the given anchor position and handles.
pub fn new_with_handles(anchor_pos: DVec2, handle_in_pos: Option<DVec2>, handle_out_pos: Option<DVec2>) -> Self {
Self {
points: match (handle_in_pos, handle_out_pos) {
(Some(pos1), Some(pos2)) => [
Some(VectorControlPoint::new(anchor_pos, ControlPointType::Anchor)),
Some(VectorControlPoint::new(pos1, ControlPointType::InHandle)),
Some(VectorControlPoint::new(pos2, ControlPointType::OutHandle)),
Some(ManipulatorPoint::new(anchor_pos, ManipulatorType::Anchor)),
Some(ManipulatorPoint::new(pos1, ManipulatorType::InHandle)),
Some(ManipulatorPoint::new(pos2, ManipulatorType::OutHandle)),
],
(None, Some(pos2)) => [
Some(VectorControlPoint::new(anchor_pos, ControlPointType::Anchor)),
Some(ManipulatorPoint::new(anchor_pos, ManipulatorType::Anchor)),
None,
Some(VectorControlPoint::new(pos2, ControlPointType::OutHandle)),
Some(ManipulatorPoint::new(pos2, ManipulatorType::OutHandle)),
],
(Some(pos1), None) => [
Some(VectorControlPoint::new(anchor_pos, ControlPointType::Anchor)),
Some(VectorControlPoint::new(pos1, ControlPointType::InHandle)),
Some(ManipulatorPoint::new(anchor_pos, ManipulatorType::Anchor)),
Some(ManipulatorPoint::new(pos1, ManipulatorType::InHandle)),
None,
],
(None, None) => [Some(VectorControlPoint::new(anchor_pos, ControlPointType::Anchor)), None, None],
(None, None) => [Some(ManipulatorPoint::new(anchor_pos, ManipulatorType::Anchor)), None, None],
},
editor_state: VectorAnchorState::default(),
editor_state: ManipulatorGroupEditorState::default(),
}
}
/// Create a VectorAnchor that represents a close path signal
// TODO Convert into bool in subpath
/// Create a [ManipulatorGroup] that represents a close path signal.
pub fn closed() -> Self {
Self {
// An anchor being None indicates a ClosePath (aka a path end)
// An anchor (the first element) being `None` indicates a ClosePath (i.e. a path end command)
points: [None, None, None],
editor_state: VectorAnchorState::default(),
editor_state: ManipulatorGroupEditorState::default(),
}
}
/// Does this [VectorAnchor] represent a close signal?
/// Answers whether this [ManipulatorGroup] represent a close shape command.
pub fn is_close(&self) -> bool {
self.points[ControlPointType::Anchor].is_none() && self.points[ControlPointType::InHandle].is_none()
self.points[ManipulatorType::Anchor].is_none() && self.points[ManipulatorType::InHandle].is_none()
}
/// Finds the closest VectorControlPoint owned by this anchor. This can be the handles or the anchor itself
/// Finds the closest [ManipulatorPoint] owned by this [ManipulatorGroup]. This may return the anchor or either handle.
pub fn closest_point(&self, transform_space: &DAffine2, target: glam::DVec2) -> usize {
let mut closest_index: usize = 0;
let mut closest_distance_squared: f64 = f64::MAX; // Not ideal
@@ -89,7 +94,7 @@ impl VectorAnchor {
closest_index
}
/// Move the selected points by the provided transform
/// Move the selected points by the provided transform.
pub fn move_selected_points(&mut self, delta: DVec2, absolute_position: DVec2, viewspace: &DAffine2) {
let mirror_angle = self.editor_state.mirror_angle_between_handles;
// Invert distance since we want it to start disabled
@@ -97,20 +102,20 @@ impl VectorAnchor {
// TODO Use an ID as opposed to distance, stopgap for now
// Transformed into viewspace so SELECTION_THRESHOLD is in pixels
let is_drag_target = |point: &mut VectorControlPoint| -> bool { viewspace.transform_point2(absolute_position).distance(viewspace.transform_point2(point.position)) < SELECTION_THRESHOLD };
let is_drag_target = |point: &mut ManipulatorPoint| -> bool { viewspace.transform_point2(absolute_position).distance(viewspace.transform_point2(point.position)) < SELECTION_THRESHOLD };
// Move the point absolutely or relatively depending on if the point is under the cursor (the last selected point)
let move_point = |point: &mut VectorControlPoint, delta: DVec2, absolute_position: DVec2| {
let move_point = |point: &mut ManipulatorPoint, delta: DVec2, absolute_position: DVec2| {
if is_drag_target(point) {
point.position = absolute_position;
} else {
point.position += delta;
}
assert!(point.position.is_finite(), "Point is not finite")
assert!(point.position.is_finite(), "Point is not finite!")
};
// Find the correctly mirrored handle position based on mirroring settings
let move_symmetrical_handle = |position: DVec2, opposing_handle: Option<&mut VectorControlPoint>, center: DVec2| {
let move_symmetrical_handle = |position: DVec2, opposing_handle: Option<&mut ManipulatorPoint>, center: DVec2| {
// Early out for cases where we can't mirror
if !mirror_angle || opposing_handle.is_none() {
return;
@@ -122,7 +127,7 @@ impl VectorAnchor {
if let Some(offset) = (position - center).try_normalize() {
opposing_handle.position = center - offset * radius;
assert!(opposing_handle.position.is_finite(), "Oposing handle not finite")
assert!(opposing_handle.position.is_finite(), "Opposing handle not finite!")
}
};
@@ -131,8 +136,7 @@ impl VectorAnchor {
return;
}
// If the anchor is selected ignore any handle mirroring / dragging
// Drag all points
// If the anchor is selected, ignore any handle mirroring/dragging and drag all points
if self.is_anchor_selected() {
for point in self.points_mut() {
move_point(point, delta, absolute_position);
@@ -140,8 +144,7 @@ impl VectorAnchor {
return;
}
// If the anchor isn't selected, but both handles are
// Drag only handles
// If the anchor isn't selected, but both handles are, drag only handles
if self.both_handles_selected() {
for point in self.selected_handles_mut() {
move_point(point, delta, absolute_position);
@@ -151,7 +154,7 @@ impl VectorAnchor {
// If the anchor isn't selected, and only one handle is selected
// Drag the single handle
let reflect_center = self.points[ControlPointType::Anchor].as_ref().unwrap().position;
let reflect_center = self.points[ManipulatorType::Anchor].as_ref().unwrap().position;
let selected_handle = self.selected_handles_mut().next().unwrap();
move_point(selected_handle, delta, absolute_position);
@@ -161,7 +164,7 @@ impl VectorAnchor {
move_symmetrical_handle(selected_handle.position, opposing_handle, reflect_center);
}
/// Delete any VectorControlPoint that are selected, this includes handles or the anchor
/// Delete any [ManipulatorPoint] that are selected, this includes handles or the anchor.
pub fn delete_selected(&mut self) {
for point_option in self.points.iter_mut() {
if let Some(point) = point_option {
@@ -172,12 +175,12 @@ impl VectorAnchor {
}
}
/// Returns true if any points in this anchor are selected
/// Returns true if any points in this [ManipulatorGroup] are selected.
pub fn any_points_selected(&self) -> bool {
self.points.iter().flatten().any(|pnt| pnt.editor_state.is_selected)
self.points.iter().flatten().any(|point| point.editor_state.is_selected)
}
/// Returns true if the anchor point is selected
/// Returns true if the anchor point is selected.
pub fn is_anchor_selected(&self) -> bool {
if let Some(anchor) = &self.points[0] {
anchor.editor_state.is_selected
@@ -186,73 +189,83 @@ impl VectorAnchor {
}
}
/// Determines if two handle points are selected
/// Determines if the two handle points are selected.
pub fn both_handles_selected(&self) -> bool {
self.points.iter().skip(1).flatten().filter(|pnt| pnt.editor_state.is_selected).count() == 2
}
/// Set a point to selected by ID
pub fn select_point(&mut self, point_id: usize, selected: bool) -> Option<&mut VectorControlPoint> {
/// Set a point, given its [ManipulatorType] enum integer ID, to a chosen selected state.
pub fn select_point(&mut self, point_id: usize, selected: bool) -> Option<&mut ManipulatorPoint> {
if let Some(point) = self.points[point_id].as_mut() {
point.set_selected(selected);
}
self.points[point_id].as_mut()
}
/// Clear the selected points for this anchor
/// Clear the selected points for this [ManipulatorGroup].
pub fn clear_selected_points(&mut self) {
for point in self.points.iter_mut().flatten() {
point.set_selected(false);
}
}
/// Provides the points in this anchor
pub fn points(&self) -> impl Iterator<Item = &VectorControlPoint> {
/// Provides the points in this [ManipulatorGroup].
pub fn points(&self) -> impl Iterator<Item = &ManipulatorPoint> {
self.points.iter().flatten()
}
/// Provides the points in this anchor
pub fn points_mut(&mut self) -> impl Iterator<Item = &mut VectorControlPoint> {
/// Provides the points in this [ManipulatorGroup] as mutable.
pub fn points_mut(&mut self) -> impl Iterator<Item = &mut ManipulatorPoint> {
self.points.iter_mut().flatten()
}
/// Provides the selected points in this anchor
pub fn selected_points(&self) -> impl Iterator<Item = &VectorControlPoint> {
/// Provides the selected points in this [ManipulatorGroup].
pub fn selected_points(&self) -> impl Iterator<Item = &ManipulatorPoint> {
self.points.iter().flatten().filter(|pnt| pnt.editor_state.is_selected)
}
/// Provides mutable selected points in this anchor
pub fn selected_points_mut(&mut self) -> impl Iterator<Item = &mut VectorControlPoint> {
/// Provides mutable selected points in this [ManipulatorGroup].
pub fn selected_points_mut(&mut self) -> impl Iterator<Item = &mut ManipulatorPoint> {
self.points.iter_mut().flatten().filter(|pnt| pnt.editor_state.is_selected)
}
/// Provides the selected handles attached to this anchor
pub fn selected_handles(&self) -> impl Iterator<Item = &VectorControlPoint> {
/// Provides the selected handles attached to this [ManipulatorGroup].
pub fn selected_handles(&self) -> impl Iterator<Item = &ManipulatorPoint> {
self.points.iter().skip(1).flatten().filter(|pnt| pnt.editor_state.is_selected)
}
/// Provides the mutable selected handles attached to this anchor
pub fn selected_handles_mut(&mut self) -> impl Iterator<Item = &mut VectorControlPoint> {
/// Provides the mutable selected handles attached to this [ManipulatorGroup].
pub fn selected_handles_mut(&mut self) -> impl Iterator<Item = &mut ManipulatorPoint> {
self.points.iter_mut().skip(1).flatten().filter(|pnt| pnt.editor_state.is_selected)
}
/// Angle between handles in radians
/// Angle between handles, in radians.
pub fn angle_between_handles(&self) -> f64 {
if let [Some(a1), Some(h1), Some(h2)] = &self.points {
return (a1.position - h1.position).angle_between(a1.position - h2.position);
(a1.position - h1.position).angle_between(a1.position - h2.position)
} else {
0.
}
0.0
}
/// Returns the opposing handle to the handle provided
/// Returns the anchor handle if the anchor is provided
pub fn opposing_handle(&self, handle: &VectorControlPoint) -> Option<&VectorControlPoint> {
self.points[!handle.manipulator_type].as_ref()
/// Returns the opposing handle to the handle provided.
/// Returns [None] if the provided handle is of type [ManipulatorType::Anchor].
/// Returns [None] if the opposing handle doesn't exist.
pub fn opposing_handle(&self, handle: &ManipulatorPoint) -> Option<&ManipulatorPoint> {
if handle.manipulator_type == ManipulatorType::Anchor {
return None;
}
self.points[handle.manipulator_type.opposite_handle()].as_ref()
}
/// Returns the opposing handle to the handle provided, mutable
/// Returns the anchor handle if the anchor is provided, mutable
pub fn opposing_handle_mut(&mut self, handle: &VectorControlPoint) -> Option<&mut VectorControlPoint> {
self.points[!handle.manipulator_type].as_mut()
/// Returns the opposing handle to the handle provided, mutable.
/// Returns [None] if the provided handle is of type [ManipulatorType::Anchor].
/// Returns [None] if the opposing handle doesn't exist.
pub fn opposing_handle_mut(&mut self, handle: &ManipulatorPoint) -> Option<&mut ManipulatorPoint> {
if handle.manipulator_type == ManipulatorType::Anchor {
return None;
}
self.points[handle.manipulator_type.opposite_handle()].as_mut()
}
/// Set the mirroring state
@@ -265,13 +278,13 @@ impl VectorAnchor {
}
}
/// Helper function to more easily set position of VectorControlPoints
/// Helper function to more easily set position of [ManipulatorPoints]
pub fn set_point_position(&mut self, point_index: usize, position: DVec2) {
assert!(position.is_finite(), "Tried to set_point_position to non finite");
if let Some(point) = &mut self.points[point_index] {
point.position = position;
} else {
self.points[point_index] = Some(VectorControlPoint::new(position, ControlPointType::from_index(point_index)))
self.points[point_index] = Some(ManipulatorPoint::new(position, ManipulatorType::from_index(point_index)))
}
}
@@ -284,14 +297,14 @@ impl VectorAnchor {
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VectorAnchorState {
// If we should maintain the angle between the handles
pub struct ManipulatorGroupEditorState {
// Whether the angle between the handles should be maintained
pub mirror_angle_between_handles: bool,
// If we should make the handles equidistance from the anchor?
// Whether the distance between the handles should be equidistant to the anchor
pub mirror_distance_between_handles: bool,
}
impl Default for VectorAnchorState {
impl Default for ManipulatorGroupEditorState {
fn default() -> Self {
Self {
mirror_angle_between_handles: true,
@@ -1,46 +1,48 @@
use super::constants::ControlPointType;
use super::constants::ManipulatorType;
use glam::{DAffine2, DVec2};
use serde::{Deserialize, Serialize};
/// VectorControlPoint represents any editable point, anchor or handle
/// [ManipulatorPoint] represents any editable Bezier point, either an anchor or handle
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub struct VectorControlPoint {
pub struct ManipulatorPoint {
/// The sibling element if this is a handle
pub position: glam::DVec2,
/// The type of manipulator this point is
pub manipulator_type: ControlPointType,
pub manipulator_type: ManipulatorType,
#[serde(skip)]
/// The state specific to the editor
pub editor_state: VectorControlPointState,
// TODO Remove this from Graphene, editor state should be stored in the frontend if possible.
pub editor_state: ManipulatorPointEditorState,
}
impl Default for VectorControlPoint {
impl Default for ManipulatorPoint {
fn default() -> Self {
Self {
position: DVec2::ZERO,
manipulator_type: ControlPointType::Anchor,
editor_state: VectorControlPointState::default(),
manipulator_type: ManipulatorType::Anchor,
editor_state: ManipulatorPointEditorState::default(),
}
}
}
impl VectorControlPoint {
/// Initialize a new control point
pub fn new(position: glam::DVec2, manipulator_type: ControlPointType) -> Self {
impl ManipulatorPoint {
/// Initialize a new [ManipulatorPoint].
pub fn new(position: glam::DVec2, manipulator_type: ManipulatorType) -> Self {
assert!(position.is_finite(), "tried to create point with non finite position");
Self {
position,
manipulator_type,
editor_state: VectorControlPointState::default(),
editor_state: ManipulatorPointEditorState::default(),
}
}
/// Sets if this point is selected
/// Sets this [ManipulatorPoint] to a chosen selection state.
pub fn set_selected(&mut self, selected: bool) {
self.editor_state.is_selected = selected;
}
/// Whether this [ManipulatorPoint] is currently selected.
pub fn is_selected(&self) -> bool {
self.editor_state.is_selected
}
@@ -59,14 +61,14 @@ impl VectorControlPoint {
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct VectorControlPointState {
/// If this control point can be selected
pub struct ManipulatorPointEditorState {
/// Whether or not this manipulator point can be selected.
pub can_be_selected: bool,
/// Is this control point currently selected
/// Whether or not this manipulator point is currently selected.
pub is_selected: bool,
}
impl Default for VectorControlPointState {
impl Default for ManipulatorPointEditorState {
fn default() -> Self {
Self {
can_be_selected: true,
+3 -3
View File
@@ -1,4 +1,4 @@
pub mod constants;
pub mod vector_anchor;
pub mod vector_control_point;
pub mod vector_shape;
pub mod manipulator_group;
pub mod manipulator_point;
pub mod subpath;
+541
View File
@@ -0,0 +1,541 @@
use super::constants::ManipulatorType;
use super::manipulator_group::ManipulatorGroup;
use super::manipulator_point::ManipulatorPoint;
use crate::layers::id_vec::IdBackedVec;
use crate::layers::layer_info::{Layer, LayerDataType};
use glam::{DAffine2, DVec2};
use kurbo::{BezPath, PathEl, Rect, Shape};
use serde::{Deserialize, Serialize};
/// [Subpath] represents a single vector path, containing many [ManipulatorGroups].
/// For each closed shape we keep a [Subpath] which contains the [ManipulatorGroup]s (handles and anchors) that define that shape.
// TODO Add "closed" bool to subpath
#[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
pub struct Subpath(IdBackedVec<ManipulatorGroup>);
impl Subpath {
// ** INITIALIZATION **
/// Create a new [Subpath] with no [ManipulatorGroup]s.
pub fn new() -> Self {
Subpath { ..Default::default() }
}
/// Construct a [Subpath] from a point iterator
pub fn from_points(points: impl Iterator<Item = DVec2>, closed: bool) -> Self {
let manipulator_groups = points.map(ManipulatorGroup::new_with_anchor);
let mut p_line = Subpath(IdBackedVec::default());
p_line.0.push_range(manipulator_groups);
if closed {
p_line.0.push(ManipulatorGroup::closed());
}
p_line
}
/// Create a new [Subpath] from a [kurbo Shape](Shape).
/// This exists to smooth the transition away from Kurbo
pub fn from_kurbo_shape<T: Shape>(shape: &T) -> Self {
shape.path_elements(0.1).into()
}
// ** PRIMITIVE CONSTRUCTION **
/// constructs a rectangle with `p1` as the lower left and `p2` as the top right
pub fn new_rect(p1: DVec2, p2: DVec2) -> Self {
Subpath(
vec![
ManipulatorGroup::new_with_anchor(p1),
ManipulatorGroup::new_with_anchor(DVec2::new(p1.x, p2.y)),
ManipulatorGroup::new_with_anchor(p2),
ManipulatorGroup::new_with_anchor(DVec2::new(p2.x, p1.y)),
ManipulatorGroup::closed(),
]
.into_iter()
.collect(),
)
}
pub fn new_ellipse(p1: DVec2, p2: DVec2) -> Self {
let x_height = DVec2::new((p2.x - p1.x).abs(), 0.);
let y_height = DVec2::new(0., (p2.y - p1.y).abs());
let center = (p1 + p2) * 0.5;
let top = center + y_height * 0.5;
let bottom = center - y_height * 0.5;
let left = center + x_height * 0.5;
let right = center - x_height * 0.5;
// Constant explained here https://stackoverflow.com/a/27863181
let curve_constant = 0.55228_3;
let handle_offset_x = x_height * curve_constant * 0.5;
let handle_offset_y = y_height * curve_constant * 0.5;
Subpath(
vec![
ManipulatorGroup::new_with_handles(top, Some(top + handle_offset_x), Some(top - handle_offset_x)),
ManipulatorGroup::new_with_handles(right, Some(right + handle_offset_y), Some(right - handle_offset_y)),
ManipulatorGroup::new_with_handles(bottom, Some(bottom - handle_offset_x), Some(bottom + handle_offset_x)),
ManipulatorGroup::new_with_handles(left, Some(left - handle_offset_y), Some(left + handle_offset_y)),
ManipulatorGroup::closed(),
]
.into_iter()
.collect(),
)
}
/// constructs an ngon
/// `radius` is the distance from the `center` to any vertex, or the radius of the circle the ngon may be inscribed inside
/// `sides` is the number of sides
pub fn new_ngon(center: DVec2, sides: u64, radius: f64) -> Self {
let mut manipulator_groups = vec![];
for i in 0..sides {
let angle = (i as f64) * std::f64::consts::TAU / (sides as f64);
let center = center + DVec2::ONE * radius;
let position = ManipulatorGroup::new_with_anchor(DVec2::new(center.x + radius * f64::cos(angle), center.y + radius * f64::sin(angle)) * 0.5);
manipulator_groups.push(position);
}
manipulator_groups.push(ManipulatorGroup::closed());
Subpath(manipulator_groups.into_iter().collect())
}
/// Constructs a line from `p1` to `p2`
pub fn new_line(p1: DVec2, p2: DVec2) -> Self {
Subpath(vec![ManipulatorGroup::new_with_anchor(p1), ManipulatorGroup::new_with_anchor(p2)].into_iter().collect())
}
/// Constructs a set of lines from `p1` to `pN`
pub fn new_poly_line<T: Into<glam::DVec2>>(points: Vec<T>) -> Self {
let manipulator_groups = points.into_iter().map(|point| ManipulatorGroup::new_with_anchor(point.into()));
let mut p_line = Subpath(IdBackedVec::default());
p_line.0.push_range(manipulator_groups);
p_line
}
pub fn new_spline<T: Into<glam::DVec2>>(points: Vec<T>) -> Self {
let mut new = Self::default();
// shadow `points`
let points: Vec<DVec2> = points.into_iter().map(Into::<glam::DVec2>::into).collect();
// Number of points = number of points to find handles for
let n = points.len();
// matrix coefficients a, b and c (see https://mathworld.wolfram.com/CubicSpline.html)
// because the 'a' coefficients are all 1 they need not be stored
// this algorithm does a variation of the above algorithm.
// Instead of using the traditional cubic: a + bt + ct^2 + dt^3, we use the bezier cubic.
let mut b = vec![DVec2::new(4.0, 4.0); n];
b[0] = DVec2::new(2.0, 2.0);
b[n - 1] = DVec2::new(2.0, 2.0);
let mut c = vec![DVec2::new(1.0, 1.0); n];
// 'd' is the the second point in a cubic bezier, which is what we solve for
let mut d = vec![DVec2::ZERO; n];
d[0] = DVec2::new(2.0 * points[1].x + points[0].x, 2.0 * points[1].y + points[0].y);
d[n - 1] = DVec2::new(3.0 * points[n - 1].x, 3.0 * points[n - 1].y);
for idx in 1..(n - 1) {
d[idx] = DVec2::new(4.0 * points[idx].x + 2.0 * points[idx + 1].x, 4.0 * points[idx].y + 2.0 * points[idx + 1].y);
}
// Solve with Thomas algorithm (see https://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm)
// do row operations to eliminate `a` coefficients
c[0] /= -b[0];
d[0] /= -b[0];
for i in 1..n {
b[i] += c[i - 1];
// for some reason the below line makes the borrow checker mad
//d[i] += d[i-1]
d[i] = d[i] + d[i - 1];
c[i] /= -b[i];
d[i] /= -b[i];
}
// at this point b[i] == -a[i + 1], a[i] == 0,
// do row operations to eliminate 'c' coefficients and solve
d[n - 1] *= -1.0;
for i in (0..n - 1).rev() {
d[i] = d[i] - (c[i] * d[i + 1]);
d[i] *= -1.0; //d[i] /= b[i]
}
// given the second point in the n'th cubic bezier, the third point is given by 2 * points[n+1] - b[n+1].
// to find 'handle1_pos' for the n'th point we need the n-1 cubic bezier
new.0.push_end(ManipulatorGroup::new_with_handles(points[0], None, Some(d[0])));
for i in 1..n - 1 {
new.0.push_end(ManipulatorGroup::new_with_handles(points[i], Some(2.0 * points[i] - d[i]), Some(d[i])));
}
new.0.push_end(ManipulatorGroup::new_with_handles(points[n - 1], Some(2.0 * points[n - 1] - d[n - 1]), None));
new
}
/// Move the selected points by the delta vector
pub fn move_selected(&mut self, delta: DVec2, absolute_position: DVec2, viewspace: &DAffine2) {
self.selected_manipulator_groups_any_points_mut()
.for_each(|manipulator_group| manipulator_group.move_selected_points(delta, absolute_position, viewspace));
}
/// Delete the selected points from the [Subpath]
pub fn delete_selected(&mut self) {
let mut ids_to_delete: Vec<u64> = vec![];
for (id, manipulator_group) in self.manipulator_groups_mut().enumerate_mut() {
if manipulator_group.is_anchor_selected() {
ids_to_delete.push(*id);
} else {
manipulator_group.delete_selected();
}
}
for id in ids_to_delete {
self.manipulator_groups_mut().remove(id);
}
}
// Apply a transformation to all of the Subpath points
pub fn apply_affine(&mut self, affine: DAffine2) {
for manipulator_group in self.manipulator_groups_mut().iter_mut() {
manipulator_group.transform(&affine);
}
}
// ** SELECTION OF POINTS **
/// Set a single point to a chosen selection state by providing `(manipulator group ID, manipulator type)`.
pub fn select_point(&mut self, point: (u64, ManipulatorType), selected: bool) -> Option<&mut ManipulatorGroup> {
let (manipulator_group_id, point_id) = point;
if let Some(manipulator_group) = self.manipulator_groups_mut().by_id_mut(manipulator_group_id) {
manipulator_group.select_point(point_id as usize, selected);
Some(manipulator_group)
} else {
None
}
}
/// Set points in the [Subpath] to a chosen selection state, given by `(manipulator group ID, manipulator type)`.
pub fn select_points(&mut self, points: &[(u64, ManipulatorType)], selected: bool) {
points.iter().for_each(|point| {
self.select_point(*point, selected);
});
}
/// Select all the anchors in this shape
pub fn select_all_anchors(&mut self) {
for manipulator_group in self.manipulator_groups_mut().iter_mut() {
manipulator_group.select_point(ManipulatorType::Anchor as usize, true);
}
}
/// Select an anchor by index
pub fn select_anchor_by_index(&mut self, manipulator_group_index: usize) -> Option<&mut ManipulatorGroup> {
if let Some(manipulator_group) = self.manipulator_groups_mut().by_index_mut(manipulator_group_index) {
manipulator_group.select_point(ManipulatorType::Anchor as usize, true);
Some(manipulator_group)
} else {
None
}
}
/// The last anchor in the shape
pub fn select_last_anchor(&mut self) -> Option<&mut ManipulatorGroup> {
if let Some(manipulator_group) = self.manipulator_groups_mut().last_mut() {
manipulator_group.select_point(ManipulatorType::Anchor as usize, true);
Some(manipulator_group)
} else {
None
}
}
/// Clear all the selected manipulator groups, i.e., clear the selected points inside the manipulator groups
pub fn clear_selected_manipulator_groups(&mut self) {
for manipulator_group in self.manipulator_groups_mut().iter_mut() {
manipulator_group.clear_selected_points();
}
}
// ** ACCESSING MANIPULATORGROUPS **
/// Return all the selected anchors, reference
pub fn selected_manipulator_groups(&self) -> impl Iterator<Item = &ManipulatorGroup> {
self.manipulator_groups().iter().filter(|manipulator_group| manipulator_group.is_anchor_selected())
}
/// Return all the selected anchors, mutable
pub fn selected_manipulator_groups_mut(&mut self) -> impl Iterator<Item = &mut ManipulatorGroup> {
self.manipulator_groups_mut().iter_mut().filter(|manipulator_group| manipulator_group.is_anchor_selected())
}
/// Return all the selected [ManipulatorPoint]s by reference
pub fn selected_manipulator_groups_any_points(&self) -> impl Iterator<Item = &ManipulatorGroup> {
self.manipulator_groups().iter().filter(|manipulator_group| manipulator_group.any_points_selected())
}
/// Return all the selected [ManipulatorPoint]s by mutable reference
pub fn selected_manipulator_groups_any_points_mut(&mut self) -> impl Iterator<Item = &mut ManipulatorGroup> {
self.manipulator_groups_mut().iter_mut().filter(|manipulator_group| manipulator_group.any_points_selected())
}
/// An alias for `self.0`
pub fn manipulator_groups(&self) -> &IdBackedVec<ManipulatorGroup> {
&self.0
}
/// Returns a [ManipulatorPoint] from the last [ManipulatorGroup] in the [Subpath].
pub fn last_point(&self, control_type: ManipulatorType) -> Option<&ManipulatorPoint> {
self.manipulator_groups().last().and_then(|manipulator_group| manipulator_group.points[control_type].as_ref())
}
/// Returns a [ManipulatorPoint] from the last [ManipulatorGroup], mutably
pub fn last_point_mut(&mut self, control_type: ManipulatorType) -> Option<&mut ManipulatorPoint> {
self.manipulator_groups_mut().last_mut().and_then(|manipulator_group| manipulator_group.points[control_type].as_mut())
}
/// Returns a [ManipulatorPoint] from the first [ManipulatorGroup]
pub fn first_point(&self, control_type: ManipulatorType) -> Option<&ManipulatorPoint> {
self.manipulator_groups().first().and_then(|manipulator_group| manipulator_group.points[control_type].as_ref())
}
/// Returns a [ManipulatorPoint] from the first [ManipulatorGroup]
pub fn first_point_mut(&mut self, control_type: ManipulatorType) -> Option<&mut ManipulatorPoint> {
self.manipulator_groups_mut().first_mut().and_then(|manipulator_group| manipulator_group.points[control_type].as_mut())
}
/// Should we close the shape?
pub fn should_close_shape(&self) -> bool {
if self.last_point(ManipulatorType::Anchor).is_none() {
return false;
}
self.first_point(ManipulatorType::Anchor)
.unwrap()
.position
.distance(self.last_point(ManipulatorType::Anchor).unwrap().position)
< 0.001 // TODO Replace with constant, a small epsilon
}
/// Close the shape if able
pub fn close_shape(&mut self) {
if self.should_close_shape() {
self.manipulator_groups_mut().push_end(ManipulatorGroup::closed());
}
}
/// An alias for `self.0` mutable
pub fn manipulator_groups_mut(&mut self) -> &mut IdBackedVec<ManipulatorGroup> {
&mut self.0
}
// ** INTERFACE WITH KURBO **
// TODO Implement our own a local bounding box calculation
/// Return the bounding box of the shape
pub fn bounding_box(&self) -> Rect {
<&Self as Into<BezPath>>::into(self).bounding_box()
}
/// Use kurbo to convert this shape into an SVG path
pub fn to_svg(&mut self) -> String {
fn write_positions(result: &mut String, values: [Option<DVec2>; 3]) {
use std::fmt::Write;
let count = values.into_iter().flatten().count();
for (index, pos) in values.into_iter().flatten().enumerate() {
write!(result, "{},{}", pos.x, pos.y).unwrap();
if index != count - 1 {
result.push(' ');
}
}
}
let mut result = String::new();
// The out position from the previous ManipulatorGroup
let mut last_out_handle = None;
// The values from the last moveto (for closing the path)
let (mut first_in_handle, mut first_in_anchor) = (None, None);
// Should the next element be a moveto?
let mut start_new_contour = true;
for manipulator_group in self.manipulator_groups().iter() {
let in_handle = manipulator_group.points[ManipulatorType::InHandle].as_ref().map(|point| point.position);
let anchor = manipulator_group.points[ManipulatorType::Anchor].as_ref().map(|point| point.position);
let out_handle = manipulator_group.points[ManipulatorType::OutHandle].as_ref().map(|point| point.position);
let command = match (last_out_handle.is_some(), in_handle.is_some(), anchor.is_some()) {
(_, _, true) if start_new_contour => 'M',
(true, false, true) | (false, true, true) => 'Q',
(true, true, true) => 'C',
(false, false, true) => 'L',
(_, false, false) => 'Z',
_ => panic!("Invalid shape {:#?}", self),
};
// Complete the last curve
if command == 'Z' {
if last_out_handle.is_some() && first_in_handle.is_some() {
result.push('C');
write_positions(&mut result, [last_out_handle, first_in_handle, first_in_anchor]);
} else if last_out_handle.is_some() || first_in_handle.is_some() {
result.push('Q');
write_positions(&mut result, [last_out_handle, first_in_handle, first_in_anchor]);
} else {
result.push('Z');
}
} else if command == 'M' {
// Update the last moveto position
(first_in_handle, first_in_anchor) = (in_handle, anchor);
result.push(command);
write_positions(&mut result, [None, None, anchor]);
} else {
result.push(command);
write_positions(&mut result, [last_out_handle, in_handle, anchor]);
}
start_new_contour = command == 'Z';
last_out_handle = out_handle;
}
result
}
}
// ** CONVERSIONS **
impl<'a> TryFrom<&'a mut Layer> for &'a mut Subpath {
type Error = &'static str;
/// Convert a mutable layer into a mutable [Subpath].
fn try_from(layer: &'a mut Layer) -> Result<&'a mut Subpath, Self::Error> {
match &mut layer.data {
LayerDataType::Shape(layer) => Ok(&mut layer.shape),
// TODO Resolve converting text into a Subpath at the layer level
// LayerDataType::Text(text) => Some(Subpath::new(path_to_shape.to_vec(), viewport_transform, true)),
_ => Err("Did not find any shape data in the layer"),
}
}
}
impl<'a> TryFrom<&'a Layer> for &'a Subpath {
type Error = &'static str;
/// Convert a reference to a layer into a reference of a [Subpath].
fn try_from(layer: &'a Layer) -> Result<&'a Subpath, Self::Error> {
match &layer.data {
LayerDataType::Shape(layer) => Ok(&layer.shape),
// TODO Resolve converting text into a Subpath at the layer level
// LayerDataType::Text(text) => Some(Subpath::new(path_to_shape.to_vec(), viewport_transform, true)),
_ => Err("Did not find any shape data in the layer"),
}
}
}
impl From<&Subpath> for BezPath {
/// Create a [BezPath] from a [Subpath].
fn from(subpath: &Subpath) -> Self {
// Take manipulator groups and create path elements: line, quad or curve, or a close indicator
let manipulator_groups_to_path_el = |first: &ManipulatorGroup, second: &ManipulatorGroup| -> (PathEl, bool) {
match [
&first.points[ManipulatorType::OutHandle],
&second.points[ManipulatorType::InHandle],
&second.points[ManipulatorType::Anchor],
] {
[None, None, Some(anchor)] => (PathEl::LineTo(point_to_kurbo(anchor)), false),
[None, Some(in_handle), Some(anchor)] => (PathEl::QuadTo(point_to_kurbo(in_handle), point_to_kurbo(anchor)), false),
[Some(out_handle), None, Some(anchor)] => (PathEl::QuadTo(point_to_kurbo(out_handle), point_to_kurbo(anchor)), false),
[Some(out_handle), Some(in_handle), Some(anchor)] => (PathEl::CurveTo(point_to_kurbo(out_handle), point_to_kurbo(in_handle), point_to_kurbo(anchor)), false),
[Some(out_handle), None, None] => {
if let Some(first_anchor) = subpath.manipulator_groups().first() {
(
if let Some(in_handle) = &first_anchor.points[ManipulatorType::InHandle] {
PathEl::CurveTo(
point_to_kurbo(out_handle),
point_to_kurbo(in_handle),
point_to_kurbo(first_anchor.points[ManipulatorType::Anchor].as_ref().unwrap()),
)
} else {
PathEl::QuadTo(point_to_kurbo(out_handle), point_to_kurbo(first_anchor.points[ManipulatorType::Anchor].as_ref().unwrap()))
},
true,
)
} else {
(PathEl::ClosePath, true)
}
}
[None, None, None] => (PathEl::ClosePath, true),
_ => panic!("Invalid path element {:#?}", subpath),
}
};
if subpath.manipulator_groups().is_empty() {
return BezPath::new();
}
let mut bez_path = vec![];
let mut start_new_shape = true;
for elements in subpath.manipulator_groups().windows(2) {
let first = &elements[0];
let second = &elements[1];
// Tell kurbo cursor to move to the first anchor
if start_new_shape {
if let Some(anchor) = &first.points[ManipulatorType::Anchor] {
bez_path.push(PathEl::MoveTo(point_to_kurbo(anchor)));
}
}
// Create a path element from our first and second manipulator groups in the window
let (path_el, should_start_new_shape) = manipulator_groups_to_path_el(first, second);
start_new_shape = should_start_new_shape;
bez_path.push(path_el);
if should_start_new_shape && bez_path.last().filter(|&&el| el == PathEl::ClosePath).is_none() {
bez_path.push(PathEl::ClosePath)
}
}
BezPath::from_vec(bez_path)
}
}
impl<T: Iterator<Item = PathEl>> From<T> for Subpath {
/// Create a Subpath from a [BezPath].
fn from(path: T) -> Self {
let mut subpath = Subpath::new();
for path_el in path {
match path_el {
PathEl::MoveTo(p) => {
subpath.manipulator_groups_mut().push_end(ManipulatorGroup::new_with_anchor(kurbo_point_to_dvec2(p)));
}
PathEl::LineTo(p) => {
subpath.manipulator_groups_mut().push_end(ManipulatorGroup::new_with_anchor(kurbo_point_to_dvec2(p)));
}
PathEl::QuadTo(p0, p1) => {
subpath.manipulator_groups_mut().push_end(ManipulatorGroup::new_with_anchor(kurbo_point_to_dvec2(p1)));
subpath.manipulator_groups_mut().last_mut().unwrap().points[ManipulatorType::InHandle] = Some(ManipulatorPoint::new(kurbo_point_to_dvec2(p0), ManipulatorType::InHandle));
}
PathEl::CurveTo(p0, p1, p2) => {
subpath.manipulator_groups_mut().last_mut().unwrap().points[ManipulatorType::OutHandle] = Some(ManipulatorPoint::new(kurbo_point_to_dvec2(p0), ManipulatorType::OutHandle));
subpath.manipulator_groups_mut().push_end(ManipulatorGroup::new_with_anchor(kurbo_point_to_dvec2(p2)));
subpath.manipulator_groups_mut().last_mut().unwrap().points[ManipulatorType::InHandle] = Some(ManipulatorPoint::new(kurbo_point_to_dvec2(p1), ManipulatorType::InHandle));
}
PathEl::ClosePath => {
subpath.manipulator_groups_mut().push_end(ManipulatorGroup::closed());
}
}
}
subpath
}
}
#[inline]
fn point_to_kurbo(point: &ManipulatorPoint) -> kurbo::Point {
kurbo::Point::new(point.position.x, point.position.y)
}
#[inline]
fn kurbo_point_to_dvec2(point: kurbo::Point) -> DVec2 {
DVec2::new(point.x, point.y)
}
-529
View File
@@ -1,529 +0,0 @@
use super::constants::ControlPointType;
use super::vector_anchor::VectorAnchor;
use super::vector_control_point::VectorControlPoint;
use crate::layers::id_vec::IdBackedVec;
use crate::layers::layer_info::{Layer, LayerDataType};
use glam::{DAffine2, DVec2};
use kurbo::{BezPath, PathEl, Rect, Shape};
use serde::{Deserialize, Serialize};
/// VectorShape represents a single vector shape, containing many anchors
/// For each closed shape we keep a VectorShape which contains the handles and anchors that define that shape.
#[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
pub struct VectorShape(IdBackedVec<VectorAnchor>);
impl VectorShape {
// ** SHAPE INITIALIZATION **
/// Create a new VectorShape with no anchors or handles
pub fn new() -> Self {
VectorShape { ..Default::default() }
}
/// Construct a [VectorShape] from a point iterator
pub fn from_points(points: impl Iterator<Item = DVec2>, closed: bool) -> Self {
let anchors = points.map(VectorAnchor::new);
let mut p_line = VectorShape(IdBackedVec::default());
p_line.0.push_range(anchors);
if closed {
p_line.0.push(VectorAnchor::closed());
}
p_line
}
/// Create a new VectorShape from a kurbo Shape
/// This exists to smooth the transition away from Kurbo
pub fn from_kurbo_shape<T: Shape>(shape: &T) -> Self {
shape.path_elements(0.1).into()
}
// ** PRIMITIVE CONSTRUCTION **
/// constructs a rectangle with `p1` as the lower left and `p2` as the top right
pub fn new_rect(p1: DVec2, p2: DVec2) -> Self {
VectorShape(
vec![
VectorAnchor::new(p1),
VectorAnchor::new(DVec2::new(p1.x, p2.y)),
VectorAnchor::new(p2),
VectorAnchor::new(DVec2::new(p2.x, p1.y)),
VectorAnchor::closed(),
]
.into_iter()
.collect(),
)
}
pub fn new_ellipse(p1: DVec2, p2: DVec2) -> Self {
let x_height = DVec2::new((p2.x - p1.x).abs(), 0.);
let y_height = DVec2::new(0., (p2.y - p1.y).abs());
let center = (p1 + p2) * 0.5;
let top = center + y_height * 0.5;
let bottom = center - y_height * 0.5;
let left = center + x_height * 0.5;
let right = center - x_height * 0.5;
// Constant explained here https://stackoverflow.com/a/27863181
let curve_constant = 0.55228_3;
let handle_offset_x = x_height * curve_constant * 0.5;
let handle_offset_y = y_height * curve_constant * 0.5;
VectorShape(
vec![
VectorAnchor::new_with_handles(top, Some(top + handle_offset_x), Some(top - handle_offset_x)),
VectorAnchor::new_with_handles(right, Some(right + handle_offset_y), Some(right - handle_offset_y)),
VectorAnchor::new_with_handles(bottom, Some(bottom - handle_offset_x), Some(bottom + handle_offset_x)),
VectorAnchor::new_with_handles(left, Some(left - handle_offset_y), Some(left + handle_offset_y)),
VectorAnchor::closed(),
]
.into_iter()
.collect(),
)
}
/// constructs an ngon
/// `radius` is the distance from the `center` to any vertex, or the radius of the circle the ngon may be inscribed inside
/// `sides` is the number of sides
pub fn new_ngon(center: DVec2, sides: u64, radius: f64) -> Self {
let mut anchors = vec![];
for i in 0..sides {
let angle = (i as f64) * std::f64::consts::TAU / (sides as f64);
let center = center + DVec2::ONE * radius;
let position = VectorAnchor::new(DVec2::new(center.x + radius * f64::cos(angle), center.y + radius * f64::sin(angle)) * 0.5);
anchors.push(position);
}
anchors.push(VectorAnchor::closed());
VectorShape(anchors.into_iter().collect())
}
/// Constructs a line from `p1` to `p2`
pub fn new_line(p1: DVec2, p2: DVec2) -> Self {
VectorShape(vec![VectorAnchor::new(p1), VectorAnchor::new(p2)].into_iter().collect())
}
/// Constructs a set of lines from `p1` to `pN`
pub fn new_poly_line<T: Into<glam::DVec2>>(points: Vec<T>) -> Self {
let anchors = points.into_iter().map(|point| VectorAnchor::new(point.into()));
let mut p_line = VectorShape(IdBackedVec::default());
p_line.0.push_range(anchors);
p_line
}
pub fn new_spline<T: Into<glam::DVec2>>(points: Vec<T>) -> Self {
let mut new = Self::default();
// shadow `points`
let points: Vec<DVec2> = points.into_iter().map(Into::<glam::DVec2>::into).collect();
// Number of points = number of points to find handles for
let n = points.len();
// matrix coefficients a, b and c (see https://mathworld.wolfram.com/CubicSpline.html)
// because the 'a' coefficients are all 1 they need not be stored
// this algorithm does a variation of the above algorithm.
// Instead of using the traditional cubic: a + bt + ct^2 + dt^3, we use the bezier cubic.
let mut b = vec![DVec2::new(4.0, 4.0); n];
b[0] = DVec2::new(2.0, 2.0);
b[n - 1] = DVec2::new(2.0, 2.0);
let mut c = vec![DVec2::new(1.0, 1.0); n];
// 'd' is the the second point in a cubic bezier, which is what we solve for
let mut d = vec![DVec2::ZERO; n];
d[0] = DVec2::new(2.0 * points[1].x + points[0].x, 2.0 * points[1].y + points[0].y);
d[n - 1] = DVec2::new(3.0 * points[n - 1].x, 3.0 * points[n - 1].y);
for idx in 1..(n - 1) {
d[idx] = DVec2::new(4.0 * points[idx].x + 2.0 * points[idx + 1].x, 4.0 * points[idx].y + 2.0 * points[idx + 1].y);
}
// Solve with Thomas algorithm (see https://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm)
// do row operations to eliminate `a` coefficients
c[0] /= -b[0];
d[0] /= -b[0];
for i in 1..n {
b[i] += c[i - 1];
// for some reason the below line makes the borrow checker mad
//d[i] += d[i-1]
d[i] = d[i] + d[i - 1];
c[i] /= -b[i];
d[i] /= -b[i];
}
// at this point b[i] == -a[i + 1], a[i] == 0,
// do row operations to eliminate 'c' coefficients and solve
d[n - 1] *= -1.0;
for i in (0..n - 1).rev() {
d[i] = d[i] - (c[i] * d[i + 1]);
d[i] *= -1.0; //d[i] /= b[i]
}
// given the second point in the n'th cubic bezier, the third point is given by 2 * points[n+1] - b[n+1].
// to find 'handle1_pos' for the n'th point we need the n-1 cubic bezier
new.0.push_end(VectorAnchor::new_with_handles(points[0], None, Some(d[0])));
for i in 1..n - 1 {
new.0.push_end(VectorAnchor::new_with_handles(points[i], Some(2.0 * points[i] - d[i]), Some(d[i])));
}
new.0.push_end(VectorAnchor::new_with_handles(points[n - 1], Some(2.0 * points[n - 1] - d[n - 1]), None));
new
}
/// Move the selected points by the delta vector
pub fn move_selected(&mut self, delta: DVec2, absolute_position: DVec2, viewspace: &DAffine2) {
self.selected_anchors_any_points_mut()
.for_each(|anchor| anchor.move_selected_points(delta, absolute_position, viewspace));
}
/// Delete the selected points from the VectorShape
pub fn delete_selected(&mut self) {
let mut ids_to_delete: Vec<u64> = vec![];
for (id, anchor) in self.anchors_mut().enumerate_mut() {
if anchor.is_anchor_selected() {
ids_to_delete.push(*id);
} else {
anchor.delete_selected();
}
}
for id in ids_to_delete {
self.anchors_mut().remove(id);
}
}
// Apply a transformation to all of the VectorShape points
pub fn apply_affine(&mut self, affine: DAffine2) {
for anchor in self.anchors_mut().iter_mut() {
anchor.transform(&affine);
}
}
// ** SELECTION OF POINTS **
/// Select a single point by providing (AnchorId, ControlPointType)
pub fn select_point(&mut self, point: (u64, ControlPointType), selected: bool) -> Option<&mut VectorAnchor> {
let (anchor_id, point_id) = point;
if let Some(anchor) = self.anchors_mut().by_id_mut(anchor_id) {
anchor.select_point(point_id as usize, selected);
return Some(anchor);
}
None
}
/// Select points in the VectorShape, given by (AnchorId, ControlPointType)
pub fn select_points(&mut self, points: &[(u64, ControlPointType)], selected: bool) {
points.iter().for_each(|point| {
self.select_point(*point, selected);
});
}
/// Select all the anchors in this shape
pub fn select_all_anchors(&mut self) {
for anchor in self.anchors_mut().iter_mut() {
anchor.select_point(ControlPointType::Anchor as usize, true);
}
}
/// Select an anchor by index
pub fn select_anchor_by_index(&mut self, anchor_index: usize) -> Option<&mut VectorAnchor> {
if let Some(anchor) = self.anchors_mut().by_index_mut(anchor_index) {
anchor.select_point(ControlPointType::Anchor as usize, true);
return Some(anchor);
}
None
}
/// The last anchor in the shape
pub fn select_last_anchor(&mut self) -> Option<&mut VectorAnchor> {
if let Some(anchor) = self.anchors_mut().last_mut() {
anchor.select_point(ControlPointType::Anchor as usize, true);
return Some(anchor);
}
None
}
/// Clear all the selected anchors, and clear the selected points on the anchors
pub fn clear_selected_anchors(&mut self) {
for anchor in self.anchors_mut().iter_mut() {
anchor.clear_selected_points();
}
}
// ** ACCESSING ANCHORS **
/// Return all the selected anchors, reference
pub fn selected_anchors(&self) -> impl Iterator<Item = &VectorAnchor> {
self.anchors().iter().filter(|anchor| anchor.is_anchor_selected())
}
/// Return all the selected anchors, mutable
pub fn selected_anchors_mut(&mut self) -> impl Iterator<Item = &mut VectorAnchor> {
self.anchors_mut().iter_mut().filter(|anchor| anchor.is_anchor_selected())
}
/// Return all the selected anchors that have any children points selected, reference
pub fn selected_anchors_any_points(&self) -> impl Iterator<Item = &VectorAnchor> {
self.anchors().iter().filter(|anchor| anchor.any_points_selected())
}
/// Return all the selected anchors that have any children points selected, mutable
pub fn selected_anchors_any_points_mut(&mut self) -> impl Iterator<Item = &mut VectorAnchor> {
self.anchors_mut().iter_mut().filter(|anchor| anchor.any_points_selected())
}
/// An alias for `self.0`
pub fn anchors(&self) -> &IdBackedVec<VectorAnchor> {
&self.0
}
/// Returns a [VectorControlPoint] from the last [VectorAnchor]
pub fn last_point(&self, control_type: ControlPointType) -> Option<&VectorControlPoint> {
self.anchors().last().and_then(|anchor| anchor.points[control_type].as_ref())
}
/// Returns a [VectorControlPoint] from the last [VectorAnchor], mutably
pub fn last_point_mut(&mut self, control_type: ControlPointType) -> Option<&mut VectorControlPoint> {
self.anchors_mut().last_mut().and_then(|anchor| anchor.points[control_type].as_mut())
}
/// Returns a [VectorControlPoint] from the first [VectorAnchor]
pub fn first_point(&self, control_type: ControlPointType) -> Option<&VectorControlPoint> {
self.anchors().first().and_then(|anchor| anchor.points[control_type].as_ref())
}
/// Returns a [VectorControlPoint] from the first [VectorAnchor]
pub fn first_point_mut(&mut self, control_type: ControlPointType) -> Option<&mut VectorControlPoint> {
self.anchors_mut().first_mut().and_then(|anchor| anchor.points[control_type].as_mut())
}
/// Should we close the shape?
pub fn should_close_shape(&self) -> bool {
if self.last_point(ControlPointType::Anchor).is_none() {
return false;
}
self.first_point(ControlPointType::Anchor)
.unwrap()
.position
.distance(self.last_point(ControlPointType::Anchor).unwrap().position)
< 0.001 // TODO Replace with constant, a small epsilon
}
/// Close the shape if able
pub fn close_shape(&mut self) {
if self.should_close_shape() {
self.anchors_mut().push_end(VectorAnchor::closed());
}
}
/// An alias for `self.0` mutable
pub fn anchors_mut(&mut self) -> &mut IdBackedVec<VectorAnchor> {
&mut self.0
}
// ** INTERFACE WITH KURBO **
// TODO Implement our own a local bounding box calculation
/// Return the bounding box of the shape
pub fn bounding_box(&self) -> Rect {
<&Self as Into<BezPath>>::into(self).bounding_box()
}
/// Use kurbo to convert this shape into an SVG path
pub fn to_svg(&mut self) -> String {
fn write_positions(result: &mut String, values: [Option<DVec2>; 3]) {
use std::fmt::Write;
let count = values.into_iter().flatten().count();
for (index, pos) in values.into_iter().flatten().enumerate() {
write!(result, "{},{}", pos.x, pos.y).unwrap();
if index != count - 1 {
result.push(' ');
}
}
}
let mut result = String::new();
// The out position from the previous VectorAnchor
let mut last_out_handle = None;
// The values from the last moveto (for closing the path)
let (mut first_in_handle, mut first_in_anchor) = (None, None);
// Should the next element be a moveto?
let mut start_new_contour = true;
for vector_anchor in self.anchors().iter() {
let in_handle = vector_anchor.points[ControlPointType::InHandle].as_ref().map(|anchor| anchor.position);
let anchor = vector_anchor.points[ControlPointType::Anchor].as_ref().map(|anchor| anchor.position);
let out_handle = vector_anchor.points[ControlPointType::OutHandle].as_ref().map(|anchor| anchor.position);
let command = match (last_out_handle.is_some(), in_handle.is_some(), anchor.is_some()) {
(_, _, true) if start_new_contour => 'M',
(true, false, true) | (false, true, true) => 'Q',
(true, true, true) => 'C',
(false, false, true) => 'L',
(_, false, false) => 'Z',
_ => panic!("Invalid shape {:#?}", self),
};
// Complete the last curve
if command == 'Z' {
if last_out_handle.is_some() && first_in_handle.is_some() {
result.push('C');
write_positions(&mut result, [last_out_handle, first_in_handle, first_in_anchor]);
} else if last_out_handle.is_some() || first_in_handle.is_some() {
result.push('Q');
write_positions(&mut result, [last_out_handle, first_in_handle, first_in_anchor]);
} else {
result.push('Z');
}
} else if command == 'M' {
// Update the last moveto position
(first_in_handle, first_in_anchor) = (in_handle, anchor);
result.push(command);
write_positions(&mut result, [None, None, anchor]);
} else {
result.push(command);
write_positions(&mut result, [last_out_handle, in_handle, anchor]);
}
start_new_contour = command == 'Z';
last_out_handle = out_handle;
}
result
}
}
// ** CONVERSIONS **
/// Convert a mutable layer into a mutable VectorShape
impl<'a> TryFrom<&'a mut Layer> for &'a mut VectorShape {
type Error = &'static str;
fn try_from(layer: &'a mut Layer) -> Result<&'a mut VectorShape, Self::Error> {
match &mut layer.data {
LayerDataType::Shape(layer) => Ok(&mut layer.shape),
// TODO Resolve converting text into a VectorShape at the layer level
// LayerDataType::Text(text) => Some(VectorShape::new(path_to_shape.to_vec(), viewport_transform, true)),
_ => Err("Did not find any shape data in the layer"),
}
}
}
/// Convert a reference to a layer into a reference of a VectorShape
impl<'a> TryFrom<&'a Layer> for &'a VectorShape {
type Error = &'static str;
fn try_from(layer: &'a Layer) -> Result<&'a VectorShape, Self::Error> {
match &layer.data {
LayerDataType::Shape(layer) => Ok(&layer.shape),
// TODO Resolve converting text into a VectorShape at the layer level
// LayerDataType::Text(text) => Some(VectorShape::new(path_to_shape.to_vec(), viewport_transform, true)),
_ => Err("Did not find any shape data in the layer"),
}
}
}
/// Create a BezPath from a VectorShape
impl From<&VectorShape> for BezPath {
fn from(vector_shape: &VectorShape) -> Self {
// Take anchors and create path elements: line, quad or curve, or a close indicator
let anchors_to_path_el = |first: &VectorAnchor, second: &VectorAnchor| -> (PathEl, bool) {
match [
&first.points[ControlPointType::OutHandle],
&second.points[ControlPointType::InHandle],
&second.points[ControlPointType::Anchor],
] {
[None, None, Some(anchor)] => (PathEl::LineTo(point_to_kurbo(anchor)), false),
[None, Some(in_handle), Some(anchor)] => (PathEl::QuadTo(point_to_kurbo(in_handle), point_to_kurbo(anchor)), false),
[Some(out_handle), None, Some(anchor)] => (PathEl::QuadTo(point_to_kurbo(out_handle), point_to_kurbo(anchor)), false),
[Some(out_handle), Some(in_handle), Some(anchor)] => (PathEl::CurveTo(point_to_kurbo(out_handle), point_to_kurbo(in_handle), point_to_kurbo(anchor)), false),
[Some(out_handle), None, None] => {
if let Some(first_anchor) = vector_shape.anchors().first() {
(
if let Some(in_handle) = &first_anchor.points[ControlPointType::InHandle] {
PathEl::CurveTo(
point_to_kurbo(out_handle),
point_to_kurbo(in_handle),
point_to_kurbo(first_anchor.points[ControlPointType::Anchor].as_ref().unwrap()),
)
} else {
PathEl::QuadTo(point_to_kurbo(out_handle), point_to_kurbo(first_anchor.points[ControlPointType::Anchor].as_ref().unwrap()))
},
true,
)
} else {
(PathEl::ClosePath, true)
}
}
[None, None, None] => (PathEl::ClosePath, true),
_ => panic!("Invalid path element {:#?}", vector_shape),
}
};
if vector_shape.anchors().is_empty() {
return BezPath::new();
}
let mut bez_path = vec![];
let mut start_new_shape = true;
for elements in vector_shape.anchors().windows(2) {
let first = &elements[0];
let second = &elements[1];
// Tell kurbo cursor to move to the first anchor
if start_new_shape {
if let Some(anchor) = &first.points[ControlPointType::Anchor] {
bez_path.push(PathEl::MoveTo(point_to_kurbo(anchor)));
}
}
// Create a path element from our first, second anchors in the window
let (path_el, should_start_new_shape) = anchors_to_path_el(first, second);
start_new_shape = should_start_new_shape;
bez_path.push(path_el);
if should_start_new_shape && bez_path.last().filter(|&&el| el == PathEl::ClosePath).is_none() {
bez_path.push(PathEl::ClosePath)
}
}
BezPath::from_vec(bez_path)
}
}
/// Create a VectorShape from a BezPath
impl<T: Iterator<Item = PathEl>> From<T> for VectorShape {
fn from(path: T) -> Self {
let mut vector_shape = VectorShape::new();
for path_el in path {
match path_el {
PathEl::MoveTo(p) => {
vector_shape.anchors_mut().push_end(VectorAnchor::new(kurbo_point_to_dvec2(p)));
}
PathEl::LineTo(p) => {
vector_shape.anchors_mut().push_end(VectorAnchor::new(kurbo_point_to_dvec2(p)));
}
PathEl::QuadTo(p0, p1) => {
vector_shape.anchors_mut().push_end(VectorAnchor::new(kurbo_point_to_dvec2(p1)));
vector_shape.anchors_mut().last_mut().unwrap().points[ControlPointType::InHandle] = Some(VectorControlPoint::new(kurbo_point_to_dvec2(p0), ControlPointType::InHandle));
}
PathEl::CurveTo(p0, p1, p2) => {
vector_shape.anchors_mut().last_mut().unwrap().points[ControlPointType::OutHandle] = Some(VectorControlPoint::new(kurbo_point_to_dvec2(p0), ControlPointType::OutHandle));
vector_shape.anchors_mut().push_end(VectorAnchor::new(kurbo_point_to_dvec2(p2)));
vector_shape.anchors_mut().last_mut().unwrap().points[ControlPointType::InHandle] = Some(VectorControlPoint::new(kurbo_point_to_dvec2(p1), ControlPointType::InHandle));
}
PathEl::ClosePath => {
vector_shape.anchors_mut().push_end(VectorAnchor::closed());
}
}
}
vector_shape
}
}
#[inline]
fn point_to_kurbo(point: &VectorControlPoint) -> kurbo::Point {
kurbo::Point::new(point.position.x, point.position.y)
}
#[inline]
fn kurbo_point_to_dvec2(point: kurbo::Point) -> DVec2 {
DVec2::new(point.x, point.y)
}
+26 -25
View File
@@ -2,9 +2,9 @@ use crate::boolean_ops::BooleanOperation as BooleanOperationType;
use crate::layers::blend_mode::BlendMode;
use crate::layers::layer_info::Layer;
use crate::layers::style::{self, Stroke};
use crate::layers::vector::constants::ControlPointType;
use crate::layers::vector::vector_anchor::VectorAnchor;
use crate::layers::vector::vector_shape::VectorShape;
use crate::layers::vector::constants::ManipulatorType;
use crate::layers::vector::manipulator_group::ManipulatorGroup;
use crate::layers::vector::subpath::Subpath;
use crate::LayerId;
use serde::{Deserialize, Serialize};
@@ -89,7 +89,8 @@ pub enum Operation {
path: Vec<LayerId>,
transform: [f64; 6],
insert_index: isize,
vector_path: VectorShape,
// TODO This will become a compound path once we support them.
subpath: Subpath,
style: style::PathStyle,
},
BooleanOperation {
@@ -99,14 +100,14 @@ pub enum Operation {
DeleteLayer {
path: Vec<LayerId>,
},
DeleteSelectedVectorPoints {
DeleteSelectedManipulatorPoints {
layer_paths: Vec<Vec<LayerId>>,
},
DeselectVectorPoints {
DeselectManipulatorPoints {
layer_path: Vec<LayerId>,
point_ids: Vec<(u64, ControlPointType)>,
point_ids: Vec<(u64, ManipulatorType)>,
},
DeselectAllVectorPoints {
DeselectAllManipulatorPoints {
layer_path: Vec<LayerId>,
},
DuplicateLayer {
@@ -118,11 +119,17 @@ pub enum Operation {
font_style: String,
size: f64,
},
MoveSelectedVectorPoints {
MoveSelectedManipulatorPoints {
layer_path: Vec<LayerId>,
delta: (f64, f64),
absolute_position: (f64, f64),
},
MoveManipulatorPoint {
layer_path: Vec<LayerId>,
id: u64,
manipulator_type: ManipulatorType,
position: (f64, f64),
},
RenameLayer {
layer_path: Vec<LayerId>,
new_name: String,
@@ -147,38 +154,32 @@ pub enum Operation {
path: Vec<LayerId>,
transform: [f64; 6],
},
SelectVectorPoints {
SelectManipulatorPoints {
layer_path: Vec<LayerId>,
point_ids: Vec<(u64, ControlPointType)>,
point_ids: Vec<(u64, ManipulatorType)>,
add: bool,
},
SetShapePath {
path: Vec<LayerId>,
vector_path: VectorShape,
subpath: Subpath,
},
InsertVectorAnchor {
InsertManipulatorGroup {
layer_path: Vec<LayerId>,
anchor: VectorAnchor,
manipulator_group: ManipulatorGroup,
after_id: u64,
},
PushVectorAnchor {
PushManipulatorGroup {
layer_path: Vec<LayerId>,
anchor: VectorAnchor,
manipulator_group: ManipulatorGroup,
},
RemoveVectorAnchor {
RemoveManipulatorGroup {
layer_path: Vec<LayerId>,
id: u64,
},
MoveVectorPoint {
RemoveManipulatorPoint {
layer_path: Vec<LayerId>,
id: u64,
control_type: ControlPointType,
position: (f64, f64),
},
RemoveVectorPoint {
layer_path: Vec<LayerId>,
id: u64,
control_type: ControlPointType,
manipulator_type: ManipulatorType,
},
TransformLayerInScope {
path: Vec<LayerId>,