diff --git a/editor/src/consts.rs b/editor/src/consts.rs index f835d4b963..9b58d589bf 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -136,3 +136,6 @@ pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document"; pub const FILE_SAVE_SUFFIX: &str = ".graphite"; pub const MAX_UNDO_HISTORY_LEN: usize = 100; // TODO: Add this to user preferences pub const AUTO_SAVE_TIMEOUT_SECONDS: u64 = 15; + +// INPUT +pub const DOUBLE_CLICK_MILLISECONDS: u64 = 500; diff --git a/editor/src/messages/tool/common_functionality/shape_editor.rs b/editor/src/messages/tool/common_functionality/shape_editor.rs index 39053e93ac..7800a64def 100644 --- a/editor/src/messages/tool/common_functionality/shape_editor.rs +++ b/editor/src/messages/tool/common_functionality/shape_editor.rs @@ -1538,6 +1538,7 @@ impl ShapeState { } } } + /// Converts a nearby clicked anchor point's handles between sharp (zero-length handles) and smooth (pulled-apart handle(s)). /// If both handles aren't zero-length, they are set that. If both are zero-length, they are stretched apart by a reasonable amount. /// This can can be activated by double clicking on an anchor with the Path tool. @@ -1568,44 +1569,47 @@ impl ShapeState { .count(); // Check by comparing the handle positions to the anchor if this manipulator group is a point - if positions != 0 { - self.convert_manipulator_handles_to_colinear(&vector_data, id, responses, layer); - } else { - for handle in vector_data.all_connected(id) { - let Some(bezier) = vector_data.segment_from_id(handle.segment) else { continue }; - - match bezier.handles { - BezierHandles::Linear => {} - BezierHandles::Quadratic { .. } => { - let segment = handle.segment; - // Convert to linear - let modification_type = VectorModificationType::SetHandles { segment, handles: [None; 2] }; - responses.add(GraphOperationMessage::Vector { layer, modification_type }); + for point in self.selected_points() { + let Some(point_id) = point.as_anchor() else { continue }; + if positions != 0 { + self.convert_manipulator_handles_to_colinear(&vector_data, point_id, responses, layer); + } else { + for handle in vector_data.all_connected(point_id) { + let Some(bezier) = vector_data.segment_from_id(handle.segment) else { continue }; + + match bezier.handles { + BezierHandles::Linear => {} + BezierHandles::Quadratic { .. } => { + let segment = handle.segment; + // Convert to linear + let modification_type = VectorModificationType::SetHandles { segment, handles: [None; 2] }; + responses.add(GraphOperationMessage::Vector { layer, modification_type }); - // Set the manipulator to have non-colinear handles - for &handles in &vector_data.colinear_manipulators { - if handles.contains(&HandleId::primary(segment)) { - let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false }; - responses.add(GraphOperationMessage::Vector { layer, modification_type }); + // Set the manipulator to have non-colinear handles + for &handles in &vector_data.colinear_manipulators { + if handles.contains(&HandleId::primary(segment)) { + let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false }; + responses.add(GraphOperationMessage::Vector { layer, modification_type }); + } } } - } - BezierHandles::Cubic { .. } => { - // Set handle position to anchor position - let modification_type = handle.set_relative_position(DVec2::ZERO); - responses.add(GraphOperationMessage::Vector { layer, modification_type }); + BezierHandles::Cubic { .. } => { + // Set handle position to anchor position + let modification_type = handle.set_relative_position(DVec2::ZERO); + responses.add(GraphOperationMessage::Vector { layer, modification_type }); - // Set the manipulator to have non-colinear handles - for &handles in &vector_data.colinear_manipulators { - if handles.contains(&handle) { - let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false }; - responses.add(GraphOperationMessage::Vector { layer, modification_type }); + // Set the manipulator to have non-colinear handles + for &handles in &vector_data.colinear_manipulators { + if handles.contains(&handle) { + let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false }; + responses.add(GraphOperationMessage::Vector { layer, modification_type }); + } } } } } - } - }; + }; + } Some(true) }; diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 5d9f622860..81d5345aaf 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -1,8 +1,8 @@ use super::select_tool::extend_lasso; use super::tool_prelude::*; use crate::consts::{ - COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE, SEGMENT_INSERTION_DISTANCE, - SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE, + COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DOUBLE_CLICK_MILLISECONDS, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE, + SEGMENT_INSERTION_DISTANCE, SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE, }; use crate::messages::portfolio::document::overlays::utility_functions::{path_overlays, selected_segments}; use crate::messages::portfolio::document::overlays::utility_types::{DrawHandles, OverlayContext}; @@ -365,6 +365,7 @@ struct PathToolData { select_anchor_toggled: bool, saved_points_before_handle_drag: Vec, handle_drag_toggle: bool, + anchors_before_flip: HashSet, dragging_state: DraggingState, angle: f64, opposite_handle_position: Option, @@ -374,6 +375,8 @@ struct PathToolData { alt_dragging_from_anchor: bool, angle_locked: bool, temporary_colinear_handles: bool, + time: u64, + anchors_flipped: bool, } impl PathToolData { @@ -447,6 +450,19 @@ impl PathToolData { self.opposing_handle_lengths = None; self.drag_start_pos = input.mouse.position; + let current_time = input.time; + + if !self.anchors_before_flip.is_empty() && (current_time - self.time > DOUBLE_CLICK_MILLISECONDS) { + self.anchors_flipped = false; + self.anchors_before_flip.clear(); + } + + // When cont + if self.anchors_before_flip.is_empty() && self.anchors_flipped && (current_time - self.time < DOUBLE_CLICK_MILLISECONDS) { + responses.add(PathToolMessage::FlipSmoothSharp); + } + + self.time = current_time; let old_selection = shape_editor.selected_points().cloned().collect::>(); @@ -1296,6 +1312,12 @@ impl Fsm for PathToolFsmState { (PathToolFsmState::Ready, PathToolMessage::PointerMove { delete_segment, .. }) => { tool_data.delete_segment_pressed = input.keyboard.get(delete_segment as usize); + if !tool_data.anchors_before_flip.is_empty() { + tool_data.anchors_before_flip.clear(); + } + + tool_data.anchors_flipped = false; + // If there is a point nearby, then remove the overlay if shape_editor .find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD) @@ -1404,7 +1426,6 @@ impl Fsm for PathToolFsmState { if tool_data.handle_drag_toggle && tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD { shape_editor.deselect_all_points(); shape_editor.select_points_by_manipulator_id(&tool_data.saved_points_before_handle_drag); - tool_data.saved_points_before_handle_drag.clear(); tool_data.handle_drag_toggle = false; } @@ -1490,6 +1511,9 @@ impl Fsm for PathToolFsmState { if !drag_occurred && !extend_selection { let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == point); if clicked_selected { + if tool_data.anchors_before_flip.is_empty() { + tool_data.anchors_before_flip = shape_editor.selected_points().copied().collect::>(); + } shape_editor.deselect_all_points(); shape_editor.selected_shape_state.entry(layer).or_default().select_point(nearest_point); responses.add(OverlaysMessage::Draw); @@ -1537,7 +1561,10 @@ impl Fsm for PathToolFsmState { // Flip the selected point between smooth and sharp if !tool_data.double_click_handled && tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD { responses.add(DocumentMessage::StartTransaction); + shape_editor.select_points_by_manipulator_id(&tool_data.anchors_before_flip.iter().copied().collect::>()); shape_editor.flip_smooth_sharp(&document.network_interface, input.mouse.position, SELECTION_TOLERANCE, responses); + tool_data.anchors_before_flip.clear(); + tool_data.anchors_flipped = true; responses.add(DocumentMessage::EndTransaction); responses.add(PathToolMessage::SelectedPointUpdated); }