diff --git a/editor/src/consts.rs b/editor/src/consts.rs index 44a5b1d210..0e8e714745 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -108,6 +108,8 @@ pub const SEGMENT_INSERTION_DISTANCE: f64 = 5.; pub const SEGMENT_OVERLAY_SIZE: f64 = 10.; pub const SEGMENT_SELECTED_THICKNESS: f64 = 3.; pub const HANDLE_LENGTH_FACTOR: f64 = 0.5; +pub const PEEL_SEGMENT_OFFSET_FACTOR: f64 = 20.; +pub const SEPARATE_SEGMENT_OFFSET_FACTOR: f64 = 10.; // PEN TOOL pub const CREATE_CURVE_THRESHOLD: f64 = 5.; diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index cdf453f997..3444d433d5 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -2,7 +2,8 @@ use super::select_tool::extend_lasso; use super::tool_prelude::*; use crate::consts::{ COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GRAY, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DEFAULT_STROKE_WIDTH, DOUBLE_CLICK_MILLISECONDS, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD, - DRILL_THROUGH_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE, SEGMENT_INSERTION_DISTANCE, SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE, + DRILL_THROUGH_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE, PEEL_SEGMENT_OFFSET_FACTOR, SEGMENT_INSERTION_DISTANCE, SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE, + SEPARATE_SEGMENT_OFFSET_FACTOR, }; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; @@ -27,7 +28,7 @@ use graphene_std::renderer::Quad; use graphene_std::transform::ReferencePoint; use graphene_std::uuid::NodeId; use graphene_std::vector::click_target::ClickTargetType; -use graphene_std::vector::misc::{HandleId, ManipulatorPointId}; +use graphene_std::vector::misc::{HandleId, HandleType, ManipulatorPointId}; use graphene_std::vector::{HandleExt, NoHashBuilder, PointId, SegmentId, Vector, VectorModificationType}; use std::vec; @@ -338,8 +339,8 @@ impl LayoutHolder for PathTool { path_overlay_mode_widget, unrelated_seperator.clone(), path_node_button, - separate_segment_button, unrelated_seperator.clone(), + separate_segment_button, peel_segment_button, unrelated_seperator.clone(), // checkbox.clone(), @@ -655,6 +656,11 @@ impl PathToolData { fn update_separate_segment_toggle(&mut self, shape_editor: &mut ShapeState, document: &DocumentMessageHandler) { //TODO: Implement this + if single_segment_selected(shape_editor, document).is_some() { + self.segment_separation_enabled = true; + } else { + self.segment_separation_enabled = false; + } } fn remove_saved_points(&mut self) { @@ -3034,6 +3040,7 @@ impl Fsm for PathToolFsmState { tool_data.make_path_editable_is_allowed = make_path_editable_is_allowed(&document.network_interface, document.metadata()).is_some(); tool_data.update_selection_status(shape_editor, document); + tool_data.update_separate_segment_toggle(shape_editor, document); self } (_, PathToolMessage::ManipulatorMakeHandlesColinear) => { @@ -3062,16 +3069,187 @@ impl Fsm for PathToolFsmState { self } (_, PathToolMessage::SeparateSegment) => { - // TODO: Implement this + let Some((layer, vector, old_segment)) = single_segment_selected(shape_editor, document) else { + return self; + }; - // It only makes sense to use this if we have more than two segments connected - // Basically we will have a segment selected and we need to first remove the segment, add two segments with same bezier on both of them - // Collect the segments that will be connected to start from + let (_, bezier, start, end) = vector.segment_bezier_iter().find(|(id, _, _, _)| *id == old_segment).expect(" Couldn't get segment data"); + + // Need to collect all of the connections of start and end points + let start_conections = vector.all_connected(start).collect::>(); + let end_connections = vector.all_connected(end).collect::>(); + + // TODO: Prevent all edge cases of a hanging segmentS + if start_conections.len() < 2 || end_connections.len() < 2 { + return self; + } + + responses.add(DocumentMessage::StartTransaction); + + let start_pos = ManipulatorPointId::Anchor(start).get_position(&vector).expect("No position for point"); + let end_pos = ManipulatorPointId::Anchor(end).get_position(&vector).expect("No position for point"); + + let direction = start_pos - end_pos; + let perp = direction.perp(); + + // first remove the points start and end + let modification_type = VectorModificationType::RemovePoint { id: start }; + responses.add(GraphOperationMessage::Vector { layer, modification_type }); + + let modification_type = VectorModificationType::RemovePoint { id: end }; + responses.add(GraphOperationMessage::Vector { layer, modification_type }); + + let offset = perp.try_normalize().unwrap_or_default() * SEPARATE_SEGMENT_OFFSET_FACTOR; + + // (start point, end point) for both new segments + let new_points = [(PointId::generate(), PointId::generate()), (PointId::generate(), PointId::generate())]; + + let handles = |bezier: Bezier| -> [Option; 2] { + match bezier.handles { + BezierHandles::Linear => [None, None], + BezierHandles::Quadratic { handle } => [Some(handle - bezier.start), None], + BezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - bezier.start), Some(handle_end - bezier.end)], + } + }; + + for ((new_start, new_end), factor) in new_points.iter().zip([-1., 1.]) { + let offset = offset * factor; + let new_start_pos = start_pos + offset; + let new_end_pos = end_pos + offset; + + let modification_type = VectorModificationType::InsertPoint { + id: *new_start, + position: new_start_pos, + }; + responses.add(GraphOperationMessage::Vector { layer, modification_type }); + + let modification_type = VectorModificationType::InsertPoint { id: *new_end, position: new_end_pos }; + responses.add(GraphOperationMessage::Vector { layer, modification_type }); + + // Insert the bezier between new start and new end + let id = SegmentId::generate(); + let points = [*new_start, *new_end]; + let handles = handles(bezier); + let modification_type = VectorModificationType::InsertSegment { id, points, handles }; + responses.add(GraphOperationMessage::Vector { layer, modification_type }); + } + + // Now add connections + for connection in start_conections { + let segment = connection.segment; + let other_point_id = connection.opposite().to_manipulator_point().get_anchor(&vector).unwrap(); + let connection_bez = vector.segment_from_id(segment).unwrap(); + let other_pos = match (connection_bez.handles, connection.ty) { + (BezierHandles::Cubic { handle_start, handle_end: _ }, HandleType::Primary) => { + if handle_start.distance(connection_bez.start) < 1e-8 { + connection_bez.end + } else { + handle_start + } + } + (BezierHandles::Cubic { handle_start: _, handle_end }, HandleType::End) => { + if handle_end.distance(connection_bez.end) < 1e-8 { + connection_bez.start + } else { + handle_end + } + } + (BezierHandles::Quadratic { handle }, _) => handle, + (BezierHandles::Linear, _) => connection.opposite().to_manipulator_point().get_anchor_position(&vector).unwrap(), + }; + let connection_direction = start_pos - other_pos; + let new_segment = SegmentId::generate(); + let points = if connection_direction.dot(offset) > 0. { + if connection.ty == HandleType::Primary { + [new_points[0].0, other_point_id] + } else { + [other_point_id, new_points[0].0] + } + } else { + if connection.ty == HandleType::Primary { + [new_points[1].0, other_point_id] + } else { + [other_point_id, new_points[1].0] + } + }; + let handles = handles(connection_bez); + let modification_type = VectorModificationType::InsertSegment { id: new_segment, points, handles }; + responses.add(GraphOperationMessage::Vector { layer, modification_type }); + } + + for connection in end_connections { + let other_point_id = connection.opposite().to_manipulator_point().get_anchor(&vector).unwrap(); + let connection_bez = vector.segment_from_id(connection.segment).unwrap(); + let other_pos = match (connection_bez.handles, connection.ty) { + (BezierHandles::Cubic { handle_start, handle_end: _ }, HandleType::Primary) => { + // handle_start + if handle_start.distance(connection_bez.start) < 1e-8 { connection_bez.end } else { handle_start } + } + (BezierHandles::Cubic { handle_start: _, handle_end }, HandleType::End) => { + if handle_end.distance(connection_bez.end) < 1e-8 { + connection_bez.start + } else { + handle_end + } + } + (BezierHandles::Quadratic { handle }, _) => handle, + (BezierHandles::Linear, _) => connection.opposite().to_manipulator_point().get_anchor_position(&vector).unwrap(), + }; + let connection_direction = end_pos - other_pos; + let new_segment = SegmentId::generate(); + let points = if connection_direction.dot(offset) > 0. { + if connection.ty == HandleType::Primary { + [new_points[0].1, other_point_id] + } else { + [other_point_id, new_points[0].1] + } + } else { + if connection.ty == HandleType::Primary { + [new_points[1].1, other_point_id] + } else { + [other_point_id, new_points[1].1] + } + }; + let handles = handles(connection_bez); + let modification_type = VectorModificationType::InsertSegment { id: new_segment, points, handles }; + responses.add(GraphOperationMessage::Vector { layer, modification_type }); + } + + responses.add(DocumentMessage::EndTransaction); self } (_, PathToolMessage::PeelSegment) => { - //TODO: Implement this + let Some((layer, vector, old_segment)) = single_segment_selected(shape_editor, document) else { + return self; + }; + responses.add(DocumentMessage::StartTransaction); + + let (_, bezier, start, end) = vector.segment_bezier_iter().find(|(id, _, _, _)| *id == old_segment).expect(" Couldn't get segment data"); + + // Calculate the perpendicular direction to the start and end point and nudge the handles in that direction to give peel effect + let start_pos = ManipulatorPointId::Anchor(start).get_position(&vector).expect("No position for point"); + let end_pos = ManipulatorPointId::Anchor(end).get_position(&vector).expect("No position for point"); + + let direction = start_pos - end_pos; + let perp = direction.perp(); + let offset = perp.try_normalize().unwrap_or_default() * PEEL_SEGMENT_OFFSET_FACTOR; + + // Generate a new id and insert new bezier + let id = SegmentId::generate(); + let points = [start, end]; + let handles = |bezier: Bezier| -> [Option; 2] { + match bezier.handles { + BezierHandles::Linear => [None, None], + BezierHandles::Quadratic { handle } => [Some(handle - bezier.start + offset), None], + BezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - bezier.start + offset), Some(handle_end - bezier.end + offset)], + } + }; + let handles = handles(bezier); + let modification_type = VectorModificationType::InsertSegment { id, points, handles }; + responses.add(GraphOperationMessage::Vector { layer, modification_type }); + + responses.add(DocumentMessage::EndTransaction); self } @@ -3516,3 +3694,23 @@ fn update_dynamic_hints( }; responses.add(FrontendMessage::UpdateInputHints { hint_data }); } + +fn single_segment_selected(shape_editor: &mut ShapeState, document: &DocumentMessageHandler) -> Option<(LayerNodeIdentifier, Vector, SegmentId)> { + let mut non_empty_layers = shape_editor.selected_shape_state.iter().filter(|(_, state)| !state.is_empty()); + + let (layer, _) = non_empty_layers.next()?; + if non_empty_layers.next().is_some() { + return None; + } + + let segments = shape_editor.selected_segments().collect::>(); + if segments.len() != 1 { + return None; + } + + let old_segment = segments[0]; + + let vector = document.network_interface.compute_modified_vector(*layer)?; + + Some((*layer, vector, *old_segment)) +}