diff --git a/editor/src/messages/portfolio/portfolio_message.rs b/editor/src/messages/portfolio/portfolio_message.rs index 5fadad7d3a..dfd93a3f8a 100644 --- a/editor/src/messages/portfolio/portfolio_message.rs +++ b/editor/src/messages/portfolio/portfolio_message.rs @@ -90,6 +90,9 @@ pub enum PortfolioMessage { PasteSerializedData { data: String, }, + CenterPastedLayers { + layers: Vec, + }, PasteImage { name: Option, image: Image, diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 2afb06d6b5..1b6ab69355 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -10,6 +10,7 @@ use crate::messages::dialog::simple_dialogs; use crate::messages::frontend::utility_types::FrontendDocumentDetails; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::DocumentMessageData; +use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; 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::nodes::SelectedNodes; @@ -18,9 +19,10 @@ use crate::messages::prelude::*; use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType}; use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor}; use bezier_rs::Subpath; -use glam::IVec2; +use glam::{DAffine2, DVec2, IVec2}; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput}; +use graphene_core::renderer::Quad; use graphene_core::text::{Font, TypesettingConfig}; use graphene_std::vector::style::{Fill, FillType, Gradient}; use graphene_std::vector::{VectorData, VectorDataTable}; @@ -1003,6 +1005,7 @@ impl MessageHandler> for PortfolioMes if let Some(document) = self.active_document() { if let Ok(data) = serde_json::from_str::>(&data) { let parent = document.new_layer_parent(false); + let mut layers = Vec::new(); let mut added_nodes = false; @@ -1012,16 +1015,126 @@ impl MessageHandler> for PortfolioMes responses.add(DocumentMessage::AddTransaction); added_nodes = true; } + document.load_layer_resources(responses); let new_ids: HashMap<_, _> = entry.nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect(); let layer = LayerNodeIdentifier::new_unchecked(new_ids[&NodeId(0)]); responses.add(NodeGraphMessage::AddNodes { nodes: entry.nodes, new_ids }); responses.add(NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index: 0 }); + layers.push(layer); } + responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(Message::StartBuffer); + responses.add(PortfolioMessage::CenterPastedLayers { layers }); } } } + PortfolioMessage::CenterPastedLayers { layers } => { + if let Some(document) = self.active_document_mut() { + let viewport_bounds_quad_pixels = Quad::from_box([DVec2::ZERO, ipp.viewport_bounds.size()]); + let viewport_center_pixels = viewport_bounds_quad_pixels.center(); // In viewport pixel coordinates + + let doc_to_viewport_transform = document.metadata().document_to_viewport; + let viewport_to_doc_transform = doc_to_viewport_transform.inverse(); + + let viewport_quad_doc_space = viewport_to_doc_transform * viewport_bounds_quad_pixels; + + let mut top_level_items_to_center: Vec = Vec::new(); + let mut artboards_in_selection: Vec = Vec::new(); + + for &layer_id in &layers { + if document.network_interface.is_artboard(&layer_id.to_node(), &document.node_graph_handler.network) { + artboards_in_selection.push(layer_id); + } + } + + for &layer_id in &layers { + let is_child_of_selected_artboard = artboards_in_selection.iter().any(|&artboard_id| { + if layer_id == artboard_id { + return false; + } + layer_id.ancestors(document.metadata()).any(|ancestor| ancestor == artboard_id) + }); + + if !is_child_of_selected_artboard { + top_level_items_to_center.push(layer_id); + } + } + + if top_level_items_to_center.is_empty() { + return; + } + + let mut combined_min_doc = DVec2::MAX; + let mut combined_max_doc = DVec2::MIN; + let mut has_any_bounds = false; + + for &item_id in &top_level_items_to_center { + if let Some(bounds_doc) = document.metadata().bounding_box_document(item_id) { + combined_min_doc = combined_min_doc.min(bounds_doc[0]); + combined_max_doc = combined_max_doc.max(bounds_doc[1]); + has_any_bounds = true; + } + } + + if !has_any_bounds { + return; + } + + let combined_bounds_doc_quad = Quad::from_box([combined_min_doc, combined_max_doc]); + + if combined_bounds_doc_quad.intersects(viewport_quad_doc_space) { + return; + } + + let combined_center_doc = combined_bounds_doc_quad.center(); + let combined_center_viewport_pixels = doc_to_viewport_transform.transform_point2(combined_center_doc); + let translation_viewport_pixels_rounded = (viewport_center_pixels - combined_center_viewport_pixels).round(); + + let final_translation_offset_doc = viewport_to_doc_transform.transform_vector2(translation_viewport_pixels_rounded); + + if final_translation_offset_doc.abs_diff_eq(glam::DVec2::ZERO, 1e-9) { + return; + } + + responses.add(DocumentMessage::AddTransaction); + + for &item_id in &top_level_items_to_center { + if document.network_interface.is_artboard(&item_id.to_node(), &document.node_graph_handler.network) { + if let Some(bounds_doc) = document.metadata().bounding_box_document(item_id) { + let current_artboard_origin_doc = bounds_doc[0]; + let dimensions_doc = bounds_doc[1] - bounds_doc[0]; + let new_artboard_origin_doc = current_artboard_origin_doc + final_translation_offset_doc; + + responses.add(GraphOperationMessage::ResizeArtboard { + layer: item_id, + location: new_artboard_origin_doc.round().as_ivec2(), + dimensions: dimensions_doc.round().as_ivec2(), + }); + } + } else { + let current_abs_doc_transform = document.metadata().transform_to_document(item_id); + + let new_abs_doc_transform = DAffine2 { + matrix2: current_abs_doc_transform.matrix2, + translation: current_abs_doc_transform.translation + final_translation_offset_doc, + }; + + let transform = doc_to_viewport_transform * new_abs_doc_transform; + + responses.add(GraphOperationMessage::TransformSet { + layer: item_id, + transform, + transform_in: TransformIn::Viewport, + skip_rerender: false, + }); + } + } + + responses.add(NodeGraphMessage::RunDocumentGraph); + } + } PortfolioMessage::PasteImage { name, image,