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..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 @@ -338,110 +338,153 @@ 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); - } + 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) { + 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 Node hookedNode = hooked.get(); + final P hookedCoordinates = positions.get(hookedNode); + if (hookedCoordinates == null) { + hooked = Optional.empty(); + return; + } + 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); - } - 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))) + final List obstacleShapes = obstacles.parallelStream() + .map(this::convertObstacle) + .collect(Collectors.toList()); + obstacleShapes.forEach(g::fill); + } + + private void drawLinks(final Graphics2D g, final Map, Point> onView) { + if (!paintLinks) { + return; + } + 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) { + final Point point = onView.get(node); + if (point != null) { + onView.put( + node, + new Point( + point.x + (endingPoint.x - originPoint.x), + point.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)); + .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) { @@ -899,7 +942,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 {