diff --git a/editor/src/messages/tool/common_functionality/shape_editor.rs b/editor/src/messages/tool/common_functionality/shape_editor.rs index f434cdc079..57a4911ffc 100644 --- a/editor/src/messages/tool/common_functionality/shape_editor.rs +++ b/editor/src/messages/tool/common_functionality/shape_editor.rs @@ -489,11 +489,16 @@ impl ShapeState { !self.selected_shape_state.is_empty() } - /// Provide the currently selected points by reference. + /// Provide the currently selected points by reference.(TODO: Use unique ids) pub fn selected_points(&self) -> impl Iterator { self.selected_shape_state.values().flat_map(|state| &state.selected_points) } + // Alternative for above + pub fn selected_points_by_layer(&self) -> HashMap + '_> { + self.selected_shape_state.iter().map(|(&k, v)| (k, v.selected_points.iter())).collect() + } + pub fn selected_points_in_layer(&self, layer: LayerNodeIdentifier) -> Option<&HashSet> { self.selected_shape_state.get(&layer).map(|state| &state.selected_points) } @@ -1318,51 +1323,69 @@ 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. - pub fn flip_smooth_sharp(&self, network_interface: &NodeNetworkInterface, target: glam::DVec2, tolerance: f64, responses: &mut VecDeque) -> bool { - let mut process_layer = |layer| { - let vector_data = network_interface.compute_modified_vector(layer)?; - let transform_to_screenspace = network_interface.document_metadata().transform_to_viewport(layer); - let mut result = None; - let mut closest_distance_squared = tolerance * tolerance; - - // Find the closest anchor point on the current layer - for (&id, &anchor) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) { - let screenspace = transform_to_screenspace.transform_point2(anchor); - let distance_squared = screenspace.distance_squared(target); - - if distance_squared < closest_distance_squared { - closest_distance_squared = distance_squared; - result = Some((id, anchor)); + pub fn select_points_by_manipulator_id_by_layer(&mut self, points: &HashMap>) { + for (layer, point_iter) in points { + if let Some(state) = self.selected_shape_state.get_mut(layer) { + for point in point_iter { + state.select_point(*point); } } + } + } + + /// 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. + pub fn flip_smooth_sharp(&mut self, network_interface: &NodeNetworkInterface, target: glam::DVec2, tolerance: f64, responses: &mut VecDeque) { + if let Some((layer, manipulator_point_id)) = self.find_nearest_point_indices(network_interface, target, tolerance) { + let Some(vector_data) = network_interface.compute_modified_vector(layer) else { + return; + }; + let Some(id) = manipulator_point_id.as_anchor() else { + return; + }; + let Some(anchor) = manipulator_point_id.get_position(&vector_data) else { + return; + }; - let (id, anchor) = result?; let handles = vector_data.all_connected(id); let mut positions = handles .filter_map(|handle| handle.to_manipulator_point().get_position(&vector_data)) .filter(|&handle| !anchor.abs_diff_eq(handle, 1e-5)); - - // Check by comparing the handle positions to the anchor if this manipulator group is a point let already_sharp = positions.next().is_none(); + for &layer in self.selected_shape_state.keys() { + self.flip_smooth_sharp_each(network_interface, layer, already_sharp, responses); + } + } + } + + fn flip_smooth_sharp_each(&self, network_interface: &NodeNetworkInterface, layer: LayerNodeIdentifier, already_sharp: bool, responses: &mut VecDeque) { + let Some(vector_data) = network_interface.compute_modified_vector(layer) else { + return; + }; + let Some(selected_state) = self.selected_shape_state.get(&layer) else { + return; + }; + + let anchors = selected_state.selected_points.iter().filter_map(|point| point.as_anchor()); + + for id in anchors { if already_sharp { 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 }; + 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 }; @@ -1371,11 +1394,9 @@ impl ShapeState { } } 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 }; @@ -1385,18 +1406,8 @@ impl ShapeState { } } } - }; - - Some(true) - }; - - for &layer in self.selected_shape_state.keys() { - if let Some(result) = process_layer(layer) { - return result; } } - - false } pub fn select_all_in_shape(&mut self, network_interface: &NodeNetworkInterface, selection_shape: SelectionShape, selection_change: SelectionChange) { diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 4aac4f15c3..e813afe2a0 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -11,7 +11,7 @@ use crate::messages::portfolio::document::utility_types::transformation::Axis; use crate::messages::preferences::SelectionMode; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::shape_editor::{ - ClosestSegment, ManipulatorAngle, OpposingHandleLengths, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState, + ClosestSegment, ManipulatorAngle, OpposingHandleLengths, SelectedLayerState, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState, }; use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager}; use graphene_core::renderer::Quad; @@ -370,6 +370,8 @@ struct PathToolData { select_anchor_toggled: bool, saved_points_before_handle_drag: Vec, handle_drag_toggle: bool, + saved_points_before_flip: HashMap>, + flip_toggle: bool, dragging_state: DraggingState, current_selected_handle_id: Option, angle: f64, @@ -496,6 +498,7 @@ impl PathToolData { self.drag_start_pos = input.mouse.position; let old_selection = shape_editor.selected_points().cloned().collect::>(); + log::info!("old {:?}", old_selection); // Select the first point within the threshold (in pixels) if let Some(selected_points) = shape_editor.change_point_selection(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD, extend_selection) { @@ -514,6 +517,7 @@ impl PathToolData { } if dragging_only_handles && !self.handle_drag_toggle && !old_selection.is_empty() { self.saved_points_before_handle_drag = old_selection; + log::info!("{:?}", self.saved_points_before_handle_drag); } self.start_dragging_point(selected_points, input, document, shape_editor); @@ -1245,7 +1249,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; } @@ -1290,6 +1293,7 @@ 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); + log::info!("after drag {:?}", shape_editor.selected_points().collect::>()); tool_data.saved_points_before_handle_drag.clear(); tool_data.handle_drag_toggle = false; @@ -1310,6 +1314,10 @@ impl Fsm for PathToolFsmState { if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !extend_selection { let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == point); if clicked_selected { + if tool_data.flip_toggle { + tool_data.saved_points_before_flip = shape_editor.selected_shape_state.iter().map(|(k, v)| (*k, v.selected().collect::>())).collect(); + tool_data.flip_toggle = false; + } shape_editor.deselect_all_points(); shape_editor.selected_shape_state.entry(layer).or_default().select_point(nearest_point); responses.add(OverlaysMessage::Draw); @@ -1356,8 +1364,11 @@ impl Fsm for PathToolFsmState { if nearest_point.is_some() { // 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 { + tool_data.flip_toggle = true; responses.add(DocumentMessage::StartTransaction); + shape_editor.select_points_by_manipulator_id_by_layer(&tool_data.saved_points_before_flip); shape_editor.flip_smooth_sharp(&document.network_interface, input.mouse.position, SELECTION_TOLERANCE, responses); + tool_data.saved_points_before_flip.clear(); responses.add(DocumentMessage::EndTransaction); responses.add(PathToolMessage::SelectedPointUpdated); }