@@ -13,6 +13,7 @@ use crate::messages::{
1313 prelude:: { DocumentMessageHandler , FrontendMessage } ,
1414} ;
1515use glam:: DVec2 ;
16+ use graph_craft:: document:: NodeId ;
1617use graph_craft:: document:: NodeInput ;
1718use graph_craft:: document:: value:: TaggedValue ;
1819use std:: collections:: VecDeque ;
@@ -41,6 +42,7 @@ pub struct SweepAngleGizmo {
4142 endpoint : EndpointType ,
4243 initial_start_angle : f64 ,
4344 initial_sweep_angle : f64 ,
45+ initial_start_point : DVec2 ,
4446 previous_mouse_position : DVec2 ,
4547 total_angle_delta : f64 ,
4648 snap_angles : Vec < f64 > ,
@@ -203,6 +205,7 @@ impl SweepAngleGizmo {
203205 let offset_angle = initial_vector. to_angle ( ) + tilt_offset;
204206
205207 let angle = initial_vector. angle_to ( final_vector) . to_degrees ( ) ;
208+ log:: info!( "angle {:?}" , angle) ;
206209 let display_angle = calculate_display_angle ( angle) ;
207210
208211 let text = format ! ( "{}°" , format_rounded( display_angle, 2 ) ) ;
@@ -224,52 +227,161 @@ impl SweepAngleGizmo {
224227 return ;
225228 } ;
226229
227- let Some ( ( _, start_angle , _ , _) ) = extract_arc_parameters ( Some ( layer) , document) else {
230+ let Some ( ( _, current_start_angle , current_sweep_angle , _) ) = extract_arc_parameters ( Some ( layer) , document) else {
228231 return ;
229232 } ;
230233
231234 let viewport = document. metadata ( ) . transform_to_viewport ( layer) ;
232- let angle = self . total_angle_delta
233- + viewport
234- . inverse ( )
235- . transform_point2 ( self . previous_mouse_position )
236- . angle_to ( viewport . inverse ( ) . transform_point2 ( input . mouse . position ) )
237- . to_degrees ( ) ;
235+ let angle_delta = viewport
236+ . inverse ( )
237+ . transform_point2 ( self . previous_mouse_position )
238+ . angle_to ( viewport . inverse ( ) . transform_point2 ( input . mouse . position ) )
239+ . to_degrees ( ) ;
240+ let angle = self . total_angle_delta + angle_delta ;
238241
239242 let Some ( node_id) = graph_modification_utils:: get_arc_id ( layer, & document. network_interface ) else {
240243 return ;
241244 } ;
242245
243246 self . update_state ( SweepAngleGizmoState :: Dragging ) ;
244- if self . endpoint == EndpointType :: End {
245- let mut total = angle;
246- if let Some ( snapped_delta) = self . check_snapping ( start_angle, self . initial_sweep_angle + angle) {
247- total += snapped_delta;
248- self . update_state ( SweepAngleGizmoState :: Snapped ) ;
247+
248+ match self . endpoint {
249+ EndpointType :: Start => {
250+ // Dragging start changes both start and sweep
251+
252+ let sign = angle. signum ( ) * -1. ;
253+ let mut total = angle;
254+
255+ let new_start_angle = self . initial_start_angle + total;
256+ let new_sweep_angle = self . initial_sweep_angle + total. abs ( ) * sign;
257+
258+ // Clamp sweep angle to 360°
259+ if new_sweep_angle > 360. {
260+ let wrapped = new_sweep_angle % 360. ;
261+ self . total_angle_delta = -wrapped;
262+
263+ // Remaining drag gets passed to the end endpoint
264+ let rest_angle = angle_delta + wrapped;
265+ self . endpoint = EndpointType :: End ;
266+
267+ self . initial_sweep_angle = 360. ;
268+ self . initial_start_angle = current_start_angle + rest_angle;
269+
270+ self . apply_arc_update ( node_id, self . initial_start_angle , self . initial_sweep_angle - wrapped, input, responses) ;
271+ return ;
272+ }
273+
274+ if new_sweep_angle < 0. {
275+ let rest_angle = angle_delta + new_sweep_angle;
276+
277+ self . total_angle_delta = new_sweep_angle. abs ( ) ;
278+ self . endpoint = EndpointType :: End ;
279+
280+ self . initial_sweep_angle = 0. ;
281+ self . initial_start_angle = current_start_angle + rest_angle;
282+
283+ self . apply_arc_update ( node_id, self . initial_start_angle , new_sweep_angle. abs ( ) , input, responses) ;
284+ return ;
285+ }
286+
287+ // Wrap start angle > 180° back into [-180°, 180°] and adjust sweep
288+ if new_start_angle > 180. {
289+ let overflow = new_start_angle % 180. ;
290+ let rest_angle = angle_delta - overflow;
291+
292+ // We wrap the angle back into [-180°, 180°] range by jumping from +180° to -180°
293+ // Example: dragging past 190° becomes -170°, and we subtract the overshoot from sweep
294+ // Sweep angle must shrink to maintain consistent arc
295+ self . total_angle_delta = rest_angle;
296+ self . initial_start_angle = -180. ;
297+ self . initial_sweep_angle = current_sweep_angle - rest_angle;
298+
299+ self . apply_arc_update ( node_id, self . initial_start_angle + overflow, self . initial_sweep_angle - overflow, input, responses) ;
300+ return ;
301+ }
302+
303+ // Wrap start angle < -180° back into [-180°, 180°] and adjust sweep
304+ if new_start_angle < -180. {
305+ let underflow = new_start_angle % 180. ;
306+ let rest_angle = angle_delta - underflow;
307+
308+ // We wrap the angle back into [-180°, 180°] by jumping from -190° to +170°
309+ // Sweep must grow to reflect continued clockwise drag past -180°
310+ // Start angle flips from -190° to +170°, and sweep increases accordingly
311+ self . total_angle_delta = underflow;
312+ self . initial_start_angle = 180. ;
313+ self . initial_sweep_angle = current_sweep_angle + rest_angle. abs ( ) ;
314+
315+ self . apply_arc_update ( node_id, self . initial_start_angle + underflow, self . initial_sweep_angle + underflow. abs ( ) , input, responses) ;
316+ return ;
317+ }
318+
319+ if let Some ( snapped_delta) = self . check_snapping ( self . initial_start_angle + angle, self . initial_sweep_angle + total. abs ( ) * sign) {
320+ total += snapped_delta;
321+ self . update_state ( SweepAngleGizmoState :: Snapped ) ;
322+ }
323+
324+ self . total_angle_delta = angle;
325+ self . apply_arc_update ( node_id, self . initial_start_angle + total, self . initial_sweep_angle + total. abs ( ) * sign, input, responses) ;
249326 }
250- responses. add ( NodeGraphMessage :: SetInput {
251- input_connector : InputConnector :: node ( node_id, 3 ) ,
252- input : NodeInput :: value ( TaggedValue :: F64 ( self . initial_sweep_angle + total) , false ) ,
253- } ) ;
254- } else {
255- let sign = angle. signum ( ) * -1. ;
256- let mut total = angle;
327+ EndpointType :: End => {
328+ // Dragging the end only changes sweep angle
329+
330+ let mut total = angle;
331+ let new_sweep_angle = self . initial_sweep_angle + angle;
332+
333+ // Clamp sweep angle below 0°, switch to start
334+ if new_sweep_angle < 0. {
335+ let delta = angle_delta - current_sweep_angle;
336+ let sign = delta. signum ( ) * -1. ;
337+
338+ self . initial_sweep_angle = 0. ;
339+ self . total_angle_delta = delta;
340+ self . endpoint = EndpointType :: Start ;
341+
342+ self . apply_arc_update ( node_id, self . initial_start_angle + delta, self . initial_sweep_angle + delta. abs ( ) * sign, input, responses) ;
343+ return ;
344+ }
257345
258- if let Some ( snapped_delta) = self . check_snapping ( self . initial_start_angle + angle, self . initial_sweep_angle + total. abs ( ) * sign) {
259- total += snapped_delta;
260- self . update_state ( SweepAngleGizmoState :: Snapped ) ;
346+ // Clamp sweep angle above 360°, switch to start
347+ if new_sweep_angle > 360. {
348+ let delta = angle_delta - ( 360. - current_sweep_angle) ;
349+ let sign = delta. signum ( ) * -1. ;
350+
351+ self . total_angle_delta = angle_delta;
352+ self . initial_sweep_angle = 360. ;
353+ self . endpoint = EndpointType :: Start ;
354+
355+ self . apply_arc_update ( node_id, self . initial_start_angle + angle_delta, self . initial_sweep_angle + angle_delta. abs ( ) * sign, input, responses) ;
356+ return ;
357+ }
358+
359+ if let Some ( snapped_delta) = self . check_snapping ( self . initial_start_angle , self . initial_sweep_angle + angle) {
360+ total += snapped_delta;
361+ self . update_state ( SweepAngleGizmoState :: Snapped ) ;
362+ }
363+
364+ self . total_angle_delta = angle;
365+ self . apply_arc_update ( node_id, self . initial_start_angle , self . initial_sweep_angle + total, input, responses) ;
261366 }
262- responses. add ( NodeGraphMessage :: SetInput {
263- input_connector : InputConnector :: node ( node_id, 2 ) ,
264- input : NodeInput :: value ( TaggedValue :: F64 ( self . initial_start_angle + total) , false ) ,
265- } ) ;
266- responses. add ( NodeGraphMessage :: SetInput {
267- input_connector : InputConnector :: node ( node_id, 3 ) ,
268- input : NodeInput :: value ( TaggedValue :: F64 ( self . initial_sweep_angle + total. abs ( ) * sign) , false ) ,
269- } ) ;
367+ EndpointType :: None => { }
270368 }
369+ }
370+
371+ /// Applies the updated start and sweep angles to the arc.
372+ fn apply_arc_update ( & mut self , node_id : NodeId , start_angle : f64 , sweep_angle : f64 , input : & InputPreprocessorMessageHandler , responses : & mut VecDeque < Message > ) {
373+ self . snap_angles = self . calculate_snap_angles ( start_angle, sweep_angle) ;
374+
375+ responses. add ( NodeGraphMessage :: SetInput {
376+ input_connector : InputConnector :: node ( node_id, 2 ) ,
377+ input : NodeInput :: value ( TaggedValue :: F64 ( start_angle) , false ) ,
378+ } ) ;
379+ responses. add ( NodeGraphMessage :: SetInput {
380+ input_connector : InputConnector :: node ( node_id, 3 ) ,
381+ input : NodeInput :: value ( TaggedValue :: F64 ( sweep_angle) , false ) ,
382+ } ) ;
383+
271384 self . previous_mouse_position = input. mouse . position ;
272- self . total_angle_delta = angle;
273385 responses. add ( NodeGraphMessage :: RunDocumentGraph ) ;
274386 }
275387
@@ -299,7 +411,7 @@ impl SweepAngleGizmo {
299411
300412 if self . endpoint == EndpointType :: End {
301413 for i in 0 ..8 {
302- let snap_point = i as f64 * FRAC_PI_4 ;
414+ let snap_point = wrap_to_tau ( i as f64 * FRAC_PI_4 + initial_start_angle ) ;
303415 snap_points. push ( snap_point. to_degrees ( ) ) ;
304416 }
305417 }
0 commit comments