This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch feature/CAMEL-23672-tui-diagram in repository https://gitbox.apache.org/repos/asf/camel.git
commit 278d21c12514a14be8f666c26d233423ead335ee Author: Claus Ibsen <[email protected]> AuthorDate: Thu Jun 4 12:26:20 2026 +0200 CAMEL-23672: camel-tui - Add topology minimap overlay in route drill-down Co-Authored-By: Claude Opus 4.6 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../jbang/core/commands/tui/DiagramSupport.java | 31 +++++++ .../tui/diagram/TopologyMinimapWidget.java | 103 +++++++++++++++++++++ 2 files changed, 134 insertions(+) diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java index 3e36dbf77af6..d9549e4a3e9f 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java @@ -38,6 +38,7 @@ import dev.tamboui.text.Line; import dev.tamboui.text.Span; import dev.tamboui.text.Text; import dev.tamboui.tui.event.KeyEvent; +import dev.tamboui.widgets.Clear; import dev.tamboui.widgets.block.Block; import dev.tamboui.widgets.block.BorderType; import dev.tamboui.widgets.block.Title; @@ -1031,6 +1032,36 @@ class DiagramSupport { Scrollbar.horizontal(), vChunks.get(1), hScrollState); } + + renderMinimap(frame, hChunks.get(0), currentRouteId); + } + + private void renderMinimap(Frame frame, Rect area, String currentRouteId) { + if (topologyLayout == null || topologyLayout.nodes.size() < 2) { + return; + } + int mapW = Math.min(22, area.width() / 3); + int mapH = Math.min(8, area.height() / 3); + if (mapW < 6 || mapH < 3 || area.width() < 40 || area.height() < 12) { + return; + } + + int mapX = area.x() + area.width() - mapW - 1; + int mapY = area.y() + area.height() - mapH - 1; + Rect mapRect = new Rect(mapX, mapY, mapW, mapH); + + frame.renderWidget(Clear.INSTANCE, mapRect); + + Block mapBlock = Block.builder() + .borderType(BorderType.ROUNDED) + .title(Title.from(Line.from(Span.styled(" Map ", Style.EMPTY.dim())))) + .build(); + frame.renderWidget(mapBlock, mapRect); + + Rect innerRect = mapBlock.inner(mapRect); + var minimap = new org.apache.camel.dsl.jbang.core.commands.tui.diagram.TopologyMinimapWidget( + topologyLayout, currentRouteId); + frame.renderWidget(minimap, innerRect); } // ---- Rendering (legacy text) ---- diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/TopologyMinimapWidget.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/TopologyMinimapWidget.java new file mode 100644 index 000000000000..f18ae2803244 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/diagram/TopologyMinimapWidget.java @@ -0,0 +1,103 @@ +/* + * 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 dev.tamboui.buffer.Buffer; +import dev.tamboui.layout.Rect; +import dev.tamboui.style.Color; +import dev.tamboui.style.Style; +import dev.tamboui.widget.Widget; +import org.apache.camel.diagram.TopologyLayoutEngine.TopologyLayoutNode; +import org.apache.camel.diagram.TopologyLayoutEngine.TopologyLayoutResult; + +public class TopologyMinimapWidget implements Widget { + + private static final Style CURRENT_STYLE = Style.EMPTY.fg(Color.YELLOW).bold(); + private static final Style OTHER_STYLE = Style.EMPTY.fg(Color.DARK_GRAY); + private static final Style EXTERNAL_STYLE = Style.EMPTY.fg(Color.CYAN).dim(); + + private final TopologyLayoutResult layout; + private final String currentRouteId; + + public TopologyMinimapWidget(TopologyLayoutResult layout, String currentRouteId) { + this.layout = layout; + this.currentRouteId = currentRouteId; + } + + @Override + public void render(Rect area, Buffer buffer) { + if (layout == null || layout.nodes.isEmpty() || area.width() < 2 || area.height() < 1) { + return; + } + + int mapW = area.width(); + int mapH = area.height(); + int totalW = layout.totalWidth; + int totalH = layout.totalHeight; + + if (totalW <= 0 || totalH <= 0) { + return; + } + + for (TopologyLayoutNode node : layout.nodes) { + boolean isCurrent = node.routeId != null && node.routeId.equals(currentRouteId); + boolean isExternal = "external-in".equals(node.nodeType) || "external-out".equals(node.nodeType); + + int col = node.x * mapW / totalW; + int row = node.y * mapH / totalH; + int nodeW = Math.min(3, Math.max(1, node.width * mapW / totalW)); + int nodeH = Math.max(1, node.height * mapH / totalH); + + col = Math.min(col, mapW - nodeW); + row = Math.min(row, mapH - nodeH); + col = Math.max(0, col); + row = Math.max(0, row); + + Style style; + if (isCurrent) { + style = CURRENT_STYLE; + } else if (isExternal) { + style = EXTERNAL_STYLE; + } else { + style = OTHER_STYLE; + } + + drawMiniBox(buffer, area, col, row, nodeW, nodeH, style, isCurrent); + } + } + + private void drawMiniBox(Buffer buffer, Rect area, int col, int row, int w, int h, Style style, boolean current) { + int sx = area.x() + col; + int sy = area.y() + row; + + String fill = current ? "█" : "▪"; + + for (int r = 0; r < h; r++) { + int y = sy + r; + if (y >= area.y() + area.height()) { + break; + } + for (int c = 0; c < w; c++) { + int x = sx + c; + if (x >= area.x() + area.width()) { + break; + } + buffer.setString(x, y, fill, style); + } + } + } +}
