From aab9222f55dd5521fc63aadb106ace6d275f5d8f Mon Sep 17 00:00:00 2001 From: Danilo Pianini Date: Tue, 28 Apr 2026 15:27:50 +0200 Subject: [PATCH 1/7] style(swingui): simplify Generic2DDisplay rendering --- .../monitor/impl/Generic2DDisplay.java | 211 ++++++++++-------- 1 file changed, 121 insertions(+), 90 deletions(-) diff --git a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java index fce553dc6f..464615364d 100644 --- a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java +++ b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java @@ -338,110 +338,141 @@ protected void drawEnvOnView(final Graphics2D g) { return; } accessData(); - if (hooked.isPresent()) { - final P hcoor = positions.get(hooked.get()); - final Point hp = wormhole.getViewPoint(hcoor); - if (hp.distance(getCenter()) > FREEDOM_RADIUS) { - wormhole.setViewPosition(hp); - } + updateHookedNodeView(); + final Map, Point> onView = computeNodesOnView(); + g.setColor(Color.BLACK); + drawObstacles(g); + drawLinks(g, onView); + releaseData(); + moveSelectedNodesOnView(onView); + g.setColor(Color.GREEN); + if (effectStack != null) { + effectStack.forEach(effect -> onView.forEach((node, point) -> + effect.apply(g, node, currentEnv, wormhole))); + } + highlightClosestNode(g, onView); + drawSelectionRectangle(g, onView); + highlightSelectedNodes(g, onView); + } + + private Map, Point> computeNodesOnView() { + return positions.entrySet().parallelStream() + .map(pair -> new Pair<>(pair.getKey(), wormhole.getViewPoint(pair.getValue()))) + .filter(p -> wormhole.isInsideView(p.getSecond())) + .collect(Collectors.toMap(Pair::getKey, Pair::getValue)); + } + + private void updateHookedNodeView() { + if (hooked.isEmpty()) { + return; + } + final P hookedCoordinates = positions.get(hooked.get()); + final Point hookedPoint = wormhole.getViewPoint(hookedCoordinates); + if (hookedPoint.distance(getCenter()) > FREEDOM_RADIUS) { + wormhole.setViewPosition(hookedPoint); + } + } + + private void drawObstacles(final Graphics2D g) { + if (obstacles == null) { + return; } /* - * Compute nodes in sight and their screen position + * This currently draws all obstacles, even when they are fully outside the viewport. */ - final Map, Point> onView = positions.entrySet().parallelStream() - .map(pair -> new Pair<>(pair.getKey(), wormhole.getViewPoint(pair.getValue()))) - .filter(p -> wormhole.isInsideView(p.getSecond())) - .collect(Collectors.toMap(Pair::getKey, Pair::getValue)); - g.setColor(Color.BLACK); - if (obstacles != null) { - /* - * TODO: only draw obstacles if on view - */ - obstacles.parallelStream() - .map(this::convertObstacle) - .forEachOrdered(g::fill); + obstacles.parallelStream() + .map(this::convertObstacle) + .forEachOrdered(g::fill); + } + + private void drawLinks(final Graphics2D g, final Map, Point> onView) { + if (!paintLinks) { + return; } - if (paintLinks) { - g.setColor(Color.GRAY); - onView.keySet().parallelStream() - .map(neighbors::get) - .flatMap(neigh -> - neigh.getNeighbors().parallelStream() - .map(node -> - node.compareTo(neigh.getCenter()) > 0 - ? new Pair<>(neigh.getCenter(), node) - : new Pair<>(node, neigh.getCenter()) - ) - ) - .distinct() - .map(pair -> - mapPair( - pair, - node -> Optional - .ofNullable(onView.get(node)) - .orElse(wormhole.getViewPoint(positions.get(node))) + g.setColor(Color.GRAY); + onView.keySet().parallelStream() + .map(neighbors::get) + .flatMap(neigh -> + neigh.getNeighbors().parallelStream() + .map(node -> + node.compareTo(neigh.getCenter()) > 0 + ? new Pair<>(neigh.getCenter(), node) + : new Pair<>(node, neigh.getCenter()) ) + ) + .distinct() + .map(pair -> + mapPair( + pair, + node -> Optional + .ofNullable(onView.get(node)) + .orElse(wormhole.getViewPoint(positions.get(node))) ) - .forEachOrdered(line -> { - final Point p1 = line.getFirst(); - final Point p2 = line.getSecond(); - g.drawLine(p1.x, p1.y, p2.x, p2.y); - }); + ) + .forEachOrdered(line -> { + final Point p1 = line.getFirst(); + final Point p2 = line.getSecond(); + g.drawLine(p1.x, p1.y, p2.x, p2.y); + }); + } + + private void moveSelectedNodesOnView(final Map, Point> onView) { + if (!isDraggingMouse || status != ViewStatus.MOVING_SELECTED_NODES || originPoint == null || endingPoint == null) { + return; } - releaseData(); - if ( - isDraggingMouse - && status == ViewStatus.MOVING_SELECTED_NODES - && originPoint != null - && endingPoint != null - ) { - for (final Node n : selectedNodes) { - if (onView.containsKey(n)) { - onView.put(n, new Point(onView.get(n).x + (endingPoint.x - originPoint.x), - onView.get(n).y + (endingPoint.y - originPoint.y))); - } + for (final Node node : selectedNodes) { + if (onView.containsKey(node)) { + onView.put( + node, + new Point( + onView.get(node).x + (endingPoint.x - originPoint.x), + onView.get(node).y + (endingPoint.y - originPoint.y) + ) + ); } } - g.setColor(Color.GREEN); - if (effectStack != null) { - effectStack.forEach(effect -> onView.forEach((node, point) -> - effect.apply(g, node, currentEnv, wormhole))); - } - if (isCloserNodeMarked()) { - final Optional, Point>> closest = onView.entrySet().parallelStream() - .min((pair1, pair2) -> { - final Point p1 = pair1.getValue(); - final Point p2 = pair2.getValue(); - final double d1 = Math.hypot(p1.x - mouseX, p1.y - mouseY); - final double d2 = Math.hypot(p2.x - mouseX, p2.y - mouseY); - return Double.compare(d1, d2); - }); - if (closest.isPresent()) { - nearest = closest.get().getKey(); - final int nearestX = closest.get().getValue().x; - final int nearestY = closest.get().getValue().y; - drawFriedEgg(g, nearestX, nearestY, Color.RED, Color.YELLOW); - } - } else { + } + + private void highlightClosestNode(final Graphics2D g, final Map, Point> onView) { + if (!isCloserNodeMarked()) { nearest = null; + return; } - if (isDraggingMouse && status == ViewStatus.SELECTING_NODES && originPoint != null && endingPoint != null) { - g.setColor(Color.BLACK); - final int x = Math.min(originPoint.x, endingPoint.x); - final int y = Math.min(originPoint.y, endingPoint.y); - final int width = Math.abs(endingPoint.x - originPoint.x); - final int height = Math.abs(endingPoint.y - originPoint.y); - g.drawRect(x, y, width, height); - selectedNodes = onView.entrySet().parallelStream() - .filter(nodes -> isInsideRectangle(nodes.getValue(), x, y, width, height)) - .map(Map.Entry::getKey) - .collect(Collectors.toSet()); + onView.entrySet().parallelStream() + .min((pair1, pair2) -> Double.compare(distanceFromMouse(pair1.getValue()), distanceFromMouse(pair2.getValue()))) + .ifPresent(closest -> { + nearest = closest.getKey(); + final Point point = closest.getValue(); + drawFriedEgg(g, point.x, point.y, Color.RED, Color.YELLOW); + }); + } + + private double distanceFromMouse(final Point point) { + return Math.hypot(point.x - mouseX, point.y - mouseY); + } + + private void drawSelectionRectangle(final Graphics2D g, final Map, Point> onView) { + if (!isDraggingMouse || status != ViewStatus.SELECTING_NODES || originPoint == null || endingPoint == null) { + return; } + g.setColor(Color.BLACK); + final int x = Math.min(originPoint.x, endingPoint.x); + final int y = Math.min(originPoint.y, endingPoint.y); + final int width = Math.abs(endingPoint.x - originPoint.x); + final int height = Math.abs(endingPoint.y - originPoint.y); + g.drawRect(x, y, width, height); + selectedNodes = onView.entrySet().parallelStream() + .filter(nodes -> isInsideRectangle(nodes.getValue(), x, y, width, height)) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + } + + private void highlightSelectedNodes(final Graphics2D g, final Map, Point> onView) { selectedNodes.parallelStream() - .map(e -> Optional.ofNullable(onView.get(e))) + .map(node -> Optional.ofNullable(onView.get(node))) .filter(Optional::isPresent) .map(Optional::get) - .forEachOrdered(p -> drawFriedEgg(g, p.x, p.y, Color.BLUE, Color.CYAN)); + .forEachOrdered(point -> drawFriedEgg(g, point.x, point.y, Color.BLUE, Color.CYAN)); } 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) { }); } } else { - // TODO: display proper error message + // Surface this warning through the UI if interactive feedback is added here. L.warn("Can not handle node movement on a finished simulation."); } } else { From 96b38ca0ff2d94e35e13d927cb5e59e10a279e74 Mon Sep 17 00:00:00 2001 From: Danilo Pianini Date: Tue, 28 Apr 2026 18:29:57 +0200 Subject: [PATCH 2/7] Update alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../boundary/swingui/monitor/impl/Generic2DDisplay.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java index 464615364d..266c5a4403 100644 --- a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java +++ b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java @@ -421,12 +421,13 @@ private void moveSelectedNodesOnView(final Map, Point> onView) { return; } for (final Node node : selectedNodes) { - if (onView.containsKey(node)) { + final Point point = onView.get(node); + if (point != null) { onView.put( node, new Point( - onView.get(node).x + (endingPoint.x - originPoint.x), - onView.get(node).y + (endingPoint.y - originPoint.y) + point.x + (endingPoint.x - originPoint.x), + point.y + (endingPoint.y - originPoint.y) ) ); } From 5406416d3d135a982232bf69a10ce2af6083f85a Mon Sep 17 00:00:00 2001 From: Danilo Pianini Date: Tue, 28 Apr 2026 18:30:15 +0200 Subject: [PATCH 3/7] Update alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../swingui/monitor/impl/Generic2DDisplay.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java index 266c5a4403..3bbc564473 100644 --- a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java +++ b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java @@ -338,12 +338,16 @@ protected void drawEnvOnView(final Graphics2D g) { return; } accessData(); - updateHookedNodeView(); - final Map, Point> onView = computeNodesOnView(); - g.setColor(Color.BLACK); - drawObstacles(g); - drawLinks(g, onView); - releaseData(); + final Map, Point> onView; + try { + updateHookedNodeView(); + onView = computeNodesOnView(); + g.setColor(Color.BLACK); + drawObstacles(g); + drawLinks(g, onView); + } finally { + releaseData(); + } moveSelectedNodesOnView(onView); g.setColor(Color.GREEN); if (effectStack != null) { From 6dbe46b2215b85b3a103ee4e24bf90dfcdb529d8 Mon Sep 17 00:00:00 2001 From: Danilo Pianini Date: Tue, 28 Apr 2026 18:30:53 +0200 Subject: [PATCH 4/7] Update alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../boundary/swingui/monitor/impl/Generic2DDisplay.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java index 3bbc564473..7835a762a1 100644 --- a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java +++ b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java @@ -370,7 +370,12 @@ private void updateHookedNodeView() { if (hooked.isEmpty()) { return; } - final P hookedCoordinates = positions.get(hooked.get()); + final Node hookedNode = hooked.get(); + final P hookedCoordinates = positions.get(hookedNode); + if (hookedCoordinates == null) { + hooked.set(Optional.empty()); + return; + } final Point hookedPoint = wormhole.getViewPoint(hookedCoordinates); if (hookedPoint.distance(getCenter()) > FREEDOM_RADIUS) { wormhole.setViewPosition(hookedPoint); From b0ed3f1a8f844c52ab4ef60770ae320bd26f6003 Mon Sep 17 00:00:00 2001 From: Danilo Pianini Date: Tue, 28 Apr 2026 18:31:31 +0200 Subject: [PATCH 5/7] Update alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../boundary/swingui/monitor/impl/Generic2DDisplay.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java index 7835a762a1..12268d2047 100644 --- a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java +++ b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java @@ -389,9 +389,10 @@ private void drawObstacles(final Graphics2D g) { /* * This currently draws all obstacles, even when they are fully outside the viewport. */ - obstacles.parallelStream() + final List obstacleShapes = obstacles.parallelStream() .map(this::convertObstacle) - .forEachOrdered(g::fill); + .collect(Collectors.toList()); + obstacleShapes.forEach(g::fill); } private void drawLinks(final Graphics2D g, final Map, Point> onView) { From 45f1e919b9aa62bfc16f6d60b71278d6e043cf95 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:33:20 +0000 Subject: [PATCH 6/7] style(swingui): fix Graphics2D thread-safety in highlightSelectedNodes Agent-Logs-Url: https://github.com/AlchemistSimulator/Alchemist/sessions/4322e07a-9841-40ad-a5d1-10555384ad83 Co-authored-by: DanySK <1991673+DanySK@users.noreply.github.com> --- .../boundary/swingui/monitor/impl/Generic2DDisplay.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java index 12268d2047..716b51b80a 100644 --- a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java +++ b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java @@ -483,7 +483,8 @@ private void highlightSelectedNodes(final Graphics2D g, final Map, Point .map(node -> Optional.ofNullable(onView.get(node))) .filter(Optional::isPresent) .map(Optional::get) - .forEachOrdered(point -> drawFriedEgg(g, point.x, point.y, Color.BLUE, Color.CYAN)); + .collect(Collectors.toList()) + .forEach(point -> drawFriedEgg(g, point.x, point.y, Color.BLUE, Color.CYAN)); } private void drawFriedEgg(final Graphics g, final int x, final int y, final Color c1, final Color c2) { From 205d54b96c624eb811ad3a4629bfb0609073aba8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:30:21 +0000 Subject: [PATCH 7/7] fix(swingui): replace invalid Optional.set() with field reassignment Agent-Logs-Url: https://github.com/AlchemistSimulator/Alchemist/sessions/81d95530-b420-47ed-a40d-0b662f7526fa Co-authored-by: DanySK <1991673+DanySK@users.noreply.github.com> --- .../boundary/swingui/monitor/impl/Generic2DDisplay.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java index 716b51b80a..26a5f82ab1 100644 --- a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java +++ b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java @@ -373,7 +373,7 @@ private void updateHookedNodeView() { final Node hookedNode = hooked.get(); final P hookedCoordinates = positions.get(hookedNode); if (hookedCoordinates == null) { - hooked.set(Optional.empty()); + hooked = Optional.empty(); return; } final Point hookedPoint = wormhole.getViewPoint(hookedCoordinates);