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);
+            }
+        }
+    }
+}

Reply via email to