mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-07 15:55:00 +00:00
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:
parent
5d1d93917d
commit
03633bf313
39 changed files with 1125 additions and 1095 deletions
|
@ -114,9 +114,9 @@ export default defineComponent({
|
|||
name: "Lookup Table",
|
||||
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>): void => {
|
||||
const lookupPoints = bezier.compute_lookup_table(options.steps);
|
||||
lookupPoints.forEach((serialisedPoint, index) => {
|
||||
lookupPoints.forEach((serializedPoint, index) => {
|
||||
if (index !== 0 && index !== lookupPoints.length - 1) {
|
||||
drawPoint(getContextFromCanvas(canvas), JSON.parse(serialisedPoint), 3, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||
drawPoint(getContextFromCanvas(canvas), JSON.parse(serializedPoint), 3, COLORS.NON_INTERACTIVE.STROKE_1);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -339,9 +339,7 @@ export default defineComponent({
|
|||
|
||||
<style>
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: Arial, sans-serif;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-top: 60px;
|
||||
|
|
|
@ -632,11 +632,11 @@ impl Bezier {
|
|||
}
|
||||
|
||||
/// Determine if it is possible to scale the given curve, using the following conditions:
|
||||
/// 1. All the control points are located on a single side of the curve.
|
||||
/// 1. All the handles are located on a single side of the curve.
|
||||
/// 2. The on-curve point for `t = 0.5` must occur roughly in the center of the polygon defined by the curve's endpoint normals.
|
||||
/// See [the offset section](https://pomax.github.io/bezierinfo/#offsetting) of Pomax's bezier curve primer for more details.
|
||||
fn is_scalable(&self) -> bool {
|
||||
// Verify all the control points are located on a single side of the curve.
|
||||
// Verify all the handles are located on a single side of the curve.
|
||||
if let BezierHandles::Cubic { handle_start, handle_end } = self.handles {
|
||||
let angle_1 = (self.end - self.start).angle_between(handle_start - self.start);
|
||||
let angle_2 = (self.end - self.start).angle_between(handle_end - self.start);
|
||||
|
|
|
@ -66,7 +66,7 @@ ignore = [
|
|||
[licenses]
|
||||
# The lint level for crates which do not have a detectable license
|
||||
unlicensed = "deny"
|
||||
# List of explictly allowed licenses
|
||||
# List of explicitly allowed licenses
|
||||
# See https://spdx.org/licenses/ for list of possible licenses
|
||||
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
|
||||
allow = [
|
||||
|
@ -74,7 +74,7 @@ allow = [
|
|||
"Apache-2.0",
|
||||
#"Apache-2.0 WITH LLVM-exception",
|
||||
]
|
||||
# List of explictly disallowed licenses
|
||||
# List of explicitly disallowed licenses
|
||||
# See https://spdx.org/licenses/ for list of possible licenses
|
||||
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
|
||||
deny = [
|
||||
|
|
|
@ -45,7 +45,7 @@ pub const BOUNDS_SELECT_THRESHOLD: f64 = 10.;
|
|||
pub const BOUNDS_ROTATE_THRESHOLD: f64 = 20.;
|
||||
|
||||
// Path tool
|
||||
pub const VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE: f64 = 5.;
|
||||
pub const MANIPULATOR_GROUP_MARKER_SIZE: f64 = 5.;
|
||||
pub const SELECTION_THRESHOLD: f64 = 10.;
|
||||
|
||||
// Pen tool
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::layout::widgets::*;
|
|||
use crate::message_prelude::FrontendMessage;
|
||||
use crate::misc::build_metadata::{commit_info_localized, release_series};
|
||||
|
||||
/// A dialog for displaying information on [`BuildMetadata`] viewable via *Help* > *About Graphite* in the menu bar.
|
||||
/// A dialog for displaying information on [BuildMetadata] viewable via *Help* > *About Graphite* in the menu bar.
|
||||
pub struct AboutGraphite {
|
||||
pub localized_commit_date: String,
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::message_prelude::*;
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A dialog to allow users to customise their file export.
|
||||
/// A dialog to allow users to customize their file export.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Export {
|
||||
pub file_name: String,
|
||||
|
|
|
@ -53,9 +53,9 @@ pub enum DocumentMessage {
|
|||
layer_path: Vec<LayerId>,
|
||||
},
|
||||
DeleteSelectedLayers,
|
||||
DeleteSelectedVectorPoints,
|
||||
DeleteSelectedManipulatorPoints,
|
||||
DeselectAllLayers,
|
||||
DeselectAllVectorPoints,
|
||||
DeselectAllManipulatorPoints,
|
||||
DirtyRenderDocument,
|
||||
DirtyRenderDocumentInOutlineView,
|
||||
DocumentHistoryBackward,
|
||||
|
@ -83,7 +83,7 @@ pub enum DocumentMessage {
|
|||
insert_index: isize,
|
||||
reverse_index: bool,
|
||||
},
|
||||
MoveSelectedVectorPoints {
|
||||
MoveSelectedManipulatorPoints {
|
||||
layer_path: Vec<LayerId>,
|
||||
delta: (f64, f64),
|
||||
absolute_position: (f64, f64),
|
||||
|
|
|
@ -23,7 +23,7 @@ use graphene::layers::folder_layer::FolderLayer;
|
|||
use graphene::layers::layer_info::{LayerDataType, LayerDataTypeDiscriminant};
|
||||
use graphene::layers::style::{Fill, RenderData, ViewMode};
|
||||
use graphene::layers::text_layer::{Font, FontCache};
|
||||
use graphene::layers::vector::vector_shape::VectorShape;
|
||||
use graphene::layers::vector::subpath::Subpath;
|
||||
use graphene::{DocumentError, DocumentResponse, LayerId, Operation as DocumentOperation};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
@ -201,20 +201,20 @@ impl DocumentMessageHandler {
|
|||
})
|
||||
}
|
||||
|
||||
/// Returns a copy of all the currently selected VectorShapes.
|
||||
pub fn selected_vector_shapes(&self) -> Vec<VectorShape> {
|
||||
/// Returns a copy of all the currently selected [Subpath]s.
|
||||
pub fn selected_subpaths(&self) -> Vec<Subpath> {
|
||||
self.selected_visible_layers()
|
||||
.flat_map(|layer| self.graphene_document.layer(layer))
|
||||
.flat_map(|layer| layer.as_vector_shape_copy())
|
||||
.collect::<Vec<VectorShape>>()
|
||||
.flat_map(|layer| layer.as_subpath_copy())
|
||||
.collect::<Vec<Subpath>>()
|
||||
}
|
||||
|
||||
/// Returns references to all the currently selected VectorShapes.
|
||||
pub fn selected_vector_shapes_ref(&self) -> Vec<&VectorShape> {
|
||||
/// Returns references to all the currently selected [Subpath]s.
|
||||
pub fn selected_subpaths_ref(&self) -> Vec<&Subpath> {
|
||||
self.selected_visible_layers()
|
||||
.flat_map(|layer| self.graphene_document.layer(layer))
|
||||
.flat_map(|layer| layer.as_vector_shape())
|
||||
.collect::<Vec<&VectorShape>>()
|
||||
.flat_map(|layer| layer.as_subpath())
|
||||
.collect::<Vec<&Subpath>>()
|
||||
}
|
||||
|
||||
/// Returns the bounding boxes for all visible layers and artboards, optionally excluding any paths.
|
||||
|
@ -501,7 +501,7 @@ impl DocumentMessageHandler {
|
|||
/// Calculate the path that new layers should be inserted to.
|
||||
/// Depends on the selected layers as well as their types (Folder/Non-Folder)
|
||||
pub fn get_path_for_new_layer(&self) -> Vec<u64> {
|
||||
// If the selected layers dont actually exist, a new uuid for the
|
||||
// If the selected layers don't actually exist, a new uuid for the
|
||||
// root folder will be returned
|
||||
let mut path = self.graphene_document.shallowest_common_folder(self.selected_layers()).map_or(vec![], |v| v.to_vec());
|
||||
path.push(generate_uuid());
|
||||
|
@ -1016,11 +1016,11 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
|
|||
responses.push_front(BroadcastSignal::SelectionChanged.into());
|
||||
responses.push_back(BroadcastSignal::DocumentIsDirty.into());
|
||||
}
|
||||
DeleteSelectedVectorPoints => {
|
||||
DeleteSelectedManipulatorPoints => {
|
||||
responses.push_back(StartTransaction.into());
|
||||
|
||||
responses.push_front(
|
||||
DocumentOperation::DeleteSelectedVectorPoints {
|
||||
DocumentOperation::DeleteSelectedManipulatorPoints {
|
||||
layer_paths: self.selected_layers_without_children().iter().map(|path| path.to_vec()).collect(),
|
||||
}
|
||||
.into(),
|
||||
|
@ -1030,9 +1030,9 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
|
|||
responses.push_front(SetSelectedLayers { replacement_selected_layers: vec![] }.into());
|
||||
self.layer_range_selection_reference.clear();
|
||||
}
|
||||
DeselectAllVectorPoints => {
|
||||
DeselectAllManipulatorPoints => {
|
||||
for layer_path in self.selected_layers_without_children() {
|
||||
responses.push_back(DocumentOperation::DeselectAllVectorPoints { layer_path: layer_path.to_vec() }.into());
|
||||
responses.push_back(DocumentOperation::DeselectAllManipulatorPoints { layer_path: layer_path.to_vec() }.into());
|
||||
}
|
||||
}
|
||||
DirtyRenderDocument => {
|
||||
|
@ -1074,12 +1074,7 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
|
|||
let bbox = match bounds {
|
||||
ExportBounds::AllArtwork => self.all_layer_bounds(font_cache),
|
||||
ExportBounds::Selection => self.selected_visible_layers_bounding_box(font_cache),
|
||||
ExportBounds::Artboard(id) => self
|
||||
.artboard_message_handler
|
||||
.artboards_graphene_document
|
||||
.layer(&[id])
|
||||
.ok()
|
||||
.and_then(|layer| layer.aabounding_box(font_cache)),
|
||||
ExportBounds::Artboard(id) => self.artboard_message_handler.artboards_graphene_document.layer(&[id]).ok().and_then(|layer| layer.aabb(font_cache)),
|
||||
}
|
||||
.unwrap_or_default();
|
||||
let size = bbox[1] - bbox[0];
|
||||
|
@ -1195,10 +1190,10 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
|
|||
.into(),
|
||||
);
|
||||
}
|
||||
MoveSelectedVectorPoints { layer_path, delta, absolute_position } => {
|
||||
MoveSelectedManipulatorPoints { layer_path, delta, absolute_position } => {
|
||||
self.backup(responses);
|
||||
if let Ok(_layer) = self.graphene_document.layer(&layer_path) {
|
||||
responses.push_back(DocumentOperation::MoveSelectedVectorPoints { layer_path, delta, absolute_position }.into());
|
||||
responses.push_back(DocumentOperation::MoveSelectedManipulatorPoints { layer_path, delta, absolute_position }.into());
|
||||
}
|
||||
}
|
||||
NudgeSelectedLayers { delta_x, delta_y } => {
|
||||
|
|
|
@ -226,7 +226,7 @@ impl<'a> Selected<'a> {
|
|||
.document
|
||||
.layer(path)
|
||||
.unwrap()
|
||||
.aabounding_box_for_transform(multiplied_transform, font_cache)
|
||||
.aabb_for_transform(multiplied_transform, font_cache)
|
||||
.unwrap_or([multiplied_transform.translation; 2]);
|
||||
|
||||
(bounds[0] + bounds[1]) / 2.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/// Provides metadata about the build environment.
|
||||
///
|
||||
/// This data is viewable in the editor via the [`crate::dialog::AboutGraphite`] dialog.
|
||||
//! Provides metadata about the build environment.
|
||||
//!
|
||||
//! This data is viewable in the editor via the [AboutGraphite](crate::dialog::AboutGraphite) dialog.
|
||||
|
||||
pub fn release_series() -> String {
|
||||
format!("Release Series: {}", env!("GRAPHITE_RELEASE_SERIES"))
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::message_prelude::*;
|
|||
|
||||
use graphene::layers::layer_info::{Layer, LayerDataType};
|
||||
use graphene::layers::style::{self, Stroke};
|
||||
use graphene::layers::vector::constants::ControlPointType;
|
||||
use graphene::layers::vector::constants::ManipulatorType;
|
||||
use graphene::{LayerId, Operation};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
@ -246,25 +246,25 @@ impl SnapHandler {
|
|||
}
|
||||
}
|
||||
|
||||
/// Add the control points (optionally including bézier handles) of the specified shape layer to the snapping points
|
||||
/// Add the [ManipulatorGroup]s (optionally including handles) of the specified shape layer to the snapping points
|
||||
///
|
||||
/// This should be called after start_snap
|
||||
pub fn add_snap_path(&mut self, document_message_handler: &DocumentMessageHandler, layer: &Layer, path: &[LayerId], include_handles: bool, ignore_points: &[(&[LayerId], u64, ControlPointType)]) {
|
||||
pub fn add_snap_path(&mut self, document_message_handler: &DocumentMessageHandler, layer: &Layer, path: &[LayerId], include_handles: bool, ignore_points: &[(&[LayerId], u64, ManipulatorType)]) {
|
||||
if let LayerDataType::Shape(shape_layer) = &layer.data {
|
||||
let transform = document_message_handler.graphene_document.multiply_transforms(path).unwrap();
|
||||
let snap_points = shape_layer
|
||||
.shape
|
||||
.anchors()
|
||||
.manipulator_groups()
|
||||
.enumerate()
|
||||
.flat_map(|(id, shape)| {
|
||||
if include_handles {
|
||||
[
|
||||
(*id, &shape.points[ControlPointType::Anchor]),
|
||||
(*id, &shape.points[ControlPointType::InHandle]),
|
||||
(*id, &shape.points[ControlPointType::OutHandle]),
|
||||
(*id, &shape.points[ManipulatorType::Anchor]),
|
||||
(*id, &shape.points[ManipulatorType::InHandle]),
|
||||
(*id, &shape.points[ManipulatorType::OutHandle]),
|
||||
]
|
||||
} else {
|
||||
[(*id, &shape.points[ControlPointType::Anchor]), (0, &None), (0, &None)]
|
||||
[(*id, &shape.points[ManipulatorType::Anchor]), (0, &None), (0, &None)]
|
||||
}
|
||||
})
|
||||
.filter_map(|(id, point)| point.as_ref().map(|val| (id, val)))
|
||||
|
@ -281,7 +281,7 @@ impl SnapHandler {
|
|||
document_message_handler: &DocumentMessageHandler,
|
||||
include_handles: &[&[LayerId]],
|
||||
exclude: &[&[LayerId]],
|
||||
ignore_points: &[(&[LayerId], u64, ControlPointType)],
|
||||
ignore_points: &[(&[LayerId], u64, ManipulatorType)],
|
||||
) {
|
||||
for path in document_message_handler.all_layers() {
|
||||
if !exclude.contains(&path) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::consts::{COLOR_ACCENT, LINE_ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE, VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE};
|
||||
use crate::consts::{COLOR_ACCENT, LINE_ROTATE_SNAP_ANGLE, MANIPULATOR_GROUP_MARKER_SIZE, SELECTION_TOLERANCE};
|
||||
use crate::document::DocumentMessageHandler;
|
||||
use crate::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::input::keyboard::{Key, MouseMotion};
|
||||
|
@ -141,7 +141,7 @@ impl Default for GradientToolFsmState {
|
|||
|
||||
/// Computes the transform from gradient space to layer space (where gradient space is 0..1 in layer space)
|
||||
fn gradient_space_transform(path: &[LayerId], layer: &Layer, document: &DocumentMessageHandler, font_cache: &FontCache) -> DAffine2 {
|
||||
let bounds = layer.aabounding_box_for_transform(DAffine2::IDENTITY, font_cache).unwrap();
|
||||
let bounds = layer.aabb_for_transform(DAffine2::IDENTITY, font_cache).unwrap();
|
||||
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
|
||||
|
||||
let multiplied = document.graphene_document.multiply_transforms(path).unwrap();
|
||||
|
@ -163,7 +163,7 @@ impl GradientOverlay {
|
|||
fn generate_overlay_handle(translation: DVec2, responses: &mut VecDeque<Message>, selected: bool) -> Vec<LayerId> {
|
||||
let path = vec![generate_uuid()];
|
||||
|
||||
let size = DVec2::splat(VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE);
|
||||
let size = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE);
|
||||
|
||||
let fill = if selected { Fill::solid(COLOR_ACCENT) } else { Fill::solid(Color::WHITE) };
|
||||
|
||||
|
@ -359,7 +359,7 @@ impl Fsm for GradientToolFsmState {
|
|||
responses.push_back(BroadcastSignal::DocumentIsDirty.into());
|
||||
|
||||
let mouse = input.mouse.position;
|
||||
let tolerance = VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE.powi(2);
|
||||
let tolerance = MANIPULATOR_GROUP_MARKER_SIZE.powi(2);
|
||||
|
||||
let mut dragging = false;
|
||||
for overlay in &tool_data.gradient_overlays {
|
||||
|
|
|
@ -10,7 +10,7 @@ use crate::viewport_tools::vector_editor::overlay_renderer::OverlayRenderer;
|
|||
use crate::viewport_tools::vector_editor::shape_editor::ShapeEditor;
|
||||
|
||||
use graphene::intersection::Quad;
|
||||
use graphene::layers::vector::constants::ControlPointType;
|
||||
use graphene::layers::vector::constants::ManipulatorType;
|
||||
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -152,7 +152,7 @@ impl Fsm for PathToolFsmState {
|
|||
tool_data.shape_editor.set_selected_layers(layer_paths);
|
||||
// Render the new overlays
|
||||
for layer_path in tool_data.shape_editor.selected_layers() {
|
||||
tool_data.overlay_renderer.render_vector_shape_overlays(&document.graphene_document, layer_path.to_vec(), responses);
|
||||
tool_data.overlay_renderer.render_subpath_overlays(&document.graphene_document, layer_path.to_vec(), responses);
|
||||
}
|
||||
|
||||
// This can happen in any state (which is why we return self)
|
||||
|
@ -162,7 +162,7 @@ impl Fsm for PathToolFsmState {
|
|||
// When the document has moved / needs to be redraw, re-render the overlays
|
||||
// TODO the overlay system should probably receive this message instead of the tool
|
||||
for layer_path in document.selected_visible_layers() {
|
||||
tool_data.overlay_renderer.render_vector_shape_overlays(&document.graphene_document, layer_path.to_vec(), responses);
|
||||
tool_data.overlay_renderer.render_subpath_overlays(&document.graphene_document, layer_path.to_vec(), responses);
|
||||
}
|
||||
|
||||
self
|
||||
|
@ -186,9 +186,9 @@ impl Fsm for PathToolFsmState {
|
|||
// Do not snap against handles when anchor is selected
|
||||
let mut extension = Vec::new();
|
||||
for &(path, id, point_type) in new_selected.iter() {
|
||||
if point_type == ControlPointType::Anchor {
|
||||
extension.push((path, id, ControlPointType::InHandle));
|
||||
extension.push((path, id, ControlPointType::OutHandle));
|
||||
if point_type == ManipulatorType::Anchor {
|
||||
extension.push((path, id, ManipulatorType::InHandle));
|
||||
extension.push((path, id, ManipulatorType::OutHandle));
|
||||
}
|
||||
}
|
||||
new_selected.extend(extension);
|
||||
|
@ -269,14 +269,14 @@ impl Fsm for PathToolFsmState {
|
|||
tool_data.shape_editor.delete_selected_points(responses);
|
||||
responses.push_back(SelectionChanged.into());
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.clear_vector_shape_overlays(&document.graphene_document, layer_path.to_vec(), responses);
|
||||
tool_data.overlay_renderer.clear_subpath_overlays(&document.graphene_document, layer_path.to_vec(), responses);
|
||||
}
|
||||
Ready
|
||||
}
|
||||
(_, Abort) => {
|
||||
// TODO Tell overlay manager to remove the overlays
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.clear_vector_shape_overlays(&document.graphene_document, layer_path.to_vec(), responses);
|
||||
tool_data.overlay_renderer.clear_subpath_overlays(&document.graphene_document, layer_path.to_vec(), responses);
|
||||
}
|
||||
Ready
|
||||
}
|
||||
|
|
|
@ -11,9 +11,9 @@ use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData
|
|||
use crate::viewport_tools::vector_editor::overlay_renderer::OverlayRenderer;
|
||||
|
||||
use graphene::layers::style;
|
||||
use graphene::layers::vector::constants::ControlPointType;
|
||||
use graphene::layers::vector::vector_anchor::VectorAnchor;
|
||||
use graphene::layers::vector::vector_shape::VectorShape;
|
||||
use graphene::layers::vector::constants::ManipulatorType;
|
||||
use graphene::layers::vector::manipulator_group::ManipulatorGroup;
|
||||
use graphene::layers::vector::subpath::Subpath;
|
||||
use graphene::Operation;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
@ -180,7 +180,7 @@ impl Fsm for PenToolFsmState {
|
|||
// When the document has moved / needs to be redraw, re-render the overlays
|
||||
// TODO the overlay system should probably receive this message instead of the tool
|
||||
for layer_path in document.selected_visible_layers() {
|
||||
tool_data.overlay_renderer.render_vector_shape_overlays(&document.graphene_document, layer_path.to_vec(), responses);
|
||||
tool_data.overlay_renderer.render_subpath_overlays(&document.graphene_document, layer_path.to_vec(), responses);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
@ -217,12 +217,15 @@ impl Fsm for PenToolFsmState {
|
|||
path: layer_path.clone(),
|
||||
transform: DAffine2::IDENTITY.to_cols_array(),
|
||||
insert_index: -1,
|
||||
vector_path: Default::default(),
|
||||
subpath: Default::default(),
|
||||
style: style::PathStyle::new(Some(style::Stroke::new(global_tool_data.primary_color, tool_data.weight)), style::Fill::None),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
responses.push_back(add_anchor(&tool_data.path, VectorAnchor::new_with_handles(start_position, Some(start_position), Some(start_position))));
|
||||
responses.push_back(add_manipulator_group(
|
||||
&tool_data.path,
|
||||
ManipulatorGroup::new_with_handles(start_position, Some(start_position), Some(start_position)),
|
||||
));
|
||||
}
|
||||
|
||||
PenToolFsmState::DraggingHandle
|
||||
|
@ -231,9 +234,9 @@ impl Fsm for PenToolFsmState {
|
|||
(PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => {
|
||||
// Add new point onto path
|
||||
if let Some(layer_path) = &tool_data.path {
|
||||
if let Some(vector_anchor) = get_vector_shape(layer_path, document).and_then(|shape| shape.anchors().last()) {
|
||||
if let Some(anchor) = &vector_anchor.points[ControlPointType::OutHandle] {
|
||||
responses.push_back(add_anchor(&tool_data.path, VectorAnchor::new(anchor.position)));
|
||||
if let Some(manipulator_group) = get_subpath(layer_path, document).and_then(|subpath| subpath.manipulator_groups().last()) {
|
||||
if let Some(out_handle) = &manipulator_group.points[ManipulatorType::OutHandle] {
|
||||
responses.push_back(add_manipulator_group(&tool_data.path, ManipulatorGroup::new_with_anchor(out_handle.position)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -244,29 +247,29 @@ impl Fsm for PenToolFsmState {
|
|||
if let Some(layer_path) = &tool_data.path {
|
||||
let mouse = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
|
||||
let mut pos = transform.inverse().transform_point2(mouse);
|
||||
if let Some(((&id, anchor), _previous)) = get_vector_shape(layer_path, document).and_then(last_2_anchors) {
|
||||
if let Some(anchor) = anchor.points[ControlPointType::Anchor as usize].as_ref() {
|
||||
if let Some(((&id, manipulator_group), _previous)) = get_subpath(layer_path, document).and_then(last_2_manipulator_groups) {
|
||||
if let Some(anchor) = manipulator_group.points[ManipulatorType::Anchor].as_ref() {
|
||||
pos = compute_snapped_angle(input, snap_angle, pos, anchor.position);
|
||||
}
|
||||
|
||||
// Update points on current segment (to show preview of new handle)
|
||||
let msg = Operation::MoveVectorPoint {
|
||||
let msg = Operation::MoveManipulatorPoint {
|
||||
layer_path: layer_path.clone(),
|
||||
id,
|
||||
control_type: ControlPointType::OutHandle,
|
||||
manipulator_type: ManipulatorType::OutHandle,
|
||||
position: pos.into(),
|
||||
};
|
||||
responses.push_back(msg.into());
|
||||
|
||||
// Mirror handle of last segement
|
||||
if !input.keyboard.get(break_handle as usize) && get_vector_shape(layer_path, document).map(|shape| shape.anchors().len() > 1).unwrap_or_default() {
|
||||
if let Some(anchor) = anchor.points[ControlPointType::Anchor as usize].as_ref() {
|
||||
// Mirror handle of last segment
|
||||
if !input.keyboard.get(break_handle as usize) && get_subpath(layer_path, document).map(|shape| shape.manipulator_groups().len() > 1).unwrap_or_default() {
|
||||
if let Some(anchor) = manipulator_group.points[ManipulatorType::Anchor].as_ref() {
|
||||
pos = anchor.position - (pos - anchor.position);
|
||||
}
|
||||
let msg = Operation::MoveVectorPoint {
|
||||
let msg = Operation::MoveManipulatorPoint {
|
||||
layer_path: layer_path.clone(),
|
||||
id,
|
||||
control_type: ControlPointType::InHandle,
|
||||
manipulator_type: ManipulatorType::InHandle,
|
||||
position: pos.into(),
|
||||
};
|
||||
responses.push_back(msg.into());
|
||||
|
@ -281,16 +284,16 @@ impl Fsm for PenToolFsmState {
|
|||
let mouse = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
|
||||
let mut pos = transform.inverse().transform_point2(mouse);
|
||||
|
||||
if let Some(((&id, _anchor), previous)) = get_vector_shape(layer_path, document).and_then(last_2_anchors) {
|
||||
if let Some(relative) = previous.as_ref().and_then(|(_, anchor)| anchor.points[ControlPointType::Anchor as usize].as_ref()) {
|
||||
if let Some(((&id, _), previous)) = get_subpath(layer_path, document).and_then(last_2_manipulator_groups) {
|
||||
if let Some(relative) = previous.as_ref().and_then(|(_, manipulator_group)| manipulator_group.points[ManipulatorType::Anchor].as_ref()) {
|
||||
pos = compute_snapped_angle(input, snap_angle, pos, relative.position);
|
||||
}
|
||||
|
||||
for control_type in [ControlPointType::Anchor, ControlPointType::InHandle, ControlPointType::OutHandle] {
|
||||
let msg = Operation::MoveVectorPoint {
|
||||
for manipulator_type in [ManipulatorType::Anchor, ManipulatorType::InHandle, ManipulatorType::OutHandle] {
|
||||
let msg = Operation::MoveManipulatorPoint {
|
||||
layer_path: layer_path.clone(),
|
||||
id,
|
||||
control_type,
|
||||
manipulator_type,
|
||||
position: pos.into(),
|
||||
};
|
||||
responses.push_back(msg.into());
|
||||
|
@ -303,25 +306,25 @@ impl Fsm for PenToolFsmState {
|
|||
(PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor, PenToolMessage::Abort | PenToolMessage::Confirm) => {
|
||||
// Abort or commit the transaction to the undo history
|
||||
if let Some(layer_path) = tool_data.path.as_ref() {
|
||||
if let Some(vector_shape) = (get_vector_shape(layer_path, document)).filter(|vector_shape| vector_shape.anchors().len() > 1) {
|
||||
if let Some(((&(mut id), mut anchor), previous)) = last_2_anchors(vector_shape) {
|
||||
if let Some(subpath) = (get_subpath(layer_path, document)).filter(|subpath| subpath.manipulator_groups().len() > 1) {
|
||||
if let Some(((&(mut id), mut manipulator_group), previous)) = last_2_manipulator_groups(subpath) {
|
||||
// Remove the unplaced anchor if in anchor placing mode
|
||||
if self == PenToolFsmState::PlacingAnchor {
|
||||
let layer_path = layer_path.clone();
|
||||
let op = Operation::RemoveVectorAnchor { layer_path, id };
|
||||
let op = Operation::RemoveManipulatorGroup { layer_path, id };
|
||||
responses.push_back(op.into());
|
||||
if let Some((&new_id, new_anchor)) = previous {
|
||||
if let Some((&new_id, new_manipulator_group)) = previous {
|
||||
id = new_id;
|
||||
anchor = new_anchor;
|
||||
manipulator_group = new_manipulator_group;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the out handle if in dragging handle mode
|
||||
let op = Operation::MoveVectorPoint {
|
||||
let op = Operation::MoveManipulatorPoint {
|
||||
layer_path: layer_path.clone(),
|
||||
id,
|
||||
control_type: ControlPointType::OutHandle,
|
||||
position: anchor.points[ControlPointType::Anchor as usize].as_ref().unwrap().position.into(),
|
||||
manipulator_type: ManipulatorType::OutHandle,
|
||||
position: manipulator_group.points[ManipulatorType::Anchor].as_ref().unwrap().position.into(),
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
}
|
||||
|
@ -334,7 +337,7 @@ impl Fsm for PenToolFsmState {
|
|||
|
||||
// Clean up overlays
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.clear_vector_shape_overlays(&document.graphene_document, layer_path.to_vec(), responses);
|
||||
tool_data.overlay_renderer.clear_subpath_overlays(&document.graphene_document, layer_path.to_vec(), responses);
|
||||
}
|
||||
tool_data.path = None;
|
||||
tool_data.snap_handler.cleanup(responses);
|
||||
|
@ -344,7 +347,7 @@ impl Fsm for PenToolFsmState {
|
|||
(_, PenToolMessage::Abort) => {
|
||||
// Clean up overlays
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.clear_vector_shape_overlays(&document.graphene_document, layer_path.to_vec(), responses);
|
||||
tool_data.overlay_renderer.clear_subpath_overlays(&document.graphene_document, layer_path.to_vec(), responses);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
@ -373,7 +376,7 @@ impl Fsm for PenToolFsmState {
|
|||
HintGroup(vec![HintInfo {
|
||||
key_groups: vec![],
|
||||
mouse: Some(MouseMotion::Lmb),
|
||||
label: String::from("Add Control Point"),
|
||||
label: String::from("Add Anchor"),
|
||||
plus: false,
|
||||
}]),
|
||||
HintGroup(vec![HintInfo {
|
||||
|
@ -406,7 +409,7 @@ impl Fsm for PenToolFsmState {
|
|||
}
|
||||
|
||||
// TODO: Expand `pos` name below to the full word (position?)
|
||||
/// Snap the angle of the line from relative to pos if the key is pressed
|
||||
/// Snap the angle of the line from relative to pos if the key is pressed.
|
||||
fn compute_snapped_angle(input: &InputPreprocessorMessageHandler, key: Key, pos: DVec2, relative: DVec2) -> DVec2 {
|
||||
if input.keyboard.get(key as usize) {
|
||||
let delta = relative - pos;
|
||||
|
@ -424,12 +427,12 @@ fn compute_snapped_angle(input: &InputPreprocessorMessageHandler, key: Key, pos:
|
|||
}
|
||||
}
|
||||
|
||||
/// Pushes an anchor to the current layer via an [Operation]
|
||||
fn add_anchor(layer_path: &Option<Vec<LayerId>>, anchor: VectorAnchor) -> Message {
|
||||
/// Pushes a [ManipulatorGroup] to the current layer via an [Operation].
|
||||
fn add_manipulator_group(layer_path: &Option<Vec<LayerId>>, manipulator_group: ManipulatorGroup) -> Message {
|
||||
if let Some(layer_path) = layer_path {
|
||||
Operation::PushVectorAnchor {
|
||||
Operation::PushManipulatorGroup {
|
||||
layer_path: layer_path.clone(),
|
||||
anchor,
|
||||
manipulator_group,
|
||||
}
|
||||
.into()
|
||||
} else {
|
||||
|
@ -437,20 +440,20 @@ fn add_anchor(layer_path: &Option<Vec<LayerId>>, anchor: VectorAnchor) -> Messag
|
|||
}
|
||||
}
|
||||
|
||||
/// Gets the currently editing [VectorShape]
|
||||
fn get_vector_shape<'a>(layer_path: &'a [LayerId], document: &'a DocumentMessageHandler) -> Option<&'a VectorShape> {
|
||||
document.graphene_document.layer(layer_path).ok().and_then(|layer| layer.as_vector_shape())
|
||||
/// Gets the currently editing [Subpath].
|
||||
fn get_subpath<'a>(layer_path: &'a [LayerId], document: &'a DocumentMessageHandler) -> Option<&'a Subpath> {
|
||||
document.graphene_document.layer(layer_path).ok().and_then(|layer| layer.as_subpath())
|
||||
}
|
||||
|
||||
type AnchorRef<'a> = (&'a u64, &'a VectorAnchor);
|
||||
type ManipulatorGroupRef<'a> = (&'a u64, &'a ManipulatorGroup);
|
||||
|
||||
/// Gets the last 2 [VectorAnchor] on the currently editing layer along with its id
|
||||
fn last_2_anchors(vector_shape: &VectorShape) -> Option<(AnchorRef, Option<AnchorRef>)> {
|
||||
vector_shape.anchors().enumerate().last().map(|last| {
|
||||
/// Gets the last 2 [ManipulatorGroup]s on the currently editing layer along with its ID.
|
||||
fn last_2_manipulator_groups(subpath: &Subpath) -> Option<(ManipulatorGroupRef, Option<ManipulatorGroupRef>)> {
|
||||
subpath.manipulator_groups().enumerate().last().map(|last| {
|
||||
(
|
||||
last,
|
||||
(vector_shape.anchors().len() > 1)
|
||||
.then(|| vector_shape.anchors().enumerate().nth(vector_shape.anchors().len() - 2))
|
||||
(subpath.manipulator_groups().len() > 1)
|
||||
.then(|| subpath.manipulator_groups().enumerate().nth(subpath.manipulator_groups().len() - 2))
|
||||
.flatten(),
|
||||
)
|
||||
})
|
||||
|
|
|
@ -7,7 +7,7 @@ use graphene::intersection::Quad;
|
|||
use graphene::layers::layer_info::LayerDataType;
|
||||
use graphene::layers::style::{self, Fill, Stroke};
|
||||
use graphene::layers::text_layer::FontCache;
|
||||
use graphene::layers::vector::vector_shape::VectorShape;
|
||||
use graphene::layers::vector::subpath::Subpath;
|
||||
use graphene::{LayerId, Operation};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
@ -35,12 +35,10 @@ impl PathOutline {
|
|||
|
||||
// TODO Purge this area of BezPath and Kurbo
|
||||
// Get the bezpath from the shape or text
|
||||
let vector_path = match &document_layer.data {
|
||||
let subpath = match &document_layer.data {
|
||||
LayerDataType::Shape(layer_shape) => Some(layer_shape.shape.clone()),
|
||||
LayerDataType::Text(text) => Some(text.to_vector_path_nonmut(font_cache)),
|
||||
_ => document_layer
|
||||
.aabounding_box_for_transform(DAffine2::IDENTITY, font_cache)
|
||||
.map(|[p1, p2]| VectorShape::new_rect(p1, p2)),
|
||||
LayerDataType::Text(text) => Some(text.to_subpath_nonmut(font_cache)),
|
||||
_ => document_layer.aabb_for_transform(DAffine2::IDENTITY, font_cache).map(|[p1, p2]| Subpath::new_rect(p1, p2)),
|
||||
}?;
|
||||
|
||||
// Generate a new overlay layer if necessary
|
||||
|
@ -50,7 +48,7 @@ impl PathOutline {
|
|||
let overlay_path = vec![generate_uuid()];
|
||||
let operation = Operation::AddShape {
|
||||
path: overlay_path.clone(),
|
||||
vector_path: Default::default(),
|
||||
subpath: Default::default(),
|
||||
style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, PATH_OUTLINE_WEIGHT)), Fill::None),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::IDENTITY.to_cols_array(),
|
||||
|
@ -63,7 +61,7 @@ impl PathOutline {
|
|||
};
|
||||
|
||||
// Update the shape bezpath
|
||||
let operation = Operation::SetShapePath { path: overlay.clone(), vector_path };
|
||||
let operation = Operation::SetShapePath { path: overlay.clone(), subpath };
|
||||
responses.push_back(DocumentMessage::Overlays(operation.into()).into());
|
||||
|
||||
// Update the transform to match the document
|
||||
|
@ -110,7 +108,7 @@ impl PathOutline {
|
|||
}
|
||||
}
|
||||
|
||||
/// Clears overlays for the seleted paths and removes references
|
||||
/// Clears overlays for the selected paths and removes references
|
||||
pub fn clear_selected(&mut self, responses: &mut VecDeque<Message>) {
|
||||
while let Some(path) = self.selected_overlay_paths.pop() {
|
||||
let operation = Operation::DeleteLayer { path };
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::consts::{BOUNDS_ROTATE_THRESHOLD, BOUNDS_SELECT_THRESHOLD, COLOR_ACCENT, SELECTION_DRAG_ANGLE, VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE};
|
||||
use crate::consts::{BOUNDS_ROTATE_THRESHOLD, BOUNDS_SELECT_THRESHOLD, COLOR_ACCENT, MANIPULATOR_GROUP_MARKER_SIZE, SELECTION_DRAG_ANGLE};
|
||||
use crate::document::transformation::OriginalTransforms;
|
||||
use crate::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
|
@ -219,7 +219,7 @@ impl BoundingBoxOverlays {
|
|||
}
|
||||
}
|
||||
|
||||
/// Calculats the transformed handle positions based on the bounding box and the transform
|
||||
/// Calculates the transformed handle positions based on the bounding box and the transform
|
||||
pub fn evaluate_transform_handle_positions(&self) -> [DVec2; 8] {
|
||||
let (left, top): (f64, f64) = self.bounds[0].into();
|
||||
let (right, bottom): (f64, f64) = self.bounds[1].into();
|
||||
|
@ -245,7 +245,7 @@ impl BoundingBoxOverlays {
|
|||
const BIAS: f64 = 0.0001;
|
||||
|
||||
for (position, path) in self.evaluate_transform_handle_positions().into_iter().zip(&self.transform_handles) {
|
||||
let scale = DVec2::splat(VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE);
|
||||
let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE);
|
||||
let translation = (position - (scale / 2.) - 0.5 + BIAS).round();
|
||||
let transform = DAffine2::from_scale_angle_translation(scale, 0., translation).to_cols_array();
|
||||
let path = path.clone();
|
||||
|
|
|
@ -238,7 +238,7 @@ fn update_overlays(document: &DocumentMessageHandler, tool_data: &mut TextToolDa
|
|||
.graphene_document
|
||||
.layer(layer_path)
|
||||
.unwrap()
|
||||
.aabounding_box_for_transform(document.graphene_document.multiply_transforms(layer_path).unwrap(), font_cache)
|
||||
.aabb_for_transform(document.graphene_document.multiply_transforms(layer_path).unwrap(), font_cache)
|
||||
.map(|bounds| (bounds, overlay_path))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
pub mod constants;
|
||||
pub mod overlay_renderer;
|
||||
pub mod shape_editor;
|
||||
use graphene::layers::vector::vector_anchor;
|
||||
use graphene::layers::vector::vector_control_point;
|
||||
use graphene::layers::vector::vector_shape;
|
||||
use graphene::layers::vector::manipulator_group;
|
||||
use graphene::layers::vector::manipulator_point;
|
||||
use graphene::layers::vector::subpath;
|
||||
|
|
|
@ -1,47 +1,46 @@
|
|||
use super::constants::ROUNDING_BIAS;
|
||||
use super::vector_anchor::VectorAnchor;
|
||||
use super::vector_control_point::VectorControlPoint;
|
||||
use crate::consts::{COLOR_ACCENT, PATH_OUTLINE_WEIGHT, VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE};
|
||||
use super::manipulator_group::ManipulatorGroup;
|
||||
use super::manipulator_point::ManipulatorPoint;
|
||||
use crate::consts::{COLOR_ACCENT, MANIPULATOR_GROUP_MARKER_SIZE, PATH_OUTLINE_WEIGHT};
|
||||
use crate::message_prelude::{generate_uuid, DocumentMessage, Message};
|
||||
|
||||
use graphene::color::Color;
|
||||
use graphene::document::Document;
|
||||
use graphene::layers::style::{self, Fill, Stroke};
|
||||
use graphene::layers::vector::constants::ControlPointType;
|
||||
use graphene::layers::vector::vector_shape::VectorShape;
|
||||
use graphene::layers::vector::constants::ManipulatorType;
|
||||
use graphene::layers::vector::subpath::Subpath;
|
||||
use graphene::{LayerId, Operation};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
/// AnchorOverlay is the collection of overlays that make up an anchor
|
||||
/// Notably the anchor point, handles and the lines for the handles
|
||||
type AnchorOverlays = [Option<Vec<LayerId>>; 5];
|
||||
type AnchorId = u64;
|
||||
/// [ManipulatorGroupOverlay]s is the collection of overlays that make up an [ManipulatorGroup] visible in the editor.
|
||||
type ManipulatorGroupOverlays = [Option<Vec<LayerId>>; 5];
|
||||
type ManipulatorId = u64;
|
||||
|
||||
const POINT_STROKE_WEIGHT: f64 = 2.;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct OverlayRenderer {
|
||||
shape_overlay_cache: HashMap<LayerId, Vec<LayerId>>,
|
||||
anchor_overlay_cache: HashMap<(LayerId, AnchorId), AnchorOverlays>,
|
||||
manipulator_group_overlay_cache: HashMap<(LayerId, ManipulatorId), ManipulatorGroupOverlays>,
|
||||
}
|
||||
|
||||
impl OverlayRenderer {
|
||||
pub fn new() -> Self {
|
||||
OverlayRenderer {
|
||||
anchor_overlay_cache: HashMap::new(),
|
||||
manipulator_group_overlay_cache: HashMap::new(),
|
||||
shape_overlay_cache: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_vector_shape_overlays(&mut self, document: &Document, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
|
||||
pub fn render_subpath_overlays(&mut self, document: &Document, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
|
||||
let transform = document.generate_transform_relative_to_viewport(&layer_path).ok().unwrap();
|
||||
if let Ok(layer) = document.layer(&layer_path) {
|
||||
let layer_id = layer_path.last().unwrap();
|
||||
self.layer_overlay_visibility(document, layer_path.clone(), true, responses);
|
||||
|
||||
if let Some(shape) = layer.as_vector_shape() {
|
||||
if let Some(shape) = layer.as_subpath() {
|
||||
let outline_cache = self.shape_overlay_cache.get(layer_id);
|
||||
log::trace!("Overlay: Outline cache {:?}", &outline_cache);
|
||||
|
||||
|
@ -57,27 +56,27 @@ impl OverlayRenderer {
|
|||
Self::place_outline_overlays(outline_path.clone(), &transform, responses);
|
||||
}
|
||||
|
||||
// Create, place and style the anchor / handle overlays
|
||||
for (anchor_id, anchor) in shape.anchors().enumerate() {
|
||||
let anchor_cache = self.anchor_overlay_cache.get_mut(&(*layer_id, *anchor_id));
|
||||
// Create, place, and style the manipulator overlays
|
||||
for (manipulator_group_id, manipulator_group) in shape.manipulator_groups().enumerate() {
|
||||
let manipulator_group_cache = self.manipulator_group_overlay_cache.get_mut(&(*layer_id, *manipulator_group_id));
|
||||
|
||||
// If cached update placement and style
|
||||
if let Some(anchor_overlays) = anchor_cache {
|
||||
log::trace!("Overlay: Updating detail overlays for {:?}", anchor_overlays);
|
||||
Self::place_anchor_overlays(anchor, anchor_overlays, &transform, responses);
|
||||
Self::style_overlays(anchor, anchor_overlays, responses);
|
||||
if let Some(manipulator_group_overlays) = manipulator_group_cache {
|
||||
log::trace!("Overlay: Updating detail overlays for {:?}", manipulator_group_overlays);
|
||||
Self::place_manipulator_group_overlays(manipulator_group, manipulator_group_overlays, &transform, responses);
|
||||
Self::style_overlays(manipulator_group, manipulator_group_overlays, responses);
|
||||
} else {
|
||||
// Create if not cached
|
||||
let mut anchor_overlays = [
|
||||
let mut manipulator_group_overlays = [
|
||||
Some(self.create_anchor_overlay(responses)),
|
||||
Self::create_handle_overlay_if_exists(&anchor.points[ControlPointType::InHandle], responses),
|
||||
Self::create_handle_overlay_if_exists(&anchor.points[ControlPointType::OutHandle], responses),
|
||||
Self::create_handle_line_overlay_if_exists(&anchor.points[ControlPointType::InHandle], responses),
|
||||
Self::create_handle_line_overlay_if_exists(&anchor.points[ControlPointType::OutHandle], responses),
|
||||
Self::create_handle_overlay_if_exists(&manipulator_group.points[ManipulatorType::InHandle], responses),
|
||||
Self::create_handle_overlay_if_exists(&manipulator_group.points[ManipulatorType::OutHandle], responses),
|
||||
Self::create_handle_line_overlay_if_exists(&manipulator_group.points[ManipulatorType::InHandle], responses),
|
||||
Self::create_handle_line_overlay_if_exists(&manipulator_group.points[ManipulatorType::OutHandle], responses),
|
||||
];
|
||||
Self::place_anchor_overlays(anchor, &mut anchor_overlays, &transform, responses);
|
||||
Self::style_overlays(anchor, &anchor_overlays, responses);
|
||||
self.anchor_overlay_cache.insert((*layer_id, *anchor_id), anchor_overlays);
|
||||
Self::place_manipulator_group_overlays(manipulator_group, &mut manipulator_group_overlays, &transform, responses);
|
||||
Self::style_overlays(manipulator_group, &manipulator_group_overlays, responses);
|
||||
self.manipulator_group_overlay_cache.insert((*layer_id, *manipulator_group_id), manipulator_group_overlays);
|
||||
}
|
||||
}
|
||||
// TODO Handle removing shapes from cache so we don't memory leak
|
||||
|
@ -86,7 +85,7 @@ impl OverlayRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn clear_vector_shape_overlays(&mut self, document: &Document, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
|
||||
pub fn clear_subpath_overlays(&mut self, document: &Document, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
|
||||
let layer_id = layer_path.last().unwrap();
|
||||
|
||||
// Remove the shape outline overlays
|
||||
|
@ -95,13 +94,13 @@ impl OverlayRenderer {
|
|||
}
|
||||
self.shape_overlay_cache.remove(layer_id);
|
||||
|
||||
// Remove the anchor overlays
|
||||
// Remove the ManipulatorGroup overlays
|
||||
if let Ok(layer) = document.layer(&layer_path) {
|
||||
if let Some(shape) = layer.as_vector_shape() {
|
||||
for (id, _) in shape.anchors().enumerate() {
|
||||
if let Some(anchor_overlays) = self.anchor_overlay_cache.get(&(*layer_id, *id)) {
|
||||
Self::remove_anchor_overlays(anchor_overlays, responses);
|
||||
self.anchor_overlay_cache.remove(&(*layer_id, *id));
|
||||
if let Some(shape) = layer.as_subpath() {
|
||||
for (id, _) in shape.manipulator_groups().enumerate() {
|
||||
if let Some(manipulator_group_overlays) = self.manipulator_group_overlay_cache.get(&(*layer_id, *id)) {
|
||||
Self::remove_manipulator_group_overlays(manipulator_group_overlays, responses);
|
||||
self.manipulator_group_overlay_cache.remove(&(*layer_id, *id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -116,24 +115,24 @@ impl OverlayRenderer {
|
|||
Self::set_outline_overlay_visibility(overlay_path.clone(), visibility, responses);
|
||||
}
|
||||
|
||||
// Hide the anchor overlays
|
||||
// Hide the manipulator group overlays
|
||||
if let Ok(layer) = document.layer(&layer_path) {
|
||||
if let Some(shape) = layer.as_vector_shape() {
|
||||
for (id, _) in shape.anchors().enumerate() {
|
||||
if let Some(anchor_overlays) = self.anchor_overlay_cache.get(&(*layer_id, *id)) {
|
||||
Self::set_anchor_overlay_visibility(anchor_overlays, visibility, responses);
|
||||
if let Some(shape) = layer.as_subpath() {
|
||||
for (id, _) in shape.manipulator_groups().enumerate() {
|
||||
if let Some(manipulator_group_overlays) = self.manipulator_group_overlay_cache.get(&(*layer_id, *id)) {
|
||||
Self::set_manipulator_group_overlay_visibility(manipulator_group_overlays, visibility, responses);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create the kurbo shape that matches the selected viewport shape
|
||||
fn create_shape_outline_overlay(&self, vector_path: VectorShape, responses: &mut VecDeque<Message>) -> Vec<LayerId> {
|
||||
/// Create the kurbo shape that matches the selected viewport shape.
|
||||
fn create_shape_outline_overlay(&self, subpath: Subpath, responses: &mut VecDeque<Message>) -> Vec<LayerId> {
|
||||
let layer_path = vec![generate_uuid()];
|
||||
let operation = Operation::AddShape {
|
||||
path: layer_path.clone(),
|
||||
vector_path,
|
||||
subpath,
|
||||
style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, PATH_OUTLINE_WEIGHT)), Fill::None),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::IDENTITY.to_cols_array(),
|
||||
|
@ -143,7 +142,7 @@ impl OverlayRenderer {
|
|||
layer_path
|
||||
}
|
||||
|
||||
/// Create a single anchor overlay and return its layer id
|
||||
/// Create a single anchor overlay and return its layer ID.
|
||||
fn create_anchor_overlay(&self, responses: &mut VecDeque<Message>) -> Vec<LayerId> {
|
||||
let layer_path = vec![generate_uuid()];
|
||||
let operation = Operation::AddRect {
|
||||
|
@ -156,7 +155,7 @@ impl OverlayRenderer {
|
|||
layer_path
|
||||
}
|
||||
|
||||
/// Create a single handle overlay and return its layer id
|
||||
/// Create a single handle overlay and return its layer ID.
|
||||
fn create_handle_overlay(responses: &mut VecDeque<Message>) -> Vec<LayerId> {
|
||||
let layer_path = vec![generate_uuid()];
|
||||
let operation = Operation::AddEllipse {
|
||||
|
@ -169,12 +168,12 @@ impl OverlayRenderer {
|
|||
layer_path
|
||||
}
|
||||
|
||||
/// Create a single handle overlay and return its layer id if it exists
|
||||
fn create_handle_overlay_if_exists(handle: &Option<VectorControlPoint>, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> {
|
||||
/// Create a single handle overlay and return its layer id if it exists.
|
||||
fn create_handle_overlay_if_exists(handle: &Option<ManipulatorPoint>, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> {
|
||||
handle.as_ref().map(|_| Self::create_handle_overlay(responses))
|
||||
}
|
||||
|
||||
/// Create the shape outline overlay and return its layer id
|
||||
/// Create the shape outline overlay and return its layer ID.
|
||||
fn create_handle_line_overlay(responses: &mut VecDeque<Message>) -> Vec<LayerId> {
|
||||
let layer_path = vec![generate_uuid()];
|
||||
let operation = Operation::AddLine {
|
||||
|
@ -187,8 +186,8 @@ impl OverlayRenderer {
|
|||
layer_path
|
||||
}
|
||||
|
||||
/// Create the shape outline overlay and return its layer id
|
||||
fn create_handle_line_overlay_if_exists(handle: &Option<VectorControlPoint>, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> {
|
||||
/// Create the shape outline overlay and return its layer ID.
|
||||
fn create_handle_line_overlay_if_exists(handle: &Option<ManipulatorPoint>, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> {
|
||||
handle.as_ref().map(|_| Self::create_handle_line_overlay(responses))
|
||||
}
|
||||
|
||||
|
@ -197,18 +196,18 @@ impl OverlayRenderer {
|
|||
responses.push_back(transform_message);
|
||||
}
|
||||
|
||||
fn modify_outline_overlays(outline_path: Vec<LayerId>, vector_path: VectorShape, responses: &mut VecDeque<Message>) {
|
||||
let outline_modify_message = Self::overlay_modify_message(outline_path, vector_path);
|
||||
fn modify_outline_overlays(outline_path: Vec<LayerId>, subpath: Subpath, responses: &mut VecDeque<Message>) {
|
||||
let outline_modify_message = Self::overlay_modify_message(outline_path, subpath);
|
||||
responses.push_back(outline_modify_message);
|
||||
}
|
||||
|
||||
/// Updates the position of the overlays based on the VectorShape points
|
||||
fn place_anchor_overlays(anchor: &VectorAnchor, overlays: &mut AnchorOverlays, parent_transform: &DAffine2, responses: &mut VecDeque<Message>) {
|
||||
if let Some(anchor_point) = &anchor.points[ControlPointType::Anchor] {
|
||||
// Helper function to keep things DRY
|
||||
let mut place_handle_and_line = |handle: &VectorControlPoint, line_source: &mut Option<Vec<LayerId>>, marker_source: &mut Option<Vec<LayerId>>| {
|
||||
/// Updates the position of the overlays based on the [Subpath] points.
|
||||
fn place_manipulator_group_overlays(manipulator_group: &ManipulatorGroup, overlays: &mut ManipulatorGroupOverlays, parent_transform: &DAffine2, responses: &mut VecDeque<Message>) {
|
||||
if let Some(manipulator_point) = &manipulator_group.points[ManipulatorType::Anchor] {
|
||||
// Helper function to keep things DRY (don't-repeat-yourself)
|
||||
let mut place_handle_and_line = |handle: &ManipulatorPoint, line_source: &mut Option<Vec<LayerId>>, marker_source: &mut Option<Vec<LayerId>>| {
|
||||
let line_overlay = line_source.take().unwrap_or_else(|| Self::create_handle_line_overlay(responses));
|
||||
let line_vector = parent_transform.transform_point2(anchor_point.position) - parent_transform.transform_point2(handle.position);
|
||||
let line_vector = parent_transform.transform_point2(manipulator_point.position) - parent_transform.transform_point2(handle.position);
|
||||
let scale = DVec2::splat(line_vector.length());
|
||||
let angle = -line_vector.angle_between(DVec2::X);
|
||||
let translation = (parent_transform.transform_point2(handle.position) + ROUNDING_BIAS).round() + DVec2::splat(0.5);
|
||||
|
@ -217,7 +216,7 @@ impl OverlayRenderer {
|
|||
*line_source = Some(line_overlay);
|
||||
|
||||
let marker_overlay = marker_source.take().unwrap_or_else(|| Self::create_handle_overlay(responses));
|
||||
let scale = DVec2::splat(VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE);
|
||||
let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE);
|
||||
let angle = 0.;
|
||||
let translation = (parent_transform.transform_point2(handle.position) - (scale / 2.) + ROUNDING_BIAS).round();
|
||||
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
|
||||
|
@ -226,7 +225,7 @@ impl OverlayRenderer {
|
|||
};
|
||||
|
||||
// Place the handle overlays
|
||||
let [_, h1, h2] = &anchor.points;
|
||||
let [_, h1, h2] = &manipulator_group.points;
|
||||
let [a, b, c, line1, line2] = overlays;
|
||||
let markers = [a, b, c];
|
||||
if let Some(handle) = &h1 {
|
||||
|
@ -237,10 +236,10 @@ impl OverlayRenderer {
|
|||
}
|
||||
|
||||
// Place the anchor point overlay
|
||||
if let Some(anchor_overlay) = &overlays[ControlPointType::Anchor as usize] {
|
||||
let scale = DVec2::splat(VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE);
|
||||
if let Some(anchor_overlay) = &overlays[ManipulatorType::Anchor as usize] {
|
||||
let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE);
|
||||
let angle = 0.;
|
||||
let translation = (parent_transform.transform_point2(anchor_point.position) - (scale / 2.) + ROUNDING_BIAS).round();
|
||||
let translation = (parent_transform.transform_point2(manipulator_point.position) - (scale / 2.) + ROUNDING_BIAS).round();
|
||||
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
|
||||
|
||||
let message = Self::overlay_transform_message(anchor_overlay.clone(), transform);
|
||||
|
@ -249,8 +248,8 @@ impl OverlayRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
/// Removes the anchor / handle overlays from the overlay document
|
||||
fn remove_anchor_overlays(overlay_paths: &AnchorOverlays, responses: &mut VecDeque<Message>) {
|
||||
/// Removes the manipulator overlays from the overlay document.
|
||||
fn remove_manipulator_group_overlays(overlay_paths: &ManipulatorGroupOverlays, responses: &mut VecDeque<Message>) {
|
||||
overlay_paths.iter().flatten().for_each(|layer_id| {
|
||||
log::trace!("Overlay: Sending delete message for: {:?}", layer_id);
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::DeleteLayer { path: layer_id.clone() }.into()).into());
|
||||
|
@ -261,9 +260,9 @@ impl OverlayRenderer {
|
|||
responses.push_back(DocumentMessage::Overlays(Operation::DeleteLayer { path: overlay_path }.into()).into());
|
||||
}
|
||||
|
||||
/// Sets the visibility of the handles overlay
|
||||
fn set_anchor_overlay_visibility(anchor_overlays: &AnchorOverlays, visibility: bool, responses: &mut VecDeque<Message>) {
|
||||
anchor_overlays.iter().flatten().for_each(|layer_id| {
|
||||
/// Sets the visibility of the handles overlay.
|
||||
fn set_manipulator_group_overlay_visibility(manipulator_group_overlays: &ManipulatorGroupOverlays, visibility: bool, responses: &mut VecDeque<Message>) {
|
||||
manipulator_group_overlays.iter().flatten().for_each(|layer_id| {
|
||||
responses.push_back(Self::overlay_visibility_message(layer_id.clone(), visibility));
|
||||
});
|
||||
}
|
||||
|
@ -272,7 +271,7 @@ impl OverlayRenderer {
|
|||
responses.push_back(Self::overlay_visibility_message(overlay_path, visibility));
|
||||
}
|
||||
|
||||
/// Create a visibility message for an overlay
|
||||
/// Create a visibility message for an overlay.
|
||||
fn overlay_visibility_message(layer_path: Vec<LayerId>, visibility: bool) -> Message {
|
||||
DocumentMessage::Overlays(
|
||||
Operation::SetLayerVisibility {
|
||||
|
@ -284,28 +283,27 @@ impl OverlayRenderer {
|
|||
.into()
|
||||
}
|
||||
|
||||
/// Create a transform message for an overlay
|
||||
/// Create a transform message for an overlay.
|
||||
fn overlay_transform_message(layer_path: Vec<LayerId>, transform: [f64; 6]) -> Message {
|
||||
DocumentMessage::Overlays(Operation::SetLayerTransformInViewport { path: layer_path, transform }.into()).into()
|
||||
}
|
||||
|
||||
/// Create an update message for an overlay
|
||||
fn overlay_modify_message(layer_path: Vec<LayerId>, vector_path: VectorShape) -> Message {
|
||||
DocumentMessage::Overlays(Operation::SetShapePath { path: layer_path, vector_path }.into()).into()
|
||||
/// Create an update message for an overlay.
|
||||
fn overlay_modify_message(layer_path: Vec<LayerId>, subpath: Subpath) -> Message {
|
||||
DocumentMessage::Overlays(Operation::SetShapePath { path: layer_path, subpath }.into()).into()
|
||||
}
|
||||
|
||||
/// Sets the overlay style for this point
|
||||
fn style_overlays(anchor: &VectorAnchor, overlays: &AnchorOverlays, responses: &mut VecDeque<Message>) {
|
||||
// TODO Move the style definitions out of the VectorShape, should be looked up from a stylesheet or similar
|
||||
/// Sets the overlay style for this point.
|
||||
fn style_overlays(manipulator_group: &ManipulatorGroup, overlays: &ManipulatorGroupOverlays, responses: &mut VecDeque<Message>) {
|
||||
// TODO Move the style definitions out of the Subpath, should be looked up from a stylesheet or similar
|
||||
let selected_style = style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, POINT_STROKE_WEIGHT + 1.0)), Fill::solid(COLOR_ACCENT));
|
||||
let deselected_style = style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, POINT_STROKE_WEIGHT)), Fill::solid(Color::WHITE));
|
||||
|
||||
// Update if the anchor / handle points are shown as selected
|
||||
// Update if the manipulator points are shown as selected
|
||||
// Here the index is important, even though overlays[..] has five elements we only care about the first three
|
||||
for (index, point) in anchor.points.iter().enumerate() {
|
||||
for (index, point) in manipulator_group.points.iter().enumerate() {
|
||||
if let Some(point) = point {
|
||||
if let Some(overlay) = &overlays[index] {
|
||||
// log::debug!("style_overlays: {:?}", &overlay);
|
||||
let style = if point.editor_state.is_selected { selected_style.clone() } else { deselected_style.clone() };
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::SetLayerStyle { path: overlay.clone(), style }.into()).into());
|
||||
}
|
||||
|
|
|
@ -1,37 +1,38 @@
|
|||
// Overview:
|
||||
// ShapeEditor
|
||||
// / \
|
||||
// selected_shape_layers <- Paths to selected layers that may contain VectorShapes
|
||||
// | |
|
||||
// VectorShape ... VectorShape <- Reference from layer paths, one Vectorshape per layer
|
||||
// / \
|
||||
// VectorAnchor ... VectorAnchor <- VectorShape contains many VectorAnchors
|
||||
|
||||
use super::vector_anchor::VectorAnchor;
|
||||
use super::vector_control_point::VectorControlPoint;
|
||||
use super::vector_shape::VectorShape;
|
||||
use super::manipulator_group::ManipulatorGroup;
|
||||
use super::manipulator_point::ManipulatorPoint;
|
||||
use super::subpath::Subpath;
|
||||
use crate::message_prelude::{DocumentMessage, Message};
|
||||
|
||||
use graphene::layers::vector::constants::ControlPointType;
|
||||
use graphene::layers::vector::constants::ManipulatorType;
|
||||
use graphene::{LayerId, Operation};
|
||||
|
||||
use glam::DVec2;
|
||||
use graphene::document::Document;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
/// ShapeEditor is the container for all of the layer paths that are
|
||||
/// represented as VectorShapes and provides functionality required
|
||||
/// to query and create the VectorShapes / VectorAnchors / VectorControlPoints
|
||||
/// ShapeEditor is the container for all of the layer paths that are represented as [Subpath]s and provides
|
||||
/// functionality required to query and create the [Subpath] / [ManipulatorGroup]s / [ManipulatorPoint]s.
|
||||
///
|
||||
/// Overview:
|
||||
/// ```text
|
||||
/// ShapeEditor
|
||||
/// |
|
||||
/// selected_layers <- Paths to selected layers that may contain Subpaths
|
||||
/// / | \
|
||||
/// Subpath ... Subpath <- Reference from layer paths, one Subpath per layer (for now, will eventually be a CompoundPath)
|
||||
/// / | \
|
||||
/// ManipulatorGroup ... ManipulatorGroup <- Subpath contains many ManipulatorGroups
|
||||
/// ```
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ShapeEditor {
|
||||
// The layers we can select and edit anchors / handles from
|
||||
// The layers we can select and edit manipulators (anchors and handles) from
|
||||
selected_layers: Vec<Vec<LayerId>>,
|
||||
}
|
||||
|
||||
// TODO Consider keeping a list of selected anchors to minimize traversals of the layers
|
||||
// TODO Consider keeping a list of selected manipulators to minimize traversals of the layers
|
||||
impl ShapeEditor {
|
||||
/// Select the first point within the selection threshold
|
||||
/// Returns the points if found, none otherwise
|
||||
/// Select the first point within the selection threshold.
|
||||
/// Returns the points if found, None otherwise.
|
||||
pub fn select_point(
|
||||
&self,
|
||||
document: &Document,
|
||||
|
@ -39,23 +40,23 @@ impl ShapeEditor {
|
|||
select_threshold: f64,
|
||||
add_to_selection: bool,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Option<Vec<(&[LayerId], u64, ControlPointType)>> {
|
||||
) -> Option<Vec<(&[LayerId], u64, ManipulatorType)>> {
|
||||
if self.selected_layers.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some((shape_layer_path, anchor_id, point_index)) = self.find_nearest_point_indicies(document, mouse_position, select_threshold) {
|
||||
log::trace!("Selecting: anchor {} / point {}", anchor_id, point_index);
|
||||
if let Some((shape_layer_path, manipulator_group_id, manipulator_point_index)) = self.find_nearest_point_indices(document, mouse_position, select_threshold) {
|
||||
log::trace!("Selecting... manipulator group ID: {}, manipulator point index: {}", manipulator_group_id, manipulator_point_index);
|
||||
|
||||
// If the point we're selecting has already been selected
|
||||
// we can assume this point exists.. since we did just click on it hense the unwrap
|
||||
let is_point_selected = self.shape(document, shape_layer_path).unwrap().anchors().by_id(anchor_id).unwrap().points[point_index]
|
||||
// we can assume this point exists.. since we did just click on it hence the unwrap
|
||||
let is_point_selected = self.shape(document, shape_layer_path).unwrap().manipulator_groups().by_id(manipulator_group_id).unwrap().points[manipulator_point_index]
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.editor_state
|
||||
.is_selected;
|
||||
|
||||
let point_position = self.shape(document, shape_layer_path).unwrap().anchors().by_id(anchor_id).unwrap().points[point_index]
|
||||
let point_position = self.shape(document, shape_layer_path).unwrap().manipulator_groups().by_id(manipulator_group_id).unwrap().points[manipulator_point_index]
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.position;
|
||||
|
@ -65,35 +66,33 @@ impl ShapeEditor {
|
|||
.selected_layers()
|
||||
.iter()
|
||||
.filter_map(|path| document.layer(path).ok().map(|layer| (path, layer)))
|
||||
.filter_map(|(path, shape)| shape.as_vector_shape().map(|vector| (path, vector)))
|
||||
.filter_map(|(path, shape)| shape.as_subpath().map(|subpath| (path, subpath)))
|
||||
.flat_map(|(path, shape)| {
|
||||
shape
|
||||
.anchors()
|
||||
.manipulator_groups()
|
||||
.enumerate()
|
||||
.filter(|(_id, anchor)| anchor.is_anchor_selected())
|
||||
.flat_map(|(id, anchor)| anchor.selected_points().map(move |point| (id, point.manipulator_type)))
|
||||
.map(|(anchor, control_point)| (path.as_slice(), *anchor, control_point))
|
||||
.filter(|(_id, manipulator_group)| manipulator_group.is_anchor_selected())
|
||||
.flat_map(|(id, manipulator_group)| manipulator_group.selected_points().map(move |point| (id, point.manipulator_type)))
|
||||
.map(|(anchor, manipulator_point)| (path.as_slice(), *anchor, manipulator_point))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// let selected_shape = self.shape(document, shape_layer_path).unwrap();
|
||||
|
||||
// Should we select or deselect the point?
|
||||
let should_select = if is_point_selected { !add_to_selection } else { true };
|
||||
|
||||
// This is selecting the anchor only for now, next to generalize to points
|
||||
// This is selecting the manipulator only for now, next to generalize to points
|
||||
if should_select {
|
||||
let add = add_to_selection || is_point_selected;
|
||||
let point = (anchor_id, ControlPointType::from_index(point_index));
|
||||
let point = (manipulator_group_id, ManipulatorType::from_index(manipulator_point_index));
|
||||
// Clear all point in other selected shapes
|
||||
if !(add) {
|
||||
responses.push_back(DocumentMessage::DeselectAllVectorPoints.into());
|
||||
if !add {
|
||||
responses.push_back(DocumentMessage::DeselectAllManipulatorPoints.into());
|
||||
points = vec![(shape_layer_path, point.0, point.1)];
|
||||
} else {
|
||||
points.push((shape_layer_path, point.0, point.1));
|
||||
}
|
||||
responses.push_back(
|
||||
Operation::SelectVectorPoints {
|
||||
Operation::SelectManipulatorPoints {
|
||||
layer_path: shape_layer_path.to_vec(),
|
||||
point_ids: vec![point],
|
||||
add,
|
||||
|
@ -106,34 +105,34 @@ impl ShapeEditor {
|
|||
}
|
||||
} else {
|
||||
responses.push_back(
|
||||
Operation::DeselectVectorPoints {
|
||||
Operation::DeselectManipulatorPoints {
|
||||
layer_path: shape_layer_path.to_vec(),
|
||||
point_ids: vec![(anchor_id, ControlPointType::from_index(point_index))],
|
||||
point_ids: vec![(manipulator_group_id, ManipulatorType::from_index(manipulator_point_index))],
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
points.retain(|x| *x != (shape_layer_path, anchor_id, ControlPointType::from_index(point_index)))
|
||||
points.retain(|x| *x != (shape_layer_path, manipulator_group_id, ManipulatorType::from_index(manipulator_point_index)))
|
||||
}
|
||||
|
||||
return Some(points);
|
||||
}
|
||||
|
||||
// Deselect all points if no nearby point
|
||||
responses.push_back(DocumentMessage::DeselectAllVectorPoints.into());
|
||||
responses.push_back(DocumentMessage::DeselectAllManipulatorPoints.into());
|
||||
None
|
||||
}
|
||||
|
||||
/// A wrapper for find_nearest_point_indicies and returns a VectorControlPoint
|
||||
pub fn find_nearest_point<'a>(&'a self, document: &'a Document, mouse_position: DVec2, select_threshold: f64) -> Option<&'a VectorControlPoint> {
|
||||
let (shape_layer_path, anchor_id, point_index) = self.find_nearest_point_indicies(document, mouse_position, select_threshold)?;
|
||||
/// A wrapper for `find_nearest_point_indices()` and returns a [ManipulatorPoint].
|
||||
pub fn find_nearest_point<'a>(&'a self, document: &'a Document, mouse_position: DVec2, select_threshold: f64) -> Option<&'a ManipulatorPoint> {
|
||||
let (shape_layer_path, manipulator_group_id, manipulator_point_index) = self.find_nearest_point_indices(document, mouse_position, select_threshold)?;
|
||||
let selected_shape = self.shape(document, shape_layer_path).unwrap();
|
||||
if let Some(anchor) = selected_shape.anchors().by_id(anchor_id) {
|
||||
return anchor.points[point_index].as_ref();
|
||||
if let Some(manipulator_group) = selected_shape.manipulator_groups().by_id(manipulator_group_id) {
|
||||
return manipulator_group.points[manipulator_point_index].as_ref();
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Set the shapes we consider for selection, we will choose draggable handles / anchors from these shapes.
|
||||
/// Set the shapes we consider for selection, we will choose draggable manipulators from these shapes.
|
||||
pub fn set_selected_layers(&mut self, target_layers: Vec<Vec<LayerId>>) {
|
||||
self.selected_layers = target_layers;
|
||||
}
|
||||
|
@ -146,7 +145,7 @@ impl ShapeEditor {
|
|||
self.selected_layers.iter().map(|l| l.as_slice()).collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
/// Clear all of the shapes we can modify
|
||||
/// Clear all of the shapes we can modify.
|
||||
pub fn clear_selected_layers(&mut self) {
|
||||
self.selected_layers.clear();
|
||||
}
|
||||
|
@ -155,26 +154,26 @@ impl ShapeEditor {
|
|||
!self.selected_layers.is_empty()
|
||||
}
|
||||
|
||||
/// Provide the currently selected anchor by reference
|
||||
pub fn selected_anchors<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a VectorAnchor> {
|
||||
self.iter(document).flat_map(|shape| shape.selected_anchors())
|
||||
/// Provide the currently selected manipulators by reference.
|
||||
pub fn selected_manipulator_groups<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a ManipulatorGroup> {
|
||||
self.iter(document).flat_map(|shape| shape.selected_manipulator_groups())
|
||||
}
|
||||
|
||||
/// A mutable iterator of all the anchors, regardless of selection
|
||||
pub fn anchors<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a VectorAnchor> {
|
||||
self.iter(document).flat_map(|shape| shape.anchors().iter())
|
||||
/// A mutable iterator of all the manipulators, regardless of selection.
|
||||
pub fn manipulator_groups<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a ManipulatorGroup> {
|
||||
self.iter(document).flat_map(|shape| shape.manipulator_groups().iter())
|
||||
}
|
||||
|
||||
/// Provide the currently selected points by reference
|
||||
pub fn selected_points<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a VectorControlPoint> {
|
||||
self.selected_anchors(document).flat_map(|anchors| anchors.selected_points())
|
||||
/// Provide the currently selected points by reference.
|
||||
pub fn selected_points<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a ManipulatorPoint> {
|
||||
self.selected_manipulator_groups(document).flat_map(|manipulator_group| manipulator_group.selected_points())
|
||||
}
|
||||
|
||||
/// Move the selected points by dragging the moue
|
||||
/// Move the selected points by dragging the mouse.
|
||||
pub fn move_selected_points(&self, delta: DVec2, absolute_position: DVec2, responses: &mut VecDeque<Message>) {
|
||||
for layer_path in &self.selected_layers {
|
||||
responses.push_back(
|
||||
DocumentMessage::MoveSelectedVectorPoints {
|
||||
DocumentMessage::MoveSelectedManipulatorPoints {
|
||||
layer_path: layer_path.clone(),
|
||||
delta: (delta.x, delta.y),
|
||||
absolute_position: (absolute_position.x, absolute_position.y),
|
||||
|
@ -184,12 +183,12 @@ impl ShapeEditor {
|
|||
}
|
||||
}
|
||||
|
||||
/// Dissolve the selected points
|
||||
/// Dissolve the selected points.
|
||||
pub fn delete_selected_points(&self, responses: &mut VecDeque<Message>) {
|
||||
responses.push_back(DocumentMessage::DeleteSelectedVectorPoints.into());
|
||||
responses.push_back(DocumentMessage::DeleteSelectedManipulatorPoints.into());
|
||||
}
|
||||
|
||||
/// Toggle if the handles should mirror angle across the anchor positon
|
||||
/// Toggle if the handles should mirror angle across the anchor position.
|
||||
pub fn toggle_handle_mirroring_on_selected(&self, toggle_angle: bool, toggle_distance: bool, responses: &mut VecDeque<Message>) {
|
||||
for layer_path in &self.selected_layers {
|
||||
responses.push_back(
|
||||
|
@ -203,18 +202,19 @@ impl ShapeEditor {
|
|||
}
|
||||
}
|
||||
|
||||
/// Deselect all anchors from the shapes the manipulation handler has created
|
||||
/// Deselect all manipulators from the shapes that the manipulation handler has created.
|
||||
pub fn deselect_all_points(&self, responses: &mut VecDeque<Message>) {
|
||||
responses.push_back(DocumentMessage::DeselectAllVectorPoints.into());
|
||||
responses.push_back(DocumentMessage::DeselectAllManipulatorPoints.into());
|
||||
}
|
||||
|
||||
/// Iterate over the shapes
|
||||
pub fn iter<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a VectorShape> + 'a {
|
||||
self.selected_layers.iter().flat_map(|layer_id| document.layer(layer_id)).filter_map(|shape| shape.as_vector_shape())
|
||||
/// Iterate over the shapes.
|
||||
pub fn iter<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a Subpath> + 'a {
|
||||
self.selected_layers.iter().flat_map(|layer_id| document.layer(layer_id)).filter_map(|shape| shape.as_subpath())
|
||||
}
|
||||
|
||||
/// Find a point that is within the selection threshold and return an index to the shape, anchor, and point
|
||||
fn find_nearest_point_indicies(&self, document: &Document, mouse_position: DVec2, select_threshold: f64) -> Option<(&[LayerId], u64, usize)> {
|
||||
/// Find a [ManipulatorPoint] that is within the selection threshold and return the layer path, an index to the [ManipulatorGroup], and an enum index for [ManipulatorPoint].
|
||||
/// Return value is an `Option` of the tuple representing `(layer path, ManipulatorGroup ID, ManipulatorType enum index)`.
|
||||
fn find_nearest_point_indices(&self, document: &Document, mouse_position: DVec2, select_threshold: f64) -> Option<(&[LayerId], u64, usize)> {
|
||||
if self.selected_layers.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
@ -222,34 +222,36 @@ impl ShapeEditor {
|
|||
let select_threshold_squared = select_threshold * select_threshold;
|
||||
// Find the closest control point among all elements of shapes_to_modify
|
||||
for layer in self.selected_layers.iter() {
|
||||
if let Some((anchor_id, point_index, distance_squared)) = self.closest_point_in_layer(document, layer, mouse_position) {
|
||||
if let Some((manipulator_id, manipulator_point_index, distance_squared)) = self.closest_point_in_layer(document, layer, mouse_position) {
|
||||
// Choose the first point under the threshold
|
||||
if distance_squared < select_threshold_squared {
|
||||
log::trace!("Selecting: anchor {} / point {}", anchor_id, point_index);
|
||||
return Some((layer, anchor_id, point_index));
|
||||
log::trace!("Selecting... manipulator ID: {}, manipulator point index: {}", manipulator_id, manipulator_point_index);
|
||||
return Some((layer, manipulator_id, manipulator_point_index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
// TODO Use quadtree or some equivalent spatial acceleration structure to improve this to O(log(n))
|
||||
/// Find the closest point, anchor and distance so we can select path elements
|
||||
/// Brute force comparison to determine which handle / anchor we want to select, O(n)
|
||||
/// Find the closest manipulator, manipulator point, and distance so we can select path elements.
|
||||
/// Brute force comparison to determine which manipulator (handle or anchor) we want to select taking O(n) time.
|
||||
/// Return value is an `Option` of the tuple representing `(manipulator ID, manipulator point index, distance squared)`.
|
||||
fn closest_point_in_layer(&self, document: &Document, layer_path: &[LayerId], pos: glam::DVec2) -> Option<(u64, usize, f64)> {
|
||||
let mut closest_distance_squared: f64 = f64::MAX; // Not ideal
|
||||
let mut result: Option<(u64, usize, f64)> = None;
|
||||
|
||||
if let Some(shape) = document.layer(layer_path).ok()?.as_vector_shape() {
|
||||
if let Some(shape) = document.layer(layer_path).ok()?.as_subpath() {
|
||||
let viewspace = document.generate_transform_relative_to_viewport(layer_path).ok()?;
|
||||
for (anchor_id, anchor) in shape.anchors().enumerate() {
|
||||
let point_index = anchor.closest_point(&viewspace, pos);
|
||||
if let Some(point) = &anchor.points[point_index] {
|
||||
for (manipulator_id, manipulator) in shape.manipulator_groups().enumerate() {
|
||||
let manipulator_point_index = manipulator.closest_point(&viewspace, pos);
|
||||
if let Some(point) = &manipulator.points[manipulator_point_index] {
|
||||
if point.editor_state.can_be_selected {
|
||||
let distance_squared = viewspace.transform_point2(point.position).distance_squared(pos);
|
||||
if distance_squared < closest_distance_squared {
|
||||
closest_distance_squared = distance_squared;
|
||||
result = Some((*anchor_id, point_index, distance_squared));
|
||||
result = Some((*manipulator_id, manipulator_point_index, distance_squared));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -258,7 +260,7 @@ impl ShapeEditor {
|
|||
result
|
||||
}
|
||||
|
||||
fn shape<'a>(&'a self, document: &'a Document, layer_id: &[u64]) -> Option<&'a VectorShape> {
|
||||
document.layer(layer_id).ok()?.as_vector_shape()
|
||||
fn shape<'a>(&'a self, document: &'a Document, layer_id: &[u64]) -> Option<&'a Subpath> {
|
||||
document.layer(layer_id).ok()?.as_subpath()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
|
@ -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
graphene/src/layers/vector/subpath.rs
Normal file
541
graphene/src/layers/vector/subpath.rs
Normal 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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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>,
|
||||
|
|
|
@ -138,8 +138,8 @@ pub struct SimpleCommaDelimeted<T>(pub Vec<T>);
|
|||
|
||||
impl<T: Parse> Parse for SimpleCommaDelimeted<T> {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let punct = Punctuated::<T, Token![,]>::parse_terminated(input)?;
|
||||
Ok(Self(punct.into_iter().collect()))
|
||||
let punctuated = Punctuated::<T, Token![,]>::parse_terminated(input)?;
|
||||
Ok(Self(punctuated.into_iter().collect()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ pub fn call_site_ident<S: AsRef<str>>(s: S) -> Ident {
|
|||
Ident::new(s.as_ref(), Span::call_site())
|
||||
}
|
||||
|
||||
/// Creates the path `left::right` from the idents `left` and `right`
|
||||
/// Creates the path `left::right` from the identifiers `left` and `right`
|
||||
pub fn two_segment_path(left_ident: Ident, right_ident: Ident) -> Path {
|
||||
let mut segments: Punctuated<PathSegment, Token![::]> = Punctuated::new();
|
||||
segments.push(PathSegment {
|
||||
|
|
|
@ -187,7 +187,7 @@ pub fn derive_message(input_item: TokenStream) -> TokenStream {
|
|||
TokenStream::from(derive_as_message_impl(input_item.into()).unwrap_or_else(|err| err.to_compile_error()))
|
||||
}
|
||||
|
||||
/// This macro is basically an abbreviation for the usual [`ToDiscriminant`], [`TransitiveChild`] and [`AsMessage`] invocations
|
||||
/// This macro is basically an abbreviation for the usual [ToDiscriminant], [TransitiveChild] and [AsMessage] invocations.
|
||||
///
|
||||
/// This macro is enum-only.
|
||||
///
|
||||
|
|
|
@ -8,7 +8,7 @@ feed_filename = "rss.xml"
|
|||
|
||||
[markdown]
|
||||
# Whether to do syntax highlighting
|
||||
# Theme can be customised by setting the `highlight_theme` variable to a theme supported by Zola
|
||||
# Theme can be customized by setting the `highlight_theme` variable to a theme supported by Zola
|
||||
highlight_code = true
|
||||
highlight_theme = "css"
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue