@@ -338,110 +338,141 @@ protected void drawEnvOnView(final Graphics2D g) {
338338 return ;
339339 }
340340 accessData ();
341- if (hooked .isPresent ()) {
342- final P hcoor = positions .get (hooked .get ());
343- final Point hp = wormhole .getViewPoint (hcoor );
344- if (hp .distance (getCenter ()) > FREEDOM_RADIUS ) {
345- wormhole .setViewPosition (hp );
346- }
341+ updateHookedNodeView ();
342+ final Map <Node <T >, Point > onView = computeNodesOnView ();
343+ g .setColor (Color .BLACK );
344+ drawObstacles (g );
345+ drawLinks (g , onView );
346+ releaseData ();
347+ moveSelectedNodesOnView (onView );
348+ g .setColor (Color .GREEN );
349+ if (effectStack != null ) {
350+ effectStack .forEach (effect -> onView .forEach ((node , point ) ->
351+ effect .apply (g , node , currentEnv , wormhole )));
352+ }
353+ highlightClosestNode (g , onView );
354+ drawSelectionRectangle (g , onView );
355+ highlightSelectedNodes (g , onView );
356+ }
357+
358+ private Map <Node <T >, Point > computeNodesOnView () {
359+ return positions .entrySet ().parallelStream ()
360+ .map (pair -> new Pair <>(pair .getKey (), wormhole .getViewPoint (pair .getValue ())))
361+ .filter (p -> wormhole .isInsideView (p .getSecond ()))
362+ .collect (Collectors .toMap (Pair ::getKey , Pair ::getValue ));
363+ }
364+
365+ private void updateHookedNodeView () {
366+ if (hooked .isEmpty ()) {
367+ return ;
368+ }
369+ final P hookedCoordinates = positions .get (hooked .get ());
370+ final Point hookedPoint = wormhole .getViewPoint (hookedCoordinates );
371+ if (hookedPoint .distance (getCenter ()) > FREEDOM_RADIUS ) {
372+ wormhole .setViewPosition (hookedPoint );
373+ }
374+ }
375+
376+ private void drawObstacles (final Graphics2D g ) {
377+ if (obstacles == null ) {
378+ return ;
347379 }
348380 /*
349- * Compute nodes in sight and their screen position
381+ * This currently draws all obstacles, even when they are fully outside the viewport.
350382 */
351- final Map <Node <T >, Point > onView = positions .entrySet ().parallelStream ()
352- .map (pair -> new Pair <>(pair .getKey (), wormhole .getViewPoint (pair .getValue ())))
353- .filter (p -> wormhole .isInsideView (p .getSecond ()))
354- .collect (Collectors .toMap (Pair ::getKey , Pair ::getValue ));
355- g .setColor (Color .BLACK );
356- if (obstacles != null ) {
357- /*
358- * TODO: only draw obstacles if on view
359- */
360- obstacles .parallelStream ()
361- .map (this ::convertObstacle )
362- .forEachOrdered (g ::fill );
383+ obstacles .parallelStream ()
384+ .map (this ::convertObstacle )
385+ .forEachOrdered (g ::fill );
386+ }
387+
388+ private void drawLinks (final Graphics2D g , final Map <Node <T >, Point > onView ) {
389+ if (!paintLinks ) {
390+ return ;
363391 }
364- if (paintLinks ) {
365- g .setColor (Color .GRAY );
366- onView .keySet ().parallelStream ()
367- .map (neighbors ::get )
368- .flatMap (neigh ->
369- neigh .getNeighbors ().parallelStream ()
370- .map (node ->
371- node .compareTo (neigh .getCenter ()) > 0
372- ? new Pair <>(neigh .getCenter (), node )
373- : new Pair <>(node , neigh .getCenter ())
374- )
375- )
376- .distinct ()
377- .map (pair ->
378- mapPair (
379- pair ,
380- node -> Optional
381- .ofNullable (onView .get (node ))
382- .orElse (wormhole .getViewPoint (positions .get (node )))
392+ g .setColor (Color .GRAY );
393+ onView .keySet ().parallelStream ()
394+ .map (neighbors ::get )
395+ .flatMap (neigh ->
396+ neigh .getNeighbors ().parallelStream ()
397+ .map (node ->
398+ node .compareTo (neigh .getCenter ()) > 0
399+ ? new Pair <>(neigh .getCenter (), node )
400+ : new Pair <>(node , neigh .getCenter ())
383401 )
402+ )
403+ .distinct ()
404+ .map (pair ->
405+ mapPair (
406+ pair ,
407+ node -> Optional
408+ .ofNullable (onView .get (node ))
409+ .orElse (wormhole .getViewPoint (positions .get (node )))
384410 )
385- .forEachOrdered (line -> {
386- final Point p1 = line .getFirst ();
387- final Point p2 = line .getSecond ();
388- g .drawLine (p1 .x , p1 .y , p2 .x , p2 .y );
389- });
411+ )
412+ .forEachOrdered (line -> {
413+ final Point p1 = line .getFirst ();
414+ final Point p2 = line .getSecond ();
415+ g .drawLine (p1 .x , p1 .y , p2 .x , p2 .y );
416+ });
417+ }
418+
419+ private void moveSelectedNodesOnView (final Map <Node <T >, Point > onView ) {
420+ if (!isDraggingMouse || status != ViewStatus .MOVING_SELECTED_NODES || originPoint == null || endingPoint == null ) {
421+ return ;
390422 }
391- releaseData ();
392- if (
393- isDraggingMouse
394- && status == ViewStatus .MOVING_SELECTED_NODES
395- && originPoint != null
396- && endingPoint != null
397- ) {
398- for (final Node <T > n : selectedNodes ) {
399- if (onView .containsKey (n )) {
400- onView .put (n , new Point (onView .get (n ).x + (endingPoint .x - originPoint .x ),
401- onView .get (n ).y + (endingPoint .y - originPoint .y )));
402- }
423+ for (final Node <T > node : selectedNodes ) {
424+ if (onView .containsKey (node )) {
425+ onView .put (
426+ node ,
427+ new Point (
428+ onView .get (node ).x + (endingPoint .x - originPoint .x ),
429+ onView .get (node ).y + (endingPoint .y - originPoint .y )
430+ )
431+ );
403432 }
404433 }
405- g .setColor (Color .GREEN );
406- if (effectStack != null ) {
407- effectStack .forEach (effect -> onView .forEach ((node , point ) ->
408- effect .apply (g , node , currentEnv , wormhole )));
409- }
410- if (isCloserNodeMarked ()) {
411- final Optional <Map .Entry <Node <T >, Point >> closest = onView .entrySet ().parallelStream ()
412- .min ((pair1 , pair2 ) -> {
413- final Point p1 = pair1 .getValue ();
414- final Point p2 = pair2 .getValue ();
415- final double d1 = Math .hypot (p1 .x - mouseX , p1 .y - mouseY );
416- final double d2 = Math .hypot (p2 .x - mouseX , p2 .y - mouseY );
417- return Double .compare (d1 , d2 );
418- });
419- if (closest .isPresent ()) {
420- nearest = closest .get ().getKey ();
421- final int nearestX = closest .get ().getValue ().x ;
422- final int nearestY = closest .get ().getValue ().y ;
423- drawFriedEgg (g , nearestX , nearestY , Color .RED , Color .YELLOW );
424- }
425- } else {
434+ }
435+
436+ private void highlightClosestNode (final Graphics2D g , final Map <Node <T >, Point > onView ) {
437+ if (!isCloserNodeMarked ()) {
426438 nearest = null ;
439+ return ;
427440 }
428- if (isDraggingMouse && status == ViewStatus .SELECTING_NODES && originPoint != null && endingPoint != null ) {
429- g .setColor (Color .BLACK );
430- final int x = Math .min (originPoint .x , endingPoint .x );
431- final int y = Math .min (originPoint .y , endingPoint .y );
432- final int width = Math .abs (endingPoint .x - originPoint .x );
433- final int height = Math .abs (endingPoint .y - originPoint .y );
434- g .drawRect (x , y , width , height );
435- selectedNodes = onView .entrySet ().parallelStream ()
436- .filter (nodes -> isInsideRectangle (nodes .getValue (), x , y , width , height ))
437- .map (Map .Entry ::getKey )
438- .collect (Collectors .toSet ());
441+ onView .entrySet ().parallelStream ()
442+ .min ((pair1 , pair2 ) -> Double .compare (distanceFromMouse (pair1 .getValue ()), distanceFromMouse (pair2 .getValue ())))
443+ .ifPresent (closest -> {
444+ nearest = closest .getKey ();
445+ final Point point = closest .getValue ();
446+ drawFriedEgg (g , point .x , point .y , Color .RED , Color .YELLOW );
447+ });
448+ }
449+
450+ private double distanceFromMouse (final Point point ) {
451+ return Math .hypot (point .x - mouseX , point .y - mouseY );
452+ }
453+
454+ private void drawSelectionRectangle (final Graphics2D g , final Map <Node <T >, Point > onView ) {
455+ if (!isDraggingMouse || status != ViewStatus .SELECTING_NODES || originPoint == null || endingPoint == null ) {
456+ return ;
439457 }
458+ g .setColor (Color .BLACK );
459+ final int x = Math .min (originPoint .x , endingPoint .x );
460+ final int y = Math .min (originPoint .y , endingPoint .y );
461+ final int width = Math .abs (endingPoint .x - originPoint .x );
462+ final int height = Math .abs (endingPoint .y - originPoint .y );
463+ g .drawRect (x , y , width , height );
464+ selectedNodes = onView .entrySet ().parallelStream ()
465+ .filter (nodes -> isInsideRectangle (nodes .getValue (), x , y , width , height ))
466+ .map (Map .Entry ::getKey )
467+ .collect (Collectors .toSet ());
468+ }
469+
470+ private void highlightSelectedNodes (final Graphics2D g , final Map <Node <T >, Point > onView ) {
440471 selectedNodes .parallelStream ()
441- .map (e -> Optional .ofNullable (onView .get (e )))
472+ .map (node -> Optional .ofNullable (onView .get (node )))
442473 .filter (Optional ::isPresent )
443474 .map (Optional ::get )
444- .forEachOrdered (p -> drawFriedEgg (g , p .x , p .y , Color .BLUE , Color .CYAN ));
475+ .forEachOrdered (point -> drawFriedEgg (g , point .x , point .y , Color .BLUE , Color .CYAN ));
445476 }
446477
447478 private void drawFriedEgg (final Graphics g , final int x , final int y , final Color c1 , final Color c2 ) {
@@ -899,7 +930,7 @@ public void mouseReleased(final MouseEvent e) {
899930 });
900931 }
901932 } else {
902- // TODO: display proper error message
933+ // Surface this warning through the UI if interactive feedback is added here.
903934 L .warn ("Can not handle node movement on a finished simulation." );
904935 }
905936 } else {
0 commit comments