@@ -4,7 +4,9 @@ use super::utility_types::misc::{GroupFolderType, SNAP_FUNCTIONS_FOR_BOUNDING_BO
44use super :: utility_types:: network_interface:: { self , NodeNetworkInterface , TransactionStatus } ;
55use super :: utility_types:: nodes:: { CollapsedLayers , LayerStructureEntry , SelectedNodes } ;
66use crate :: application:: { GRAPHITE_GIT_COMMIT_HASH , generate_uuid} ;
7- use crate :: consts:: { ASYMPTOTIC_EFFECT , COLOR_OVERLAY_GRAY , DEFAULT_DOCUMENT_NAME , FILE_EXTENSION , SCALE_EFFECT , SCROLLBAR_SPACING , VIEWPORT_ROTATE_SNAP_INTERVAL } ;
7+ use crate :: consts:: {
8+ ASYMPTOTIC_EFFECT , COLOR_OVERLAY_GRAY , DEFAULT_DOCUMENT_NAME , FILE_EXTENSION , LAYER_INDENT_OFFSET , NODE_CHAIN_WIDTH , SCALE_EFFECT , SCROLLBAR_SPACING , VIEWPORT_ROTATE_SNAP_INTERVAL ,
9+ } ;
810use crate :: messages:: input_mapper:: utility_types:: macros:: action_shortcut;
911use crate :: messages:: layout:: utility_types:: widget_prelude:: * ;
1012use crate :: messages:: portfolio:: document:: data_panel:: { DataPanelMessageContext , DataPanelMessageHandler } ;
@@ -658,29 +660,35 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
658660 image,
659661 mouse,
660662 parent_and_insert_index,
663+ place_at_origin,
661664 } => {
662665 // All the image's pixels have been converted to 0..=1, linear, and premultiplied by `Color::from_rgba8_srgb`
663666
667+ let layer_parent = self . new_layer_parent ( true ) ;
664668 let image_size = DVec2 :: new ( image. width as f64 , image. height as f64 ) ;
665669
666- // Align the layer with the mouse or center of viewport
667- let viewport_location = mouse. map_or ( viewport. center_in_viewport_space ( ) . into_dvec2 ( ) + viewport. offset ( ) . into_dvec2 ( ) , |pos| pos. into ( ) ) ;
668-
669- let document_to_viewport = self . navigation_handler . calculate_offset_transform ( viewport. center_in_viewport_space ( ) . into ( ) , & self . document_ptz ) ;
670- let center_in_viewport = DAffine2 :: from_translation ( document_to_viewport. inverse ( ) . transform_point2 ( viewport_location - viewport. offset ( ) . into_dvec2 ( ) ) ) ;
671- let center_in_viewport_layerspace = center_in_viewport;
672-
673- // Make layer the size of the image
674- let fit_image_size = DAffine2 :: from_scale_angle_translation ( image_size, 0. , image_size / -2. ) ;
675-
676- let transform = center_in_viewport_layerspace * fit_image_size;
670+ let mut transform = if place_at_origin {
671+ // File-open flow: place at document origin without centering so `WrapContentInArtboard` can wrap it
672+ DAffine2 :: from_scale ( image_size)
673+ } else {
674+ // Clipboard paste or drag-drop: center at cursor or viewport center.
675+ // Convert the document-space cursor to the parent's local coordinate space so that
676+ // an artboard at a non-zero position does not offset the placement.
677+ let parent_to_document = {
678+ let metadata = self . metadata ( ) ;
679+ metadata. document_to_viewport . inverse ( ) * metadata. transform_to_viewport ( layer_parent)
680+ } ;
681+ let cursor_in_parent = parent_to_document. inverse ( ) * self . document_transform_from_mouse ( mouse, viewport) ;
682+ cursor_in_parent * DAffine2 :: from_scale_angle_translation ( image_size, 0. , image_size / -2. )
683+ } ;
684+ transform. translation = transform. translation . round ( ) ;
677685
678686 let layer_node_id = NodeId :: new ( ) ;
679687 let layer_id = LayerNodeIdentifier :: new_unchecked ( layer_node_id) ;
680688
681689 responses. add ( DocumentMessage :: AddTransaction ) ;
682690
683- let layer = graph_modification_utils:: new_image_layer ( Table :: new_from_element ( Raster :: new_cpu ( image) ) , layer_node_id, self . new_layer_parent ( true ) , responses) ;
691+ let layer = graph_modification_utils:: new_image_layer ( Table :: new_from_element ( Raster :: new_cpu ( image) ) , layer_node_id, layer_parent , responses) ;
684692
685693 if let Some ( name) = name {
686694 responses. add ( NodeGraphMessage :: SetDisplayName {
@@ -715,17 +723,29 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
715723 svg,
716724 mouse,
717725 parent_and_insert_index,
726+ place_at_origin,
718727 } => {
719- let document_to_viewport = self . navigation_handler . calculate_offset_transform ( viewport. center_in_viewport_space ( ) . into ( ) , & self . document_ptz ) ;
720- let viewport_location = mouse. map_or ( viewport. center_in_viewport_space ( ) . into_dvec2 ( ) + viewport. offset ( ) . into_dvec2 ( ) , |pos| pos. into ( ) ) ;
721- let center_in_viewport = DAffine2 :: from_translation ( document_to_viewport. inverse ( ) . transform_point2 ( viewport_location - viewport. offset ( ) . into_dvec2 ( ) ) ) ;
728+ let layer_parent = self . new_layer_parent ( true ) ;
729+ let transform = if place_at_origin {
730+ // File-open flow: place at document origin so `WrapContentInArtboard` can wrap it without extra Transform nodes
731+ DAffine2 :: IDENTITY
732+ } else {
733+ // Clipboard paste or drag-drop: center at cursor or viewport center.
734+ // Convert the document-space cursor to the parent's local coordinate space so that
735+ // an artboard at a non-zero position does not offset the placement.
736+ let parent_to_document = {
737+ let metadata = self . metadata ( ) ;
738+ metadata. document_to_viewport . inverse ( ) * metadata. transform_to_viewport ( layer_parent)
739+ } ;
740+ parent_to_document. inverse ( ) * self . document_transform_from_mouse ( mouse, viewport)
741+ } ;
722742
723743 let layer_node_id = NodeId :: new ( ) ;
724744 let layer_id = LayerNodeIdentifier :: new_unchecked ( layer_node_id) ;
725745
726746 responses. add ( DocumentMessage :: AddTransaction ) ;
727747
728- let layer = graph_modification_utils:: new_svg_layer ( svg, center_in_viewport , layer_node_id, self . new_layer_parent ( true ) , responses) ;
748+ let layer = graph_modification_utils:: new_svg_layer ( svg, transform , !place_at_origin , layer_node_id, layer_parent , responses) ;
729749
730750 if let Some ( name) = name {
731751 responses. add ( NodeGraphMessage :: SetDisplayName {
@@ -1347,27 +1367,46 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
13471367 self . network_interface . selection_step_forward ( & self . selection_network_path ) ;
13481368 responses. add ( EventMessage :: SelectionChanged ) ;
13491369 }
1350- DocumentMessage :: WrapContentInArtboard { place_artboard_at_origin } => {
1351- // Get bounding box of all layers
1370+ DocumentMessage :: WrapContentInArtboard {
1371+ place_artboard_at_origin,
1372+ artboard_canvas,
1373+ } => {
1374+ // Get bounding box of all layers (always needed to confirm there is content)
13521375 let bounds = self . network_interface . document_bounds_document_space ( false ) ;
13531376 let Some ( bounds) = bounds else { return } ;
1354- let bounds_rounded_dimensions = ( bounds[ 1 ] - bounds[ 0 ] ) . round ( ) ;
1377+
1378+ // When artboard_canvas is provided (SVG file-open flow), use the declared canvas origin and dimensions;
1379+ // no content-shift Transform node needed since the SVG was already placed at its natural coordinates.
1380+ let ( artboard_location, artboard_dimensions, content_shift) = if let Some ( ( origin, dimensions) ) = artboard_canvas {
1381+ ( origin, dimensions, DVec2 :: ZERO )
1382+ } else {
1383+ // No declared canvas (image or clipboard paste): derive location and dimensions from the content bounding box.
1384+ let location = if place_artboard_at_origin { IVec2 :: ZERO } else { bounds[ 0 ] . round ( ) . as_ivec2 ( ) } ;
1385+ ( location, ( bounds[ 1 ] - bounds[ 0 ] ) . round ( ) . as_ivec2 ( ) , -bounds[ 0 ] . round ( ) )
1386+ } ;
13551387
13561388 // Create an artboard and set its dimensions to the bounding box size and location
13571389 let node_id = NodeId :: new ( ) ;
13581390 let node_layer_id = LayerNodeIdentifier :: new_unchecked ( node_id) ;
13591391 let new_artboard_node = document_node_definitions:: resolve_network_node_type ( "Artboard" )
13601392 . expect ( "Failed to create artboard node" )
1361- . default_node_template ( ) ;
1393+ // Enable clipping by default (input index 5) so imported content is masked to the artboard bounds
1394+ . node_template_input_override ( [ None , None , None , None , None , Some ( NodeInput :: value ( TaggedValue :: Bool ( true ) , false ) ) ] ) ;
13621395 responses. add ( NodeGraphMessage :: InsertNode {
13631396 node_id,
13641397 node_template : Box :: new ( new_artboard_node) ,
13651398 } ) ;
1366- responses. add ( NodeGraphMessage :: ShiftNodePosition { node_id, x : 15 , y : -3 } ) ;
1399+ let needs_content_transform = !content_shift. abs_diff_eq ( DVec2 :: ZERO , 1e-6 ) ;
1400+ // With a content Transform node: shift by the layer indent plus the node width. Without: use just the layer indent.
1401+ responses. add ( NodeGraphMessage :: ShiftNodePosition {
1402+ node_id,
1403+ x : if needs_content_transform { LAYER_INDENT_OFFSET + NODE_CHAIN_WIDTH } else { LAYER_INDENT_OFFSET } ,
1404+ y : -3 ,
1405+ } ) ;
13671406 responses. add ( GraphOperationMessage :: ResizeArtboard {
13681407 layer : LayerNodeIdentifier :: new_unchecked ( node_id) ,
1369- location : if place_artboard_at_origin { IVec2 :: ZERO } else { bounds [ 0 ] . round ( ) . as_ivec2 ( ) } ,
1370- dimensions : bounds_rounded_dimensions . as_ivec2 ( ) ,
1408+ location : artboard_location ,
1409+ dimensions : artboard_dimensions ,
13711410 } ) ;
13721411
13731412 // Connect the current output data to the artboard's input data, and the artboard's output to the document output
@@ -1377,10 +1416,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
13771416 insert_node_input_index : 1 ,
13781417 } ) ;
13791418
1380- // Shift the content by half its width and height so it gets centered in the artboard
1419+ // Shift the content to align its top-left to the artboard's origin (no-op when content is already at origin)
13811420 responses. add ( GraphOperationMessage :: TransformChange {
13821421 layer : node_layer_id,
1383- transform : DAffine2 :: from_translation ( bounds_rounded_dimensions / 2. ) ,
1422+ transform : DAffine2 :: from_translation ( content_shift ) ,
13841423 transform_in : TransformIn :: Local ,
13851424 skip_rerender : false ,
13861425 } ) ;
@@ -1474,6 +1513,13 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
14741513}
14751514
14761515impl DocumentMessageHandler {
1516+ /// Translates a viewport mouse position to a document-space transform, or uses the viewport center if no mouse position is given.
1517+ fn document_transform_from_mouse ( & self , mouse : Option < ( f64 , f64 ) > , viewport : & ViewportMessageHandler ) -> DAffine2 {
1518+ let viewport_pos: DVec2 = mouse. map_or_else ( || viewport. center_in_viewport_space ( ) . into_dvec2 ( ) + viewport. offset ( ) . into_dvec2 ( ) , |pos| pos. into ( ) ) ;
1519+ let document_to_viewport = self . navigation_handler . calculate_offset_transform ( viewport. center_in_viewport_space ( ) . into ( ) , & self . document_ptz ) ;
1520+ DAffine2 :: from_translation ( document_to_viewport. inverse ( ) . transform_point2 ( viewport_pos - viewport. offset ( ) . into_dvec2 ( ) ) )
1521+ }
1522+
14771523 /// Runs an intersection test with all layers and a viewport space quad
14781524 pub fn intersect_quad < ' a > ( & ' a self , viewport_quad : graphene_std:: renderer:: Quad , viewport : & ViewportMessageHandler ) -> impl Iterator < Item = LayerNodeIdentifier > + use < ' a > {
14791525 let document_to_viewport = self . navigation_handler . calculate_offset_transform ( viewport. center_in_viewport_space ( ) . into ( ) , & self . document_ptz ) ;
0 commit comments