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


The following commit(s) were added to refs/heads/main by this push:
     new 23838d02947a CAMEL-23976: TUI - pinned log panel via Ctrl+L
23838d02947a is described below

commit 23838d02947ae69bb25729e32d2e0d4fb0e90ce7
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Jul 9 19:41:35 2026 +0200

    CAMEL-23976: TUI - pinned log panel via Ctrl+L
    
    Adds ability to pin the Log tab to the bottom of the screen while
    navigating other tabs. Ctrl+L cycles through 25%/50%/75% sizes and
    then turns off. Shell/AI panels take priority when open.
    
    CAMEL-23976: TUI - pinned log starts at 25% and shows hint in panel title
    
    Cycle is now 25→50→75→off. The Ctrl+L hint with percentage is shown
    in the pinned log panel title bar instead of the global footer.
    
    CAMEL-23976: TUI - log pin refinements and More popup scroll fix
    
    Log pin: start at 25%, cycle 25→50→75→off, show Ctrl+L hint with
    percentage in the pinned panel title bar. Move popup overlays (More,
    Switch, Files) to render over full content area so they are not
    clipped by bottom panels. More popup: dynamic height based on item
    count with auto-scroll when space is limited.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../dsl/jbang/core/commands/tui/CamelMonitor.java  | 59 ++++++++++++++++------
 .../camel/dsl/jbang/core/commands/tui/LogTab.java  | 40 ++++++++++++---
 .../jbang/core/commands/tui/MonitorContext.java    |  2 +
 .../jbang/core/commands/tui/PanelAnimation.java    |  6 +++
 .../dsl/jbang/core/commands/tui/PopupManager.java  |  7 +--
 5 files changed, 90 insertions(+), 24 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
index 0ae4cf4dff5c..0ebba077758d 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
@@ -137,6 +137,8 @@ public class CamelMonitor extends CamelCommand {
     private final HelpOverlay helpOverlay = new HelpOverlay();
     private final ShellPanel shellPanel = new ShellPanel();
     private final AiPanel aiPanel = new AiPanel();
+    private boolean logPinned;
+    private final PanelAnimation logPinAnim = new PanelAnimation();
 
     private ActionsPopup actionsPopup;
     private TuiRunner runner;
@@ -727,6 +729,21 @@ public class CamelMonitor extends CamelCommand {
             }
             return true;
         }
+        if (ke.hasCtrl() && ke.isCharIgnoreCase('l')) {
+            if (!logPinned) {
+                logPinned = true;
+                logPinAnim.reset(0);
+            } else if (lastContentArea != null) {
+                logPinAnim.cycleHeight(lastContentArea.height());
+                if (logPinAnim.cyclePercent() >= 100) {
+                    logPinned = false;
+                    logPinAnim.reset(0);
+                }
+            }
+            ctx.logPinned = logPinned;
+            ctx.logPinPercent = logPinned ? logPinAnim.cyclePercent() : 0;
+            return true;
+        }
         if (ke.isKey(KeyCode.F2) && ke.hasShift()) {
             actionsPopup.openGotoTab();
             return true;
@@ -824,7 +841,9 @@ public class CamelMonitor extends CamelCommand {
 
     private boolean handleMouseEvent(MouseEvent me, TuiRunner runner) {
         // Panel border drag resize: detect press on the border row, then 
track drag
-        if (lastContentArea != null && (shellPanel.isOpen() || 
aiPanel.isOpen())
+        boolean hasBottomPanel = shellPanel.isOpen() || aiPanel.isOpen()
+                || (logPinned && tabRegistry.selectedTabIndex() != TAB_LOG);
+        if (lastContentArea != null && hasBottomPanel
                 && panelSplit.handleMouse(me, me.y())) {
             if (panelSplit.isDragging() && me.kind() == MouseEventKind.DRAG) {
                 int contentHeight = lastContentArea.height();
@@ -833,8 +852,10 @@ public class CamelMonitor extends CamelCommand {
                     newHeight = Math.max(3, Math.min(contentHeight - 3, 
newHeight));
                     if (shellPanel.isOpen()) {
                         shellPanel.setPanelHeight(newHeight);
-                    } else {
+                    } else if (aiPanel.isOpen()) {
                         aiPanel.setPanelHeight(newHeight);
+                    } else {
+                        logPinAnim.setPanelHeight(newHeight);
                     }
                 }
             }
@@ -1128,6 +1149,7 @@ public class CamelMonitor extends CamelCommand {
         }
         shellPanel.tickAnimation();
         aiPanel.tickAnimation();
+        logPinAnim.tickAnimation();
         if (canvasOverlay.isVisible()) {
             canvasOverlay.render(frame, contentArea);
         } else if (shellPanel.isOpen()) {
@@ -1155,11 +1177,30 @@ public class CamelMonitor extends CamelCommand {
             renderContent(frame, splitChunks.get(0));
             aiPanel.render(frame, splitChunks.get(1));
             panelSplit.setBorderPos(splitChunks.get(1).y());
+        } else if (logPinned && tabRegistry.selectedTabIndex() != TAB_LOG) {
+            logPinAnim.initHeight(contentArea.height());
+            int ph = logPinAnim.panelHeight();
+            ctx.shellPercent = ph * 100 / Math.max(1, contentArea.height());
+            List<Rect> splitChunks = Layout.vertical()
+                    .constraints(Constraint.fill(), Constraint.length(ph))
+                    .split(contentArea);
+            renderContent(frame, splitChunks.get(0));
+            tabRegistry.logTab().render(frame, splitChunks.get(1));
+            panelSplit.setBorderPos(splitChunks.get(1).y());
         } else {
             ctx.shellPercent = 0;
             renderContent(frame, contentArea);
             panelSplit.clearBorderPos();
         }
+        if (popupManager.isMorePopupVisible()) {
+            popupManager.renderMorePopup(frame, contentArea);
+        }
+        if (popupManager.isSwitchPopupVisible()) {
+            popupManager.renderSwitchPopup(frame, contentArea);
+        }
+        if (filesBrowser.isVisible()) {
+            filesBrowser.render(frame, contentArea);
+        }
         if (drawOverlay.isVisible()) {
             drawOverlay.render(frame, contentArea);
         }
@@ -1385,18 +1426,6 @@ public class CamelMonitor extends CamelCommand {
         if (tab != null) {
             tab.render(frame, area);
         }
-        // Render "More" popup overlay when visible
-        if (popupManager.isMorePopupVisible()) {
-            popupManager.renderMorePopup(frame, area);
-        }
-        // Render "Switch integration" popup overlay when visible
-        if (popupManager.isSwitchPopupVisible()) {
-            popupManager.renderSwitchPopup(frame, area);
-        }
-        // Render "Files" popup overlay when visible
-        if (filesBrowser.isVisible()) {
-            filesBrowser.render(frame, area);
-        }
     }
 
     /**
@@ -1951,7 +1980,7 @@ public class CamelMonitor extends CamelCommand {
     // ---- Data Loading ----
 
     private void refreshLogData() {
-        if (tabRegistry.selectedTabIndex() != TAB_LOG) {
+        if (tabRegistry.selectedTabIndex() != TAB_LOG && !logPinned) {
             return;
         }
         String logPid = null;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
index afd96c85ef84..71fcadec2d75 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
@@ -298,14 +298,17 @@ class LogTab extends AbstractTab {
             logLabel = " Log";
         }
         Line titleLine;
+        List<Span> titleSpans = new ArrayList<>();
+        titleSpans.add(Span.raw(logLabel + " "));
         if (hasNew) {
-            titleLine = Line.from(
-                    Span.raw(logLabel + " "),
-                    Span.styled("(*)", Theme.label()),
-                    Span.raw(" "));
-        } else {
-            titleLine = Line.from(Span.raw(logLabel + " "));
+            titleSpans.add(Span.styled("(*)", Theme.label()));
+            titleSpans.add(Span.raw(" "));
+        }
+        if (ctx.logPinned && ctx.logPinPercent > 0) {
+            titleSpans.add(Span.styled(" Ctrl+L ", Theme.hintKey()));
+            titleSpans.add(Span.raw(" pin (" + ctx.logPinPercent + "%)  "));
         }
+        titleLine = Line.from(titleSpans);
         Block block = Block.builder()
                 .borderType(BorderType.ROUNDED).borders(Borders.ALL)
                 .title(Title.from(titleLine))
@@ -424,6 +427,11 @@ class LogTab extends AbstractTab {
             hint(spans, "l", "level");
         }
         hint(spans, "f", "follow" + (followMode ? " [on]" : " [off]"));
+        if (ctx.logPinned) {
+            hint(spans, "Ctrl+L", "pin (" + ctx.logPinPercent + "%)");
+        } else {
+            hint(spans, "Ctrl+L", "pin");
+        }
     }
 
     private void renderLogLevelPopup(Frame frame, Rect area) {
@@ -765,6 +773,25 @@ class LogTab extends AbstractTab {
                 tells you which thread produced the log message. This helps 
correlate
                 log entries with specific routes when multiple routes run 
concurrently.
 
+                ## Pinned Log
+
+                Press `Ctrl+L` from any tab to pin the log to the bottom of 
the screen.
+                This lets you watch log output while working in other tabs 
(Routes,
+                Diagram, Overview, etc.) without switching back and forth.
+
+                When pinned, pressing `Ctrl+L` again cycles the panel height 
through
+                25%, 50%, 75%, and then turns the pin off. The default pin 
size is 50%.
+
+                When you navigate to the Log tab itself (key `2`), the log goes
+                full-screen as usual. Leaving the Log tab brings the pin back.
+
+                If the Shell (`F6`) or AI panel (`F8`) is opened while the log 
is
+                pinned, it temporarily takes over the bottom panel. Closing 
Shell/AI
+                restores the pinned log automatically.
+
+                The pinned log panel can also be resized by dragging its top 
border
+                with the mouse.
+
                 ## Keys
 
                 - `Up/Down` — scroll log
@@ -777,6 +804,7 @@ class LogTab extends AbstractTab {
                 - `l` — change log level filter
                 - `f` — toggle follow mode
                 - `w` — toggle word wrap
+                - `Ctrl+L` — pin/cycle/unpin log panel (works from any tab)
                 - `Esc` — clear find / back
                 """;
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java
index 8446ad605148..e48295238a9e 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java
@@ -35,6 +35,8 @@ class MonitorContext {
     String selectedPid;
     String lastSelectedName;
     int shellPercent;
+    boolean logPinned;
+    int logPinPercent;
 
     MonitorContext(
                    AtomicReference<List<IntegrationInfo>> data,
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/PanelAnimation.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/PanelAnimation.java
index 837968c07838..d3136886aa2b 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/PanelAnimation.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/PanelAnimation.java
@@ -71,4 +71,10 @@ class PanelAnimation {
         panelHeight = height;
         targetPanelHeight = height;
     }
+
+    void reset(int startIndex) {
+        panelHeight = -1;
+        targetPanelHeight = -1;
+        cycleIndex = startIndex;
+    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManager.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManager.java
index 12a0b954abdb..f998564d2fd0 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManager.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManager.java
@@ -395,7 +395,8 @@ class PopupManager {
 
     void renderMorePopup(Frame frame, Rect area) {
         int popupW = 28;
-        int popupH = 21;
+        int itemCount = moreTabCount();
+        int popupH = Math.min(itemCount + 2, area.height());
         // Position just below the More tab label
         int dividerW = CharWidth.of(" | ");
         int tabBarX = 0;
@@ -411,7 +412,7 @@ class PopupManager {
         if (x + popupW > area.right()) {
             x = Math.max(area.left(), area.right() - popupW);
         }
-        Rect popup = new Rect(x, y, Math.min(popupW, area.width() - (x - 
area.left())), Math.min(popupH, area.height()));
+        Rect popup = new Rect(x, y, Math.min(popupW, area.width() - (x - 
area.left())), popupH);
         lastMorePopupRect = popup;
 
         frame.renderWidget(Clear.INSTANCE, popup);
@@ -422,7 +423,7 @@ class PopupManager {
                 .items(items)
                 .highlightStyle(Theme.selectionBg())
                 .highlightSymbol("")
-                .scrollMode(ScrollMode.NONE)
+                .scrollMode(ScrollMode.AUTO_SCROLL)
                 .block(Block.builder()
                         .borderType(BorderType.ROUNDED).borders(Borders.ALL)
                         .title(Title.from(Line.from(Span.styled(

Reply via email to