22
33use super :: tool_prelude:: * ;
44use crate :: application:: generate_uuid;
5- use crate :: consts:: { ROTATE_SNAP_ANGLE , SELECTION_TOLERANCE } ;
5+ use crate :: consts:: { DRAG_BEYOND_VIEWPORT_MAX_OVEREXTENSION_PIXELS , DRAG_BEYOND_VIEWPORT_SPEED_FACTOR , ROTATE_SNAP_ANGLE , SELECTION_TOLERANCE } ;
66use crate :: messages:: input_mapper:: utility_types:: input_mouse:: ViewportPosition ;
77use crate :: messages:: portfolio:: document:: overlays:: utility_types:: OverlayContext ;
88use crate :: messages:: portfolio:: document:: utility_types:: document_metadata:: LayerNodeIdentifier ;
@@ -52,6 +52,14 @@ impl fmt::Display for NestedSelectionBehavior {
5252 }
5353}
5454
55+ #[ derive( PartialEq , Clone , Debug , Serialize , Deserialize , specta:: Type ) ]
56+ pub struct SelectToolPointerKeys {
57+ pub axis_align : Key ,
58+ pub snap_angle : Key ,
59+ pub center : Key ,
60+ pub duplicate : Key ,
61+ }
62+
5563#[ remain:: sorted]
5664#[ impl_message( Message , ToolMessage , Select ) ]
5765#[ derive( PartialEq , Clone , Debug , Serialize , Deserialize , specta:: Type ) ]
@@ -72,16 +80,13 @@ pub enum SelectToolMessage {
7280 } ,
7381 EditLayer ,
7482 Enter ,
75- PointerMove {
76- axis_align : Key ,
77- snap_angle : Key ,
78- center : Key ,
79- duplicate : Key ,
80- } ,
83+ PointerMove ( SelectToolPointerKeys ) ,
84+ PointerOutsideViewport ( SelectToolPointerKeys ) ,
8185 SelectOptions ( SelectOptionsUpdate ) ,
8286 SetPivot {
8387 position : PivotPosition ,
8488 } ,
89+ ShiftViewport ,
8590}
8691
8792impl ToolMetadata for SelectTool {
@@ -269,6 +274,7 @@ struct SelectToolData {
269274 selected_layers_count : usize ,
270275 selected_layers_changed : bool ,
271276 snap_candidates : Vec < SnapCandidatePoint > ,
277+ subscribed_to_animation_frame : bool ,
272278}
273279
274280impl SelectToolData {
@@ -590,16 +596,16 @@ impl Fsm for SelectToolFsmState {
590596
591597 state
592598 }
593- ( SelectToolFsmState :: Dragging , SelectToolMessage :: PointerMove { axis_align , duplicate , .. } ) => {
599+ ( SelectToolFsmState :: Dragging , SelectToolMessage :: PointerMove ( modifier_keys ) ) => {
594600 tool_data. has_dragged = true ;
595601
596- if input. keyboard . key ( duplicate) && tool_data. non_duplicated_layers . is_none ( ) {
602+ if input. keyboard . key ( modifier_keys . duplicate ) && tool_data. non_duplicated_layers . is_none ( ) {
597603 tool_data. start_duplicates ( document, responses) ;
598- } else if !input. keyboard . key ( duplicate) && tool_data. non_duplicated_layers . is_some ( ) {
604+ } else if !input. keyboard . key ( modifier_keys . duplicate ) && tool_data. non_duplicated_layers . is_some ( ) {
599605 tool_data. stop_duplicates ( document, responses) ;
600606 }
601607
602- let axis_align = input. keyboard . key ( axis_align) ;
608+ let axis_align = input. keyboard . key ( modifier_keys . axis_align ) ;
603609 let mouse_position = axis_align_drag ( axis_align, input. mouse . position , tool_data. drag_start ) ;
604610 let total_mouse_delta_document = document. metadata . document_to_viewport . inverse ( ) . transform_vector2 ( mouse_position - tool_data. drag_start ) ;
605611
@@ -644,12 +650,14 @@ impl Fsm for SelectToolFsmState {
644650 }
645651 tool_data. drag_current += mouse_delta;
646652
653+ setup_pointer_outside_edge_event ( input. mouse . position , input. viewport_bounds . size ( ) , tool_data, modifier_keys, responses) ;
654+
647655 SelectToolFsmState :: Dragging
648656 }
649- ( SelectToolFsmState :: ResizingBounds , SelectToolMessage :: PointerMove { axis_align , center , .. } ) => {
650- if let Some ( bounds) = & mut tool_data. bounding_box_manager {
657+ ( SelectToolFsmState :: ResizingBounds , SelectToolMessage :: PointerMove ( modifier_keys ) ) => {
658+ if let Some ( ref mut bounds) = & mut tool_data. bounding_box_manager {
651659 if let Some ( movement) = & mut bounds. selected_edges {
652- let ( center, constrain) = ( input. keyboard . key ( center) , input. keyboard . key ( axis_align) ) ;
660+ let ( center, constrain) = ( input. keyboard . key ( modifier_keys . center ) , input. keyboard . key ( modifier_keys . axis_align ) ) ;
653661
654662 let center = center. then_some ( bounds. center_of_transformation ) ;
655663 let snap = Some ( SizeSnapData {
@@ -677,11 +685,13 @@ impl Fsm for SelectToolFsmState {
677685 ) ;
678686
679687 selected. apply_transformation ( bounds. original_bound_transform * transformation * bounds. original_bound_transform . inverse ( ) ) ;
688+
689+ setup_pointer_outside_edge_event ( input. mouse . position , input. viewport_bounds . size ( ) , tool_data, modifier_keys, responses) ;
680690 }
681691 }
682692 SelectToolFsmState :: ResizingBounds
683693 }
684- ( SelectToolFsmState :: RotatingBounds , SelectToolMessage :: PointerMove { snap_angle , .. } ) => {
694+ ( SelectToolFsmState :: RotatingBounds , SelectToolMessage :: PointerMove ( modifier_keys ) ) => {
685695 if let Some ( bounds) = & mut tool_data. bounding_box_manager {
686696 let angle = {
687697 let start_offset = tool_data. drag_start - bounds. center_of_transformation ;
@@ -690,7 +700,7 @@ impl Fsm for SelectToolFsmState {
690700 start_offset. angle_between ( end_offset)
691701 } ;
692702
693- let snapped_angle = if input. keyboard . key ( snap_angle) {
703+ let snapped_angle = if input. keyboard . key ( modifier_keys . snap_angle ) {
694704 let snap_resolution = ROTATE_SNAP_ANGLE . to_radians ( ) ;
695705 ( angle / snap_resolution) . round ( ) * snap_resolution
696706 } else {
@@ -716,20 +726,24 @@ impl Fsm for SelectToolFsmState {
716726
717727 SelectToolFsmState :: RotatingBounds
718728 }
719- ( SelectToolFsmState :: DraggingPivot , SelectToolMessage :: PointerMove { .. } ) => {
729+ ( SelectToolFsmState :: DraggingPivot , SelectToolMessage :: PointerMove ( modifier_keys ) ) => {
720730 let mouse_position = input. mouse . position ;
721731 let snapped_mouse_position = mouse_position; //tool_data.snap_manager.snap_position(responses, document, mouse_position);
722732 tool_data. pivot . set_viewport_position ( snapped_mouse_position, document, responses) ;
723733
734+ setup_pointer_outside_edge_event ( mouse_position, input. viewport_bounds . size ( ) , tool_data, modifier_keys, responses) ;
735+
724736 SelectToolFsmState :: DraggingPivot
725737 }
726- ( SelectToolFsmState :: DrawingBox , SelectToolMessage :: PointerMove { .. } ) => {
738+ ( SelectToolFsmState :: DrawingBox , SelectToolMessage :: PointerMove ( modifier_keys ) ) => {
727739 tool_data. drag_current = input. mouse . position ;
728740 responses. add ( OverlaysMessage :: Draw ) ;
729741
742+ setup_pointer_outside_edge_event ( input. mouse . position , input. viewport_bounds . size ( ) , tool_data, modifier_keys, responses) ;
743+
730744 SelectToolFsmState :: DrawingBox
731745 }
732- ( SelectToolFsmState :: Ready , SelectToolMessage :: PointerMove { .. } ) => {
746+ ( SelectToolFsmState :: Ready , SelectToolMessage :: PointerMove ( _ ) ) => {
733747 let mut cursor = tool_data. bounding_box_manager . as_ref ( ) . map_or ( MouseCursorIcon :: Default , |bounds| bounds. get_cursor ( input, true ) ) ;
734748
735749 // Dragging the pivot overrules the other operations
@@ -747,6 +761,50 @@ impl Fsm for SelectToolFsmState {
747761
748762 SelectToolFsmState :: Ready
749763 }
764+ ( SelectToolFsmState :: Dragging , SelectToolMessage :: PointerOutsideViewport ( modifier_keys) ) => {
765+ responses. add ( SelectToolMessage :: PointerMove ( modifier_keys) ) ;
766+
767+ if let Some ( shift) = shift_viewport_if_mouse_beyond_edge ( input. mouse . position , input. viewport_bounds . size ( ) , responses) {
768+ tool_data. drag_current += shift;
769+ tool_data. drag_start += shift;
770+ }
771+
772+ SelectToolFsmState :: Dragging
773+ }
774+ ( SelectToolFsmState :: ResizingBounds | SelectToolFsmState :: DraggingPivot | SelectToolFsmState :: DrawingBox , SelectToolMessage :: PointerOutsideViewport ( modifier_keys) ) => {
775+ responses. add ( SelectToolMessage :: PointerMove ( modifier_keys) ) ;
776+
777+ responses. add ( SelectToolMessage :: ShiftViewport ) ;
778+
779+ self
780+ }
781+ ( SelectToolFsmState :: ResizingBounds , SelectToolMessage :: ShiftViewport ) => {
782+ if let Some ( shift) = shift_viewport_if_mouse_beyond_edge ( input. mouse . position , input. viewport_bounds . size ( ) , responses) {
783+ if let Some ( ref mut bounds) = & mut tool_data. bounding_box_manager {
784+ bounds. center_of_transformation += shift;
785+ bounds. original_bound_transform . translation += shift;
786+ }
787+ }
788+
789+ self
790+ }
791+ ( SelectToolFsmState :: DraggingPivot , SelectToolMessage :: ShiftViewport ) => {
792+ let _ = shift_viewport_if_mouse_beyond_edge ( input. mouse . position , input. viewport_bounds . size ( ) , responses) ;
793+
794+ self
795+ }
796+ ( SelectToolFsmState :: DrawingBox , SelectToolMessage :: ShiftViewport ) => {
797+ if let Some ( shift) = shift_viewport_if_mouse_beyond_edge ( input. mouse . position , input. viewport_bounds . size ( ) , responses) {
798+ tool_data. drag_start += shift;
799+ }
800+
801+ self
802+ }
803+ ( state, SelectToolMessage :: PointerOutsideViewport ( modifier_keys) ) => {
804+ unsubscribe_animation_frame ( tool_data, modifier_keys, responses) ;
805+
806+ state
807+ }
750808 ( SelectToolFsmState :: Dragging , SelectToolMessage :: Enter ) => {
751809 let response = match input. mouse . position . distance ( tool_data. drag_start ) < 10. * f64:: EPSILON {
752810 true => DocumentMessage :: Undo ,
@@ -1011,3 +1069,68 @@ fn edit_layer_deepest_manipulation(layer: LayerNodeIdentifier, document_network:
10111069 responses. add_front ( ToolMessage :: ActivateTool { tool_type : ToolType :: Path } ) ;
10121070 }
10131071}
1072+
1073+ /// Shifts the viewport when the mouse reaches the edge of the viewport.
1074+ ///
1075+ /// If the mouse was beyond any edge, it returns the amount shifted. Otherwise it returns None.
1076+ /// The shift is proportional to the distance between edge and mouse. It is also guaranteed to be integral.
1077+ fn shift_viewport_if_mouse_beyond_edge ( mouse_position : DVec2 , viewport_size : DVec2 , responses : & mut VecDeque < Message > ) -> Option < DVec2 > {
1078+ let mouse_position = mouse_position. clamp (
1079+ DVec2 :: ZERO - DVec2 :: splat ( DRAG_BEYOND_VIEWPORT_MAX_OVEREXTENSION_PIXELS ) ,
1080+ viewport_size + DVec2 :: splat ( DRAG_BEYOND_VIEWPORT_MAX_OVEREXTENSION_PIXELS ) ,
1081+ ) ;
1082+ let mouse_position_percent = mouse_position / viewport_size;
1083+
1084+ let mut shift_percent = DVec2 :: ZERO ;
1085+
1086+ if mouse_position_percent. x < 0. {
1087+ shift_percent. x = -mouse_position_percent. x ;
1088+ } else if mouse_position_percent. x > 1. {
1089+ shift_percent. x = 1. - mouse_position_percent. x ;
1090+ }
1091+
1092+ if mouse_position_percent. y < 0. {
1093+ shift_percent. y = -mouse_position_percent. y ;
1094+ } else if mouse_position_percent. y > 1. {
1095+ shift_percent. y = 1. - mouse_position_percent. y ;
1096+ }
1097+
1098+ if shift_percent. x == 0. && shift_percent. y == 0. {
1099+ return None ;
1100+ }
1101+
1102+ let delta = ( shift_percent * DRAG_BEYOND_VIEWPORT_SPEED_FACTOR * viewport_size) . round ( ) ;
1103+ responses. add ( NavigationMessage :: TranslateCanvas { delta } ) ;
1104+ Some ( delta)
1105+ }
1106+
1107+ fn setup_pointer_outside_edge_event ( mouse_position : DVec2 , viewport_size : DVec2 , tool_data : & mut SelectToolData , modifier_keys : SelectToolPointerKeys , responses : & mut VecDeque < Message > ) {
1108+ let is_pointer_outside_edge = mouse_position. x < 0. || mouse_position. x > viewport_size. x || mouse_position. y < 0. || mouse_position. y > viewport_size. y ;
1109+
1110+ match is_pointer_outside_edge {
1111+ true => subscribe_animation_frame ( tool_data, modifier_keys, responses) ,
1112+ false => unsubscribe_animation_frame ( tool_data, modifier_keys, responses) ,
1113+ }
1114+ }
1115+
1116+ fn subscribe_animation_frame ( tool_data : & mut SelectToolData , modifier_keys : SelectToolPointerKeys , responses : & mut VecDeque < Message > ) {
1117+ if !tool_data. subscribed_to_animation_frame {
1118+ tool_data. subscribed_to_animation_frame = true ;
1119+
1120+ responses. add ( BroadcastMessage :: SubscribeEvent {
1121+ on : BroadcastEvent :: AnimationFrame ,
1122+ send : Box :: new ( SelectToolMessage :: PointerOutsideViewport ( modifier_keys) . into ( ) ) ,
1123+ } ) ;
1124+ }
1125+ }
1126+
1127+ fn unsubscribe_animation_frame ( tool_data : & mut SelectToolData , modifier_keys : SelectToolPointerKeys , responses : & mut VecDeque < Message > ) {
1128+ if tool_data. subscribed_to_animation_frame {
1129+ tool_data. subscribed_to_animation_frame = false ;
1130+
1131+ responses. add ( BroadcastMessage :: UnsubscribeEvent {
1132+ on : BroadcastEvent :: AnimationFrame ,
1133+ message : Box :: new ( SelectToolMessage :: PointerOutsideViewport ( modifier_keys) . into ( ) ) ,
1134+ } ) ;
1135+ }
1136+ }
0 commit comments