diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 6b0fabbf..2233f10f 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -398,12 +398,29 @@ impl MessageHandler> for DocumentMessageHand responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer }); } DuplicateSelectedLayers => { - // TODO: Reimplement selected layer duplication - // self.backup(responses); - // self.layer_range_selection_reference = None; - // for path in self.selected_layers_sorted() { - // responses.add(DocumentOperation::DuplicateLayer { path: path.to_vec() }); - // } + self.backup(responses); + for layer_ancestors in self.metadata.shallowest_unique_layers(self.selected_nodes.selected_layers(&self.metadata)) { + let Some(layer) = layer_ancestors.last().copied() else { continue }; + let Some(parent) = layer.parent(&self.metadata) else { continue }; + let Some(node) = self.network().nodes.get(&layer.to_node()).and_then(|node| node.inputs.first()).and_then(|input| input.as_node()) else { + continue; + }; + + let nodes = NodeGraphMessageHandler::copy_nodes( + self.network(), + &self + .network() + .upstream_flow_back_from_nodes(vec![node], false) + .enumerate() + .map(|(index, (_, node_id))| (node_id, NodeId(index as u64))) + .collect(), + ) + .collect(); + + let id = NodeId(generate_uuid()); + let insert_index = -1; + responses.add(GraphOperationMessage::NewCustomLayer { id, nodes, parent, insert_index }); + } } FlipSelectedLayers { flip_axis } => { self.backup(responses); diff --git a/editor/src/messages/tool/common_functionality/transformation_cage.rs b/editor/src/messages/tool/common_functionality/transformation_cage.rs index 71f087fc..296608ae 100644 --- a/editor/src/messages/tool/common_functionality/transformation_cage.rs +++ b/editor/src/messages/tool/common_functionality/transformation_cage.rs @@ -86,33 +86,20 @@ impl SelectedEdges { let mut pivot = self.pivot_from_bounds(min, max); if let Some(center_around) = center_around { - // The below ratio is: `dragging edge / being centered`. - // The `is_finite()` checks are in case the user is dragging the edge where the pivot is located (in which case the centering mode is ignored). + let center_around = transform.inverse().transform_point2(center_around); if self.top { - let ratio = (center_around.y - min.y) / (center_around.y - self.bounds[0].y); - if ratio.is_finite() { - max.y = center_around.y + ratio * (self.bounds[1].y - center_around.y); - pivot.y = center_around.y; - } + pivot.y = center_around.y; + max.y = center_around.y * 2. - min.y; } else if self.bottom { - let ratio = (max.y - center_around.y) / (self.bounds[1].y - center_around.y); - if ratio.is_finite() { - min.y = center_around.y - ratio * (center_around.y - self.bounds[0].y); - pivot.y = center_around.y; - } + pivot.y = center_around.y; + min.y = center_around.y * 2. - max.y; } if self.left { - let ratio = (center_around.x - min.x) / (center_around.x - self.bounds[0].x); - if ratio.is_finite() { - max.x = center_around.x + ratio * (self.bounds[1].x - center_around.x); - pivot.x = center_around.x; - } + pivot.x = center_around.x; + max.x = center_around.x * 2. - min.x; } else if self.right { - let ratio = (max.x - center_around.x) / (self.bounds[1].x - center_around.x); - if ratio.is_finite() { - min.x = center_around.x - ratio * (center_around.x - self.bounds[0].x); - pivot.x = center_around.x; - } + pivot.x = center_around.x; + min.x = center_around.x * 2. - max.x; } } @@ -128,7 +115,9 @@ impl SelectedEdges { let delta_size = new_size - size; min -= delta_size * min_pivot; max = min + new_size; - } else if let Some(SizeSnapData { manager, points, snap_data }) = snap { + } + + if let Some(SizeSnapData { manager, points, snap_data }) = snap { let view_to_doc = snap_data.document.metadata.document_to_viewport.inverse(); let bounds_to_doc = view_to_doc * transform; let mut best_snap = SnappedPoint::infinite_snap(pivot); @@ -140,7 +129,13 @@ impl SelectedEdges { let normalised = (bounds_space - self.bounds[0]) / (self.bounds[1] - self.bounds[0]); let updated = normalised * (max - min) + min; point.document_point = bounds_to_doc.transform_point2(updated); - let mut snapped = if !(self.top || self.bottom) || !(self.left || self.right) { + let mut snapped = if constrain { + let constraint = SnapConstraint::Line { + origin: point.document_point, + direction: (point.document_point - bounds_to_doc.transform_point2(pivot)).normalize_or_zero(), + }; + manager.constrained_snap(&snap_data, point, constraint, None) + } else if !(self.top || self.bottom) || !(self.left || self.right) { let axis = if !(self.top || self.bottom) { DVec2::X } else { DVec2::Y }; let constraint = SnapConstraint::Line { origin: point.document_point, @@ -158,10 +153,10 @@ impl SelectedEdges { let snapped_bounds = bounds_to_doc.inverse().transform_point2(snapped.snapped_point_document); let mut scale_factor = (snapped_bounds - pivot) / (updated - pivot); - if !(self.left || self.right) { + if !(self.left || self.right) && !constrain { scale_factor.x = 1. } - if !(self.top || self.bottom) { + if !(self.top || self.bottom) && !constrain { scale_factor.y = 1. } diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index 4f22b095..15a3df8e 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -115,7 +115,7 @@ impl ArtboardToolData { fn start_resizing(&mut self, _selected_edges: (bool, bool, bool, bool), _document: &DocumentMessageHandler, _input: &InputPreprocessorMessageHandler) { if let Some(bounds) = &mut self.bounding_box_manager { - bounds.center_of_transformation = (bounds.bounds[0] + bounds.bounds[1]) / 2.; + bounds.center_of_transformation = bounds.transform.transform_point2((bounds.bounds[0] + bounds.bounds[1]) / 2.); } } diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index f82742cc..f447e9f2 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -1,18 +1,20 @@ #![allow(clippy::too_many_arguments)] use super::tool_prelude::*; +use crate::application::generate_uuid; use crate::consts::{ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE}; use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis}; use crate::messages::portfolio::document::utility_types::transformation::Selected; +use crate::messages::tool; use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name; use crate::messages::tool::common_functionality::pivot::Pivot; use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnappedPoint}; use crate::messages::tool::common_functionality::transformation_cage::*; -use graph_craft::document::NodeNetwork; +use graph_craft::document::{NodeId, NodeNetwork}; use graphene_core::renderer::Quad; use std::fmt; @@ -96,21 +98,21 @@ impl ToolMetadata for SelectTool { } impl SelectTool { - // fn deep_selection_widget(&self) -> WidgetHolder { - // let layer_selection_behavior_entries = [NestedSelectionBehavior::Deepest, NestedSelectionBehavior::Shallowest] - // .iter() - // .map(|mode| { - // MenuListEntry::new(mode.to_string()) - // .value(mode.to_string()) - // .on_update(move |_| SelectToolMessage::SelectOptions(SelectOptionsUpdate::NestedSelectionBehavior(*mode)).into()) - // }) - // .collect(); + fn deep_selection_widget(&self) -> WidgetHolder { + let layer_selection_behavior_entries = [NestedSelectionBehavior::Deepest, NestedSelectionBehavior::Shallowest] + .iter() + .map(|mode| { + MenuListEntry::new(mode.to_string()) + .value(mode.to_string()) + .on_update(move |_| SelectToolMessage::SelectOptions(SelectOptionsUpdate::NestedSelectionBehavior(*mode)).into()) + }) + .collect(); - // DropdownInput::new(vec![layer_selection_behavior_entries]) - // .selected_index(Some((self.tool_data.nested_selection_behavior == NestedSelectionBehavior::Shallowest) as u32)) - // .tooltip("Choose if clicking nested layers directly selects the deepest, or selects the shallowest and deepens by double clicking") - // .widget_holder() - // } + DropdownInput::new(vec![layer_selection_behavior_entries]) + .selected_index(Some((self.tool_data.nested_selection_behavior == NestedSelectionBehavior::Shallowest) as u32)) + .tooltip("Choose if clicking nested layers directly selects the deepest, or selects the shallowest and deepens by double clicking") + .widget_holder() + } fn pivot_widget(&self, disabled: bool) -> WidgetHolder { PivotInput::new(self.tool_data.pivot.to_pivot_position()) @@ -163,10 +165,12 @@ impl SelectTool { impl LayoutHolder for SelectTool { fn layout(&self) -> Layout { let mut widgets = Vec::new(); - // widgets.push(self.deep_selection_widget()); // TODO: Reenable once Deep/Shallow Selection is implemented again + + // Select mode (Deep/Shallow) + widgets.push(self.deep_selection_widget()); // Pivot - // widgets.push(Separator::new(SeparatorType::Related).widget_holder()); // TODO: Reenable once Deep/Shallow Selection is implemented again + widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(self.pivot_widget(self.tool_data.selected_layers_count == 0)); // Align @@ -179,13 +183,11 @@ impl LayoutHolder for SelectTool { let disabled = self.tool_data.selected_layers_count == 0; widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.extend(self.flip_widgets(disabled)); - widgets.push(PopoverButton::new("Flip", "Coming soon").disabled(disabled).widget_holder()); // Boolean if self.tool_data.selected_layers_count >= 2 { widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.extend(self.boolean_widgets()); - widgets.push(PopoverButton::new("Boolean", "Coming soon").widget_holder()); } Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) @@ -259,7 +261,7 @@ struct SelectToolData { layer_selected_on_start: Option, select_single_layer: Option, has_dragged: bool, - not_duplicated_layers: Option>, + non_duplicated_layers: Option>, bounding_box_manager: Option, snap_manager: SnapManager, cursor: MouseCursorIcon, @@ -300,57 +302,54 @@ impl SelectToolData { /// Duplicates the currently dragging layers. Called when Alt is pressed and the layers have not yet been duplicated. fn start_duplicates(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque) { - responses.add(DocumentMessage::DeselectAllLayers); + self.non_duplicated_layers = Some(self.layers_dragging.clone()); + let mut new_dragging = Vec::new(); + for layer_ancestors in document.metadata().shallowest_unique_layers(self.layers_dragging.iter().copied().rev()) { + let Some(layer) = layer_ancestors.last().copied() else { continue }; + let Some(parent) = layer.parent(&document.metadata) else { continue }; - // Take the selected layers and store them in a separate list. - self.not_duplicated_layers = Some(self.layers_dragging.clone()); + // Copy the layer + let node = layer.to_node(); + let Some(node) = document.network().nodes.get(&node).and_then(|node| node.inputs.first()).and_then(|input| input.as_node()) else { + continue; + }; - // Duplicate each previously selected layer and select the new ones. - for layer_ancestors in document.metadata().shallowest_unique_layers(self.layers_dragging.iter().copied()) { - let layer = *layer_ancestors.last().unwrap(); - // Moves the original back to its starting position. - responses.add_front(GraphOperationMessage::TransformChange { + let nodes = NodeGraphMessageHandler::copy_nodes( + document.network(), + &document + .network() + .upstream_flow_back_from_nodes(vec![node], false) + .enumerate() + .map(|(index, (_, node_id))| (node_id, NodeId(index as u64))) + .collect(), + ) + .collect(); + + // 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 layers. - // Not using the Copy message allows us to retrieve the ids of the new layers to initialize the drag. - todo!(); - // let layer = match document.document_legacy.layer(layer_path) { - // Ok(layer) => layer.clone(), - // Err(e) => { - // warn!("Could not access selected layer {layer_path:?}: {e:?}"); - // continue; - // } - // }; - - // let layer_metadata = *document.layer_metadata(layer_path); - // *layer_path.last_mut().unwrap() = generate_uuid(); - - // responses.add(Operation::InsertLayer { - // layer: Box::new(layer), - // destination_path: layer_path.clone(), - // insert_index: -1, - // duplicating: false, - // }); - // responses.add(DocumentMessage::UpdateLayerMetadata { - // layer_path: layer_path.clone(), - // layer_metadata, - // }); + let id = NodeId(generate_uuid()); + let insert_index = -1; + let layer = LayerNodeIdentifier::new_unchecked(id); + responses.add(GraphOperationMessage::NewCustomLayer { id, nodes, parent, insert_index }); + new_dragging.push(layer); } + let nodes = new_dragging.iter().map(|layer| layer.to_node()).collect(); + responses.add(NodeGraphMessage::SelectedNodesSet { nodes }); + 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(originals) = self.not_duplicated_layers.take() else { + let Some(original) = self.non_duplicated_layers.take() else { return; }; - responses.add(DocumentMessage::DeselectAllLayers); - // Delete the duplicated layers for layer_ancestors in document.metadata().shallowest_unique_layers(self.layers_dragging.iter().copied()) { responses.add(GraphOperationMessage::DeleteLayer { @@ -358,22 +357,17 @@ impl SelectToolData { }); } - // Move the original to under the mouse - for layer_ancestors in document.metadata().shallowest_unique_layers(originals.iter().copied()) { - responses.add_front(GraphOperationMessage::TransformChange { - layer: *layer_ancestors.last().unwrap(), + 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, }); } - - // Select the originals - responses.add(NodeGraphMessage::SelectedNodesSet { - nodes: originals.iter().map(|layer| layer.to_node()).collect::>(), - }); - - self.layers_dragging = originals; + let nodes = original.iter().map(|layer| layer.to_node()).collect(); + responses.add(NodeGraphMessage::SelectedNodesSet { nodes }); + self.layers_dragging = original; } } @@ -455,7 +449,7 @@ impl Fsm for SelectToolFsmState { self } - (SelectToolFsmState::Ready, SelectToolMessage::DragStart { add_to_selection, select_deepest: _ }) => { + (SelectToolFsmState::Ready, SelectToolMessage::DragStart { add_to_selection, select_deepest }) => { tool_data.drag_start = input.mouse.position; tool_data.drag_current = input.mouse.position; @@ -538,11 +532,14 @@ impl Fsm for SelectToolFsmState { tool_data.layers_dragging = selected; SelectToolFsmState::RotatingBounds - } else if intersection.is_some_and(|intersection| selected.iter().any(|selected_layer| intersection.starts_with(*selected_layer, document.metadata()))) - && tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest - { + } else if intersection.is_some_and(|intersection| selected.iter().any(|selected_layer| intersection.starts_with(*selected_layer, document.metadata()))) { responses.add(DocumentMessage::StartTransaction); - tool_data.select_single_layer = intersection; + + if tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest { + tool_data.select_single_layer = intersection; + } else { + tool_data.select_single_layer = intersection.and_then(|intersection| intersection.ancestors(&document.metadata).find(|ancestor| selected.contains(ancestor))); + } tool_data.layers_dragging = selected; @@ -552,7 +549,7 @@ impl Fsm for SelectToolFsmState { } else { tool_data.layers_dragging = selected; - if !input.keyboard.key(add_to_selection) && tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest { + if !input.keyboard.key(add_to_selection) { responses.add(DocumentMessage::DeselectAllLayers); tool_data.layers_dragging.clear(); } @@ -564,8 +561,8 @@ impl Fsm for SelectToolFsmState { selected = vec![intersection]; match tool_data.nested_selection_behavior { - NestedSelectionBehavior::Shallowest => drag_shallowest_manipulation(responses, selected, tool_data, document), - NestedSelectionBehavior::Deepest => drag_deepest_manipulation(responses, selected, tool_data), + NestedSelectionBehavior::Shallowest if !input.keyboard.key(select_deepest) => drag_shallowest_manipulation(responses, selected, tool_data, document), + _ => drag_deepest_manipulation(responses, selected, tool_data), } tool_data.get_snap_candidates(document, input); SelectToolFsmState::Dragging @@ -579,18 +576,28 @@ impl Fsm for SelectToolFsmState { SelectToolFsmState::DrawingBox } }; - tool_data.not_duplicated_layers = None; + tool_data.non_duplicated_layers = None; state } (SelectToolFsmState::Dragging, SelectToolMessage::PointerMove { axis_align, duplicate, .. }) => { tool_data.has_dragged = true; + if input.keyboard.key(duplicate) && tool_data.non_duplicated_layers.is_none() { + tool_data.start_duplicates(document, responses); + } else if !input.keyboard.key(duplicate) && tool_data.non_duplicated_layers.is_some() { + tool_data.stop_duplicates(document, responses); + } + let axis_align = input.keyboard.key(axis_align); let mouse_position = axis_align_drag(axis_align, input.mouse.position, tool_data.drag_start); let total_mouse_delta_document = document.metadata.document_to_viewport.inverse().transform_vector2(mouse_position - tool_data.drag_start); - let snap_data = SnapData::ignore(document, input, &tool_data.layers_dragging); + // 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_target(layer).is_some()); + let ignore = tool_data.non_duplicated_layers.as_ref().filter(|_| !layers_exist).unwrap_or(&tool_data.layers_dragging); + + let snap_data = SnapData::ignore(document, input, ignore); let mouse_delta_document = document.metadata.document_to_viewport.inverse().transform_vector2(mouse_position - tool_data.drag_current); let mut offset = mouse_delta_document; let mut best_snap = SnappedPoint::infinite_snap(document.metadata.document_to_viewport.inverse().transform_point2(mouse_position)); @@ -627,22 +634,12 @@ impl Fsm for SelectToolFsmState { } tool_data.drag_current += mouse_delta; - // TODO: Reenable this feature after fixing it - if false { - if input.keyboard.key(duplicate) && tool_data.not_duplicated_layers.is_none() { - tool_data.start_duplicates(document, responses); - } else if !input.keyboard.key(duplicate) && tool_data.not_duplicated_layers.is_some() { - tool_data.stop_duplicates(document, responses); - } - } - SelectToolFsmState::Dragging } (SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerMove { axis_align, center, .. }) => { if let Some(bounds) = &mut tool_data.bounding_box_manager { if let Some(movement) = &mut bounds.selected_edges { - let (_center, constrain) = (input.keyboard.key(center), input.keyboard.key(axis_align)); - let center = false; // TODO: Reenable this feature after fixing it + let (center, constrain) = (input.keyboard.key(center), input.keyboard.key(axis_align)); let center = center.then_some(bounds.center_of_transformation); let snap = Some(SizeSnapData { @@ -944,21 +941,32 @@ impl Fsm for SelectToolFsmState { } } +fn not_artboard<'a>(document: &'a DocumentMessageHandler) -> impl Fn(&LayerNodeIdentifier) -> bool + 'a { + |&layer| !document.metadata.is_artboard(layer) +} + fn drag_shallowest_manipulation(responses: &mut VecDeque, selected: Vec, tool_data: &mut SelectToolData, document: &DocumentMessageHandler) { - let layer = selected[0]; - let ancestor = layer - .ancestors(document.metadata()) - .find(|&ancestor| document.selected_nodes.selected_layers_contains(ancestor, document.metadata())); + for layer in selected { + let ancestor = layer + .ancestors(document.metadata()) + .filter(not_artboard(document)) + .find(|&ancestor| document.selected_nodes.selected_layers_contains(ancestor, document.metadata())); - let new_selected = ancestor.unwrap_or_else(|| layer.child_of_root(document.metadata())); + let new_selected = ancestor.unwrap_or_else(|| { + layer + .ancestors(document.metadata()) + .take_while(|&layer| layer != LayerNodeIdentifier::ROOT) + .filter(not_artboard(document)) + .last() + .unwrap_or(layer) + }); + tool_data.layers_dragging.retain(|layer| !layer.ancestors(document.metadata()).any(|ancestor| ancestor == new_selected)); + tool_data.layers_dragging.push(new_selected); + } - tool_data.layers_dragging = vec![new_selected]; responses.add(NodeGraphMessage::SelectedNodesSet { nodes: tool_data.layers_dragging.iter().map(|layer| layer.to_node()).collect(), }); - // tool_data - // .snap_manager - // .start_snap(document, input, document.bounding_boxes(Some(&tool_data.layers_dragging), None, font_cache), true, true); } fn drag_deepest_manipulation(responses: &mut VecDeque, mut selected: Vec, tool_data: &mut SelectToolData) { @@ -966,9 +974,6 @@ fn drag_deepest_manipulation(responses: &mut VecDeque, mut selected: Ve responses.add(NodeGraphMessage::SelectedNodesSet { nodes: tool_data.layers_dragging.iter().map(|layer| layer.to_node()).collect(), }); - // tool_data - // .snap_manager - // .start_snap(document, input, document.bounding_boxes(Some(&tool_data.layers_dragging), None, font_cache), true, true); } fn edit_layer_shallowest_manipulation(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, responses: &mut VecDeque) { @@ -977,7 +982,7 @@ fn edit_layer_shallowest_manipulation(document: &DocumentMessageHandler, layer: return; } - let Some(new_selected) = layer.ancestors(document.metadata()).find(|ancestor| { + let Some(new_selected) = layer.ancestors(document.metadata()).filter(not_artboard(document)).find(|ancestor| { ancestor .parent(document.metadata()) .is_some_and(|parent| document.selected_nodes.selected_layers_contains(parent, document.metadata()))