diff --git a/editor/graphite-test-document.graphite b/editor/graphite-test-document.graphite index 57b1ef962..f7eba7cb5 100644 --- a/editor/graphite-test-document.graphite +++ b/editor/graphite-test-document.graphite @@ -1 +1 @@ -{"graphene_document":{"root":{"visible":true,"name":null,"data":{"Folder":{"next_assignment_id":10689566813179949075,"layer_ids":[10689566813179949074],"layers":[{"visible":true,"name":"Folder 1","data":{"Folder":{"next_assignment_id":17222868332373661780,"layer_ids":[17222868332373661779],"layers":[{"visible":true,"name":"Shape 1","data":{"Shape":{"shape":{"elements":[{"points":[{"position":[0.5,1.0],"manipulator_type":"Anchor"},{"position":[0.7761415,1.0],"manipulator_type":"InHandle"},{"position":[0.22385850000000002,1.0],"manipulator_type":"OutHandle"}]},{"points":[{"position":[0.0,0.5],"manipulator_type":"Anchor"},{"position":[0.0,0.7761415],"manipulator_type":"InHandle"},{"position":[0.0,0.22385850000000002],"manipulator_type":"OutHandle"}]},{"points":[{"position":[0.5,0.0],"manipulator_type":"Anchor"},{"position":[0.22385850000000002,0.0],"manipulator_type":"InHandle"},{"position":[0.7761415,0.0],"manipulator_type":"OutHandle"}]},{"points":[{"position":[1.0,0.5],"manipulator_type":"Anchor"},{"position":[1.0,0.22385850000000002],"manipulator_type":"InHandle"},{"position":[1.0,0.7761415],"manipulator_type":"OutHandle"}]},{"points":[null,null,null]}],"element_ids":[1,2,3,4,5]},"style":{"stroke":null,"fill":{"Solid":{"red":0.0,"green":0.0,"blue":0.0,"alpha":1.0}}},"render_index":1}},"transform":{"matrix2":[379.0,0.0,-0.0,239.0],"translation":[-479.3046875,-99.5]},"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0}]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[0.0,0.0]},"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0}]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[1060.3046875,373.5]},"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0}},"saved_document_identifier":6520881531418194372,"name":"Untitled Document","version":"0.0.13","document_mode":"DesignMode","view_mode":"Normal","snapping_enabled":true,"overlays_visible":true,"layer_metadata":[[[],{"selected":false,"expanded":true}],[[10689566813179949074],{"selected":false,"expanded":true}],[[10689566813179949074,17222868332373661779],{"selected":true,"expanded":false}]],"layer_range_selection_reference":[10689566813179949074,17222868332373661779],"navigation_handler":{"pan":[0.0,0.0],"panning":false,"snap_tilt":false,"snap_tilt_released":false,"tilt":0.0,"tilting":false,"zoom":1.0,"zooming":false,"snap_zoom":false,"mouse_position":[0.0,0.0]},"artboard_message_handler":{"artboards_graphene_document":{"root":{"visible":true,"name":null,"data":{"Folder":{"next_assignment_id":0,"layer_ids":[],"layers":[]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[1060.3046875,373.5]},"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0}},"artboard_ids":[]},"properties_panel_message_handler":{"active_selection":[[10689566813179949074,17222868332373661779],"Artwork"]}} +{"graphene_document":{"root":{"visible":true,"name":null,"data":{"Folder":{"next_assignment_id":10689566813179949075,"layer_ids":[10689566813179949074],"layers":[{"visible":true,"name":"Folder 1","data":{"Folder":{"next_assignment_id":17222868332373661780,"layer_ids":[17222868332373661779],"layers":[{"visible":true,"name":"Shape 1","data":{"Shape":{"shape":{"elements":[{"points":[{"position":[0.5,1.0],"manipulator_type":"Anchor"},{"position":[0.7761415,1.0],"manipulator_type":"InHandle"},{"position":[0.22385850000000002,1.0],"manipulator_type":"OutHandle"}]},{"points":[{"position":[0.0,0.5],"manipulator_type":"Anchor"},{"position":[0.0,0.7761415],"manipulator_type":"InHandle"},{"position":[0.0,0.22385850000000002],"manipulator_type":"OutHandle"}]},{"points":[{"position":[0.5,0.0],"manipulator_type":"Anchor"},{"position":[0.22385850000000002,0.0],"manipulator_type":"InHandle"},{"position":[0.7761415,0.0],"manipulator_type":"OutHandle"}]},{"points":[{"position":[1.0,0.5],"manipulator_type":"Anchor"},{"position":[1.0,0.22385850000000002],"manipulator_type":"InHandle"},{"position":[1.0,0.7761415],"manipulator_type":"OutHandle"}]},{"points":[null,null,null]}],"element_ids":[1,2,3,4,5],"next_id":6},"style":{"stroke":null,"fill":{"Solid":{"red":0.0,"green":0.0,"blue":0.0,"alpha":1.0}}},"render_index":1}},"transform":{"matrix2":[379.0,0.0,-0.0,239.0],"translation":[-479.3046875,-99.5]},"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0}]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[0.0,0.0]},"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0}]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[1060.3046875,373.5]},"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0}},"saved_document_identifier":6520881531418194372,"name":"Untitled Document","version":"0.0.14","document_mode":"DesignMode","view_mode":"Normal","snapping_enabled":true,"overlays_visible":true,"layer_metadata":[[[],{"selected":false,"expanded":true}],[[10689566813179949074],{"selected":false,"expanded":true}],[[10689566813179949074,17222868332373661779],{"selected":true,"expanded":false}]],"layer_range_selection_reference":[10689566813179949074,17222868332373661779],"navigation_handler":{"pan":[0.0,0.0],"panning":false,"snap_tilt":false,"snap_tilt_released":false,"tilt":0.0,"tilting":false,"zoom":1.0,"zooming":false,"snap_zoom":false,"mouse_position":[0.0,0.0]},"artboard_message_handler":{"artboards_graphene_document":{"root":{"visible":true,"name":null,"data":{"Folder":{"next_assignment_id":0,"layer_ids":[],"layers":[]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[1060.3046875,373.5]},"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0}},"artboard_ids":[]},"properties_panel_message_handler":{"active_selection":[[10689566813179949074,17222868332373661779],"Artwork"]}} diff --git a/editor/src/consts.rs b/editor/src/consts.rs index 54f79adb9..a52bddb29 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -74,7 +74,7 @@ pub const DEFAULT_FONT_FAMILY: &str = "Merriweather"; pub const DEFAULT_FONT_STYLE: &str = "Normal (400)"; // Document -pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.13"; // Remember to save a simple document and replace the test file `graphite-test-document.graphite` +pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.14"; // Remember to save a simple document and replace the test file `graphite-test-document.graphite` pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document"; pub const FILE_SAVE_SUFFIX: &str = ".graphite"; diff --git a/editor/src/messages/tool/common_functionality/shape_editor.rs b/editor/src/messages/tool/common_functionality/shape_editor.rs index 07a830a2a..254ffc715 100644 --- a/editor/src/messages/tool/common_functionality/shape_editor.rs +++ b/editor/src/messages/tool/common_functionality/shape_editor.rs @@ -31,7 +31,7 @@ pub struct ShapeEditor { // 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. + /// Returns a tuple of the points if found and the offset, or None otherwise. pub fn select_point( &self, document: &Document, @@ -39,7 +39,7 @@ impl ShapeEditor { select_threshold: f64, add_to_selection: bool, responses: &mut VecDeque, - ) -> Option> { + ) -> Option<(Vec<(&[LayerId], u64, ManipulatorType)>, DVec2)> { if self.selected_layers.is_empty() { return None; } @@ -98,12 +98,15 @@ impl ShapeEditor { } .into(), ); - // Snap the selected point to the cursor - if let Ok(viewspace) = document.generate_transform_relative_to_viewport(shape_layer_path) { - self.move_selected_points(mouse_position - viewspace.transform_point2(point_position), responses) - } - return Some(points); + // Offset to snap the selected point to the cursor + let offset = if let Ok(viewspace) = document.generate_transform_relative_to_viewport(shape_layer_path) { + mouse_position - viewspace.transform_point2(point_position) + } else { + DVec2::ZERO + }; + + return Some((points, offset)); } else { responses.push_back( Operation::DeselectManipulatorPoints { diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 14b368d7e..7f0c06b33 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -179,9 +179,10 @@ impl Fsm for PathToolFsmState { let toggle_add_to_selection = input.keyboard.get(add_to_selection as usize); // Select the first point within the threshold (in pixels) - if let Some(mut new_selected) = tool_data - .shape_editor - .select_point(&document.graphene_document, input.mouse.position, SELECTION_THRESHOLD, toggle_add_to_selection, responses) + if let Some((mut new_selected, offset)) = + tool_data + .shape_editor + .select_point(&document.graphene_document, input.mouse.position, SELECTION_THRESHOLD, toggle_add_to_selection, responses) { responses.push_back(DocumentMessage::StartTransaction.into()); @@ -203,7 +204,7 @@ impl Fsm for PathToolFsmState { let include_handles = tool_data.shape_editor.selected_layers_ref(); tool_data.snap_manager.add_all_document_handles(document, &include_handles, &[], &new_selected); - tool_data.drag_start_pos = input.mouse.position; + tool_data.drag_start_pos = input.mouse.position - offset; PathToolFsmState::Dragging } // We didn't find a point nearby, so consider selecting the nearest shape instead diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 7b82000f1..8ffa8bfec 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -279,7 +279,67 @@ impl Fsm for PenToolFsmState { // Enter the dragging handle state while the mouse is held down, allowing the user to move the mouse and position the handle PenToolFsmState::DraggingHandle } - (PenToolFsmState::PlacingAnchor, PenToolMessage::DragStart) => PenToolFsmState::DraggingHandle, + (PenToolFsmState::PlacingAnchor, PenToolMessage::DragStart) => { + // If you place the anchor on top of the previous anchor then you break the mirror + let mut check_break = || { + // Get subpath + let layer_path = tool_data.path.as_ref()?; + let subpath = document.graphene_document.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?; + + // Get the last manipulator group and the one previous to that + let mut manipulator_groups = subpath.manipulator_groups().enumerate(); + let (&last_id, last_manipulator_group) = if tool_data.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? }; + let previous = if tool_data.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() }; + + // Get correct handle types + let outwards_handle = if tool_data.from_start { ManipulatorType::InHandle } else { ManipulatorType::OutHandle }; + + // Get manipulator points + let last_anchor = last_manipulator_group.points[ManipulatorType::Anchor].as_ref()?; + + if let Some((previous_id, previous_anchor)) = previous + .as_ref() + .and_then(|(&id, manipulator_group)| manipulator_group.points[ManipulatorType::Anchor].as_ref().map(|x| (id, x))) + { + // Break the control + if transform.transform_point2(last_anchor.position).distance_squared(transform.transform_point2(previous_anchor.position)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) { + // Remove the point that has just been placed + let op = Operation::RemoveManipulatorGroup { + layer_path: layer_path.clone(), + id: last_id, + }; + responses.push_back(op.into()); + + // Move the in handle of the previous anchor to on top of the previous position + let op = Operation::MoveManipulatorPoint { + layer_path: layer_path.clone(), + id: previous_id, + manipulator_type: outwards_handle, + position: previous_anchor.position.into(), + }; + responses.push_back(op.into()); + + // Stop the handles on the last point from mirroring + let op = Operation::SetManipulatorHandleMirroring { + layer_path: layer_path.clone(), + id: previous_id, + mirror_distance: false, + mirror_angle: false, + }; + responses.push_back(op.into()); + + // The overlay system cannot detect deleted points so we must just delete all the overlays + for layer_path in document.all_layers() { + tool_data.overlay_renderer.clear_subpath_overlays(&document.graphene_document, layer_path.to_vec(), responses); + } + + tool_data.should_mirror = false; + } + } + None + }; + check_break().unwrap_or(PenToolFsmState::DraggingHandle) + } (PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => { let mut process = || { // Get subpath @@ -391,8 +451,9 @@ impl Fsm for PenToolFsmState { }; responses.push_back(msg.into()); + let should_mirror = !input.keyboard.get(break_handle as usize) && tool_data.should_mirror; // Mirror handle of last segment - if !input.keyboard.get(break_handle as usize) && tool_data.should_mirror { + if should_mirror { // Could also be written as `last_anchor.position * 2 - pos` but this way avoids overflow/underflow better let pos = last_anchor.position - (pos - last_anchor.position); @@ -405,6 +466,15 @@ impl Fsm for PenToolFsmState { responses.push_back(msg.into()); } + // Update the mirror status of the currently modifying point + let op = Operation::SetManipulatorHandleMirroring { + layer_path: layer_path.clone(), + id: last_id, + mirror_distance: should_mirror, + mirror_angle: should_mirror, + }; + responses.push_back(op.into()); + Some(()) }; if process().is_none() { @@ -440,7 +510,12 @@ impl Fsm for PenToolFsmState { } 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); + // Snap to the previously placed point (to show break control) + if mouse.distance_squared(transform.transform_point2(relative.position)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) { + pos = relative.position; + } else { + pos = compute_snapped_angle(input, snap_angle, pos, relative.position); + } } for manipulator_type in [ManipulatorType::Anchor, ManipulatorType::InHandle, ManipulatorType::OutHandle] { diff --git a/graphene/src/layers/id_vec.rs b/graphene/src/layers/id_vec.rs index 183027aed..07a6c0f76 100644 --- a/graphene/src/layers/id_vec.rs +++ b/graphene/src/layers/id_vec.rs @@ -21,7 +21,6 @@ pub struct IdBackedVec { /// The IDs of the [Elements] contained within this element_ids: Vec, /// The ID that will be assigned to the next element that is added to this - #[serde(skip)] next_id: ElementId, }