@@ -44,6 +44,8 @@ pub enum GradientToolMessage {
4444#[ derive( PartialEq , Eq , Clone , Debug , Hash , serde:: Serialize , serde:: Deserialize , specta:: Type ) ]
4545pub enum GradientOptionsUpdate {
4646 Type ( GradientType ) ,
47+ ReverseStops ,
48+ ReverseDirection ,
4749}
4850
4951impl ToolMetadata for GradientTool {
@@ -63,52 +65,27 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Grad
6365 fn process_message ( & mut self , message : ToolMessage , responses : & mut VecDeque < Message > , context : & mut ToolActionMessageContext < ' a > ) {
6466 let ToolMessage :: Gradient ( GradientToolMessage :: UpdateOptions { options } ) = message else {
6567 self . fsm_state . process_event ( message, & mut self . data , context, & self . options , responses, false ) ;
68+
69+ let has_gradient = has_gradient_on_selected_layers ( context. document ) ;
70+ if has_gradient != self . data . has_selected_gradient {
71+ self . data . has_selected_gradient = has_gradient;
72+ responses. add ( ToolMessage :: RefreshToolOptions ) ;
73+ }
74+
6675 return ;
6776 } ;
6877 match options {
6978 GradientOptionsUpdate :: Type ( gradient_type) => {
7079 self . options . gradient_type = gradient_type;
71- let selected_layers: Vec < _ > = context
72- . document
73- . network_interface
74- . selected_nodes ( )
75- . selected_visible_layers ( & context. document . network_interface )
76- . collect ( ) ;
77-
78- let mut transaction_started = false ;
79- for layer in selected_layers {
80- if NodeGraphLayer :: is_raster_layer ( layer, & mut context. document . network_interface ) {
81- continue ;
82- }
83-
84- if let Some ( mut gradient) = get_gradient ( layer, & context. document . network_interface )
85- && gradient. gradient_type != gradient_type
86- {
87- if !transaction_started {
88- responses. add ( DocumentMessage :: StartTransaction ) ;
89- transaction_started = true ;
90- }
91- gradient. gradient_type = gradient_type;
92- responses. add ( GraphOperationMessage :: FillSet {
93- layer,
94- fill : Fill :: Gradient ( gradient) ,
95- } ) ;
96- }
97- }
98-
99- if transaction_started {
100- responses. add ( DocumentMessage :: AddTransaction ) ;
101- }
102- if let Some ( selected_gradient) = & mut self . data . selected_gradient
103- && let Some ( layer) = selected_gradient. layer
104- && !NodeGraphLayer :: is_raster_layer ( layer, & mut context. document . network_interface )
105- {
106- selected_gradient. gradient . gradient_type = gradient_type;
107- }
80+ apply_gradient_update ( & mut self . data , context, responses, |g| g. gradient_type != gradient_type, |g| g. gradient_type = gradient_type) ;
10881 responses. add ( ToolMessage :: UpdateHints ) ;
109- responses. add ( PropertiesPanelMessage :: Refresh ) ;
11082 responses. add ( ToolMessage :: UpdateCursor ) ;
111- responses. add ( ToolMessage :: RefreshToolOptions ) ;
83+ }
84+ GradientOptionsUpdate :: ReverseStops => {
85+ apply_gradient_update ( & mut self . data , context, responses, |_| true , |g| g. stops = g. stops . reversed ( ) ) ;
86+ }
87+ GradientOptionsUpdate :: ReverseDirection => {
88+ apply_gradient_update ( & mut self . data , context, responses, |_| true , |g| std:: mem:: swap ( & mut g. start , & mut g. end ) ) ;
11289 }
11390 }
11491 }
@@ -142,7 +119,52 @@ impl LayoutHolder for GradientTool {
142119 . selected_index ( Some ( ( self . options . gradient_type == GradientType :: Radial ) as u32 ) )
143120 . widget_instance ( ) ;
144121
145- Layout ( vec ! [ LayoutGroup :: Row { widgets: vec![ gradient_type] } ] )
122+ let reverse_stops = IconButton :: new ( "Reverse" , 24 )
123+ . tooltip_label ( "Reverse Stops" )
124+ . tooltip_description ( "Reverse the gradient color stops." )
125+ . disabled ( !self . data . has_selected_gradient )
126+ . on_update ( |_| {
127+ GradientToolMessage :: UpdateOptions {
128+ options : GradientOptionsUpdate :: ReverseStops ,
129+ }
130+ . into ( )
131+ } )
132+ . widget_instance ( ) ;
133+
134+ let mut widgets = vec ! [ gradient_type, Separator :: new( SeparatorStyle :: Unrelated ) . widget_instance( ) , reverse_stops] ;
135+
136+ if self . options . gradient_type == GradientType :: Radial {
137+ let orientation = self
138+ . data
139+ . selected_gradient
140+ . as_ref ( )
141+ . map ( |selected_gradient| {
142+ let ( start, end) = ( selected_gradient. gradient . start , selected_gradient. gradient . end ) ;
143+ if ( end. x - start. x ) . abs ( ) > f64:: EPSILON * 1e6 {
144+ end. x > start. x
145+ } else {
146+ ( start. x + start. y ) < ( end. x + end. y )
147+ }
148+ } )
149+ . unwrap_or ( true ) ;
150+
151+ let reverse_direction = IconButton :: new ( if orientation { "ReverseRadialGradientToRight" } else { "ReverseRadialGradientToLeft" } , 24 )
152+ . tooltip_label ( "Reverse Direction" )
153+ . tooltip_description ( "Reverse which end the gradient radiates from." )
154+ . disabled ( !self . data . has_selected_gradient )
155+ . on_update ( |_| {
156+ GradientToolMessage :: UpdateOptions {
157+ options : GradientOptionsUpdate :: ReverseDirection ,
158+ }
159+ . into ( )
160+ } )
161+ . widget_instance ( ) ;
162+
163+ widgets. push ( Separator :: new ( SeparatorStyle :: Related ) . widget_instance ( ) ) ;
164+ widgets. push ( reverse_direction) ;
165+ }
166+
167+ Layout ( vec ! [ LayoutGroup :: Row { widgets } ] )
146168 }
147169}
148170
@@ -492,6 +514,7 @@ struct GradientToolData {
492514 auto_panning : AutoPanning ,
493515 auto_pan_shift : DVec2 ,
494516 gradient_angle : f64 ,
517+ has_selected_gradient : bool ,
495518}
496519
497520impl Fsm for GradientToolFsmState {
@@ -993,9 +1016,12 @@ impl Fsm for GradientToolFsmState {
9931016 }
9941017
9951018 // Initialize `gradient_angle` from the existing gradient so Ctrl (lock angle) works from the first mouse move
996- if let Some ( sg) = & tool_data. selected_gradient {
997- let ( vp_start, vp_end) = ( sg. transform . transform_point2 ( sg. gradient . start ) , sg. transform . transform_point2 ( sg. gradient . end ) ) ;
998- let delta = match sg. dragging {
1019+ if let Some ( selected_gradient) = & tool_data. selected_gradient {
1020+ let ( vp_start, vp_end) = (
1021+ selected_gradient. transform . transform_point2 ( selected_gradient. gradient . start ) ,
1022+ selected_gradient. transform . transform_point2 ( selected_gradient. gradient . end ) ,
1023+ ) ;
1024+ let delta = match selected_gradient. dragging {
9991025 // When dragging End, the fixed point is start and the mouse begins at end
10001026 GradientDragTarget :: End => vp_start - vp_end,
10011027 // When dragging Start, the fixed point is end and the mouse begins at start
@@ -1318,6 +1344,63 @@ fn compute_selected_target(tool_data: &GradientToolData) -> GradientSelectedTarg
13181344 }
13191345}
13201346
1347+ fn apply_gradient_update (
1348+ data : & mut GradientToolData ,
1349+ context : & mut ToolActionMessageContext ,
1350+ responses : & mut VecDeque < Message > ,
1351+ condition : impl Fn ( & Gradient ) -> bool ,
1352+ update : impl Fn ( & mut Gradient ) ,
1353+ ) {
1354+ let selected_layers: Vec < _ > = context
1355+ . document
1356+ . network_interface
1357+ . selected_nodes ( )
1358+ . selected_visible_layers ( & context. document . network_interface )
1359+ . collect ( ) ;
1360+
1361+ let mut transaction_started = false ;
1362+ for layer in selected_layers {
1363+ if NodeGraphLayer :: is_raster_layer ( layer, & mut context. document . network_interface ) {
1364+ continue ;
1365+ }
1366+
1367+ if let Some ( mut gradient) = get_gradient ( layer, & context. document . network_interface )
1368+ && condition ( & gradient)
1369+ {
1370+ if !transaction_started {
1371+ responses. add ( DocumentMessage :: StartTransaction ) ;
1372+ transaction_started = true ;
1373+ }
1374+ update ( & mut gradient) ;
1375+ responses. add ( GraphOperationMessage :: FillSet {
1376+ layer,
1377+ fill : Fill :: Gradient ( gradient) ,
1378+ } ) ;
1379+ }
1380+ }
1381+
1382+ if transaction_started {
1383+ responses. add ( DocumentMessage :: AddTransaction ) ;
1384+ }
1385+ if let Some ( selected_gradient) = & mut data. selected_gradient
1386+ && let Some ( layer) = selected_gradient. layer
1387+ && !NodeGraphLayer :: is_raster_layer ( layer, & mut context. document . network_interface )
1388+ {
1389+ update ( & mut selected_gradient. gradient ) ;
1390+ }
1391+ responses. add ( PropertiesPanelMessage :: Refresh ) ;
1392+ data. has_selected_gradient = has_gradient_on_selected_layers ( context. document ) ;
1393+ responses. add ( ToolMessage :: RefreshToolOptions ) ;
1394+ }
1395+
1396+ fn has_gradient_on_selected_layers ( document : & DocumentMessageHandler ) -> bool {
1397+ document
1398+ . network_interface
1399+ . selected_nodes ( )
1400+ . selected_visible_layers ( & document. network_interface )
1401+ . any ( |layer| get_gradient ( layer, & document. network_interface ) . is_some ( ) )
1402+ }
1403+
13211404#[ inline( always) ]
13221405fn midpoint_is_resettable ( value : f64 ) -> bool {
13231406 ( value - 0.5 ) . abs ( ) >= f64:: EPSILON * 1000.
0 commit comments