From 974a37f1276b2959e0e9259dcd071b11de693cbc Mon Sep 17 00:00:00 2001 From: OllieDolan Date: Sun, 16 Apr 2023 15:56:52 -0700 Subject: [PATCH] Path tool points manipulation: nudging, drag axis snapping, and G/R/S (#1068) * issue820: implemented nudging points * nudge points triggered only when the mouse is moved after selecting one point * issue 820 // bullet 1 done * grab working / rotate not * rotate works (sensitive) * G/R/S and Shift/Drag working * Cargo formatted // implemented Hypercubes comments * Refactored G/R/S - need to fix fast transform * Finished refactored G/R/S * Typed Angle needs further touch up * Cargo formatted * Dealt with dangerous unwraps * Cargo fmt (again) - unwraps fixed * Cleaned up * cargo fmt * Ready for Review * Ready for Review- cargo fmt * Code review fixes * Remove duplicate constant for nudging * Fix consts.rs spacing * Apply suggestions from code review Added suggestions Co-authored-by: Keavon Chambers * Added typo/grammar suggestions * Nits --------- Co-authored-by: Shiro Co-authored-by: hypercube <0hypercube@gmail.com> Co-authored-by: 0HyperCube <78500760+0HyperCube@users.noreply.github.com> Co-authored-by: Keavon Chambers --- document-legacy/src/operation.rs | 3 +- .../messages/input_mapper/default_mapping.rs | 24 ++ .../document/utility_types/transformation.rs | 205 +++++++++++++++--- .../messages/tool/tool_messages/path_tool.rs | 20 +- .../tool/tool_messages/select_tool.rs | 36 ++- .../transform_layer_message_handler.rs | 56 ++++- 6 files changed, 294 insertions(+), 50 deletions(-) diff --git a/document-legacy/src/operation.rs b/document-legacy/src/operation.rs index e39a4856..8e847810 100644 --- a/document-legacy/src/operation.rs +++ b/document-legacy/src/operation.rs @@ -4,6 +4,7 @@ use crate::layers::layer_info::Layer; use crate::layers::style::{self, Stroke}; use crate::LayerId; +use graphene_core::vector::SelectedType; use graphene_std::vector::consts::ManipulatorType; use graphene_std::vector::manipulator_group::ManipulatorGroup; use graphene_std::vector::subpath::Subpath; @@ -141,7 +142,7 @@ pub enum Operation { MoveManipulatorPoint { layer_path: Vec, id: u64, - manipulator_type: ManipulatorType, + manipulator_type: SelectedType, position: (f64, f64), }, SetManipulatorPoints { diff --git a/editor/src/messages/input_mapper/default_mapping.rs b/editor/src/messages/input_mapper/default_mapping.rs index 860a6f31..afb1ce99 100644 --- a/editor/src/messages/input_mapper/default_mapping.rs +++ b/editor/src/messages/input_mapper/default_mapping.rs @@ -180,6 +180,30 @@ pub fn default_mapping() -> Mapping { entry!(KeyDown(Backspace); action_dispatch=PathToolMessage::Delete), entry!(KeyUp(Lmb); action_dispatch=PathToolMessage::DragStop { shift_mirror_distance: Shift }), entry!(DoubleClick; action_dispatch=PathToolMessage::InsertPoint), + entry!(KeyDown(ArrowRight); action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: 0. }), + entry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0. }), + entry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), + entry!(KeyDown(ArrowRight); modifiers=[ArrowDown], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), + entry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowUp], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), + entry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowDown], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), + entry!(KeyDown(ArrowUp); action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: 0., delta_y: -NUDGE_AMOUNT }), + entry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT }), + entry!(KeyDown(ArrowUp); modifiers=[ArrowLeft], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), + entry!(KeyDown(ArrowUp); modifiers=[ArrowRight], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), + entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), + entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), + entry!(KeyDown(ArrowLeft); action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: -NUDGE_AMOUNT, delta_y: 0. }), + entry!(KeyDown(ArrowLeft); modifiers=[Shift], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: -BIG_NUDGE_AMOUNT, delta_y: 0. }), + entry!(KeyDown(ArrowLeft); modifiers=[ArrowUp], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), + entry!(KeyDown(ArrowLeft); modifiers=[ArrowDown], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), + entry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowUp], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), + entry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowDown], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), + entry!(KeyDown(ArrowDown); action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: 0., delta_y: NUDGE_AMOUNT }), + entry!(KeyDown(ArrowDown); modifiers=[Shift], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: 0., delta_y: BIG_NUDGE_AMOUNT }), + entry!(KeyDown(ArrowDown); modifiers=[ArrowLeft], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), + entry!(KeyDown(ArrowDown); modifiers=[ArrowRight], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), + entry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowLeft], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), + entry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowRight], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), // // PenToolMessage entry!(PointerMove; refresh_keys=[Shift, Control], action_dispatch=PenToolMessage::PointerMove { snap_angle: Shift, break_handle: Alt, lock_angle: Control}), diff --git a/editor/src/messages/portfolio/document/utility_types/transformation.rs b/editor/src/messages/portfolio/document/utility_types/transformation.rs index 34c185fb..bf40d4a1 100644 --- a/editor/src/messages/portfolio/document/utility_types/transformation.rs +++ b/editor/src/messages/portfolio/document/utility_types/transformation.rs @@ -1,14 +1,34 @@ use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL}; +use crate::messages::portfolio::document::node_graph::VectorDataModification; use crate::messages::prelude::*; - +use crate::messages::tool::common_functionality::shape_editor::ShapeState; +use crate::messages::tool::utility_types::ToolType; use document_legacy::document::Document; use document_legacy::layers::style::RenderData; use document_legacy::LayerId; +use graphene_core::vector::{ManipulatorPointId, SelectedType}; use glam::{DAffine2, DVec2}; use std::collections::{HashMap, VecDeque}; -pub type OriginalTransforms = HashMap, DAffine2>; +#[derive(Debug, PartialEq, Clone)] +pub enum OriginalTransforms { + Layer(HashMap, DAffine2>), + Path(HashMap, Vec<(ManipulatorPointId, DVec2)>>), +} +impl Default for OriginalTransforms { + fn default() -> Self { + OriginalTransforms::Path(HashMap::new()) + } +} +impl OriginalTransforms { + pub fn clear(&mut self) { + match self { + OriginalTransforms::Layer(layer_map) => layer_map.clear(), + OriginalTransforms::Path(path_map) => path_map.clear(), + } + } +} #[derive(Default, Debug, Clone, PartialEq, Eq, Copy)] pub enum Axis { @@ -63,6 +83,13 @@ impl Translation { constraint: self.constraint, } } + pub fn set_amount(self, change: DVec2) -> Self { + Self { + dragged_distance: change, + typed_distance: None, + constraint: self.constraint, + } + } } #[derive(Default, Debug, Clone, PartialEq, Copy)] @@ -90,6 +117,12 @@ impl Rotation { typed_angle: None, } } + pub fn set_amount(self, angle: f64) -> Self { + Self { + dragged_angle: angle, + typed_angle: None, + } + } } #[derive(Debug, Clone, PartialEq, Copy)] @@ -129,6 +162,14 @@ impl Scale { constraint: self.constraint, } } + + pub fn set_amount(self, change: f64) -> Self { + Self { + dragged_factor: 1. + change, + typed_factor: None, + constraint: self.constraint, + } + } } #[derive(Default, Debug, Clone, PartialEq, Copy)] @@ -228,16 +269,74 @@ pub struct Selected<'a> { pub document: &'a Document, pub original_transforms: &'a mut OriginalTransforms, pub pivot: &'a mut DVec2, + pub shape_editor: Option<&'a ShapeState>, + pub tool_type: &'a ToolType, } impl<'a> Selected<'a> { - pub fn new(original_transforms: &'a mut OriginalTransforms, pivot: &'a mut DVec2, selected: &'a [&'a Vec], responses: &'a mut VecDeque, document: &'a Document) -> Self { - for path in selected { - if !original_transforms.contains_key(*path) { - if let Ok(layer) = document.layer(path) { - original_transforms.insert(path.to_vec(), layer.transform); - } else { - warn!("Didn't find a layer for {:?}", path); + pub fn new( + original_transforms: &'a mut OriginalTransforms, + pivot: &'a mut DVec2, + selected: &'a [&'a Vec], + responses: &'a mut VecDeque, + document: &'a Document, + shape_editor: Option<&'a ShapeState>, + tool_type: &'a ToolType, + ) -> Self { + // If user is using the Select tool then use the original layer transforms + if (*tool_type == ToolType::Select) && (*original_transforms == OriginalTransforms::Path(HashMap::new())) { + *original_transforms = OriginalTransforms::Layer(HashMap::new()); + } + + match original_transforms { + OriginalTransforms::Layer(layer_map) => { + for layer_path in selected { + if !layer_map.contains_key(*layer_path) { + if let Ok(layer) = document.layer(layer_path) { + layer_map.insert(layer_path.to_vec(), layer.transform); + } else { + warn!("Didn't find a layer for {:?}", layer_path); + } + } + } + } + OriginalTransforms::Path(path_map) => { + for path in selected { + let Some(shape_editor) = shape_editor else { + warn!("No shape editor structure found, which only happens in select tool, which cannot reach this point as we check for ToolType"); + continue; + }; + // Anchors also move their handles + let expand_anchors = |&point: &ManipulatorPointId| { + if point.manipulator_type.is_handle() { + [Some(point), None, None] + } else { + [ + Some(point), + Some(ManipulatorPointId::new(point.group, SelectedType::InHandle)), + Some(ManipulatorPointId::new(point.group, SelectedType::OutHandle)), + ] + } + }; + let points = shape_editor.selected_points().flat_map(expand_anchors).flatten(); + if path_map.contains_key(*path) { + continue; + } + let Ok(layer) = document.layer(path) else { + warn!("Didn't find a layer for {:?}", path); + continue; + }; + let Some(vector_data) = layer.as_vector_data() else { + warn!("Didn't find a vectordata for {:?}", layer); + continue; + }; + let get_manipulator_point_position = |point_id: ManipulatorPointId| { + vector_data + .manipulator_from_id(point_id.group) + .and_then(|manipulator_group| point_id.manipulator_type.get_position(manipulator_group)) + .map(|position| (point_id, position)) + }; + path_map.insert(path.to_vec(), points.filter_map(get_manipulator_point_position).collect()); } } } @@ -247,6 +346,8 @@ impl<'a> Selected<'a> { document, original_transforms, pivot, + shape_editor, + tool_type, } } @@ -278,33 +379,79 @@ impl<'a> Selected<'a> { // TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481 for layer_path in Document::shallowest_unique_layers(self.selected.iter()) { let parent_folder_path = &layer_path[..layer_path.len() - 1]; - let original_layer_transforms = *self.original_transforms.get(*layer_path).unwrap(); + if *self.tool_type == ToolType::Select { + let original_layer_transforms = match self.original_transforms { + OriginalTransforms::Layer(layer_map) => *layer_map.get(*layer_path).unwrap(), + OriginalTransforms::Path(_path_map) => { + warn!("Found Path variant in original_transforms, returning identity transform for layer {:?}", layer_path); + DAffine2::IDENTITY + } + }; + let to = self.document.generate_transform_across_scope(parent_folder_path, None).unwrap(); + let new = to.inverse() * transformation * to * original_layer_transforms; + self.responses.add(GraphOperationMessage::TransformSet { + layer: layer_path.to_vec(), + transform: new, + transform_in: TransformIn::Local, + skip_rerender: true, + }); + } + if *self.tool_type == ToolType::Path { + let viewspace = self.document.generate_transform_relative_to_viewport(layer_path).ok().unwrap_or_default(); + let layerspace_rotation = viewspace.inverse() * transformation; - let to = self.document.generate_transform_across_scope(parent_folder_path, None).unwrap(); - let new = to.inverse() * transformation * to * original_layer_transforms; + let initial_points = match self.original_transforms { + OriginalTransforms::Layer(_layer_map) => { + warn!("Found Layer variant in original_transforms when Path wanted, returning identity transform for layer"); + None + } + OriginalTransforms::Path(path_map) => path_map.get(*layer_path), + }; - self.responses.add(GraphOperationMessage::TransformSet { - layer: layer_path.to_vec(), - transform: new, - transform_in: TransformIn::Local, - skip_rerender: true, - }); + let Some(original) = initial_points else { + warn!("Initial Points empty, it should not be possible to reach here without points"); + continue; + }; + for (point_id, position) in original { + let viewport_point = viewspace.transform_point2(*position); + let new_pos_viewport = layerspace_rotation.transform_point2(viewport_point); + let point = *point_id; + let position = new_pos_viewport; + + self.responses.add(GraphOperationMessage::Vector { + layer: (*layer_path).to_vec(), + modification: VectorDataModification::SetManipulatorPosition { point, position }, + }); + } + } + self.responses.push_back(BroadcastEvent::DocumentIsDirty.into()); } - - self.responses.push_back(BroadcastEvent::DocumentIsDirty.into()); } } pub fn revert_operation(&mut self) { - for layer in self.selected { - if let Some(&transform) = self.original_transforms.get(*layer) { - // Push front to stop document switching before sending the transform - self.responses.add(GraphOperationMessage::TransformSet { - layer: layer.to_vec(), - transform, - transform_in: TransformIn::Local, - skip_rerender: false, - }); + for path in self.selected.iter().copied() { + let original_transform = &self.original_transforms; + match original_transform { + OriginalTransforms::Layer(hash) => { + let Some(matrix) = hash.get(path) else { continue }; + self.responses.add(GraphOperationMessage::TransformSet { + layer: path.to_vec(), + transform: *matrix, + transform_in: TransformIn::Local, + skip_rerender: false, + }); + } + OriginalTransforms::Path(path) => { + for (layer_path, points) in path { + for &(point, position) in points { + self.responses.add(GraphOperationMessage::Vector { + layer: (*layer_path).clone(), + modification: VectorDataModification::SetManipulatorPosition { point, position }, + }); + } + } + } } } } diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 0a96d535..f28d556f 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -24,7 +24,7 @@ pub struct PathTool { #[remain::sorted] #[impl_message(Message, ToolMessage, Path)] -#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize, specta::Type)] +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)] pub enum PathToolMessage { // Standard messages #[remain::unsorted] @@ -43,6 +43,10 @@ pub enum PathToolMessage { shift_mirror_distance: Key, }, InsertPoint, + NudgeSelectedPoints { + delta_x: f64, + delta_y: f64, + }, PointerMove { alt_mirror_angle: Key, shift_mirror_distance: Key, @@ -77,6 +81,7 @@ impl<'a> MessageHandler> for PathToo InsertPoint, DragStart, Delete, + NudgeSelectedPoints, ), Dragging => actions!(PathToolMessageDiscriminant; InsertPoint, @@ -108,7 +113,6 @@ enum PathToolFsmState { #[derive(Default)] struct PathToolData { snap_manager: SnapManager, - drag_start_pos: DVec2, previous_mouse_position: DVec2, alt_debounce: bool, @@ -203,6 +207,7 @@ impl Fsm for PathToolFsmState { let include_handles: Vec<_> = selected_layers.iter().map(|x| x.as_slice()).collect(); tool_data.snap_manager.add_all_document_handles(document, input, &include_handles, &[], &selected_points.points); + tool_data.drag_start_pos = input.mouse.position; tool_data.previous_mouse_position = input.mouse.position - selected_points.offset; @@ -241,9 +246,11 @@ impl Fsm for PathToolFsmState { responses.push_back(DocumentMessage::DeselectAllLayers.into()); } } + PathToolFsmState::Ready } } + // Dragging ( PathToolFsmState::Dragging, @@ -282,6 +289,7 @@ impl Fsm for PathToolFsmState { tool_data.previous_mouse_position = snapped_position; PathToolFsmState::Dragging } + // Mouse up (_, PathToolMessage::DragStop { shift_mirror_distance }) => { let nearest_point = shape_editor @@ -335,6 +343,10 @@ impl Fsm for PathToolFsmState { shift_mirror_distance: _, }, ) => self, + (_, PathToolMessage::NudgeSelectedPoints { delta_x, delta_y }) => { + shape_editor.move_selected_points(&document.document_legacy, (delta_x, delta_y).into(), true, responses); + PathToolFsmState::Ready + } } } else { self @@ -346,8 +358,8 @@ impl Fsm for PathToolFsmState { PathToolFsmState::Ready => HintData(vec![ HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Select Point"), HintInfo::keys([Key::Shift], "Extend Selection").prepend_plus()]), HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")]), - HintGroup(vec![HintInfo::arrow_keys("Nudge Selected (coming soon)"), HintInfo::keys([Key::Shift], "10x").prepend_plus()]), - HintGroup(vec![HintInfo::keys([Key::KeyG, Key::KeyR, Key::KeyS], "Grab/Rotate/Scale Selected (coming soon)")]), + HintGroup(vec![HintInfo::arrow_keys("Nudge Selected"), HintInfo::keys([Key::Shift], "10x").prepend_plus()]), + HintGroup(vec![HintInfo::keys([Key::KeyG, Key::KeyR, Key::KeyS], "Grab/Rotate/Scale Selected")]), ]), PathToolFsmState::Dragging => HintData(vec![HintGroup(vec![ HintInfo::keys([Key::Alt], "Split/Align Handles (Toggle)"), diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index eb4247ef..beab8495 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -539,7 +539,15 @@ impl Fsm for SelectToolFsmState { let document = &document.document_legacy; let selected = &tool_data.layers_dragging.iter().collect::>(); - let mut selected = Selected::new(&mut bounds.original_transforms, &mut bounds.center_of_transformation, selected, responses, document); + let mut selected = Selected::new( + &mut bounds.original_transforms, + &mut bounds.center_of_transformation, + selected, + responses, + document, + None, + &ToolType::Select, + ); bounds.center_of_transformation = selected.mean_average_of_pivots(render_data); } @@ -549,7 +557,15 @@ impl Fsm for SelectToolFsmState { if let Some(bounds) = &mut tool_data.bounding_box_overlays { let selected = selected.iter().collect::>(); - let mut selected = Selected::new(&mut bounds.original_transforms, &mut bounds.center_of_transformation, &selected, responses, &document.document_legacy); + let mut selected = Selected::new( + &mut bounds.original_transforms, + &mut bounds.center_of_transformation, + &selected, + responses, + &document.document_legacy, + None, + &ToolType::Select, + ); bounds.center_of_transformation = selected.mean_average_of_pivots(render_data); } @@ -650,10 +666,10 @@ impl Fsm for SelectToolFsmState { let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position); let (position, size) = movement.new_size(snapped_mouse_position, bounds.transform, center, bounds.center_of_transformation, axis_align); - let (delta, mut pivot) = movement.bounds_to_scale_transform(position, size); + let (delta, mut _pivot) = movement.bounds_to_scale_transform(position, size); let selected = &tool_data.layers_dragging.iter().collect::>(); - let mut selected = Selected::new(&mut bounds.original_transforms, &mut pivot, selected, responses, &document.document_legacy); + let mut selected = Selected::new(&mut bounds.original_transforms, &mut _pivot, selected, responses, &document.document_legacy, None, &ToolType::Select); selected.update_transforms(delta); } @@ -679,7 +695,15 @@ impl Fsm for SelectToolFsmState { let delta = DAffine2::from_angle(snapped_angle); let selected = tool_data.layers_dragging.iter().collect::>(); - let mut selected = Selected::new(&mut bounds.original_transforms, &mut bounds.center_of_transformation, &selected, responses, &document.document_legacy); + let mut selected = Selected::new( + &mut bounds.original_transforms, + &mut bounds.center_of_transformation, + &selected, + responses, + &document.document_legacy, + None, + &ToolType::Select, + ); selected.update_transforms(delta); } @@ -872,6 +896,8 @@ impl Fsm for SelectToolFsmState { &selected, responses, &document.document_legacy, + None, + &ToolType::Select, ); selected.revert_operation(); 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 b416e69a..fc01afc3 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 @@ -6,6 +6,7 @@ use crate::messages::tool::common_functionality::shape_editor::ShapeState; use crate::messages::tool::utility_types::{ToolData, ToolType}; use document_legacy::layers::style::RenderData; +use graphene_core::vector::ManipulatorPointId; use glam::DVec2; @@ -43,16 +44,48 @@ impl<'a> MessageHandler> for TransformL fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque, (document, ipp, render_data, tool_data, shape_editor): TransformData) { use TransformLayerMessage::*; - // TODO: Transform individual points when using the path tool. - let _using_path_tool = tool_data.active_tool_type == ToolType::Path; + let using_path_tool = tool_data.active_tool_type == ToolType::Path; let selected_layers = document.layer_metadata.iter().filter_map(|(layer_path, data)| data.selected.then_some(layer_path)).collect::>(); - let mut selected = Selected::new(&mut self.original_transforms, &mut self.pivot, &selected_layers, responses, &document.document_legacy); + + let mut selected = Selected::new( + &mut self.original_transforms, + &mut self.pivot, + &selected_layers, + responses, + &document.document_legacy, + Some(shape_editor), + &tool_data.active_tool_type, + ); let mut begin_operation = |operation: TransformOperation, typing: &mut Typing, mouse_position: &mut DVec2, start_mouse: &mut DVec2| { if operation != TransformOperation::None { selected.revert_operation(); typing.clear(); + } + + if using_path_tool { + if let Ok(layer) = document.document_legacy.layer(&selected_layers[0]) { + if let Some(vector_data) = layer.as_vector_data() { + *selected.original_transforms = OriginalTransforms::default(); + let viewspace = &mut document.document_legacy.generate_transform_relative_to_viewport(&selected_layers[0]).ok().unwrap_or_default(); + + let mut point_count: usize = 0; + let count_point = |position| { + point_count += 1; + position + }; + let get_location = |point: &ManipulatorPointId| { + vector_data + .manipulator_from_id(point.group) + .and_then(|manipulator_group| point.manipulator_type.get_position(manipulator_group)) + .map(|position| viewspace.transform_point2(position)) + }; + let points = shape_editor.selected_points(); + + *selected.pivot = points.filter_map(get_location).map(count_point).sum::() / point_count as f64; + } + } } else { *selected.pivot = selected.mean_average_of_pivots(render_data); } @@ -65,7 +98,8 @@ impl<'a> MessageHandler> for TransformL #[remain::sorted] match message { ApplyTransformOperation => { - self.original_transforms.clear(); + selected.original_transforms.clear(); + self.typing.clear(); self.transform_operation = TransformOperation::None; @@ -90,6 +124,7 @@ impl<'a> MessageHandler> for TransformL self.transform_operation = TransformOperation::Grabbing(Default::default()); + selected.original_transforms.clear(); responses.push_back(BroadcastEvent::DocumentIsDirty.into()); } BeginRotate => { @@ -106,6 +141,7 @@ impl<'a> MessageHandler> for TransformL self.transform_operation = TransformOperation::Rotating(Default::default()); + selected.original_transforms.clear(); responses.push_back(BroadcastEvent::DocumentIsDirty.into()); } BeginScale => { @@ -122,6 +158,7 @@ impl<'a> MessageHandler> for TransformL self.transform_operation = TransformOperation::Scaling(Default::default()); + selected.original_transforms.clear(); responses.push_back(BroadcastEvent::DocumentIsDirty.into()); } CancelTransformOperation => { @@ -163,15 +200,12 @@ impl<'a> MessageHandler> for TransformL self.transform_operation.apply_transform_operation(&mut selected, self.snap, axis_constraint); } TransformOperation::Rotating(rotation) => { - let selected_pivot = selected.mean_average_of_pivots(render_data); - let angle = { - let start_offset = self.mouse_position - selected_pivot; - let end_offset = ipp.mouse.position - selected_pivot; - - start_offset.angle_between(end_offset) - }; + let start_offset = *selected.pivot - self.mouse_position; + let end_offset = *selected.pivot - ipp.mouse.position; + let angle = start_offset.angle_between(end_offset); let change = if self.slow { angle / SLOWING_DIVISOR } else { angle }; + self.transform_operation = TransformOperation::Rotating(rotation.increment_amount(change)); self.transform_operation.apply_transform_operation(&mut selected, self.snap, Axis::Both); }