1
+ use super :: spline_tool:: { delete_preview, extend_spline, try_merging_latest_endpoint, EndpointPosition , SplineToolData } ;
1
2
use super :: tool_prelude:: * ;
2
- use crate :: consts:: { DEFAULT_STROKE_WIDTH , HIDE_HANDLE_DISTANCE , LINE_ROTATE_SNAP_ANGLE } ;
3
+ use crate :: consts:: { DEFAULT_STROKE_WIDTH , DRAG_THRESHOLD , HIDE_HANDLE_DISTANCE , LINE_ROTATE_SNAP_ANGLE , PATH_JOIN_THRESHOLD , SNAP_POINT_TOLERANCE } ;
3
4
use crate :: messages:: portfolio:: document:: node_graph:: document_node_definitions:: resolve_document_node_type;
4
- use crate :: messages:: portfolio:: document:: overlays:: utility_functions:: path_overlays;
5
+ use crate :: messages:: portfolio:: document:: overlays:: utility_functions:: { path_endpoint_overlays , path_overlays} ;
5
6
use crate :: messages:: portfolio:: document:: overlays:: utility_types:: { DrawHandles , OverlayContext } ;
6
7
use crate :: messages:: portfolio:: document:: utility_types:: document_metadata:: LayerNodeIdentifier ;
7
8
use crate :: messages:: tool:: common_functionality:: auto_panning:: AutoPanning ;
8
9
use crate :: messages:: tool:: common_functionality:: color_selector:: { ToolColorOptions , ToolColorType } ;
9
- use crate :: messages:: tool:: common_functionality:: graph_modification_utils:: { self , merge_layers} ;
10
+ use crate :: messages:: tool:: common_functionality:: graph_modification_utils:: { self , find_spline , merge_layers, merge_points } ;
10
11
use crate :: messages:: tool:: common_functionality:: snapping:: { SnapCandidatePoint , SnapConstraint , SnapData , SnapManager , SnapTypeConfiguration } ;
11
12
use crate :: messages:: tool:: common_functionality:: utility_functions:: { closest_point, should_extend} ;
12
13
13
14
use bezier_rs:: { Bezier , BezierHandles } ;
14
- use graph_craft:: document:: NodeId ;
15
+ use graph_craft:: document:: { NodeId , NodeInput } ;
15
16
use graphene_core:: vector:: { PointId , VectorModificationType } ;
16
17
use graphene_core:: Color ;
17
18
use graphene_std:: vector:: { HandleId , ManipulatorPointId , SegmentId , VectorData } ;
@@ -30,6 +31,7 @@ pub struct PenOptions {
30
31
fill : ToolColorOptions ,
31
32
stroke : ToolColorOptions ,
32
33
pen_overlay_mode : PenOverlayMode ,
34
+ tool_mode : ToolMode ,
33
35
}
34
36
35
37
impl Default for PenOptions {
@@ -39,6 +41,7 @@ impl Default for PenOptions {
39
41
fill : ToolColorOptions :: new_secondary ( ) ,
40
42
stroke : ToolColorOptions :: new_primary ( ) ,
41
43
pen_overlay_mode : PenOverlayMode :: FrontierHandles ,
44
+ tool_mode : ToolMode :: Path ,
42
45
}
43
46
}
44
47
}
@@ -64,11 +67,14 @@ pub enum PenToolMessage {
64
67
Redo ,
65
68
Undo ,
66
69
UpdateOptions ( PenOptionsUpdate ) ,
67
- ChangeToolMode ( ToolMode ) ,
70
+ ToolModeChanged ,
68
71
RecalculateLatestPointsPosition ,
69
72
RemovePreviousHandle ,
70
73
GRS { grab : Key , rotate : Key , scale : Key } ,
71
74
FinalPosition { final_position : DVec2 } ,
75
+
76
+ // Specific to the Spline mode.
77
+ SplineMergeEndpoints ,
72
78
}
73
79
74
80
#[ derive( Clone , Copy , Debug , Default , PartialEq , Eq ) ]
@@ -78,6 +84,8 @@ enum PenToolFsmState {
78
84
DraggingHandle ( HandleMode ) ,
79
85
PlacingAnchor ,
80
86
GRSHandle ,
87
+ SplineDrawing ,
88
+ SplineMergingEndpoints ,
81
89
}
82
90
83
91
#[ derive( PartialEq , Eq , Hash , Copy , Clone , Debug , serde:: Serialize , serde:: Deserialize , specta:: Type ) ]
@@ -95,6 +103,7 @@ pub enum PenOptionsUpdate {
95
103
StrokeColorType ( ToolColorType ) ,
96
104
WorkingColors ( Option < Color > , Option < Color > ) ,
97
105
OverlayModeType ( PenOverlayMode ) ,
106
+ ToolMode ( ToolMode ) ,
98
107
}
99
108
100
109
impl PenTool {
@@ -104,12 +113,12 @@ impl PenTool {
104
113
. map ( |mode| {
105
114
MenuListEntry :: new ( format ! ( "{mode:?}" ) )
106
115
. label ( mode. to_string ( ) )
107
- . on_commit ( move |_| PenToolMessage :: ChangeToolMode ( * mode) . into ( ) )
116
+ . on_commit ( move |_| PenToolMessage :: UpdateOptions ( PenOptionsUpdate :: ToolMode ( * mode) ) . into ( ) )
108
117
} )
109
118
. collect ( ) ;
110
119
111
120
DropdownInput :: new ( vec ! [ tool_mode_entries] )
112
- . selected_index ( Some ( ( self . tool_data . tool_mode ) as u32 ) )
121
+ . selected_index ( Some ( ( self . options . tool_mode ) as u32 ) )
113
122
. tooltip ( "Select Spline to draw smooth curves or select Path to draw a path." )
114
123
. widget_holder ( )
115
124
}
@@ -167,20 +176,22 @@ impl LayoutHolder for PenTool {
167
176
168
177
widgets. push ( Separator :: new ( SeparatorType :: Unrelated ) . widget_holder ( ) ) ;
169
178
170
- widgets. push (
171
- RadioInput :: new ( vec ! [
172
- RadioEntryData :: new( "all" )
173
- . icon( "HandleVisibilityAll" )
174
- . tooltip( "Show all handles regardless of selection" )
175
- . on_update( move |_| PenToolMessage :: UpdateOptions ( PenOptionsUpdate :: OverlayModeType ( PenOverlayMode :: AllHandles ) ) . into( ) ) ,
176
- RadioEntryData :: new( "frontier" )
177
- . icon( "HandleVisibilityFrontier" )
178
- . tooltip( "Show only handles at the frontiers of the segments connected to selected points" )
179
- . on_update( move |_| PenToolMessage :: UpdateOptions ( PenOptionsUpdate :: OverlayModeType ( PenOverlayMode :: FrontierHandles ) ) . into( ) ) ,
180
- ] )
181
- . selected_index ( Some ( self . options . pen_overlay_mode as u32 ) )
182
- . widget_holder ( ) ,
183
- ) ;
179
+ if self . options . tool_mode == ToolMode :: Path {
180
+ widgets. push (
181
+ RadioInput :: new ( vec ! [
182
+ RadioEntryData :: new( "all" )
183
+ . icon( "HandleVisibilityAll" )
184
+ . tooltip( "Show all handles regardless of selection" )
185
+ . on_update( move |_| PenToolMessage :: UpdateOptions ( PenOptionsUpdate :: OverlayModeType ( PenOverlayMode :: AllHandles ) ) . into( ) ) ,
186
+ RadioEntryData :: new( "frontier" )
187
+ . icon( "HandleVisibilityFrontier" )
188
+ . tooltip( "Show only handles at the frontiers of the segments connected to selected points" )
189
+ . on_update( move |_| PenToolMessage :: UpdateOptions ( PenOptionsUpdate :: OverlayModeType ( PenOverlayMode :: FrontierHandles ) ) . into( ) ) ,
190
+ ] )
191
+ . selected_index ( Some ( self . options . pen_overlay_mode as u32 ) )
192
+ . widget_holder ( ) ,
193
+ ) ;
194
+ }
184
195
185
196
Layout :: WidgetLayout ( WidgetLayout :: new ( vec ! [ LayoutGroup :: Row { widgets } ] ) )
186
197
}
@@ -214,6 +225,10 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool
214
225
self . options . fill . primary_working_color = primary;
215
226
self . options . fill . secondary_working_color = secondary;
216
227
}
228
+ PenOptionsUpdate :: ToolMode ( tool_mode) => {
229
+ self . options . tool_mode = tool_mode;
230
+ responses. add ( PenToolMessage :: ToolModeChanged ) ;
231
+ }
217
232
}
218
233
219
234
self . send_layout ( responses, LayoutTarget :: ToolOptions ) ;
@@ -239,6 +254,15 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool
239
254
RemovePreviousHandle ,
240
255
GRS ,
241
256
) ,
257
+ PenToolFsmState :: SplineDrawing => actions ! ( PenToolMessageDiscriminant ;
258
+ DragStop ,
259
+ PointerMove ,
260
+ Confirm ,
261
+ Abort ,
262
+ ) ,
263
+ PenToolFsmState :: SplineMergingEndpoints => actions ! ( PenToolMessageDiscriminant ;
264
+ SplineMergeEndpoints ,
265
+ ) ,
242
266
}
243
267
}
244
268
}
@@ -306,7 +330,8 @@ impl fmt::Display for ToolMode {
306
330
307
331
#[ derive( Clone , Debug , Default ) ]
308
332
struct PenToolData {
309
- tool_mode : ToolMode ,
333
+ spline_mode_tool_data : SplineToolData ,
334
+
310
335
snap_manager : SnapManager ,
311
336
latest_points : Vec < LastPoint > ,
312
337
point_index : usize ,
@@ -952,10 +977,108 @@ impl Fsm for PenToolFsmState {
952
977
953
978
let ToolMessage :: Pen ( event) = event else { return self } ;
954
979
match ( self , event) {
955
- ( state, PenToolMessage :: ChangeToolMode ( mode) ) => {
956
- tool_data. tool_mode = mode;
980
+ ( state, PenToolMessage :: ToolModeChanged ) => {
981
+ if !matches ! ( state, PenToolFsmState :: Ready ) {
982
+ responses. add ( PenToolMessage :: Abort ) ;
983
+ responses. add ( PenToolMessage :: ToolModeChanged ) ;
984
+ return state;
985
+ }
957
986
state
958
987
}
988
+ ( PenToolFsmState :: SplineDrawing , PenToolMessage :: DragStop ) => {
989
+ let tool_data = & mut tool_data. spline_mode_tool_data ;
990
+ // The first DragStop event will be ignored to prevent insertion of new point.
991
+ if tool_data. extend {
992
+ tool_data. extend = false ;
993
+ return PenToolFsmState :: SplineDrawing ;
994
+ }
995
+ if tool_data. current_layer . is_none ( ) {
996
+ return PenToolFsmState :: Ready ;
997
+ } ;
998
+ tool_data. next_point = tool_data. snapped_point ( document, input) . snapped_point_document ;
999
+ if tool_data. points . last ( ) . map_or ( true , |last_pos| last_pos. 1 . distance ( tool_data. next_point ) > DRAG_THRESHOLD ) {
1000
+ let preview_point = tool_data. preview_point ;
1001
+ extend_spline ( tool_data, false , responses) ;
1002
+ tool_data. preview_point = preview_point;
1003
+
1004
+ if try_merging_latest_endpoint ( document, tool_data, preferences) . is_some ( ) {
1005
+ responses. add ( PenToolMessage :: Confirm ) ;
1006
+ }
1007
+ }
1008
+
1009
+ PenToolFsmState :: SplineDrawing
1010
+ }
1011
+ ( PenToolFsmState :: SplineDrawing , PenToolMessage :: PointerMove { .. } ) => {
1012
+ let tool_data = & mut tool_data. spline_mode_tool_data ;
1013
+ let Some ( layer) = tool_data. current_layer else { return PenToolFsmState :: Ready } ;
1014
+ let ignore = |cp : PointId | tool_data. preview_point . is_some_and ( |pp| pp == cp) || tool_data. points . last ( ) . is_some_and ( |( ep, _) | * ep == cp) ;
1015
+ let join_point = closest_point ( document, input. mouse . position , PATH_JOIN_THRESHOLD , vec ! [ layer] . into_iter ( ) , ignore, preferences) ;
1016
+
1017
+ // Endpoints snapping
1018
+ if let Some ( ( _, _, point) ) = join_point {
1019
+ tool_data. next_point = point;
1020
+ tool_data. snap_manager . clear_indicator ( ) ;
1021
+ } else {
1022
+ let snapped_point = tool_data. snapped_point ( document, input) ;
1023
+ tool_data. next_point = snapped_point. snapped_point_document ;
1024
+ tool_data. snap_manager . update_indicator ( snapped_point) ;
1025
+ }
1026
+
1027
+ extend_spline ( tool_data, true , responses) ;
1028
+
1029
+ // Auto-panning
1030
+ let messages = [ SplineToolMessage :: PointerOutsideViewport . into ( ) , SplineToolMessage :: PointerMove . into ( ) ] ;
1031
+ tool_data. auto_panning . setup_by_mouse_position ( input, & messages, responses) ;
1032
+
1033
+ PenToolFsmState :: SplineDrawing
1034
+ }
1035
+ ( PenToolFsmState :: SplineDrawing , PenToolMessage :: PointerOutsideViewport { .. } ) => {
1036
+ // Auto-panning
1037
+ let _ = tool_data. auto_panning . shift_viewport ( input, responses) ;
1038
+
1039
+ PenToolFsmState :: SplineDrawing
1040
+ }
1041
+ ( PenToolFsmState :: SplineDrawing , PenToolMessage :: Confirm ) => {
1042
+ let tool_data = & mut tool_data. spline_mode_tool_data ;
1043
+ if tool_data. points . len ( ) >= 2 {
1044
+ delete_preview ( tool_data, responses) ;
1045
+ }
1046
+ responses. add ( PenToolMessage :: SplineMergeEndpoints ) ;
1047
+ PenToolFsmState :: SplineMergingEndpoints
1048
+ }
1049
+ ( PenToolFsmState :: SplineDrawing , PenToolMessage :: Abort ) => {
1050
+ responses. add ( DocumentMessage :: AbortTransaction ) ;
1051
+ PenToolFsmState :: Ready
1052
+ }
1053
+ ( PenToolFsmState :: SplineMergingEndpoints , PenToolMessage :: SplineMergeEndpoints ) => {
1054
+ let tool_data = & mut tool_data. spline_mode_tool_data ;
1055
+ let Some ( current_layer) = tool_data. current_layer else { return PenToolFsmState :: Ready } ;
1056
+
1057
+ if let Some ( & layer) = tool_data. merge_layers . iter ( ) . last ( ) {
1058
+ merge_layers ( document, current_layer, layer, responses) ;
1059
+ tool_data. merge_layers . remove ( & layer) ;
1060
+
1061
+ responses. add ( PenToolMessage :: SplineMergeEndpoints ) ;
1062
+ return PenToolFsmState :: SplineMergingEndpoints ;
1063
+ }
1064
+
1065
+ let Some ( ( start_endpoint, _) ) = tool_data. points . first ( ) else { return PenToolFsmState :: Ready } ;
1066
+ let Some ( ( last_endpoint, _) ) = tool_data. points . last ( ) else { return PenToolFsmState :: Ready } ;
1067
+
1068
+ if let Some ( ( position, second_endpoint) ) = tool_data. merge_endpoints . pop ( ) {
1069
+ let first_endpoint = match position {
1070
+ EndpointPosition :: Start => * start_endpoint,
1071
+ EndpointPosition :: End => * last_endpoint,
1072
+ } ;
1073
+ merge_points ( document, current_layer, first_endpoint, second_endpoint, responses) ;
1074
+
1075
+ responses. add ( PenToolMessage :: SplineMergeEndpoints ) ;
1076
+ return PenToolFsmState :: SplineMergingEndpoints ;
1077
+ }
1078
+
1079
+ responses. add ( DocumentMessage :: EndTransaction ) ;
1080
+ PenToolFsmState :: Ready
1081
+ }
959
1082
( PenToolFsmState :: PlacingAnchor | PenToolFsmState :: GRSHandle , PenToolMessage :: GRS { grab, rotate, scale } ) => {
960
1083
let Some ( layer) = layer else { return PenToolFsmState :: PlacingAnchor } ;
961
1084
@@ -1095,6 +1218,12 @@ impl Fsm for PenToolFsmState {
1095
1218
self
1096
1219
}
1097
1220
( _, PenToolMessage :: Overlays ( mut overlay_context) ) => {
1221
+ if tool_options. tool_mode == ToolMode :: Spline {
1222
+ let spline_tool_data = & mut tool_data. spline_mode_tool_data ;
1223
+ path_endpoint_overlays ( document, shape_editor, & mut overlay_context, preferences) ;
1224
+ spline_tool_data. snap_manager . draw_overlays ( SnapData :: new ( document, input) , & mut overlay_context) ;
1225
+ return self ;
1226
+ }
1098
1227
let valid = |point : DVec2 , handle : DVec2 | point. distance_squared ( handle) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE ;
1099
1228
1100
1229
let transform = document. metadata ( ) . document_to_viewport * transform;
@@ -1198,6 +1327,76 @@ impl Fsm for PenToolFsmState {
1198
1327
self
1199
1328
}
1200
1329
( PenToolFsmState :: Ready , PenToolMessage :: DragStart { append_to_selected } ) => {
1330
+ if tool_options. tool_mode == ToolMode :: Spline {
1331
+ let tool_data = & mut tool_data. spline_mode_tool_data ;
1332
+ responses. add ( DocumentMessage :: StartTransaction ) ;
1333
+
1334
+ tool_data. snap_manager . cleanup ( responses) ;
1335
+ tool_data. cleanup ( ) ;
1336
+ tool_data. weight = tool_options. line_weight ;
1337
+
1338
+ let point = SnapCandidatePoint :: handle ( document. metadata ( ) . document_to_viewport . inverse ( ) . transform_point2 ( input. mouse . position ) ) ;
1339
+ let snapped = tool_data. snap_manager . free_snap ( & SnapData :: new ( document, input) , & point, SnapTypeConfiguration :: default ( ) ) ;
1340
+ let viewport = document. metadata ( ) . document_to_viewport . transform_point2 ( snapped. snapped_point_document ) ;
1341
+
1342
+ let layers = LayerNodeIdentifier :: ROOT_PARENT
1343
+ . descendants ( document. metadata ( ) )
1344
+ . filter ( |layer| !document. network_interface . is_artboard ( & layer. to_node ( ) , & [ ] ) ) ;
1345
+
1346
+ // Extend an endpoint of the selected path
1347
+ if let Some ( ( layer, point, position) ) = should_extend ( document, viewport, SNAP_POINT_TOLERANCE , layers, preferences) {
1348
+ if find_spline ( document, layer) . is_some ( ) {
1349
+ // If the point is the part of Spline then we extend it.
1350
+ tool_data. current_layer = Some ( layer) ;
1351
+ tool_data. points . push ( ( point, position) ) ;
1352
+ tool_data. next_point = position;
1353
+ tool_data. extend = true ;
1354
+
1355
+ extend_spline ( tool_data, true , responses) ;
1356
+
1357
+ return PenToolFsmState :: SplineDrawing ;
1358
+ } else {
1359
+ tool_data. merge_layers . insert ( layer) ;
1360
+ tool_data. merge_endpoints . push ( ( EndpointPosition :: Start , point) ) ;
1361
+ }
1362
+ }
1363
+
1364
+ let selected_nodes = document. network_interface . selected_nodes ( & [ ] ) . unwrap ( ) ;
1365
+ let mut selected_layers_except_artboards = selected_nodes. selected_layers_except_artboards ( & document. network_interface ) ;
1366
+ let selected_layer = selected_layers_except_artboards. next ( ) . filter ( |_| selected_layers_except_artboards. next ( ) . is_none ( ) ) ;
1367
+
1368
+ let append_to_selected_layer = input. keyboard . key ( append_to_selected) ;
1369
+
1370
+ // Create new path in the selected layer when shift is down
1371
+ if let ( Some ( layer) , true ) = ( selected_layer, append_to_selected_layer) {
1372
+ tool_data. current_layer = Some ( layer) ;
1373
+
1374
+ let transform = document. metadata ( ) . transform_to_viewport ( layer) ;
1375
+ let position = transform. inverse ( ) . transform_point2 ( input. mouse . position ) ;
1376
+ tool_data. next_point = position;
1377
+
1378
+ return PenToolFsmState :: SplineDrawing ;
1379
+ }
1380
+
1381
+ responses. add ( DocumentMessage :: DeselectAllLayers ) ;
1382
+
1383
+ let parent = document. new_layer_bounding_artboard ( input) ;
1384
+
1385
+ let path_node_type = resolve_document_node_type ( "Path" ) . expect ( "Path node does not exist" ) ;
1386
+ let path_node = path_node_type. default_node_template ( ) ;
1387
+ let spline_node_type = resolve_document_node_type ( "Spline" ) . expect ( "Spline node does not exist" ) ;
1388
+ let spline_node = spline_node_type. node_template_input_override ( [ Some ( NodeInput :: node ( NodeId ( 1 ) , 0 ) ) ] ) ;
1389
+ let nodes = vec ! [ ( NodeId ( 1 ) , path_node) , ( NodeId ( 0 ) , spline_node) ] ;
1390
+
1391
+ let layer = graph_modification_utils:: new_custom ( NodeId :: new ( ) , nodes, parent, responses) ;
1392
+ tool_options. fill . apply_fill ( layer, responses) ;
1393
+ tool_options. stroke . apply_stroke ( tool_data. weight , layer, responses) ;
1394
+ tool_data. current_layer = Some ( layer) ;
1395
+
1396
+ responses. add ( Message :: StartBuffer ) ;
1397
+
1398
+ return PenToolFsmState :: SplineDrawing ;
1399
+ }
1201
1400
responses. add ( DocumentMessage :: StartTransaction ) ;
1202
1401
tool_data. handle_mode = HandleMode :: Free ;
1203
1402
@@ -1522,6 +1721,12 @@ impl Fsm for PenToolFsmState {
1522
1721
dragging_hint_data. 0 . push ( HintGroup ( hold_group) ) ;
1523
1722
dragging_hint_data
1524
1723
}
1724
+ PenToolFsmState :: SplineDrawing => HintData ( vec ! [
1725
+ HintGroup ( vec![ HintInfo :: mouse( MouseMotion :: Rmb , "" ) , HintInfo :: keys( [ Key :: Escape ] , "Cancel" ) . prepend_slash( ) ] ) ,
1726
+ HintGroup ( vec![ HintInfo :: mouse( MouseMotion :: Lmb , "Extend Spline" ) ] ) ,
1727
+ HintGroup ( vec![ HintInfo :: keys( [ Key :: Enter ] , "End Spline" ) ] ) ,
1728
+ ] ) ,
1729
+ PenToolFsmState :: SplineMergingEndpoints => HintData ( vec ! [ ] ) ,
1525
1730
} ;
1526
1731
1527
1732
responses. add ( FrontendMessage :: UpdateInputHints { hint_data } ) ;
0 commit comments