This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch CAMEL-23634
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 766d24da6b1435e7c1b0b298ae38b81385340cee
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu May 28 14:39:50 2026 +0200

    CAMEL-23615: camel-jbang - TUI organize F2 actions menu with dividers
    
    Group actions into four sections separated by dividers:
    User Actions, Diagnostics, Recording, and MCP.
    Also sync bundled tui-hello-world example.
    
    Co-Authored-By: Claude <[email protected]>
---
 .../examples/camel-jbang-example-catalog.json      |  19 +++
 .../examples/tui-hello-world/tui-hello-world.yaml  |  19 +++
 .../dsl/jbang/core/commands/tui/ActionsPopup.java  | 157 +++++++++++++++------
 3 files changed, 151 insertions(+), 44 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json
index fca58c3f0788..d88e50b1dc0f 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json
@@ -456,6 +456,25 @@
             "timer-log.camel.yaml"
         ]
     },
+    {
+        "name": "tui-hello-world",
+        "title": "TUI Hello World",
+        "description": "Say hello via TUI Send Message (F2) or CLI",
+        "level": "beginner",
+        "tags": [
+            "beginner",
+            "direct",
+            "send",
+            "tui"
+        ],
+        "bundled": true,
+        "requiresDocker": false,
+        "hasCitrusTests": false,
+        "files": [
+            "README.md",
+            "tui-hello-world.yaml"
+        ]
+    },
     {
         "name": "xslt",
         "title": "XSLT Transformation",
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/tui-hello-world/tui-hello-world.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/tui-hello-world/tui-hello-world.yaml
new file mode 100644
index 000000000000..28db17370b76
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/tui-hello-world/tui-hello-world.yaml
@@ -0,0 +1,19 @@
+- route:
+    id: welcome
+    from:
+      uri: timer:welcome
+      parameters:
+        repeatCount: 1
+      steps:
+        - setBody:
+            constant: "Welcome! Open the TUI (camel tui) and press F2 → Send 
Message to say hello."
+        - log: "${body}"
+
+- route:
+    id: greet
+    from:
+      uri: direct:greet
+      steps:
+        - log: "Received: ${body}"
+        - setBody:
+            simple: "Hello ${body}!"
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
index 2b65b0441824..e569c49861fc 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
@@ -56,20 +56,27 @@ import static 
org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.hintLa
 
 class ActionsPopup {
 
-    private static final int ACTION_RUN_EXAMPLE = 0;
-    private static final int ACTION_SHOW_DOCS = 1;
-    private static final int ACTION_CAPTION = 2;
-    private static final int ACTION_SCREENSHOT = 3;
-    private static final int ACTION_SHOW_KEYSTROKES = 4;
-    private static final int ACTION_TAPE_RECORDING = 5;
-    private static final int ACTION_TAPE_INSTRUCTIONS = 6;
-    private static final int ACTION_DOCTOR = 7;
-    private static final int ACTION_CLASSPATH = 8;
-    private static final int ACTION_MCP_INFO = 9;
-    private static final int ACTION_MCP_LOG = 10;
-    private static final int ACTION_SEND_MESSAGE = 11;
-    private static final int ACTION_RESET_STATS = 12;
-    private static final int ACTION_STOP_ALL = 13;
+    // Group 1: User Actions
+    private static final int ACTION_SEND_MESSAGE = 0;
+    private static final int ACTION_RUN_EXAMPLE = 1;
+    private static final int ACTION_SHOW_DOCS = 2;
+    // Group 2: Diagnostics
+    private static final int ACTION_DOCTOR = 3;
+    private static final int ACTION_CLASSPATH = 4;
+    private static final int ACTION_RESET_STATS = 5;
+    private static final int ACTION_STOP_ALL = 6;
+    // Group 3: Recording & Presentation
+    private static final int ACTION_SCREENSHOT = 7;
+    private static final int ACTION_TAPE_RECORDING = 8;
+    private static final int ACTION_TAPE_INSTRUCTIONS = 9;
+    private static final int ACTION_CAPTION = 10;
+    private static final int ACTION_SHOW_KEYSTROKES = 11;
+    // Group 4: MCP
+    private static final int ACTION_MCP_INFO = 12;
+    private static final int ACTION_MCP_LOG = 13;
+
+    private static final int[] GROUP_SIZES = { 3, 4, 5 };
+    private static final int MCP_GROUP_SIZE = 2;
 
     private final Supplier<Set<String>> runningNames;
     private final Supplier<List<IntegrationInfo>> integrations;
@@ -161,8 +168,61 @@ class ActionsPopup {
         mcpLogPopup.setActivityLog(activityLog);
     }
 
-    private int actionCount() {
-        return mcpEnabled ? 14 : 12;
+    private int visualActionCount() {
+        if (mcpEnabled) {
+            // 3 + 4 + 5 + 2 actions = 14, plus 3 dividers = 17
+            return 14 + 3;
+        } else {
+            // 3 + 4 + 5 actions = 12, plus 2 dividers = 14
+            return 12 + 2;
+        }
+    }
+
+    private boolean isDividerIndex(int visualIndex) {
+        int pos = 0;
+        for (int i = 0; i < GROUP_SIZES.length; i++) {
+            pos += GROUP_SIZES[i];
+            if (visualIndex == pos) {
+                return true;
+            }
+            pos++;
+        }
+        if (mcpEnabled) {
+            pos += MCP_GROUP_SIZE;
+        }
+        return false;
+    }
+
+    private int resolveAction(int visualIndex) {
+        int dividers = 0;
+        int pos = 0;
+        int groupCount = mcpEnabled ? GROUP_SIZES.length + 1 : 
GROUP_SIZES.length;
+        for (int i = 0; i < groupCount; i++) {
+            int gs = i < GROUP_SIZES.length ? GROUP_SIZES[i] : MCP_GROUP_SIZE;
+            pos += gs;
+            if (visualIndex < pos + dividers) {
+                break;
+            }
+            if (i < groupCount - 1) {
+                dividers++;
+                pos++;
+            }
+        }
+        return visualIndex - dividers;
+    }
+
+    private void navigateActionsMenu(int direction) {
+        int total = visualActionCount();
+        Integer current = actionsMenuState.selected();
+        int next = (current != null ? current : 0) + direction;
+        next = Math.max(0, Math.min(next, total - 1));
+        while (isDividerIndex(next) && next > 0 && next < total - 1) {
+            next += direction;
+        }
+        next = Math.max(0, Math.min(next, total - 1));
+        if (!isDividerIndex(next)) {
+            actionsMenuState.select(next);
+        }
     }
 
     boolean isVisible() {
@@ -190,7 +250,7 @@ class ActionsPopup {
         if (showActionsMenu) {
             List<String> items = getActionLabels();
             Integer sel = actionsMenuState.selected();
-            return new SelectionContext("popup", items, sel != null ? sel : 
-1, items.size(), "Actions");
+            return new SelectionContext("popup", items, sel != null ? sel : 
-1, visualActionCount(), "Actions");
         }
         return null;
     }
@@ -205,22 +265,29 @@ class ActionsPopup {
 
     List<String> getActionLabels() {
         List<String> labels = new ArrayList<>();
+        // Group 1: User Actions
+        labels.add("Send Message");
         labels.add("Run an example...");
         labels.add("Show Documentation");
-        labels.add("Caption...");
+        labels.add("───");
+        // Group 2: Diagnostics
+        labels.add("Run Doctor");
+        labels.add("Show Classpath");
+        labels.add("Reset Stats");
+        labels.add("Stop All");
+        labels.add("───");
+        // Group 3: Recording & Presentation
         labels.add("Take Screenshot");
-        labels.add(keystrokesEnabled.get() ? "Hide Keystrokes" : "Show 
Keystrokes");
         labels.add(tapeRecordingActive.get() ? "Stop Tape Recording" : "Start 
Tape Recording");
         labels.add("Tape Recording Guide");
-        labels.add("Run Doctor");
-        labels.add("Show Classpath");
+        labels.add("Caption...");
+        labels.add(keystrokesEnabled.get() ? "Hide Keystrokes" : "Show 
Keystrokes");
+        // Group 4: MCP
         if (mcpEnabled) {
+            labels.add("───");
             labels.add("MCP Info");
             labels.add("MCP Log");
         }
-        labels.add("Send Message");
-        labels.add("Reset Stats");
-        labels.add("Stop All");
         return labels;
     }
 
@@ -349,9 +416,9 @@ class ActionsPopup {
             if (ke.isCancel()) {
                 showActionsMenu = false;
             } else if (ke.isUp()) {
-                actionsMenuState.selectPrevious();
+                navigateActionsMenu(-1);
             } else if (ke.isDown()) {
-                actionsMenuState.selectNext(actionCount());
+                navigateActionsMenu(1);
             } else if (ke.isConfirm()) {
                 Integer sel = actionsMenuState.selected();
                 if (sel != null) {
@@ -504,7 +571,7 @@ class ActionsPopup {
     // ---- Rendering ----
 
     private void renderActionsMenu(Frame frame, Rect area) {
-        int count = actionCount();
+        int count = visualActionCount();
         int popupW = 40;
         int popupH = 2 + count;
         int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
@@ -512,6 +579,7 @@ class ActionsPopup {
         Rect popup = new Rect(x, y, Math.min(popupW, area.width()), 
Math.min(popupH, area.height()));
 
         frame.renderWidget(Clear.INSTANCE, popup);
+        String divider = "  ─────────────────────────────────";
         // extra space after ⌨️ because it renders narrower than other emoji
         String keystrokeLabel = keystrokesEnabled.get()
                 ? "  ⌨️  Hide Keystrokes"
@@ -519,26 +587,34 @@ class ActionsPopup {
         String stopLabel = stopAllPopup.hasBothGroups()
                 ? "  🛑 Stop All..."
                 : "  🛑 Stop All";
+        String tapeLabel = tapeRecordingActive.get()
+                ? "  ⏹️  Stop Tape Recording (Ctrl+R)"
+                : "  ⏺️  Start Tape Recording (Ctrl+R)";
+
         List<ListItem> items = new ArrayList<>();
+        // Group 1: User Actions
+        items.add(ListItem.from("  📩 Send Message"));
         items.add(ListItem.from("  🐪 Run an example..."));
         items.add(ListItem.from("  📖 Show Documentation"));
-        items.add(ListItem.from("  💬 Caption..."));
+        items.add(ListItem.from(divider).style(Style.EMPTY.dim()));
+        // Group 2: Diagnostics
+        items.add(ListItem.from("  🩺 Run Doctor"));
+        items.add(ListItem.from("  📦 Show Classpath"));
+        items.add(ListItem.from("  🔄 Reset Stats"));
+        items.add(ListItem.from(stopLabel));
+        items.add(ListItem.from(divider).style(Style.EMPTY.dim()));
+        // Group 3: Recording & Presentation
         items.add(ListItem.from("  📸 Take Screenshot"));
-        items.add(ListItem.from(keystrokeLabel));
-        String tapeLabel = tapeRecordingActive.get()
-                ? "  ⏹️  Stop Tape Recording (Ctrl+R)"
-                : "  ⏺️  Start Tape Recording (Ctrl+R)";
         items.add(ListItem.from(tapeLabel));
         items.add(ListItem.from("  📄 Tape Recording Guide"));
-        items.add(ListItem.from("  🩺 Run Doctor"));
-        items.add(ListItem.from("  📦 Show Classpath"));
+        items.add(ListItem.from("  💬 Caption..."));
+        items.add(ListItem.from(keystrokeLabel));
+        // Group 4: MCP
         if (mcpEnabled) {
+            items.add(ListItem.from(divider).style(Style.EMPTY.dim()));
             items.add(ListItem.from("  🤖 MCP Info"));
             items.add(ListItem.from("  📋 MCP Log"));
         }
-        items.add(ListItem.from("  📩 Send Message"));
-        items.add(ListItem.from("  🔄 Reset Stats"));
-        items.add(ListItem.from(stopLabel));
         ListWidget list = ListWidget.builder()
                 .items(items.toArray(ListItem[]::new))
                 .highlightStyle(Style.EMPTY.fg(Color.WHITE).bold().onBlue())
@@ -806,13 +882,6 @@ class ActionsPopup {
         }
     }
 
-    private int resolveAction(int index) {
-        if (!mcpEnabled && index >= ACTION_MCP_INFO) {
-            return index + 2;
-        }
-        return index;
-    }
-
     private void openSendMessage() {
         if (ctx == null) {
             return;

Reply via email to