diff --git a/editor/src/messages/input_mapper/input_mappings.rs b/editor/src/messages/input_mapper/input_mappings.rs index f29c0fb2..a684eba0 100644 --- a/editor/src/messages/input_mapper/input_mappings.rs +++ b/editor/src/messages/input_mapper/input_mappings.rs @@ -258,6 +258,8 @@ pub fn input_mappings() -> Mapping { entry!(KeyDown(MouseRight); action_dispatch=PenToolMessage::Confirm), entry!(KeyDown(Escape); action_dispatch=PenToolMessage::Confirm), entry!(KeyDown(Enter); action_dispatch=PenToolMessage::Confirm), + entry!(KeyDown(Delete); action_dispatch=PenToolMessage::RemovePreviousHandle), + entry!(KeyDown(Backspace); action_dispatch=PenToolMessage::RemovePreviousHandle), // // FreehandToolMessage entry!(PointerMove; action_dispatch=FreehandToolMessage::PointerMove), diff --git a/editor/src/messages/tool/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs index ca855b90..239f0acc 100644 --- a/editor/src/messages/tool/tool_message_handler.rs +++ b/editor/src/messages/tool/tool_message_handler.rs @@ -76,8 +76,8 @@ impl MessageHandler> for ToolMessageHandler { self.tool_is_active = true; // Send the old and new tools a transition to their FSM Abort states - let mut send_abort_to_tool = |tool_type, update_hints_and_cursor: bool| { - if let Some(tool) = tool_data.tools.get_mut(&tool_type) { + let mut send_abort_to_tool = |old_tool: ToolType, new_tool: ToolType, update_hints_and_cursor: bool| { + if let Some(tool) = tool_data.tools.get_mut(&new_tool) { let mut data = ToolActionHandlerData { document, document_id, @@ -101,9 +101,14 @@ impl MessageHandler> for ToolMessageHandler { tool.process_message(ToolMessage::UpdateCursor, responses, &mut data); } } + + if matches!(old_tool, ToolType::Path | ToolType::Select) { + responses.add(TransformLayerMessage::CancelTransformOperation); + } }; - send_abort_to_tool(tool_type, true); - send_abort_to_tool(old_tool, false); + + send_abort_to_tool(old_tool, tool_type, true); + send_abort_to_tool(old_tool, old_tool, false); // Unsubscribe old tool from the broadcaster tool_data.tools.get(&tool_type).unwrap().deactivate(responses); diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index ab582d5e..e883a87f 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -648,19 +648,7 @@ impl Fsm for PathToolFsmState { self } (Self::InsertPoint, PathToolMessage::Escape | PathToolMessage::Delete | PathToolMessage::RightClick) => tool_data.end_insertion(shape_editor, responses, InsertEndKind::Abort), - (Self::InsertPoint, PathToolMessage::GRS { key: propagate }) => { - // MAYBE: use `InputMapperMessage::KeyDown(..)` instead - match propagate { - // TODO: Don't use `Key::G` directly, instead take it as a variable from the input mappings list like in all other places - Key::KeyG => responses.add(TransformLayerMessage::BeginGrab), - // TODO: Don't use `Key::R` directly, instead take it as a variable from the input mappings list like in all other places - Key::KeyR => responses.add(TransformLayerMessage::BeginRotate), - // TODO: Don't use `Key::S` directly, instead take it as a variable from the input mappings list like in all other places - Key::KeyS => responses.add(TransformLayerMessage::BeginScale), - _ => warn!("Unexpected GRS key"), - } - tool_data.end_insertion(shape_editor, responses, InsertEndKind::Abort) - } + (Self::InsertPoint, PathToolMessage::GRS { key: _ }) => PathToolFsmState::InsertPoint, // Mouse down ( _, diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 356200de..1d2169ca 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -62,6 +62,7 @@ pub enum PenToolMessage { Undo, UpdateOptions(PenOptionsUpdate), RecalculateLatestPointsPosition, + RemovePreviousHandle, } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] @@ -175,6 +176,7 @@ impl<'a> MessageHandler> for PenTool PointerMove, Confirm, Abort, + RemovePreviousHandle, ), } } @@ -685,6 +687,15 @@ impl Fsm for PenToolFsmState { PenToolFsmState::PlacingAnchor } } + (PenToolFsmState::PlacingAnchor, PenToolMessage::RemovePreviousHandle) => { + if let Some(last_point) = tool_data.latest_points.last_mut() { + last_point.handle_start = last_point.pos; + responses.add(OverlaysMessage::Draw); + } else { + log::warn!("No latest point available to modify handle_start."); + } + self + } (PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => tool_data .finish_placing_handle(SnapData::new(document, input), transform, responses) .unwrap_or(PenToolFsmState::PlacingAnchor), diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index 6b2f3ac5..477796c8 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -43,6 +43,7 @@ type TransformData<'a> = (&'a DocumentMessageHandler, &'a InputPreprocessorMessa impl MessageHandler> for TransformLayerMessageHandler { fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque, (document, input, tool_data, shape_editor): TransformData) { let using_path_tool = tool_data.active_tool_type == ToolType::Path; + let using_select_tool = tool_data.active_tool_type == ToolType::Select; // TODO: Add support for transforming layer not in the document network let selected_layers = document @@ -75,10 +76,18 @@ impl MessageHandler> for TransformLayer let viewspace = document.metadata().transform_to_viewport(selected_layers[0]); let mut point_count: usize = 0; - let get_location = |point: &ManipulatorPointId| point.get_position(&vector_data).map(|position| viewspace.transform_point2(position)); + let get_location = |point: &&ManipulatorPointId| point.get_position(&vector_data).map(|position| viewspace.transform_point2(position)); let points = shape_editor.selected_points(); + let selected_points: Vec<&ManipulatorPointId> = points.collect(); - *selected.pivot = points.filter_map(get_location).inspect(|_| point_count += 1).sum::() / point_count as f64; + if let [point] = selected_points.as_slice() { + if let ManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) = point { + let anchor_position = point.get_anchor_position(&vector_data).unwrap(); + *selected.pivot = viewspace.transform_point2(anchor_position); + } else { + *selected.pivot = selected_points.iter().filter_map(get_location).inspect(|_| point_count += 1).sum::() / point_count as f64; + } + } } } else { *selected.pivot = selected.mean_average_of_pivots(); @@ -104,12 +113,13 @@ impl MessageHandler> for TransformLayer responses.add(NodeGraphMessage::RunDocumentGraph); } TransformLayerMessage::BeginGrab => { - if let TransformOperation::Grabbing(_) = self.transform_operation { - return; - } + if (!using_path_tool && !using_select_tool) + || (using_path_tool && shape_editor.selected_points().next().is_none()) + || selected_layers.is_empty() + || matches!(self.transform_operation, TransformOperation::Grabbing(_)) + { + selected.original_transforms.clear(); - // Don't allow grab with no selected layers - if selected_layers.is_empty() { return; } @@ -120,13 +130,42 @@ impl MessageHandler> for TransformLayer selected.original_transforms.clear(); } TransformLayerMessage::BeginRotate => { - if let TransformOperation::Rotating(_) = self.transform_operation { + let selected_points: Vec<&ManipulatorPointId> = shape_editor.selected_points().collect(); + + if (!using_path_tool && !using_select_tool) + || (using_path_tool && selected_points.is_empty()) + || selected_layers.is_empty() + || matches!(self.transform_operation, TransformOperation::Rotating(_)) + { + selected.original_transforms.clear(); return; } - // Don't allow rotate with no selected layers - if selected_layers.is_empty() { + let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) else { + selected.original_transforms.clear(); return; + }; + + if let [point] = selected_points.as_slice() { + if matches!(point, ManipulatorPointId::Anchor(_)) { + if let Some([handle1, handle2]) = point.get_handle_pair(&vector_data) { + let handle1_length = handle1.length(&vector_data); + let handle2_length = handle2.length(&vector_data); + + if (handle1_length == 0. && handle2_length == 0.) || (handle1_length == f64::MAX && handle2_length == f64::MAX) { + return; + } + } + } else { + // TODO: Fix handle snap to anchor issue, see + + let handle_length = point.as_handle().map(|handle| handle.length(&vector_data)); + + if handle_length == Some(0.) { + selected.original_transforms.clear(); + return; + } + } } begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse); @@ -136,13 +175,41 @@ impl MessageHandler> for TransformLayer selected.original_transforms.clear(); } TransformLayerMessage::BeginScale => { - if let TransformOperation::Scaling(_) = self.transform_operation { + let selected_points: Vec<&ManipulatorPointId> = shape_editor.selected_points().collect(); + + if (using_path_tool && selected_points.is_empty()) + || (!using_path_tool && !using_select_tool) + || selected_layers.is_empty() + || matches!(self.transform_operation, TransformOperation::Scaling(_)) + { + selected.original_transforms.clear(); return; } - // Don't allow scale with no selected layers - if selected_layers.is_empty() { + let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) else { + selected.original_transforms.clear(); return; + }; + + if let [point] = selected_points.as_slice() { + if matches!(point, ManipulatorPointId::Anchor(_)) { + if let Some([handle1, handle2]) = point.get_handle_pair(&vector_data) { + let handle1_length = handle1.length(&vector_data); + let handle2_length = handle2.length(&vector_data); + + if (handle1_length == 0. && handle2_length == 0.) || (handle1_length == f64::MAX && handle2_length == f64::MAX) { + selected.original_transforms.clear(); + return; + } + } + } else { + let handle_length = point.as_handle().map(|handle| handle.length(&vector_data)); + + if handle_length == Some(0.) { + selected.original_transforms.clear(); + return; + } + } } begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse); @@ -215,6 +282,7 @@ impl MessageHandler> for TransformLayer } }; } + self.mouse_position = input.mouse.position; } TransformLayerMessage::SelectionChanged => { diff --git a/node-graph/gcore/src/vector/vector_data.rs b/node-graph/gcore/src/vector/vector_data.rs index 21e5630b..93cadbbb 100644 --- a/node-graph/gcore/src/vector/vector_data.rs +++ b/node-graph/gcore/src/vector/vector_data.rs @@ -306,6 +306,13 @@ impl ManipulatorPointId { } } + pub fn get_anchor_position(&self, vector_data: &VectorData) -> Option { + match self { + ManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_) => self.get_anchor(vector_data).and_then(|id| vector_data.point_domain.position_from_id(id)), + _ => self.get_position(vector_data), + } + } + /// Attempt to get a pair of handles. For an anchor this is the first two handles connected. For a handle it is self and the first opposing handle. #[must_use] pub fn get_handle_pair(self, vector_data: &VectorData) -> Option<[HandleId; 2]> { @@ -396,6 +403,13 @@ impl HandleId { } } + /// Calculate the magnitude of the handle from the anchor. + pub fn length(self, vector_data: &VectorData) -> f64 { + let anchor_position = self.to_manipulator_point().get_anchor_position(vector_data).unwrap(); + let handle_position = self.to_manipulator_point().get_position(vector_data); + handle_position.map(|pos| (pos - anchor_position).length()).unwrap_or(f64::MAX) + } + /// Set the handle's position relative to the anchor which is the start anchor for the primary handle and end anchor for the end handle. #[must_use] pub fn set_relative_position(self, relative_position: DVec2) -> VectorModificationType {