Transfer pasted layers to the viewport center when outside the current view area (#2306)

* Transfer pasted layers to viewport center when not in viewport

Fixes #2301

* Move a layer as a whole and use change

* Make a bbox and move content according to that

* partial fix

* Only move if none within viewport

* Fix import error

* Fix artboard

* tmp, skip ones being added to ab

* fix stuff

* fix formatting

* Code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
mTvare 2025-05-20 06:46:06 +05:30 committed by GitHub
parent 6c1ccfb5bb
commit 9236bfcec0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 117 additions and 1 deletions

View file

@ -90,6 +90,9 @@ pub enum PortfolioMessage {
PasteSerializedData {
data: String,
},
CenterPastedLayers {
layers: Vec<LayerNodeIdentifier>,
},
PasteImage {
name: Option<String>,
image: Image<Color>,

View file

@ -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<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
if let Some(document) = self.active_document() {
if let Ok(data) = serde_json::from_str::<Vec<CopyBufferEntry>>(&data) {
let parent = document.new_layer_parent(false);
let mut layers = Vec::new();
let mut added_nodes = false;
@ -1012,16 +1015,126 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> 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<LayerNodeIdentifier> = Vec::new();
let mut artboards_in_selection: Vec<LayerNodeIdentifier> = 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,