From eb255073e6e1f3246d5d452c866845596226dba8 Mon Sep 17 00:00:00 2001 From: hypercube <0hypercube@gmail.com> Date: Tue, 5 Aug 2025 17:02:27 +0100 Subject: [PATCH] Seperate duplication logic --- .vscode/settings.json | 3 +- .../input_mapper/utility_types/input_mouse.rs | 6 +- .../portfolio/portfolio_message_handler.rs | 8 +- .../tool/tool_messages/select_tool.rs | 310 +++++++----------- .../tool_messages/select_tool/duplicate.rs | 105 ++++++ 5 files changed, 230 insertions(+), 202 deletions(-) create mode 100644 editor/src/messages/tool/tool_messages/select_tool/duplicate.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 7f48a1c56a..59b061584f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -52,5 +52,6 @@ "files.insertFinalNewline": true, "files.associations": { "*.graphite": "json" - } + }, + "rust-analyzer.checkOnSave": false } diff --git a/editor/src/messages/input_mapper/utility_types/input_mouse.rs b/editor/src/messages/input_mapper/utility_types/input_mouse.rs index a9b746608d..7644972d99 100644 --- a/editor/src/messages/input_mapper/utility_types/input_mouse.rs +++ b/editor/src/messages/input_mapper/utility_types/input_mouse.rs @@ -78,7 +78,8 @@ impl ScrollDelta { } } -// TODO: Document the difference between this and EditorMouseState +/// This is similar to [`EditorMouseState`] except that the position is stored relative to the viewport rather than relative to the UI. +/// Convert using [`EditorMouseState::to_mouse_state`] #[derive(Debug, Copy, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)] pub struct MouseState { pub position: ViewportPosition, @@ -94,7 +95,8 @@ impl MouseState { } } -// TODO: Document the difference between this and MouseState +/// This is similar to [`MouseState`] except that the position is stored relative to the UI rather than relative to the viewport. +/// Convert using [`EditorMouseState::to_mouse_state`] #[derive(Debug, Copy, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)] pub struct EditorMouseState { pub editor_position: EditorPosition, diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 0a1e1d54ed..720e88e0c4 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -15,7 +15,6 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions; use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT}; use crate::messages::portfolio::document::utility_types::network_interface::OutputConnector; -use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes; use crate::messages::portfolio::document_migration::*; use crate::messages::preferences::SelectionMode; use crate::messages::prelude::*; @@ -259,12 +258,7 @@ impl MessageHandler> for Portfolio }; let mut copy_val = |buffer: &mut Vec| { - let mut ordered_last_elements = active_document.network_interface.shallowest_unique_layers(&[]).collect::>(); - - ordered_last_elements.sort_by_key(|layer| { - let Some(parent) = layer.parent(active_document.metadata()) else { return usize::MAX }; - DocumentMessageHandler::get_calculated_insert_index(active_document.metadata(), &SelectedNodes(vec![layer.to_node()]), parent) - }); + let ordered_last_elements = active_document.network_interface.shallowest_unique_layers(&[]).collect::>(); for layer in ordered_last_elements.into_iter() { let layer_node_id = layer.to_node(); diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index fc3f5e0c25..2336ab4037 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -6,8 +6,7 @@ use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; -use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeNetworkInterface, NodeTemplate}; -use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes; +use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; use crate::messages::preferences::SelectionMode; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::compass_rose::{Axis, CompassRose}; @@ -22,11 +21,11 @@ use crate::messages::tool::common_functionality::utility_functions::{resize_boun use crate::messages::tool::tool_messages::select_tool::options::NestedSelectionBehavior; use bezier_rs::Subpath; use glam::DMat2; -use graph_craft::document::NodeId; use graphene_std::renderer::Quad; use graphene_std::renderer::Rect; use graphene_std::transform::ReferencePoint; +mod duplicate; pub mod options; #[derive(Default, ExtractField)] @@ -163,23 +162,104 @@ impl Default for SelectToolFsmState { } } +/// Represents the current drag in progress +#[derive(Clone, Debug, Default)] +struct DragState { + pub start_document: DVec2, + pub current_document: DVec2, + /// Selection mode is set when the drag exceeds a certain distance. Once resolved, the selection mode cannot change. + resolved_selection_mode: Option, +} + +impl DragState { + pub fn new(input: &InputPreprocessorMessageHandler, metadata: &DocumentMetadata) -> Self { + let document_mouse = metadata.document_to_viewport.inverse().transform_point2(input.mouse.position); + Self { + start_document: document_mouse, + current_document: document_mouse, + resolved_selection_mode: None, + } + } + pub fn set_current(&mut self, input: &InputPreprocessorMessageHandler, metadata: &DocumentMetadata) { + self.current_document = metadata.document_to_viewport.inverse().transform_point2(input.mouse.position); + } + + pub fn start_viewport(&self, metadata: &DocumentMetadata) -> DVec2 { + metadata.document_to_viewport.transform_point2(self.start_document) + } + + pub fn current_viewport(&self, metadata: &DocumentMetadata) -> DVec2 { + metadata.document_to_viewport.transform_point2(self.current_document) + } + + pub fn start_current_viewport(&self, metadata: &DocumentMetadata) -> [DVec2; 2] { + [self.start_viewport(metadata), self.current_viewport(metadata)] + } + + pub fn total_drag_delta_document(&self) -> DVec2 { + self.current_document - self.start_document + } + + pub fn total_drag_delta_viewport(&self, metadata: &DocumentMetadata) -> DVec2 { + metadata.document_to_viewport.transform_vector2(self.total_drag_delta_document()) + } + + pub fn inverse_drag_delta_viewport(&self, metadata: &DocumentMetadata) -> DVec2 { + -self.total_drag_delta_viewport(metadata) + } + + fn update_selection_mode(&mut self, metadata: &DocumentMetadata, preferences: &PreferencesMessageHandler) -> SelectionMode { + if let Some(resolved_selection_mode) = self.resolved_selection_mode { + return resolved_selection_mode; + } + if preferences.get_selection_mode() != SelectionMode::Directional { + self.resolved_selection_mode = Some(preferences.get_selection_mode()); + return preferences.get_selection_mode(); + } + + let [start, current] = self.start_current_viewport(metadata); + + // Drag direction cannot be resolved TODO: why not consider only X distance? + if start.distance_squared(current) >= DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD.powi(2) { + let selection_mode = if current.x < start.x { SelectionMode::Touched } else { SelectionMode::Enclosed }; + self.resolved_selection_mode = Some(selection_mode); + return selection_mode; + } + + SelectionMode::default() + } + + /// A viewport quad representing the drag bounds. Expanded if the start == end + pub fn expanded_selection_box_viewport(&self, metadata: &DocumentMetadata) -> [DVec2; 2] { + let [start, current] = self.start_current_viewport(metadata); + if start == current { + let tolerance = DVec2::splat(SELECTION_TOLERANCE); + [current - tolerance, current + tolerance] + } else { + [start, current] + } + } +} + #[derive(Clone, Debug, Default)] struct SelectToolData { - drag_start: ViewportPosition, - drag_current: ViewportPosition, + drag: DragState, + duplicates: duplicate::DuplcateState, + + // select lasso_polygon: Vec, - selection_mode: Option, - layers_dragging: Vec, // Unordered, often used as temporary buffer - ordered_layers: Vec, // Ordered list of layers layer_selected_on_start: Option, select_single_layer: Option, + + layers_dragging: Vec, // Unordered, often used as temporary buffer + + ordered_layers: Vec, // Ordered list of layers + axis_align: bool, - non_duplicated_layers: Option>, bounding_box_manager: Option, snap_manager: SnapManager, cursor: MouseCursorIcon, pivot_gizmo: PivotGizmo, - pivot_gizmo_start: Option, pivot_gizmo_shift: Option, compass_rose: CompassRose, line_center: DVec2, @@ -205,37 +285,6 @@ impl SelectToolData { } } - pub fn selection_quad(&self) -> Quad { - let bbox = self.selection_box(); - Quad::from_box(bbox) - } - - pub fn calculate_selection_mode_from_direction(&mut self) -> SelectionMode { - let bbox: [DVec2; 2] = self.selection_box(); - let above_threshold = bbox[1].distance_squared(bbox[0]) > DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD.powi(2); - - if self.selection_mode.is_none() && above_threshold { - let mode = if bbox[1].x < bbox[0].x { - SelectionMode::Touched - } else { - // This also covers the case where they're equal: the area is zero, so we use `Enclosed` to ensure the selection ends up empty, as nothing will be enclosed by an empty area - SelectionMode::Enclosed - }; - self.selection_mode = Some(mode); - } - - self.selection_mode.unwrap_or(SelectionMode::Touched) - } - - pub fn selection_box(&self) -> [DVec2; 2] { - if self.drag_current == self.drag_start { - let tolerance = DVec2::splat(SELECTION_TOLERANCE); - [self.drag_start - tolerance, self.drag_start + tolerance] - } else { - [self.drag_start, self.drag_current] - } - } - pub fn intersect_lasso_no_artboards(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) -> Vec { if self.lasso_polygon.len() < 2 { return Vec::new(); @@ -252,101 +301,6 @@ impl SelectToolData { document.is_layer_fully_inside_polygon(layer, input, polygon) } - /// Duplicates the currently dragging layers. Called when Alt is pressed and the layers have not yet been duplicated. - fn start_duplicates(&mut self, document: &mut DocumentMessageHandler, responses: &mut VecDeque) { - self.non_duplicated_layers = Some(self.layers_dragging.clone()); - let mut new_dragging = Vec::new(); - - // Get the shallowest unique layers and sort by their index relative to parent for ordered processing - let mut layers = document.network_interface.shallowest_unique_layers(&[]).collect::>(); - - layers.sort_by_key(|layer| { - let Some(parent) = layer.parent(document.metadata()) else { return usize::MAX }; - DocumentMessageHandler::get_calculated_insert_index(document.metadata(), &SelectedNodes(vec![layer.to_node()]), parent) - }); - - for layer in layers.into_iter().rev() { - let Some(parent) = layer.parent(document.metadata()) else { continue }; - - // Moves the layer back to its starting position. - responses.add(GraphOperationMessage::TransformChange { - layer, - transform: DAffine2::from_translation(self.drag_start - self.drag_current), - transform_in: TransformIn::Viewport, - skip_rerender: true, - }); - - // Copy the layer - let mut copy_ids = HashMap::new(); - let node_id = layer.to_node(); - copy_ids.insert(node_id, NodeId(0)); - - document - .network_interface - .upstream_flow_back_from_nodes(vec![layer.to_node()], &[], FlowType::LayerChildrenUpstreamFlow) - .enumerate() - .for_each(|(index, node_id)| { - copy_ids.insert(node_id, NodeId((index + 1) as u64)); - }); - - let nodes = document.network_interface.copy_nodes(©_ids, &[]).collect::>(); - - let insert_index = DocumentMessageHandler::get_calculated_insert_index(document.metadata(), &SelectedNodes(vec![layer.to_node()]), parent); - - let new_ids: HashMap<_, _> = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect(); - - let layer_id = *new_ids.get(&NodeId(0)).expect("Node Id 0 should be a layer"); - let layer = LayerNodeIdentifier::new_unchecked(layer_id); - new_dragging.push(layer); - responses.add(NodeGraphMessage::AddNodes { nodes, new_ids }); - responses.add(NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index }); - } - let nodes = new_dragging.iter().map(|layer| layer.to_node()).collect(); - responses.add(NodeGraphMessage::SelectedNodesSet { nodes }); - responses.add(NodeGraphMessage::RunDocumentGraph); - self.layers_dragging = new_dragging; - } - - /// Removes the duplicated layers. Called when Alt is released and the layers have previously been duplicated. - fn stop_duplicates(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque) { - let Some(original) = self.non_duplicated_layers.take() else { - return; - }; - - // Delete the duplicated layers - for layer in document.network_interface.shallowest_unique_layers(&[]) { - responses.add(NodeGraphMessage::DeleteNodes { - node_ids: vec![layer.to_node()], - delete_children: true, - }); - } - - for &layer in &original { - responses.add(GraphOperationMessage::TransformChange { - layer, - transform: DAffine2::from_translation(self.drag_current - self.drag_start), - transform_in: TransformIn::Viewport, - skip_rerender: true, - }); - } - let nodes = original - .iter() - .filter_map(|layer| { - if *layer != LayerNodeIdentifier::ROOT_PARENT { - Some(layer.to_node()) - } else { - log::error!("ROOT_PARENT cannot be part of non_duplicated_layers"); - None - } - }) - .collect(); - responses.add(NodeGraphMessage::SelectedNodesSet { nodes }); - responses.add(NodeGraphMessage::RunDocumentGraph); - responses.add(NodeGraphMessage::SelectedNodesUpdated); - responses.add(NodeGraphMessage::SendGraph); - self.layers_dragging = original; - } - fn state_from_pivot_gizmo(&self, mouse: DVec2) -> Option { match self.pivot_gizmo.state.gizmo_type { PivotGizmoType::Pivot if self.pivot_gizmo.state.is_pivot() => self.pivot_gizmo.pivot.is_over(mouse).then_some(SelectToolFsmState::DraggingPivot), @@ -627,10 +581,7 @@ impl Fsm for SelectToolFsmState { tool_data.pivot_gizmo.pivot.recalculate_pivot(document); let pivot = draw_pivot.then_some(tool_data.pivot_gizmo.pivot.pivot).flatten(); if let Some(pivot) = pivot { - let offset = tool_data - .pivot_gizmo_start - .map(|offset| tool_data.pivot_gizmo.pivot_disconnected().then_some(tool_data.drag_current - offset).unwrap_or_default()) - .unwrap_or_default(); + let offset = tool_data.drag.total_drag_delta_viewport(document.metadata()); let shift = tool_data.pivot_gizmo_shift.unwrap_or_default(); overlay_context.pivot(pivot + offset + shift, angle); } @@ -681,12 +632,11 @@ impl Fsm for SelectToolFsmState { } if axis_state.is_none_or(|(axis, _)| !axis.is_constraint()) && tool_data.axis_align { - let mouse_position = mouse_position - tool_data.drag_start; let snap_resolution = SELECTION_DRAG_ANGLE.to_radians(); - let angle = -mouse_position.angle_to(DVec2::X); + let angle = -tool_data.drag.total_drag_delta_viewport(document.metadata()).angle_to(DVec2::X); let snapped_angle = (angle / snap_resolution).round() * snap_resolution; - let extension = tool_data.drag_current - tool_data.drag_start; + let extension = tool_data.drag.total_drag_delta_viewport(document.metadata()); let origin = compass_center - extension; let viewport_diagonal = input.viewport_bounds.size().length(); @@ -709,13 +659,9 @@ impl Fsm for SelectToolFsmState { // Check if the tool is in selection mode if let Self::Drawing { selection_shape, .. } = self { // Get the updated selection box bounds - let quad = Quad::from_box([tool_data.drag_start, tool_data.drag_current]); + let quad = Quad::from_box(tool_data.drag.start_current_viewport(document.metadata())); - let current_selection_mode = match tool_action_data.preferences.get_selection_mode() { - SelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(), - SelectionMode::Touched => SelectionMode::Touched, - SelectionMode::Enclosed => SelectionMode::Enclosed, - }; + let current_selection_mode = tool_data.drag.update_selection_mode(document.metadata(), tool_action_data.preferences); // Draw outline visualizations on the layers to be selected let intersected_layers = match selection_shape { @@ -761,11 +707,9 @@ impl Fsm for SelectToolFsmState { } if let Self::Dragging { .. } = self { - let quad = Quad::from_box([tool_data.drag_start, tool_data.drag_current]); - let document_start = document.metadata().document_to_viewport.inverse().transform_point2(quad.top_left()); - let document_current = document.metadata().document_to_viewport.inverse().transform_point2(quad.bottom_right()); + let quad = Quad::from_box(tool_data.drag.start_current_viewport(document.metadata())); - overlay_context.translation_box(document_current - document_start, quad, None); + overlay_context.translation_box(tool_data.drag.total_drag_delta_document(), quad, None); } self @@ -791,9 +735,8 @@ impl Fsm for SelectToolFsmState { .. }, ) => { - tool_data.drag_start = input.mouse.position; - tool_data.drag_current = input.mouse.position; - tool_data.selection_mode = None; + tool_data.drag = DragState::new(input, document.metadata()); + tool_data.duplicates.reset(); let mut selected: Vec<_> = document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface).collect(); let intersection_list = document.click_list(input).collect::>(); @@ -852,8 +795,6 @@ impl Fsm for SelectToolFsmState { (axis_state.unwrap_or_default(), axis_state.is_some()) }; - tool_data.pivot_gizmo_start = Some(tool_data.drag_current); - SelectToolFsmState::Dragging { axis, using_compass, @@ -893,8 +834,6 @@ impl Fsm for SelectToolFsmState { responses.add(DocumentMessage::StartTransaction); - tool_data.pivot_gizmo_start = Some(tool_data.drag_current); - SelectToolFsmState::Dragging { axis: Axis::None, using_compass: false, @@ -907,7 +846,6 @@ impl Fsm for SelectToolFsmState { SelectToolFsmState::Drawing { selection_shape, has_drawn: false } } }; - tool_data.non_duplicated_layers = None; state } @@ -929,20 +867,18 @@ impl Fsm for SelectToolFsmState { if !has_dragged { responses.add(ToolMessage::UpdateHints); } - if input.keyboard.key(modifier_keys.duplicate) && tool_data.non_duplicated_layers.is_none() { - tool_data.start_duplicates(document, responses); - } else if !input.keyboard.key(modifier_keys.duplicate) && tool_data.non_duplicated_layers.is_some() { - tool_data.stop_duplicates(document, responses); - } + tool_data + .duplicates + .set_duplicating(input.keyboard.key(modifier_keys.duplicate), &mut tool_data.layers_dragging, document, &tool_data.drag, responses); tool_data.axis_align = input.keyboard.key(modifier_keys.axis_align); // Ignore the non duplicated layers if the current layers have not spawned yet. let layers_exist = tool_data.layers_dragging.iter().all(|&layer| document.metadata().click_targets(layer).is_some()); - let ignore = tool_data.non_duplicated_layers.as_ref().filter(|_| !layers_exist).unwrap_or(&tool_data.layers_dragging); + let ignore = tool_data.duplicates.non_duplicated_layers.as_ref().filter(|_| !layers_exist).unwrap_or(&tool_data.layers_dragging); let snap_data = SnapData::ignore(document, input, ignore); - let (start, current) = (tool_data.drag_start, tool_data.drag_current); + let [start, current] = tool_data.drag.start_current_viewport(document.metadata()); let e0 = tool_data .bounding_box_manager .as_ref() @@ -965,7 +901,9 @@ impl Fsm for SelectToolFsmState { skip_rerender: false, }); } - tool_data.drag_current += mouse_delta; + tool_data.drag.current_document += document.metadata().document_to_viewport.inverse().transform_vector2(mouse_delta); + + info!("current {} mouse {}", tool_data.drag.current_viewport(document.metadata()), input.mouse.position); // Auto-panning let messages = [ @@ -1025,7 +963,7 @@ impl Fsm for SelectToolFsmState { responses, bounds, &mut tool_data.layers_dragging, - tool_data.drag_start, + tool_data.drag.start_viewport(document.metadata()), input.mouse.position, input.keyboard.key(Key::Shift), ToolType::Select, @@ -1056,11 +994,11 @@ impl Fsm for SelectToolFsmState { responses.add(ToolMessage::UpdateHints); } - tool_data.drag_current = input.mouse.position; + tool_data.drag.set_current(input, document.metadata()); responses.add(OverlaysMessage::Draw); if selection_shape == SelectionShapeType::Lasso { - extend_lasso(&mut tool_data.lasso_polygon, tool_data.drag_current); + extend_lasso(&mut tool_data.lasso_polygon, tool_data.drag.current_viewport(document.metadata())); } // Auto-panning @@ -1110,10 +1048,7 @@ impl Fsm for SelectToolFsmState { SelectToolMessage::PointerOutsideViewport(_), ) => { // Auto-panning - if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) { - tool_data.drag_current += shift; - tool_data.drag_start += shift; - } + tool_data.auto_panning.shift_viewport(input, responses); SelectToolFsmState::Dragging { axis, @@ -1142,9 +1077,7 @@ impl Fsm for SelectToolFsmState { } (SelectToolFsmState::Drawing { .. }, SelectToolMessage::PointerOutsideViewport(_)) => { // Auto-panning - if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) { - tool_data.drag_start += shift; - } + tool_data.auto_panning.shift_viewport(input, responses); self } @@ -1165,7 +1098,7 @@ impl Fsm for SelectToolFsmState { if !has_dragged && input.keyboard.key(remove_from_selection) && tool_data.layer_selected_on_start.is_none() { // When you click on the layer with remove from selection key (shift) pressed, we deselect all nodes that are children. - let quad = tool_data.selection_quad(); + let quad = Quad::from_box(tool_data.drag.expanded_selection_box_viewport(document.metadata())); let intersection = document.intersect_quad_no_artboards(quad, input); if let Some(path) = intersection.last() { @@ -1224,13 +1157,12 @@ impl Fsm for SelectToolFsmState { tool_data.snap_manager.cleanup(responses); tool_data.select_single_layer = None; - if let Some(start) = tool_data.pivot_gizmo_start { - let offset = tool_data.pivot_gizmo.pivot_disconnected().then_some(tool_data.drag_current - start).unwrap_or_default(); + if tool_data.pivot_gizmo.pivot_disconnected() { + let offset = tool_data.drag.total_drag_delta_viewport(document.metadata()); if let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() { *v += offset; } } - tool_data.pivot_gizmo_start = None; let pivot_gizmo = tool_data.pivot_gizmo(); responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); @@ -1241,13 +1173,10 @@ impl Fsm for SelectToolFsmState { SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. } | SelectToolFsmState::RotatingBounds | SelectToolFsmState::DraggingPivot, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter, ) => { - let drag_too_small = input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON; - let response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction }; - let pivot_gizmo = tool_data.pivot_gizmo(); responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); - responses.add(response); + input.mouse.finish_transaction(tool_data.drag.start_viewport(document.metadata()), responses); tool_data.axis_align = false; tool_data.snap_manager.cleanup(responses); @@ -1261,12 +1190,9 @@ impl Fsm for SelectToolFsmState { SelectToolFsmState::Ready } (SelectToolFsmState::Drawing { selection_shape, .. }, SelectToolMessage::DragStop { remove_from_selection }) => { - let quad = tool_data.selection_quad(); + let quad = Quad::from_box(tool_data.drag.start_current_viewport(document.metadata())); - let selection_mode = match tool_action_data.preferences.get_selection_mode() { - SelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(), - selection_mode => selection_mode, - }; + let selection_mode = tool_data.drag.update_selection_mode(document.metadata(), tool_action_data.preferences); let intersection: Vec = match selection_shape { SelectionShapeType::Box => document.intersect_quad_no_artboards(quad, input).collect(), diff --git a/editor/src/messages/tool/tool_messages/select_tool/duplicate.rs b/editor/src/messages/tool/tool_messages/select_tool/duplicate.rs new file mode 100644 index 0000000000..0babc4d02a --- /dev/null +++ b/editor/src/messages/tool/tool_messages/select_tool/duplicate.rs @@ -0,0 +1,105 @@ +use super::super::tool_prelude::*; +use super::DragState; +use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeTemplate}; +use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes; +use crate::messages::tool::tool_messages::select_tool::LayerNodeIdentifier; +use crate::messages::tool::tool_messages::select_tool::TransformIn; +use graphene_std::uuid::NodeId; + +#[derive(Clone, Debug, Default)] +pub struct DuplcateState { + pub non_duplicated_layers: Option>, +} + +impl DuplcateState { + pub fn reset(&mut self) { + self.non_duplicated_layers = None; + } + + pub fn set_duplicating(&mut self, state: bool, layers_dragging: &mut Vec, document: &mut DocumentMessageHandler, dragging: &DragState, responses: &mut VecDeque) { + if !state && self.non_duplicated_layers.is_some() { + self.stop_duplicates(layers_dragging, document, dragging, responses); + } else if state && self.non_duplicated_layers.is_none() { + self.start_duplicates(layers_dragging, document, dragging, responses); + } + } + + /// Duplicates the currently dragging layers. Called when Alt is pressed and the layers have not yet been duplicated. + fn start_duplicates(&mut self, layers_dragging: &mut Vec, document: &mut DocumentMessageHandler, dragging: &DragState, responses: &mut VecDeque) { + let mut new_dragging = Vec::new(); + + // Get the shallowest unique layers and sort by their index relative to parent for ordered processing + let layers = document.network_interface.shallowest_unique_layers(&[]).collect::>(); + + for layer in layers.into_iter().rev() { + let Some(parent) = layer.parent(document.metadata()) else { continue }; + + // Moves the layer back to its starting position. + responses.add(GraphOperationMessage::TransformChange { + layer, + transform: DAffine2::from_translation(dragging.inverse_drag_delta_viewport(document.metadata())), + transform_in: TransformIn::Viewport, + skip_rerender: true, + }); + + // Copy the layer + let mut copy_ids = HashMap::new(); + let node_id = layer.to_node(); + copy_ids.insert(node_id, NodeId(0)); + + document + .network_interface + .upstream_flow_back_from_nodes(vec![layer.to_node()], &[], FlowType::LayerChildrenUpstreamFlow) + .enumerate() + .for_each(|(index, node_id)| { + copy_ids.insert(node_id, NodeId((index + 1) as u64)); + }); + + let nodes = document.network_interface.copy_nodes(©_ids, &[]).collect::>(); + + let insert_index = DocumentMessageHandler::get_calculated_insert_index(document.metadata(), &SelectedNodes(vec![layer.to_node()]), parent); + + let new_ids: HashMap<_, _> = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect(); + + let layer_id = *new_ids.get(&NodeId(0)).expect("Node Id 0 should be a layer"); + let layer = LayerNodeIdentifier::new_unchecked(layer_id); + new_dragging.push(layer); + responses.add(NodeGraphMessage::AddNodes { nodes, new_ids }); + responses.add(NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index }); + } + let nodes = new_dragging.iter().filter(|&&layer| layer != LayerNodeIdentifier::ROOT_PARENT).map(|layer| layer.to_node()).collect(); + responses.add(NodeGraphMessage::SelectedNodesSet { nodes }); + responses.add(NodeGraphMessage::RunDocumentGraph); + self.non_duplicated_layers = Some(core::mem::replace(layers_dragging, new_dragging)); + } + + /// Removes the duplicated layers. Called when Alt is released and the layers have previously been duplicated. + fn stop_duplicates(&mut self, layers_dragging: &mut Vec, document: &DocumentMessageHandler, dragging: &DragState, responses: &mut VecDeque) { + let Some(original) = self.non_duplicated_layers.take() else { + return; + }; + + // Delete the duplicated layers + for layer in document.network_interface.shallowest_unique_layers(&[]) { + responses.add(NodeGraphMessage::DeleteNodes { + node_ids: vec![layer.to_node()], + delete_children: true, + }); + } + + for &layer in &original { + responses.add(GraphOperationMessage::TransformChange { + layer, + transform: DAffine2::from_translation(dragging.total_drag_delta_viewport(document.metadata())), + transform_in: TransformIn::Viewport, + skip_rerender: true, + }); + } + let nodes = original.iter().filter(|&&layer| layer != LayerNodeIdentifier::ROOT_PARENT).map(|layer| layer.to_node()).collect(); + responses.add(NodeGraphMessage::SelectedNodesSet { nodes }); + responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(NodeGraphMessage::SelectedNodesUpdated); + responses.add(NodeGraphMessage::SendGraph); + *layers_dragging = original; + } +}