diff --git a/editor/src/viewport_tools/tool.rs b/editor/src/viewport_tools/tool.rs index ca3b446c88..4d6eb2a383 100644 --- a/editor/src/viewport_tools/tool.rs +++ b/editor/src/viewport_tools/tool.rs @@ -200,7 +200,7 @@ pub fn standard_tool_message(tool: ToolType, message_type: StandardToolMessageTy ToolType::BlurSharpen => None, // Some(BlurSharpenMessage::DocumentIsDirty.into()), ToolType::Relight => None, // Some(RelightMessage::DocumentIsDirty.into()), ToolType::Path => Some(PathMessage::DocumentIsDirty.into()), - ToolType::Pen => None, // Some(PenMessage::DocumentIsDirty.into()), + ToolType::Pen => Some(PenMessage::DocumentIsDirty.into()), ToolType::Freehand => None, // Some(FreehandMessage::DocumentIsDirty.into()), ToolType::Spline => None, // Some(SplineMessage::DocumentIsDirty.into()), ToolType::Line => None, // Some(LineMessage::DocumentIsDirty.into()), diff --git a/editor/src/viewport_tools/tools/path.rs b/editor/src/viewport_tools/tools/path.rs index e4b0dd1605..c98be60598 100644 --- a/editor/src/viewport_tools/tools/path.rs +++ b/editor/src/viewport_tools/tools/path.rs @@ -94,6 +94,8 @@ impl Default for PathToolFsmState { struct PathToolData { shape_editor: ShapeEditor, snap_handler: SnapHandler, + + drag_start_pos: DVec2, alt_debounce: bool, shift_debounce: bool, } @@ -151,6 +153,7 @@ impl Fsm for PathToolFsmState { .map(|point| point.position) .collect(); data.snap_handler.add_snap_points(document, snap_points); + data.drag_start_pos = input.mouse.position; Dragging } // We didn't find a point nearby, so consider selecting the nearest shape instead @@ -207,7 +210,7 @@ impl Fsm for PathToolFsmState { // Move the selected points by the mouse position let snapped_position = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position); - data.shape_editor.move_selected_points(snapped_position, responses); + data.shape_editor.move_selected_points(snapped_position - data.drag_start_pos, true, responses); Dragging } // Mouse up diff --git a/editor/src/viewport_tools/tools/pen.rs b/editor/src/viewport_tools/tools/pen.rs index 96ba1a959e..b723f86d57 100644 --- a/editor/src/viewport_tools/tools/pen.rs +++ b/editor/src/viewport_tools/tools/pen.rs @@ -1,4 +1,3 @@ -use crate::consts::DRAG_THRESHOLD; use crate::document::DocumentMessageHandler; use crate::frontend::utility_types::MouseCursorIcon; use crate::input::keyboard::{Key, MouseMotion}; @@ -8,11 +7,14 @@ use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::viewport_tools::snapping::SnapHandler; use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; +use crate::viewport_tools::vector_editor::shape_editor::ShapeEditor; +use crate::viewport_tools::vector_editor::vector_shape::VectorShape; use graphene::layers::style; use graphene::Operation; use glam::{DAffine2, DVec2}; +use kurbo::{PathEl, Point}; use serde::{Deserialize, Serialize}; #[derive(Default)] @@ -38,6 +40,8 @@ impl Default for PenOptions { pub enum PenMessage { // Standard messages #[remain::unsorted] + DocumentIsDirty, + #[remain::unsorted] Abort, // Tool-specific messages @@ -111,7 +115,7 @@ impl<'a> MessageHandler> for Pen { match self.fsm_state { Ready => actions!(PenMessageDiscriminant; Undo, DragStart, DragStop, Confirm, Abort), - Drawing => actions!(PenMessageDiscriminant; DragStop, PointerMove, Confirm, Abort), + Drawing => actions!(PenMessageDiscriminant; DragStart, DragStop, PointerMove, Confirm, Abort), } } } @@ -123,11 +127,12 @@ impl Default for PenToolFsmState { } #[derive(Clone, Debug, Default)] struct PenToolData { - points: Vec, - next_point: DVec2, weight: u32, path: Option>, + curve_shape: VectorShape, + bez_path: Vec, snap_handler: SnapHandler, + shape_editor: ShapeEditor, } impl Fsm for PenToolFsmState { @@ -151,67 +156,92 @@ impl Fsm for PenToolFsmState { if let ToolMessage::Pen(event) = event { match (self, event) { + (_, DocumentIsDirty) => { + data.shape_editor.update_shapes(document, responses); + self + } (Ready, DragStart) => { responses.push_back(DocumentMessage::StartTransaction.into()); responses.push_back(DocumentMessage::DeselectAllLayers.into()); - data.path = Some(document.get_path_for_new_layer()); + // Create a new layer and prep snap system + data.path = Some(document.get_path_for_new_layer()); data.snap_handler.start_snap(document, document.bounding_boxes(None, None), true, true); let snapped_position = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position); - let pos = transform.inverse().transform_point2(snapped_position); - - data.points.push(pos); - data.next_point = pos; - + // Get the position and set properties + let start_position = transform.inverse().transform_point2(snapped_position); data.weight = tool_options.line_weight; - responses.push_back(add_polyline(data, tool_data, true)); + // Create the initial shape with a bez_path (only contains a moveto initially) + if let Some(layer_path) = &data.path { + data.bez_path = start_bez_path(start_position); + responses.push_back( + Operation::AddShape { + path: layer_path.clone(), + transform: transform.to_cols_array(), + insert_index: -1, + bez_path: data.bez_path.clone().into_iter().collect(), + style: style::PathStyle::new(Some(style::Stroke::new(tool_data.primary_color, data.weight as f32)), None), + closed: false, + } + .into(), + ); + } + add_to_curve(data, input, transform, document, responses); + Drawing + } + (Drawing, DragStart) => { + add_to_curve(data, input, transform, document, responses); Drawing } (Drawing, DragStop) => { - let snapped_position = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position); - let pos = transform.inverse().transform_point2(snapped_position); + // Deselect everything (this means we are no longer dragging the handle) + data.shape_editor.deselect_all(responses); - if let Some(last_pos) = data.points.last() { - if last_pos.distance(pos) > DRAG_THRESHOLD { - data.points.push(pos); - data.next_point = pos; - } + // Reselect the last point + if let Some(last_anchor) = data.shape_editor.select_last_anchor() { + last_anchor.select_point(0, true, responses); } - responses.push_back(remove_preview(data)); - responses.push_back(add_polyline(data, tool_data, true)); - Drawing } (Drawing, PointerMove) => { let snapped_position = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position); - let pos = transform.inverse().transform_point2(snapped_position); - data.next_point = pos; - - responses.push_back(remove_preview(data)); - responses.push_back(add_polyline(data, tool_data, true)); + //data.shape_editor.update_shapes(document, responses); + data.shape_editor.move_selected_points(snapped_position, false, responses); Drawing } (Drawing, Confirm) | (Drawing, Abort) => { - if data.points.len() >= 2 { + // Add a curve to the path + if let Some(layer_path) = &data.path { + remove_curve_from_end(&mut data.bez_path); + responses.push_back(apply_bez_path(layer_path.clone(), data.bez_path.clone(), transform)); + } + + // Cleanup, we are either canceling or finished drawing + if data.bez_path.len() >= 2 { responses.push_back(DocumentMessage::DeselectAllLayers.into()); - responses.push_back(remove_preview(data)); - responses.push_back(add_polyline(data, tool_data, false)); responses.push_back(DocumentMessage::CommitTransaction.into()); } else { responses.push_back(DocumentMessage::AbortTransaction.into()); } + data.shape_editor.remove_overlays(responses); + data.shape_editor.clear_shapes_to_modify(); + data.path = None; - data.points.clear(); data.snap_handler.cleanup(responses); Ready } + (_, Abort) => { + data.shape_editor.remove_overlays(responses); + data.shape_editor.clear_shapes_to_modify(); + Ready + } _ => self, } } else { @@ -251,22 +281,70 @@ impl Fsm for PenToolFsmState { } } -fn remove_preview(data: &PenToolData) -> Message { - Operation::DeleteLayer { path: data.path.clone().unwrap() }.into() -} +// Add to the curve and select the second anchor of the last point and the newly added anchor point +fn add_to_curve(data: &mut PenToolData, input: &InputPreprocessorMessageHandler, transform: DAffine2, document: &DocumentMessageHandler, responses: &mut VecDeque) { + // We need to make sure we have the most up-to-date bez_path + // Would like to remove this hack eventually + if !data.shape_editor.shapes_to_modify.is_empty() { + // Hacky way of saving the curve changes + data.bez_path = data.shape_editor.shapes_to_modify[0].bez_path.elements().to_vec(); + } + + // Setup our position params + let snapped_position = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position); + let position = transform.inverse().transform_point2(snapped_position); + + // Add a curve to the path + if let Some(layer_path) = &data.path { + add_curve_to_end(position, &mut data.bez_path); + responses.push_back(apply_bez_path(layer_path.clone(), data.bez_path.clone(), transform)); + + // Clear previous overlays + data.shape_editor.remove_overlays(responses); + + // Create a new shape from the updated bez_path + let bez_path = data.bez_path.clone().into_iter().collect(); + data.curve_shape = VectorShape::new(layer_path.to_vec(), transform, &bez_path, false, responses); + data.shape_editor.set_shapes_to_modify(vec![data.curve_shape.clone()]); + + // Select the second to last segment's handle + data.shape_editor.set_shape_selected(0); + let handle_element = data.shape_editor.select_nth_anchor(0, -2); + handle_element.select_point(2, true, responses); -fn add_polyline(data: &PenToolData, tool_data: &DocumentToolData, show_preview: bool) -> Message { - let mut points: Vec<(f64, f64)> = data.points.iter().map(|p| (p.x, p.y)).collect(); - if show_preview { - points.push((data.next_point.x, data.next_point.y)) + // Select the last segment's anchor point + if let Some(last_anchor) = data.shape_editor.select_last_anchor() { + last_anchor.select_point(0, true, responses); + } + data.shape_editor.set_selected_mirror_options(true, true); } +} + +// Create the initial moveto for the bez_path +fn start_bez_path(start_position: DVec2) -> Vec { + vec![PathEl::MoveTo(Point { + x: start_position.x, + y: start_position.y, + })] +} + +// Add a curve to the bez_path +fn add_curve_to_end(point: DVec2, bez_path: &mut Vec) { + let point = Point { x: point.x, y: point.y }; + bez_path.push(PathEl::CurveTo(point, point, point)); +} + +// Add a curve to the bez_path +fn remove_curve_from_end(bez_path: &mut Vec) { + bez_path.pop(); +} - Operation::AddPolyline { - path: data.path.clone().unwrap(), - insert_index: -1, - transform: DAffine2::IDENTITY.to_cols_array(), - points, - style: style::PathStyle::new(Some(style::Stroke::new(tool_data.primary_color, data.weight as f32)), None), +// Apply the bez_path to the shape in the viewport +fn apply_bez_path(layer_path: Vec, bez_path: Vec, transform: DAffine2) -> Message { + Operation::SetShapePathInViewport { + path: layer_path, + bez_path: bez_path.into_iter().collect(), + transform: transform.to_cols_array(), } .into() } diff --git a/editor/src/viewport_tools/vector_editor/shape_editor.rs b/editor/src/viewport_tools/vector_editor/shape_editor.rs index 5186f40419..713fd42133 100644 --- a/editor/src/viewport_tools/vector_editor/shape_editor.rs +++ b/editor/src/viewport_tools/vector_editor/shape_editor.rs @@ -17,8 +17,12 @@ Overview: use super::vector_shape::VectorShape; use super::{constants::MINIMUM_MIRROR_THRESHOLD, vector_anchor::VectorAnchor, vector_control_point::VectorControlPoint}; +use crate::document::DocumentMessageHandler; use crate::message_prelude::Message; -use glam::DVec2; + +use graphene::layers::layer_info::LayerDataType; + +use glam::{DAffine2, DVec2}; use std::collections::{HashSet, VecDeque}; /// ShapeEditor is the container for all of the selected kurbo paths that are @@ -30,8 +34,6 @@ pub struct ShapeEditor { pub shapes_to_modify: Vec, // Index of the shape that contained the most recent selected point pub selected_shape_indices: HashSet, - // The initial drag position of the mouse on drag start - pub drag_start_position: DVec2, } impl ShapeEditor { @@ -46,7 +48,7 @@ impl ShapeEditor { log::trace!("Selecting: shape {} / anchor {} / point {}", shape_index, anchor_index, point_index); // Add this shape to the selection - self.add_selected_shape(shape_index); + self.set_shape_selected(shape_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 @@ -65,12 +67,7 @@ impl ShapeEditor { // Add which anchor and point was selected let selected_anchor = selected_shape.select_anchor(anchor_index); - let selected_point = selected_anchor.select_point(point_index, should_select, responses); - - // Set the drag start position based on the selected point - if let Some(point) = selected_point { - self.drag_start_position = point.position; - } + selected_anchor.select_point(point_index, should_select, responses); // Due to the shape data structure not persisting across shape selection changes we need to rely on the kurbo path to know if we should mirror selected_anchor.set_mirroring((selected_anchor.angle_between_handles().abs() - std::f64::consts::PI).abs() < MINIMUM_MIRROR_THRESHOLD); @@ -111,11 +108,40 @@ impl ShapeEditor { self.shapes_to_modify = selected_shapes; } + /// Set a single shape to be modifed by providing a layer path + pub fn set_shapes_to_modify_from_layer(&mut self, layer_path: &[u64], transform: DAffine2, document: &DocumentMessageHandler, responses: &mut VecDeque) { + // Setup the shape editor + let layer = document.graphene_document.layer(layer_path); + if let Ok(layer) = layer { + let shape = match &layer.data { + LayerDataType::Shape(shape) => Some(VectorShape::new(layer_path.to_vec(), transform, &shape.path, shape.closed, responses)), + _ => None, + }; + self.set_shapes_to_modify(vec![shape.expect("The layer provided didn't have a shape we could use.")]); + } + } + + /// Clear all of the shapes we can modify + pub fn clear_shapes_to_modify(&mut self) { + self.shapes_to_modify.clear(); + } + /// Add a shape to the hashset of shapes we consider for selection - pub fn add_selected_shape(&mut self, shape_index: usize) { + pub fn set_shape_selected(&mut self, shape_index: usize) { self.selected_shape_indices.insert(shape_index); } + /// Update the currently shapes we consider for selection + pub fn update_shapes(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque) { + if self.shapes_to_modify.is_empty() { + return; + } + + for shape in self.shapes_to_modify.iter_mut() { + shape.update_shape(document, responses); + } + } + /// Provide the shapes that the currently selected points are a part of pub fn selected_shapes(&self) -> impl Iterator { self.shapes_to_modify @@ -142,6 +168,31 @@ impl ShapeEditor { self.selected_shapes_mut().flat_map(|shape| shape.selected_anchors_mut()) } + /// A mutable iterator of all the anchors, regardless of selection + pub fn anchors_mut(&mut self) -> impl Iterator { + self.shapes_to_modify.iter_mut().flat_map(|shape| shape.anchors_mut()) + } + + /// Select the last anchor in this shape + pub fn select_last_anchor(&mut self) -> Option<&mut VectorAnchor> { + if let Some(last) = self.shapes_to_modify.last_mut() { + return Some(last.select_last_anchor()); + } + None + } + + /// Select the Nth anchor of the shape, negative numbers index from the end + pub fn select_nth_anchor(&mut self, shape_index: usize, anchor_index: i32) -> &mut VectorAnchor { + let shape = &mut self.shapes_to_modify[shape_index]; + if anchor_index < 0 { + let anchor_index = shape.anchors.len() - ((-anchor_index) as usize); + shape.select_anchor(anchor_index) + } else { + let anchor_index = anchor_index as usize; + shape.select_anchor(anchor_index) + } + } + /// Provide the currently selected points by reference pub fn selected_points(&self) -> impl Iterator { self.selected_shapes().flat_map(|shape| shape.selected_anchors()).flat_map(|anchors| anchors.selected_points()) @@ -155,10 +206,9 @@ impl ShapeEditor { } /// Move the selected points by dragging the moue - pub fn move_selected_points(&mut self, mouse_position: DVec2, responses: &mut VecDeque) { - let drag_start_position = self.drag_start_position; + pub fn move_selected_points(&mut self, target: DVec2, relative: bool, responses: &mut VecDeque) { for shape in self.selected_shapes_mut() { - shape.move_selected(mouse_position - drag_start_position, responses); + shape.move_selected(target, relative, responses); } } @@ -169,6 +219,13 @@ impl ShapeEditor { } } + pub fn set_selected_mirror_options(&mut self, mirror_angle: bool, mirror_distance: bool) { + for anchor in self.selected_anchors_mut() { + anchor.handle_mirror_angle = mirror_angle; + anchor.handle_mirror_distance = mirror_distance; + } + } + /// Toggle if the handles should mirror distance across the anchor position pub fn toggle_selected_mirror_distance(&mut self) { for anchor in self.selected_anchors_mut() { diff --git a/editor/src/viewport_tools/vector_editor/vector_anchor.rs b/editor/src/viewport_tools/vector_editor/vector_anchor.rs index bb543ec2f8..437107dd41 100644 --- a/editor/src/viewport_tools/vector_editor/vector_anchor.rs +++ b/editor/src/viewport_tools/vector_editor/vector_anchor.rs @@ -1,8 +1,3 @@ -use glam::{DAffine2, DVec2}; -use graphene::{LayerId, Operation}; -use kurbo::{PathEl, Point, Vec2}; -use std::collections::VecDeque; - use crate::{ consts::VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE, message_prelude::{DocumentMessage, Message}, @@ -13,6 +8,12 @@ use super::{ vector_control_point::VectorControlPoint, }; +use graphene::{LayerId, Operation}; + +use glam::{DAffine2, DVec2}; +use kurbo::{PathEl, Point, Vec2}; +use std::collections::VecDeque; + /// VectorAnchor is used to represent an anchor point on the path that can be moved. /// It contains 0-2 handles that are optionally displayed. #[derive(PartialEq, Clone, Debug, Default)] @@ -49,7 +50,7 @@ impl VectorAnchor { // TODO Cleanup the internals of this function /// Move the selected points by the provided delta - pub fn move_selected_points(&mut self, position_delta: DVec2, path_elements: &mut Vec, transform: &DAffine2) { + pub fn move_selected_points(&mut self, translation: DVec2, relative: bool, path_elements: &mut Vec, transform: &DAffine2) { let place_mirrored_handle = |center: kurbo::Point, original: kurbo::Point, target: kurbo::Point, selected: bool, mirror_angle: bool, mirror_distance: bool| -> kurbo::Point { if !selected || !mirror_angle { return original; @@ -65,9 +66,17 @@ impl VectorAnchor { } }; + let offset = |point: Point| -> Point { + if relative { + let relative = transform.inverse().transform_vector2(translation); + point + Vec2::new(relative.x, relative.y) + } else { + let absolute = transform.inverse().transform_point2(translation); + Point { x: absolute.x, y: absolute.y } + } + }; + for selected_point in self.selected_points() { - let delta = transform.inverse().transform_vector2(position_delta); - let delta = Vec2::new(delta.x, delta.y); let h1_selected = ControlPointType::Handle1 == selected_point.manipulator_type; let h2_selected = ControlPointType::Handle2 == selected_point.manipulator_type; let dragging_anchor = !(h1_selected || h2_selected); @@ -78,10 +87,10 @@ impl VectorAnchor { let handle1_exists_and_selected = self.points[ControlPointType::Handle1].is_some() && self.points[ControlPointType::Handle1].as_ref().unwrap().is_selected; // Move the anchor point and handle on the same path element let selected_element = match &path_elements[selected_point.kurbo_element_id] { - PathEl::MoveTo(p) => PathEl::MoveTo(*p + delta), - PathEl::LineTo(p) => PathEl::LineTo(*p + delta), - PathEl::QuadTo(a1, p) => PathEl::QuadTo(*a1, *p + delta), - PathEl::CurveTo(a1, a2, p) => PathEl::CurveTo(*a1, if handle1_exists_and_selected { *a2 } else { *a2 + delta }, *p + delta), + PathEl::MoveTo(p) => PathEl::MoveTo(offset(*p)), + PathEl::LineTo(p) => PathEl::LineTo(offset(*p)), + PathEl::QuadTo(a1, p) => PathEl::QuadTo(*a1, offset(*p)), + PathEl::CurveTo(a1, a2, p) => PathEl::CurveTo(*a1, if handle1_exists_and_selected { *a2 } else { offset(*a2) }, offset(*p)), PathEl::ClosePath => PathEl::ClosePath, }; @@ -92,7 +101,7 @@ impl VectorAnchor { PathEl::MoveTo(p) => PathEl::MoveTo(*p), PathEl::LineTo(p) => PathEl::LineTo(*p), PathEl::QuadTo(a1, p) => PathEl::QuadTo(*a1, *p), - PathEl::CurveTo(a1, a2, p) => PathEl::CurveTo(*a1 + delta, *a2, *p), + PathEl::CurveTo(a1, a2, p) => PathEl::CurveTo(offset(*a1), *a2, *p), PathEl::ClosePath => PathEl::ClosePath, }; path_elements[handle.kurbo_element_id] = neighbor; @@ -102,10 +111,10 @@ impl VectorAnchor { if let Some(close_id) = self.close_element_id { // Move the invisible point that can be caused by MoveTo / closing the path path_elements[close_id] = match &path_elements[close_id] { - PathEl::MoveTo(p) => PathEl::MoveTo(*p + delta), - PathEl::LineTo(p) => PathEl::LineTo(*p + delta), - PathEl::QuadTo(a1, p) => PathEl::QuadTo(*a1, *p + delta), - PathEl::CurveTo(a1, a2, p) => PathEl::CurveTo(*a1, *a2 + delta, *p + delta), + PathEl::MoveTo(p) => PathEl::MoveTo(offset(*p)), + PathEl::LineTo(p) => PathEl::LineTo(offset(*p)), + PathEl::QuadTo(a1, p) => PathEl::QuadTo(*a1, offset(*p)), + PathEl::CurveTo(a1, a2, p) => PathEl::CurveTo(*a1, offset(*a2), offset(*p)), PathEl::ClosePath => PathEl::ClosePath, }; } @@ -121,10 +130,10 @@ impl VectorAnchor { let (selected_element, anchor, selected_handle) = match &path_elements[selected_point.kurbo_element_id] { PathEl::MoveTo(p) => (PathEl::MoveTo(*p), *p, *p), PathEl::LineTo(p) => (PathEl::LineTo(*p), *p, *p), - PathEl::QuadTo(a1, p) => (PathEl::QuadTo(*a1 + delta, *p), *p, *a1 + delta), + PathEl::QuadTo(a1, p) => (PathEl::QuadTo(offset(*a1), *p), *p, offset(*a1)), PathEl::CurveTo(a1, a2, p) => { - let a1_point = if h2_selected { *a1 + delta } else { *a1 }; - let a2_point = if h1_selected { *a2 + delta } else { *a2 }; + let a1_point = if h2_selected { offset(*a1) } else { *a1 }; + let a2_point = if h1_selected { offset(*a2) } else { *a2 }; (PathEl::CurveTo(a1_point, a2_point, *p), *p, if h1_selected { a2_point } else { a1_point }) } PathEl::ClosePath => (PathEl::ClosePath, Point::ZERO, Point::ZERO), diff --git a/editor/src/viewport_tools/vector_editor/vector_control_point.rs b/editor/src/viewport_tools/vector_editor/vector_control_point.rs index c3cab14cfc..1d13500743 100644 --- a/editor/src/viewport_tools/vector_editor/vector_control_point.rs +++ b/editor/src/viewport_tools/vector_editor/vector_control_point.rs @@ -1,17 +1,17 @@ -use glam::DVec2; +use super::constants::ControlPointType; +use crate::{ + consts::COLOR_ACCENT, + message_prelude::{DocumentMessage, Message}, +}; + use graphene::{ color::Color, layers::style::{Fill, PathStyle, Stroke}, LayerId, Operation, }; -use std::collections::VecDeque; - -use crate::{ - consts::COLOR_ACCENT, - message_prelude::{DocumentMessage, Message}, -}; -use super::constants::ControlPointType; +use glam::DVec2; +use std::collections::VecDeque; /// VectorControlPoint represents any grabbable point, anchor or handle #[derive(PartialEq, Clone, Debug)] diff --git a/editor/src/viewport_tools/vector_editor/vector_shape.rs b/editor/src/viewport_tools/vector_editor/vector_shape.rs index 9b82ffb57e..a5537a313e 100644 --- a/editor/src/viewport_tools/vector_editor/vector_shape.rs +++ b/editor/src/viewport_tools/vector_editor/vector_shape.rs @@ -1,4 +1,10 @@ -use glam::{DAffine2, DVec2}; +use super::{constants::ControlPointType, vector_anchor::VectorAnchor, vector_control_point::VectorControlPoint}; +use crate::{ + consts::COLOR_ACCENT, + document::DocumentMessageHandler, + message_prelude::{generate_uuid, DocumentMessage, Message}, +}; + use graphene::{ color::Color, layers::{ @@ -7,18 +13,12 @@ use graphene::{ }, LayerId, Operation, }; + +use glam::{DAffine2, DVec2}; use kurbo::{BezPath, PathEl}; use std::collections::HashSet; use std::collections::VecDeque; -use crate::{ - consts::COLOR_ACCENT, - document::DocumentMessageHandler, - message_prelude::{generate_uuid, DocumentMessage, Message}, -}; - -use super::{constants::ControlPointType, vector_anchor::VectorAnchor, vector_control_point::VectorControlPoint}; - /// VectorShape represents a single kurbo shape and maintains a parallel data structure /// For each kurbo path we keep a VectorShape which contains the handles and anchors for that path #[derive(PartialEq, Clone, Debug, Default)] @@ -74,6 +74,13 @@ impl VectorShape { &mut self.anchors[anchor_index] } + /// The last anchor in the shape thus far + pub fn select_last_anchor(&mut self) -> &mut VectorAnchor { + let last_index = self.anchors.len() - 1; + self.selected_anchor_indices.insert(last_index); + &mut self.anchors[last_index] + } + /// Deselect an anchor pub fn deselect_anchor(&mut self, anchor_index: usize, responses: &mut VecDeque) { self.anchors[anchor_index].clear_selected_points(responses); @@ -112,14 +119,19 @@ impl VectorShape { .filter_map(|(index, anchor)| if self.selected_anchor_indices.contains(&index) { Some(anchor) } else { None }) } + /// Return a mutable interator of the anchors regardless of selection + pub fn anchors_mut(&mut self) -> impl Iterator { + self.anchors.iter_mut() + } + /// Move the selected point based on mouse input, if this is a handle we can control if we are mirroring or not /// A wrapper around move_point to handle mirror state / submit the changes - pub fn move_selected(&mut self, position_delta: DVec2, responses: &mut VecDeque) { + pub fn move_selected(&mut self, target: DVec2, relative: bool, responses: &mut VecDeque) { let transform = &self.transform.clone(); let mut edited_bez_path = self.elements.clone(); for selected_anchor in self.selected_anchors_mut() { - selected_anchor.move_selected_points(position_delta, &mut edited_bez_path, transform); + selected_anchor.move_selected_points(target, relative, &mut edited_bez_path, transform); } // We've made our changes to the shape, submit them diff --git a/graphene/src/document.rs b/graphene/src/document.rs index 0e41803031..ce449eae8e 100644 --- a/graphene/src/document.rs +++ b/graphene/src/document.rs @@ -508,6 +508,18 @@ impl Document { Some([vec![DocumentChanged, CreatedLayer { path: path.clone() }]].concat()) } + Operation::AddShape { + path, + transform, + insert_index, + style, + bez_path, + closed, + } => { + let shape = Shape::from_bez_path(bez_path.clone(), *style, *closed); + self.set_layer(path, Layer::new(LayerDataType::Shape(shape), *transform), *insert_index)?; + Some([vec![DocumentChanged, CreatedLayer { path: path.clone() }]].concat()) + } Operation::AddPolyline { path, insert_index, diff --git a/graphene/src/operation.rs b/graphene/src/operation.rs index fcdf9fa342..fc63934b26 100644 --- a/graphene/src/operation.rs +++ b/graphene/src/operation.rs @@ -88,6 +88,14 @@ pub enum Operation { style: style::PathStyle, closed: bool, }, + AddShape { + path: Vec, + transform: [f64; 6], + insert_index: isize, + bez_path: kurbo::BezPath, + style: style::PathStyle, + closed: bool, + }, DeleteLayer { path: Vec, },