@@ -26,12 +26,25 @@ pub struct MeshGradientTool {
2626 options : MeshGradientOptions ,
2727}
2828
29- #[ derive( Default ) ]
3029pub struct MeshGradientOptions {
3130 space : GradientSpace ,
3231 interpolation : GradientInterpolation ,
3332}
3433
34+ impl Default for MeshGradientOptions {
35+ fn default ( ) -> Self {
36+ let MeshGradientSurface {
37+ gradient_space,
38+ gradient_interpolation,
39+ ..
40+ } = MeshGradientSurface :: default ( ) ;
41+ Self {
42+ space : gradient_space,
43+ interpolation : gradient_interpolation,
44+ }
45+ }
46+ }
47+
3548#[ impl_message( Message , ToolMessage , MeshGradient ) ]
3649#[ cfg_attr( feature = "wasm" , derive( tsify:: Tsify ) ) ]
3750#[ derive( PartialEq , Clone , Debug , serde:: Serialize , serde:: Deserialize ) ]
@@ -40,15 +53,13 @@ pub enum MeshGradientToolMessage {
4053 Abort ,
4154 Overlays { context : OverlayContext } ,
4255 SelectionChanged ,
43- WorkingColorChanged ,
4456
4557 // Tool-specific messages
4658 DeleteEdge ,
4759 DoubleClick ,
48- InsertStop ,
4960 PointerDown ,
50- PointerMove { constrain_axis : Key , lock_angle : Key } ,
51- PointerOutsideViewport { constrain_axis : Key , lock_angle : Key } ,
61+ PointerMove { constrain_axis : Key } ,
62+ PointerOutsideViewport { constrain_axis : Key } ,
5263 PointerUp ,
5364 StartTransactionForColorStop ,
5465 CommitTransactionForColorStop ,
@@ -86,9 +97,10 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Mesh
8697 MeshGradientOptionsUpdate :: Interpolation ( interpolation) => self . options . interpolation = interpolation,
8798 }
8899
89- apply_mesh_gradient_options ( context, responses, |surface| {
90- surface. gradient_space = self . options . space ;
91- surface. gradient_interpolation = self . options . interpolation ;
100+ // Write back only the setting that actually changed, so a layer whose other setting differs keeps it
101+ apply_mesh_gradient_options ( context, responses, |surface| match & options {
102+ MeshGradientOptionsUpdate :: Space ( space) => surface. gradient_space = * space,
103+ MeshGradientOptionsUpdate :: Interpolation ( interpolation) => surface. gradient_interpolation = * interpolation,
92104 } ) ;
93105 self . refresh_options ( responses) ;
94106 }
@@ -327,64 +339,7 @@ fn resolve_mesh_gradient_source(layer: LayerNodeIdentifier, network_interface: &
327339 }
328340}
329341
330- fn approximate_valid_region_bounds ( initial_position : DVec2 , [ min, max] : [ DVec2 ; 2 ] , mut is_valid : impl FnMut ( DVec2 ) -> bool ) -> Option < [ DVec2 ; 2 ] > {
331- const SUBDIVISIONS : usize = 12 ;
332-
333- let mut x_samples = ( 0 ..=SUBDIVISIONS ) . map ( |index| min. x + ( max. x - min. x ) * index as f64 / SUBDIVISIONS as f64 ) . collect :: < Vec < _ > > ( ) ;
334- let mut y_samples = ( 0 ..=SUBDIVISIONS ) . map ( |index| min. y + ( max. y - min. y ) * index as f64 / SUBDIVISIONS as f64 ) . collect :: < Vec < _ > > ( ) ;
335- x_samples. push ( initial_position. x ) ;
336- y_samples. push ( initial_position. y ) ;
337- x_samples. sort_by ( f64:: total_cmp) ;
338- y_samples. sort_by ( f64:: total_cmp) ;
339- x_samples. dedup ( ) ;
340- y_samples. dedup ( ) ;
341-
342- let columns = x_samples. len ( ) ;
343- let rows = y_samples. len ( ) ;
344- let seed_column = x_samples. iter ( ) . position ( |& x| x == initial_position. x ) ?;
345- let seed_row = y_samples. iter ( ) . position ( |& y| y == initial_position. y ) ?;
346- let seed_index = seed_row * columns + seed_column;
347-
348- let valid_samples = y_samples. iter ( ) . flat_map ( |& y| x_samples. iter ( ) . map ( move |& x| DVec2 :: new ( x, y) ) ) . map ( & mut is_valid) . collect :: < Vec < _ > > ( ) ;
349-
350- if !valid_samples[ seed_index] {
351- return None ;
352- }
353-
354- let mut visited = vec ! [ false ; rows * columns] ;
355- let mut queue = VecDeque :: from ( [ seed_index] ) ;
356- let mut bounds_min = initial_position;
357- let mut bounds_max = initial_position;
358-
359- while let Some ( index) = queue. pop_front ( ) {
360- if visited[ index] || !valid_samples[ index] {
361- continue ;
362- }
363- visited[ index] = true ;
364-
365- let row = index / columns;
366- let column = index % columns;
367- let position = DVec2 :: new ( x_samples[ column] , y_samples[ row] ) ;
368- bounds_min = bounds_min. min ( position) ;
369- bounds_max = bounds_max. max ( position) ;
370-
371- if row > 0 {
372- queue. push_back ( index - columns) ;
373- }
374- if row + 1 < rows {
375- queue. push_back ( index + columns) ;
376- }
377- if column > 0 {
378- queue. push_back ( index - 1 ) ;
379- }
380- if column + 1 < columns {
381- queue. push_back ( index + 1 ) ;
382- }
383- }
384-
385- Some ( [ bounds_min, bounds_max] )
386- }
387-
342+ /// Walks back from `target` toward `valid_region_center` for the furthest position that keeps the mesh free of foldovers.
388343fn constrain_to_valid_region ( target : DVec2 , valid_region_center : DVec2 , candidate : impl Fn ( DVec2 ) -> Option < MeshGradient > ) -> Option < MeshGradient > {
389344 candidate ( target) . or_else ( || {
390345 const BINARY_SEARCH_ITERATIONS : usize = 12 ;
@@ -435,7 +390,6 @@ impl ToolTransition for MeshGradientTool {
435390 EventToMessageMap {
436391 tool_abort : Some ( MeshGradientToolMessage :: Abort . into ( ) ) ,
437392 selection_changed : Some ( MeshGradientToolMessage :: SelectionChanged . into ( ) ) ,
438- working_color_changed : Some ( MeshGradientToolMessage :: WorkingColorChanged . into ( ) ) ,
439393 overlay_provider : Some ( |context| MeshGradientToolMessage :: Overlays { context } . into ( ) ) ,
440394 ..Default :: default ( )
441395 }
@@ -615,9 +569,12 @@ impl Fsm for MeshGradientToolFsmState {
615569
616570 ( _state @ MeshGradientToolFsmState :: Ready { .. } , MeshGradientToolMessage :: DeleteEdge ) => {
617571 let Some ( selected_mesh) = tool_data. selected_mesh . as_mut ( ) else { return self } ;
618- if let MeshGradientTarget :: Segment { segment_id, .. } = selected_mesh. target {
619- selected_mesh. surface . mesh . remove_edge ( segment_id) ;
620- } ;
572+ let MeshGradientTarget :: Segment { segment_id, .. } = selected_mesh. target else { return self } ;
573+ let mut mesh = selected_mesh. surface . mesh . clone ( ) ;
574+ if mesh. remove_edge ( segment_id) . is_none ( ) {
575+ return self ;
576+ }
577+ selected_mesh. surface . mesh = mesh;
621578
622579 responses. add ( DocumentMessage :: StartTransaction ) ;
623580 selected_mesh. update_gradient_in_graph ( responses) ;
@@ -721,18 +678,6 @@ impl Fsm for MeshGradientToolFsmState {
721678
722679 if distance_squared < tolerance_squared {
723680 responses. add ( DocumentMessage :: StartTransaction ) ;
724- let valid_region_center = gradient
725- . geometry ( )
726- . bounding_box ( )
727- . and_then ( |bounds| {
728- approximate_valid_region_bounds ( corner. position , bounds, |position| {
729- let mut candidate = gradient. clone ( ) ;
730- candidate. set_corner_position ( corner. index , position) . is_some ( )
731- && candidate. patches ( ) . all ( |patch| patch. is_some_and ( |patch| patch. sampled_no_foldover ( ) ) )
732- } )
733- } )
734- . map ( |[ min, max] | min. midpoint ( max) )
735- . unwrap_or ( corner. position ) ;
736681
737682 tool_data. selected_mesh = Some ( SelectedMeshGradient {
738683 layer,
@@ -744,7 +689,7 @@ impl Fsm for MeshGradientToolFsmState {
744689 corner_index : corner. index ,
745690 initial_mouse : local_mouse,
746691 initial_corner : corner. position ,
747- valid_region_center,
692+ valid_region_center : corner . position ,
748693 } ,
749694 } ) ;
750695
@@ -782,37 +727,27 @@ impl Fsm for MeshGradientToolFsmState {
782727 consider_handle ( HandleId :: end ( segment_id) , handle_end, bezier. end , None ) ;
783728 }
784729 }
730+ }
785731
786- if let Some ( ( handle_id, initial_handle, _) ) = closest_handle {
787- responses. add ( DocumentMessage :: StartTransaction ) ;
788- let valid_region_center = gradient
789- . geometry ( )
790- . bounding_box ( )
791- . and_then ( |bounds| {
792- approximate_valid_region_bounds ( initial_handle, bounds, |position| {
793- let mut candidate = gradient. clone ( ) ;
794- candidate. set_handle_position ( handle_id, position) . is_some ( ) && candidate. patches ( ) . all ( |patch| patch. is_some_and ( |patch| patch. sampled_no_foldover ( ) ) )
795- } )
796- } )
797- . map ( |[ min, max] | min. midpoint ( max) )
798- . unwrap_or ( initial_handle) ;
799-
800- tool_data. selected_mesh = Some ( SelectedMeshGradient {
801- layer,
802- mesh_index : index,
803- surface : mesh_gradient_surface ( meshes, index, gradient) ,
804- mesh_to_document,
805- source,
806- target : MeshGradientTarget :: Handle {
807- handle_id,
808- initial_mouse : local_mouse,
809- initial_handle,
810- valid_region_center,
811- } ,
812- } ) ;
732+ // Resolved only after every segment has been offered, so the nearest-wins comparison spans the whole mesh
733+ if let Some ( ( handle_id, initial_handle, _) ) = closest_handle {
734+ responses. add ( DocumentMessage :: StartTransaction ) ;
735+
736+ tool_data. selected_mesh = Some ( SelectedMeshGradient {
737+ layer,
738+ mesh_index : index,
739+ surface : mesh_gradient_surface ( meshes, index, gradient) ,
740+ mesh_to_document,
741+ source,
742+ target : MeshGradientTarget :: Handle {
743+ handle_id,
744+ initial_mouse : local_mouse,
745+ initial_handle,
746+ valid_region_center : initial_handle,
747+ } ,
748+ } ) ;
813749
814- return MeshGradientToolFsmState :: Dragging ;
815- }
750+ return MeshGradientToolFsmState :: Dragging ;
816751 }
817752
818753 for edge in gradient. edges ( ) {
@@ -826,31 +761,11 @@ impl Fsm for MeshGradientToolFsmState {
826761
827762 let handles = match ( points. p1 , points. p2 ) {
828763 ( Some ( p1) , Some ( p2) ) => [ p1, p2] ,
829- ( Some ( p1 ) , None ) | ( None , Some ( p1 ) ) => [ p1 , points. p3 ] ,
764+ ( Some ( control ) , None ) | ( None , Some ( control ) ) => [ points . p0 + ( control - points . p0 ) * 2. / 3. , points. p3 + ( control - points . p3 ) * 2. / 3. ] ,
830765 ( None , None ) => [ points. p0 + ( points. p3 - points. p0 ) / 3. , points. p3 + ( points. p0 - points. p3 ) / 3. ] ,
831766 } ;
832767
833768 responses. add ( DocumentMessage :: StartTransaction ) ;
834- let valid_region_center = gradient
835- . geometry ( )
836- . bounding_box ( )
837- . and_then ( |bounds| {
838- approximate_valid_region_bounds ( local_mouse, bounds, |position| {
839- let delta = position - local_mouse;
840- let mut candidate = gradient. clone ( ) ;
841- candidate
842- . set_edge_handles (
843- edge. segment_id ,
844- BezierHandles :: Cubic {
845- handle_start : handles[ 0 ] + delta,
846- handle_end : handles[ 1 ] + delta,
847- } ,
848- )
849- . is_some ( ) && candidate. patches ( ) . all ( |patch| patch. is_some_and ( |patch| patch. sampled_no_foldover ( ) ) )
850- } )
851- } )
852- . map ( |[ min, max] | min. midpoint ( max) )
853- . unwrap_or ( local_mouse) ;
854769
855770 tool_data. selected_mesh = Some ( SelectedMeshGradient {
856771 layer,
@@ -862,7 +777,7 @@ impl Fsm for MeshGradientToolFsmState {
862777 segment_id : edge. segment_id ,
863778 initial_mouse : local_mouse,
864779 initial_handles : handles,
865- valid_region_center,
780+ valid_region_center : local_mouse ,
866781 } ,
867782 } ) ;
868783
@@ -904,7 +819,7 @@ impl Fsm for MeshGradientToolFsmState {
904819
905820 self
906821 }
907- ( MeshGradientToolFsmState :: Dragging , MeshGradientToolMessage :: PointerMove { constrain_axis, lock_angle } ) => {
822+ ( MeshGradientToolFsmState :: Dragging , MeshGradientToolMessage :: PointerMove { constrain_axis } ) => {
908823 let MeshGradientToolData {
909824 selected_mesh,
910825 snap_manager,
@@ -1032,8 +947,8 @@ impl Fsm for MeshGradientToolFsmState {
1032947
1033948 // Auto-panning
1034949 let messages = [
1035- MeshGradientToolMessage :: PointerOutsideViewport { constrain_axis, lock_angle } . into ( ) ,
1036- MeshGradientToolMessage :: PointerMove { constrain_axis, lock_angle } . into ( ) ,
950+ MeshGradientToolMessage :: PointerOutsideViewport { constrain_axis } . into ( ) ,
951+ MeshGradientToolMessage :: PointerMove { constrain_axis } . into ( ) ,
1037952 ] ;
1038953 auto_panning. setup_by_mouse_position ( input, viewport, & messages, responses) ;
1039954
@@ -1074,10 +989,10 @@ impl Fsm for MeshGradientToolFsmState {
1074989
1075990 MeshGradientToolFsmState :: Dragging
1076991 }
1077- ( state, MeshGradientToolMessage :: PointerOutsideViewport { constrain_axis, lock_angle } ) => {
992+ ( state, MeshGradientToolMessage :: PointerOutsideViewport { constrain_axis } ) => {
1078993 let messages = [
1079- MeshGradientToolMessage :: PointerOutsideViewport { constrain_axis, lock_angle } . into ( ) ,
1080- MeshGradientToolMessage :: PointerMove { constrain_axis, lock_angle } . into ( ) ,
994+ MeshGradientToolMessage :: PointerOutsideViewport { constrain_axis } . into ( ) ,
995+ MeshGradientToolMessage :: PointerMove { constrain_axis } . into ( ) ,
1081996 ] ;
1082997 tool_data. auto_panning . stop ( & messages, responses) ;
1083998
0 commit comments