This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit aa49df83f203b3e541dbfa649a889b86d980dab0 Author: Claus Ibsen <[email protected]> AuthorDate: Thu Oct 15 12:47:03 2026 +0200 chore: camel-jbang - a selected box in the diagram has a solid frame The box of a remote system is drawn dashed, in the route diagram as in the topology and the architecture view, and so is the error handling box. Selected, its frame took the selection colour like any other, and the dashes broke up on it: the vertical ones did not line up with the corners. A selected box now has a solid frame, and goes back to dashes when the cursor moves on; the system name in its border, its colour and the Info panel still say it is remote. The help of the Diagram tab says the edges of the integration are dashed. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../commands/tui/diagram/RouteDiagramWidget.java | 5 +- .../tui/diagram/TopologyDiagramWidget.java | 5 +- .../src/main/resources/tui/help/diagram.md | 19 +-- .../tui/diagram/DashedBoxSelectionTest.java | 134 +++++++++++++++++++++ 4 files changed, 150 insertions(+), 13 deletions(-) diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/RouteDiagramWidget.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/RouteDiagramWidget.java index cd6e0156e356..8574a4cd1215 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/RouteDiagramWidget.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/RouteDiagramWidget.java @@ -347,8 +347,9 @@ public class RouteDiagramWidget implements Widget { borderStyle = borderStyle.patch(selectionStyle()); } - char hChar = external ? DASH_H : H; - char vChar = external ? DASH_V : V; + // a selected box has a solid frame like any other: dashes on the selection colour break up, the vertical ones most + char hChar = external && !selected ? DASH_H : H; + char vChar = external && !selected ? DASH_V : V; // Top border setChar(buffer, area, row, col, TL, borderStyle); diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/TopologyDiagramWidget.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/TopologyDiagramWidget.java index 1a5e5fc36616..2cd851d5cd3e 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/TopologyDiagramWidget.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/TopologyDiagramWidget.java @@ -409,8 +409,9 @@ public class TopologyDiagramWidget implements Widget { boolean highlighted = !ext && node.routeId != null && highlightRouteIds.contains(node.routeId); - char hChar = dashedBox ? DASH_H : H; - char vChar = dashedBox ? DASH_V : V; + // a selected box has a solid frame like any other: dashes on the selection colour break up, the vertical ones most + char hChar = dashedBox && !selected ? DASH_H : H; + char vChar = dashedBox && !selected ? DASH_V : V; Style borderStyle; if (highlighted) { Color hlColor = highlightFailed ? highlightFailColor() : highlightOkColor(); diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/diagram.md b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/diagram.md index a37b69d387d6..fc89f6e876ed 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/diagram.md +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/diagram.md @@ -122,15 +122,16 @@ business view (`b`), marked with `✦`; the technical view (`n`) keeps the expression. The Info panel of such a node shows the label, why the route decides there, and the code. -The endpoints of remote systems are the edges of the integration -and have their own color, in the route diagram as in the topology. -The input of a route from a remote system shows where it comes -from in the top border of its box, `AMQP ──▶`. Where a message is -sent to a remote system and no route of the integration consumes -it, the bottom border names where it leaves to: `──▶ AMQP`. A -hand-off to another route of the integration shows `↵ route` -beside the box. A route that starts itself on a schedule (timer, -cron, quartz, scheduler) shows `↻ ──▶` at its input. +The endpoints of remote systems are the edges of the integration: +they have their own color and a dashed frame (solid while +selected), in the route diagram as in the topology. The input of +a route from a remote system shows where it comes from in the top +border of its box, `AMQP ──▶`. Where a message is sent to a remote +system and no route of the integration consumes it, the bottom +border names where it leaves to: `──▶ AMQP`. A hand-off to another +route of the integration shows `↵ route` beside the box. A route +that starts itself on a schedule (timer, cron, quartz, scheduler) +shows `↻ ──▶` at its input. ## Route Diagram diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/DashedBoxSelectionTest.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/DashedBoxSelectionTest.java new file mode 100644 index 000000000000..79209fcdb096 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/DashedBoxSelectionTest.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.dsl.jbang.core.commands.tui.diagram; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import dev.tamboui.buffer.Buffer; +import dev.tamboui.layout.Rect; +import org.apache.camel.diagram.RouteDiagramLayoutEngine; +import org.apache.camel.diagram.TopologyLayoutEngine; +import org.apache.camel.diagram.TopologyLayoutEngine.TopologyEdgeInfo; +import org.apache.camel.diagram.TopologyLayoutEngine.TopologyLayoutResult; +import org.apache.camel.diagram.TopologyLayoutEngine.TopologyNodeInfo; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The box of a remote system is drawn dashed, in the route diagram as in the topology, but a selected box has a solid + * frame like any other: dashes on the selection colour break up. + */ +class DashedBoxSelectionTest { + + @Test + void aRemoteEndpointInTheRouteDiagramIsSolidWhileSelected() { + RouteDiagramLayoutEngine.RouteInfo route = new RouteDiagramLayoutEngine.RouteInfo(); + route.routeId = "checkout"; + route.nodes.add(node("from", "checkout", "from[file:orders]", "file:orders", 0, true)); + route.nodes.add(node("to", "to1", "to[direct:charge]", "direct:charge", 1, false)); + RouteDiagramLayoutEngine.LayoutRoute layout = new RouteDiagramLayoutEngine().layoutRoute(route, 0); + int nodeWidth = RouteDiagramLayoutEngine.DEFAULT_BOX_WIDTH * RouteDiagramLayoutEngine.SCALE; + + // nothing selected: the file box is dashed, the box of the local step solid + RouteDiagramWidget widget = new RouteDiagramWidget(layout, nodeWidth, -1, 0, 0, false, Map.of(), false, Map.of()); + Buffer buffer = render(widget.getTotalCols(), widget.getTotalRows(), (area, b) -> widget.render(area, b)); + RouteDiagramWidget.EipNodeBox file = widget.getNodeBoxes().get(0); + RouteDiagramWidget.EipNodeBox to = widget.getNodeBoxes().get(1); + assertThat(frame(buffer, file.startRow(), file.endRow(), file.startCol(), file.endCol())).isEqualTo("┆┄"); + assertThat(frame(buffer, to.startRow(), to.endRow(), to.startCol(), to.endCol())).isEqualTo("│─"); + + // the file box selected: solid + RouteDiagramWidget selected = new RouteDiagramWidget(layout, nodeWidth, 0, 0, 0, false, Map.of(), false, Map.of()); + buffer = render(selected.getTotalCols(), selected.getTotalRows(), (area, b) -> selected.render(area, b)); + file = selected.getNodeBoxes().get(0); + assertThat(frame(buffer, file.startRow(), file.endRow(), file.startCol(), file.endCol())).isEqualTo("│─"); + } + + @Test + void anExternalSystemInTheTopologyIsSolidWhileSelected() { + TopologyNodeInfo checkout = new TopologyNodeInfo(); + checkout.routeId = "checkout"; + checkout.from = "file:orders"; + checkout.nodeType = "route"; + TopologyNodeInfo files = new TopologyNodeInfo(); + files.routeId = "in-checkout-0"; + files.from = "file:orders"; + files.nodeType = "external-in"; + TopologyEdgeInfo reads = new TopologyEdgeInfo(); + reads.fromRouteId = "in-checkout-0"; + reads.toRouteId = "checkout"; + reads.endpoint = "file:orders"; + reads.connectionType = "external"; + TopologyLayoutEngine engine = new TopologyLayoutEngine(); + TopologyLayoutResult layout = engine.layout( + new ArrayList<>(List.of(checkout, files)), new ArrayList<>(List.of(reads))); + + TopologyDiagramWidget widget = new TopologyDiagramWidget(layout, engine.getNodeWidth(), -1, 0, 0, false, false); + Buffer buffer = render(widget.getTotalCols(), widget.getTotalRows(), (area, b) -> widget.render(area, b)); + int index = indexOf(widget.getNodeBoxes(), "in-checkout-0"); + TopologyDiagramWidget.NodeBox box = widget.getNodeBoxes().get(index); + assertThat(frame(buffer, box.startRow(), box.endRow(), box.startCol(), box.endCol())).isEqualTo("┆┄"); + + TopologyDiagramWidget selected + = new TopologyDiagramWidget(layout, engine.getNodeWidth(), index, 0, 0, false, false); + buffer = render(selected.getTotalCols(), selected.getTotalRows(), (area, b) -> selected.render(area, b)); + box = selected.getNodeBoxes().get(index); + assertThat(frame(buffer, box.startRow(), box.endRow(), box.startCol(), box.endCol())).isEqualTo("│─"); + } + + private interface Renderer { + void render(Rect area, Buffer buffer); + } + + private static Buffer render(int cols, int rows, Renderer renderer) { + Rect area = new Rect(0, 0, Math.max(100, cols), Math.max(20, rows)); + Buffer buffer = Buffer.empty(area); + renderer.render(area, buffer); + return buffer; + } + + /** The left side of a box below its top corner, then the top side right of its corner. */ + private static String frame(Buffer buffer, int top, int bottom, int left, int right) { + assertThat(bottom).isGreaterThan(top + 1); + assertThat(right).isGreaterThan(left + 1); + return buffer.get(left, top + 1).symbol() + buffer.get(right - 1, top).symbol(); + } + + private static int indexOf(List<TopologyDiagramWidget.NodeBox> boxes, String routeId) { + for (int i = 0; i < boxes.size(); i++) { + if (routeId.equals(boxes.get(i).routeId())) { + return i; + } + } + throw new AssertionError("no box for " + routeId + " in " + boxes); + } + + private static RouteDiagramLayoutEngine.NodeInfo node( + String type, String id, String code, String uri, int level, boolean remote) { + RouteDiagramLayoutEngine.NodeInfo n = new RouteDiagramLayoutEngine.NodeInfo(); + n.type = type; + n.id = id; + n.code = code; + n.uri = uri; + n.level = level; + n.remote = remote; + return n; + } +}
