@@ -7,7 +7,7 @@ use crate::messages::portfolio::document::graph_operation::utility_types::Transf
77use crate :: messages:: portfolio:: document:: node_graph:: document_node_definitions:: DefinitionIdentifier ;
88use crate :: messages:: portfolio:: document:: overlays:: utility_types:: OverlayContext ;
99use crate :: messages:: portfolio:: document:: utility_types:: document_metadata:: { DocumentMetadata , LayerNodeIdentifier } ;
10- use crate :: messages:: portfolio:: document:: utility_types:: misc:: { AlignAggregate , AlignAxis , FlipAxis , GroupFolderType } ;
10+ use crate :: messages:: portfolio:: document:: utility_types:: misc:: { AlignAggregate , AlignAxis , BoundingBoxSnapTarget , FlipAxis , GroupFolderType , PathSnapSource , SnapSource , SnapTarget } ;
1111use crate :: messages:: portfolio:: document:: utility_types:: network_interface:: { FlowType , NodeNetworkInterface , NodeTemplate } ;
1212use crate :: messages:: portfolio:: document:: utility_types:: nodes:: SelectedNodes ;
1313use crate :: messages:: preferences:: SelectionMode ;
@@ -592,6 +592,68 @@ pub fn create_bounding_box_transform(document: &DocumentMessageHandler) -> DAffi
592592 . unwrap_or_default ( )
593593}
594594
595+ fn snap_pivot_to_bounds ( document : & DocumentMessageHandler , mouse_position : DVec2 , selection_bounds : & Option < BoundingBoxManager > ) -> Option < snapping:: SnappedPoint > {
596+ let tolerance = snapping:: snap_tolerance ( document) ;
597+ let mut best_distance = f64:: INFINITY ;
598+ let mut best_snap_point: Option < DVec2 > = None ;
599+ let mut best_quad: Option < Quad > = None ;
600+ let mut best_point_type: u8 = 0 ; // 0=corner, 1=midpoint, 2=center
601+
602+ let mut check_snap = |snap_doc : DVec2 , quad : Quad , point_type : u8 | {
603+ let snap_viewport = document. metadata ( ) . document_to_viewport . transform_point2 ( snap_doc) ;
604+ let distance = mouse_position. distance ( snap_viewport) ;
605+ if distance < tolerance && distance < best_distance {
606+ best_distance = distance;
607+ best_snap_point = Some ( snap_doc) ;
608+ best_quad = Some ( quad) ;
609+ best_point_type = point_type;
610+ }
611+ } ;
612+
613+ // Snap to selection's combined bounding box
614+ if let Some ( bounds) = selection_bounds {
615+ let quad = document. metadata ( ) . document_to_viewport . inverse ( ) * bounds. transform * Quad :: from_box ( bounds. bounds ) ;
616+ for i in 0 ..4 {
617+ check_snap ( quad. 0 [ i] , quad, 0 ) ;
618+ check_snap ( ( quad. 0 [ i] + quad. 0 [ ( i + 1 ) % 4 ] ) / 2.0 , quad, 1 ) ;
619+ }
620+ check_snap ( quad. center ( ) , quad, 2 ) ;
621+ }
622+
623+ let selected: Vec < _ > = document. network_interface . selected_nodes ( ) . selected_visible_and_unlocked_layers ( & document. network_interface ) . collect ( ) ;
624+ for layer in document. metadata ( ) . all_layers ( ) {
625+ if selected. contains ( & layer) || !document. network_interface . is_visible ( & layer. to_node ( ) , & [ ] ) {
626+ continue ;
627+ }
628+ let Some ( bounds) = document. metadata ( ) . bounding_box_with_transform ( layer, document. metadata ( ) . transform_to_document ( layer) ) else {
629+ continue ;
630+ } ;
631+ let quad = Quad :: from_box ( bounds) ;
632+ for i in 0 ..4 {
633+ check_snap ( quad. 0 [ i] , quad, 0 ) ;
634+ check_snap ( ( quad. 0 [ i] + quad. 0 [ ( i + 1 ) % 4 ] ) / 2.0 , quad, 1 ) ;
635+ }
636+ check_snap ( quad. center ( ) , quad, 2 ) ;
637+ }
638+
639+ best_snap_point. zip ( best_quad) . map ( |( snap_point, quad) | {
640+ let target = match best_point_type {
641+ 0 => SnapTarget :: BoundingBox ( BoundingBoxSnapTarget :: CornerPoint ) ,
642+ 1 => SnapTarget :: BoundingBox ( BoundingBoxSnapTarget :: EdgeMidpoint ) ,
643+ _ => SnapTarget :: BoundingBox ( BoundingBoxSnapTarget :: CenterPoint ) ,
644+ } ;
645+ snapping:: SnappedPoint {
646+ snapped_point_document : snap_point,
647+ source : SnapSource :: Path ( PathSnapSource :: HandlePoint ) ,
648+ target,
649+ distance : best_distance,
650+ tolerance,
651+ target_bounds : Some ( quad) ,
652+ ..Default :: default ( )
653+ }
654+ } )
655+ }
656+
595657impl Fsm for SelectToolFsmState {
596658 type ToolData = SelectToolData ;
597659 type ToolOptions = ( ) ;
@@ -1149,6 +1211,7 @@ impl Fsm for SelectToolFsmState {
11491211 }
11501212 ( SelectToolFsmState :: DraggingPivot , SelectToolMessage :: Abort ) => {
11511213 responses. add ( DocumentMessage :: AbortTransaction ) ;
1214+ tool_data. snap_manager . cleanup ( responses) ;
11521215
11531216 let selection = tool_data. nested_selection_behavior ;
11541217 SelectToolFsmState :: Ready { selection }
@@ -1274,10 +1337,24 @@ impl Fsm for SelectToolFsmState {
12741337 }
12751338 ( SelectToolFsmState :: DraggingPivot , SelectToolMessage :: PointerMove { modifier_keys } ) => {
12761339 let mouse_position = input. mouse . position ;
1277- let snapped_mouse_position = mouse_position;
1340+ let document_mouse = document . metadata ( ) . document_to_viewport . inverse ( ) . transform_point2 ( mouse_position) ;
12781341
1342+ let snap_data = SnapData :: new ( document, input, viewport) ;
1343+ let point = SnapCandidatePoint :: handle ( document_mouse) ;
1344+ let mut snapped = tool_data. snap_manager . free_snap ( & snap_data, & point, snapping:: SnapTypeConfiguration :: default ( ) ) ;
1345+
1346+ if let Some ( pivot_snap) = snap_pivot_to_bounds ( document, mouse_position, & tool_data. bounding_box_manager )
1347+ && pivot_snap. distance < snapped. distance
1348+ {
1349+ snapped = pivot_snap;
1350+ }
1351+
1352+ tool_data. snap_manager . update_indicator ( snapped. clone ( ) ) ;
1353+
1354+ let snapped_mouse_position = document. metadata ( ) . document_to_viewport . transform_point2 ( snapped. snapped_point_document ) ;
12791355 tool_data. pivot_gizmo . pivot . set_viewport_position ( snapped_mouse_position) ;
12801356
1357+ responses. add ( OverlaysMessage :: Draw ) ;
12811358 responses. add ( NodeGraphMessage :: RunDocumentGraph ) ;
12821359
12831360 // Auto-panning
0 commit comments