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 752bb51779fd chore: camel-jbang - the panel the cursor is in has the 
focus border on About, Timeline, Routes, Diagram, Errors, Circuit Breaker, JFR 
and Heap Histogram (#29467)
752bb51779fd is described below

commit 752bb51779fd652d18d1f57b69f738aa51e513da
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 9 13:16:09 2026 +0100

    chore: camel-jbang - the panel the cursor is in has the focus border on 
About, Timeline, Routes, Diagram, Errors, Circuit Breaker, JFR and Heap 
Histogram (#29467)
    
    * chore: camel-jbang - the panel the cursor is in has the focus border …
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../dsl/jbang/core/commands/tui/AboutView.java     | 40 +++++++++++++++-------
 .../core/commands/tui/AbstractDiagramTab.java      |  1 +
 .../jbang/core/commands/tui/CircuitBreakerTab.java | 14 +++++---
 .../core/commands/tui/DiagramDetailSupport.java    |  7 ++--
 .../jbang/core/commands/tui/DiagramSupport.java    | 25 +++++++-------
 .../dsl/jbang/core/commands/tui/DiagramTab.java    |  1 +
 .../dsl/jbang/core/commands/tui/ErrorsTab.java     | 11 +++---
 .../jbang/core/commands/tui/HeapHistogramTab.java  |  8 +++--
 .../camel/dsl/jbang/core/commands/tui/JfrTab.java  | 15 ++++----
 .../dsl/jbang/core/commands/tui/RoutesTab.java     | 12 +++----
 .../dsl/jbang/core/commands/tui/TimelineTab.java   | 15 ++++----
 .../dsl/jbang/core/commands/tui/AboutViewTest.java | 12 +++++++
 .../core/commands/tui/TimelineTabRenderTest.java   | 16 +++++++++
 .../dsl/jbang/core/commands/tui/TuiTestHelper.java | 25 ++++++++++++++
 14 files changed, 146 insertions(+), 56 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutView.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutView.java
index e1e6efb42d37..4aba7b02e35f 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutView.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutView.java
@@ -96,6 +96,8 @@ final class AboutView {
     private int stripColumns = STRIP_COLUMNS;
     // the line of In, Routes or Out the cursor is on (↑ ↓), in the order of 
the columns; -1 when none is
     private int cursor = -1;
+    // the column the cursor is in: 0 In, 1 Routes, 2 Out, or -1 when it is on 
nothing; its panel has the focus border
+    private int focus = -1;
     private volatile List<Target> targets = List.of();
     private volatile List<String> targetLabels = List.of();
 
@@ -467,12 +469,13 @@ final class AboutView {
                 .constraints(Constraint.length(headHeight), 
Constraint.length(columnHeight), Constraint.length(statusHeight),
                         Constraint.length(happenedHeight), Constraint.fill())
                 .split(area);
-        
frame.renderWidget(Paragraph.builder().text(Text.from(head)).block(block(name, 
Padding.NONE)).build(), rows.get(0));
-        renderColumns(frame, rows.get(1), columns, Padding.NONE, 0);
+        
frame.renderWidget(Paragraph.builder().text(Text.from(head)).block(block(name, 
Padding.NONE, false)).build(),
+                rows.get(0));
+        renderColumns(frame, rows.get(1), columns, Padding.NONE, 0, focus);
         
frame.renderWidget(Paragraph.builder().text(Text.from(status)).build(), 
rows.get(2));
         if (happenedHeight > 2) {
             frame.renderWidget(Paragraph.builder().text(Text.from(strips))
-                    .block(block(happenedTitle(info), Padding.NONE)).build(), 
rows.get(3));
+                    .block(block(happenedTitle(info), Padding.NONE, 
false)).build(), rows.get(3));
         }
     }
 
@@ -489,13 +492,13 @@ final class AboutView {
                         Constraint.length(1), 
Constraint.length(status.size()), Constraint.length(1),
                         Constraint.length(happenedHeight), 
Constraint.length(1), Constraint.length(1), Constraint.fill())
                 .split(area);
-        
frame.renderWidget(Paragraph.builder().text(Text.from(head)).block(block(name, 
Padding.horizontal(1))).build(),
+        
frame.renderWidget(Paragraph.builder().text(Text.from(head)).block(block(name, 
Padding.horizontal(1), false)).build(),
                 rows.get(0));
-        renderColumns(frame, rows.get(2), columns, Padding.uniform(1), 1);
+        renderColumns(frame, rows.get(2), columns, Padding.uniform(1), 1, 
focus);
         
frame.renderWidget(Paragraph.builder().text(Text.from(status)).build(), 
indent(rows.get(4)));
         if (happenedHeight > 2) {
             frame.renderWidget(Paragraph.builder().text(Text.from(strips))
-                    .block(block(happenedTitle(info), 
Padding.uniform(1))).build(), rows.get(6));
+                    .block(block(happenedTitle(info), Padding.uniform(1), 
false)).build(), rows.get(6));
         }
         
frame.renderWidget(Paragraph.builder().text(Text.from(next())).build(), 
indent(rows.get(8)));
     }
@@ -505,7 +508,7 @@ final class AboutView {
         return new Rect(r.x() + 2, r.y(), Math.max(0, r.width() - 2), 
r.height());
     }
 
-    private static void renderColumns(Frame frame, Rect area, Columns columns, 
Padding padding, int spacing) {
+    private void renderColumns(Frame frame, Rect area, Columns columns, 
Padding padding, int spacing, int focus) {
         // by hand: the layout leaves the last column short of the edge when 
spaced
         int w0 = area.width() * 28 / 100;
         int w1 = area.width() * 42 / 100;
@@ -513,11 +516,13 @@ final class AboutView {
         List<Rect> cols = List.of(new Rect(area.x(), area.y(), w0, 
area.height()),
                 new Rect(area.x() + w0 + spacing, area.y(), w1, area.height()),
                 new Rect(area.x() + w0 + w1 + 2 * spacing, area.y(), w2, 
area.height()));
-        
frame.renderWidget(Paragraph.builder().text(Text.from(columns.in())).block(block("
 In ", padding)).build(),
+        
frame.renderWidget(Paragraph.builder().text(Text.from(columns.in())).block(block("
 In ", padding, focus == 0)).build(),
                 cols.get(0));
-        
frame.renderWidget(Paragraph.builder().text(Text.from(columns.routes())).block(block("
 Routes ", padding)).build(),
+        frame.renderWidget(
+                
Paragraph.builder().text(Text.from(columns.routes())).block(block(" Routes ", 
padding, focus == 1)).build(),
                 cols.get(1));
-        
frame.renderWidget(Paragraph.builder().text(Text.from(columns.out())).block(block("
 Out ", padding)).build(),
+        frame.renderWidget(
+                
Paragraph.builder().text(Text.from(columns.out())).block(block(" Out ", 
padding, focus == 2)).build(),
                 cols.get(2));
     }
 
@@ -571,6 +576,15 @@ final class AboutView {
         if (cursor >= all.size()) {
             cursor = all.size() - 1;
         }
+        focus = -1;
+        for (int i = 0, before = 0; i < columns.size() && cursor >= 0; i++) {
+            int n = (int) columns.get(i).stream().filter(t -> t != 
null).count();
+            if (cursor < before + n) {
+                focus = i;
+                break;
+            }
+            before += n;
+        }
         highlight(c.in(), c.inTargets(), c.routes(), c.routeTargets(), 
c.out(), c.outTargets());
     }
 
@@ -1220,7 +1234,9 @@ final class AboutView {
         return lines;
     }
 
-    private static Block block(String title, Padding padding) {
-        return 
Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).padding(padding).build();
+    /** A panel: the one the cursor is in has the focus border, as the panels 
of the other screens. */
+    private Block block(String title, Padding padding, boolean focused) {
+        return 
Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).borderStyle(ctx.paneBorder(focused))
+                .title(title).padding(padding).build();
     }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractDiagramTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractDiagramTab.java
index 668d0eb88c54..ad941094a4b1 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractDiagramTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractDiagramTab.java
@@ -74,6 +74,7 @@ abstract class AbstractDiagramTab extends AbstractTab {
     protected AbstractDiagramTab(MonitorContext ctx) {
         super(ctx);
         diagram.zoom = ctx.routeZoom;
+        diagram.paneBorder = ctx::paneBorder;
     }
 
     boolean isShowDiagram() {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
index 00b88047dd7d..cf5f1c612a5f 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
@@ -157,7 +157,8 @@ class CircuitBreakerTab extends AbstractTableTab {
                         Constraint.fill())
                 .highlightStyle(Theme.selectionBg())
                 .highlightSpacing(Table.HighlightSpacing.ALWAYS)
-                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title("
 Circuit Breaker ").build())
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).borderStyle(ctx.paneBorder(true))
+                        .title(" Circuit Breaker ").build())
                 .build();
 
         lastTableArea = chunks.get(0);
@@ -271,7 +272,8 @@ class CircuitBreakerTab extends AbstractTableTab {
 
         frame.renderWidget(Paragraph.builder()
                 .text(Text.from(lines))
-                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).borderStyle(ctx.paneBorder(false))
+                        .title(title).build())
                 .build(), area);
     }
 
@@ -303,7 +305,8 @@ class CircuitBreakerTab extends AbstractTableTab {
                 Span.styled("░".repeat(empty), Style.EMPTY.dim()));
         frame.renderWidget(Paragraph.builder()
                 .text(Text.from(barLine))
-                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title("
 Failure Rate ").build())
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).borderStyle(ctx.paneBorder(false))
+                        .title(" Failure Rate ").build())
                 .build(), vSplit.get(0));
 
         LinkedList<Long> successHist = cbSuccessHistory.get(key);
@@ -342,7 +345,7 @@ class CircuitBreakerTab extends AbstractTableTab {
                 .showYAxis(true)
                 .xLabels("-" + renderPoints + "s", "-" + (renderPoints * 3 / 
4) + "s",
                         "-" + (renderPoints / 2) + "s", "-" + (renderPoints / 
4) + "s", "now")
-                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).borderStyle(ctx.paneBorder(false))
                         .title(Title.from(chartTitle)).build())
                 .build(), vSplit.get(1));
 
@@ -369,7 +372,8 @@ class CircuitBreakerTab extends AbstractTableTab {
                 Span.styled("fail:", dim), Span.raw(" " + lastFail));
         frame.renderWidget(Paragraph.builder()
                 .text(Text.from(Line.from(Span.raw("")), metricsLine1, 
metricsLine2))
-                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).build())
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).borderStyle(ctx.paneBorder(false))
+                        .build())
                 .build(), vSplit.get(2));
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramDetailSupport.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramDetailSupport.java
index 23cd8ebf8b1e..2fe5127a1e1a 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramDetailSupport.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramDetailSupport.java
@@ -384,7 +384,7 @@ final class DiagramDetailSupport {
 
         Paragraph paragraph = Paragraph.builder()
                 .text(Text.from(lines))
-                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).borderStyle(ctx.paneBorder(false))
                         .title(" Info ").build())
                 .build();
         frame.renderWidget(paragraph, area);
@@ -556,7 +556,7 @@ final class DiagramDetailSupport {
 
         Paragraph paragraph = Paragraph.builder()
                 .text(Text.from(lines))
-                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).borderStyle(ctx.paneBorder(false))
                         .title(" Info ").build())
                 .build();
         frame.renderWidget(paragraph, area);
@@ -675,6 +675,7 @@ final class DiagramDetailSupport {
                     MarkdownView.builder()
                             .source("*Select an EIP node*")
                             
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                    .borderStyle(ctx.paneBorder(false))
                                     .title(" EIP Detail ").build())
                             .styles(Theme.markdownStyles())
                             .syntaxTheme(Theme.syntaxTheme())
@@ -690,6 +691,7 @@ final class DiagramDetailSupport {
                     MarkdownView.builder()
                             .source("*No detail available*")
                             
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                    .borderStyle(ctx.paneBorder(false))
                                     .title(" EIP Detail ").build())
                             .styles(Theme.markdownStyles())
                             .syntaxTheme(Theme.syntaxTheme())
@@ -741,6 +743,7 @@ final class DiagramDetailSupport {
                         .source(md.toString())
                         .scroll(detailScroll)
                         
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                .borderStyle(ctx.paneBorder(false))
                                 .title(title).build())
                         .styles(Theme.markdownStyles())
                         .syntaxTheme(Theme.syntaxTheme())
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 ba0b954ef85b..e77f407025ed 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
@@ -29,12 +29,14 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Function;
 import java.util.function.Supplier;
 
 import dev.tamboui.layout.Constraint;
 import dev.tamboui.layout.Layout;
 import dev.tamboui.layout.Rect;
 import dev.tamboui.style.Color;
+import dev.tamboui.style.Style;
 import dev.tamboui.terminal.Frame;
 import dev.tamboui.text.Line;
 import dev.tamboui.text.Span;
@@ -77,6 +79,9 @@ import static 
org.apache.camel.dsl.jbang.core.commands.tui.TuiHelper.hint;
 
 class DiagramSupport {
 
+    // the border of the diagram panel: a screen where the keys move in the 
diagram gives it the focus border
+    Function<Boolean, Style> paneBorder = focused -> Style.EMPTY;
+
     /** The width of a topology box in columns, as the layout engine draws it 
by default. */
     static final int DEFAULT_BOX_COLUMNS = new 
TopologyLayoutEngine().getNodeWidth() / 15;
     /** How wide a topology box may grow so its label and group tag fit on a 
line. */
@@ -1104,7 +1109,7 @@ class DiagramSupport {
 
     void renderNativeDiagram(Frame frame, Rect area, Line title, boolean 
metrics) {
         Block.Builder builder = Block.builder()
-                .borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                
.borderType(BorderType.ROUNDED).borders(Borders.ALL).borderStyle(paneBorder.apply(true))
                 .title(Title.from(withAiMark(title)));
         if (groupLegend != null) {
             builder.titleBottom(Title.from(groupLegend));
@@ -1541,10 +1546,8 @@ class DiagramSupport {
     void renderNativeRouteDiagram(
             Frame frame, Rect area, Line title, boolean metrics,
             String currentRouteId, RouteDiagramLayoutEngine.LayoutRoute 
routeLayout) {
-        Block block = Block.builder()
-                .borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                .title(Title.from(title))
-                .build();
+        Block block = 
Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                
.borderStyle(paneBorder.apply(true)).title(Title.from(title)).build();
         frame.renderWidget(block, area);
 
         Rect inner = block.inner(area);
@@ -1958,10 +1961,8 @@ class DiagramSupport {
             return;
         }
 
-        Block block = Block.builder()
-                .borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                .title(Title.from(withAiMark(title)))
-                .build();
+        Block block = 
Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                .title(Title.from(withAiMark(title))).build();
         frame.renderWidget(block, area);
 
         Rect inner = block.inner(area);
@@ -2041,10 +2042,8 @@ class DiagramSupport {
             return;
         }
 
-        Block block = Block.builder()
-                .borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                .title(Title.from(title))
-                .build();
+        Block block = 
Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                .title(Title.from(title)).build();
         frame.renderWidget(block, area);
 
         Rect inner = block.inner(area);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java
index 90f6877aa898..44200a466296 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java
@@ -688,6 +688,7 @@ class DiagramTab extends AbstractDiagramTab {
                                 "Loading diagram...",
                                 Style.EMPTY.dim()))))
                         
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                .borderStyle(ctx.paneBorder(true))
                                 .title(" Diagram ").build())
                         .build(),
                 area);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
index 6c71f5d8c199..48aeaa8492bf 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
@@ -411,7 +411,7 @@ class ErrorsTab extends AbstractTableTab {
                         Constraint.fill())
                 .highlightStyle(Theme.selectionBg())
                 .highlightSpacing(Table.HighlightSpacing.ALWAYS)
-                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).borderStyle(ctx.paneBorder(true))
                         .title(" Errors [" + sorted.size() + "] ").build())
                 .build();
 
@@ -681,7 +681,8 @@ class ErrorsTab extends AbstractTableTab {
 
         int[] scroll = { detailScroll };
         int[] hScroll = { detailHScroll };
-        HistoryTab.renderDetailPanel(frame, area, lines, wordWrap, hScroll, 
scroll, detailScrollState, " Detail ");
+        HistoryTab.renderDetailPanel(frame, area, lines, wordWrap, hScroll, 
scroll, detailScrollState, " Detail ",
+                ctx.paneBorder(false), null);
         detailScroll = scroll[0];
         detailHScroll = hScroll[0];
     }
@@ -777,7 +778,8 @@ class ErrorsTab extends AbstractTableTab {
             frame.renderWidget(
                     Paragraph.builder()
                             .text(Text.from(Line.from(Span.styled("No error 
selected", Style.EMPTY.dim()))))
-                            
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title("
 Info ").build())
+                            
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                    
.borderStyle(ctx.paneBorder(false)).title(" Info ").build())
                             .build(),
                     area);
             return;
@@ -847,7 +849,8 @@ class ErrorsTab extends AbstractTableTab {
 
         Paragraph.Builder pb = Paragraph.builder()
                 .text(Text.from(lines))
-                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title("
 Info ").build());
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).borderStyle(ctx.paneBorder(false))
+                        .title(" Info ").build());
         if (wordWrap) {
             pb.overflow(Overflow.WRAP_WORD);
         }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HeapHistogramTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HeapHistogramTab.java
index 47c792d58f94..036fe978593b 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HeapHistogramTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HeapHistogramTab.java
@@ -109,6 +109,7 @@ class HeapHistogramTab extends AbstractTableTab {
                             .text(Text.from(
                                     Line.from(Span.styled(" Loading heap 
histogram...", Style.EMPTY.dim()))))
                             
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                    .borderStyle(ctx.paneBorder(true))
                                     .title(" Heap Histogram ").build())
                             .build(),
                     area);
@@ -163,7 +164,8 @@ class HeapHistogramTab extends AbstractTableTab {
                         Constraint.length(15))
                 .highlightStyle(Theme.selectionBg())
                 .highlightSpacing(Table.HighlightSpacing.ALWAYS)
-                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).borderStyle(ctx.paneBorder(true))
+                        .title(title).build())
                 .build();
 
         frame.renderStatefulWidget(table, area, tableState);
@@ -176,6 +178,7 @@ class HeapHistogramTab extends AbstractTableTab {
                     Paragraph.builder()
                             .text(Text.from(Line.from(Span.styled(" Select a 
class to see details", Style.EMPTY.dim()))))
                             
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                    .borderStyle(ctx.paneBorder(false))
                                     .title(" Detail ").build())
                             .build(),
                     area);
@@ -262,7 +265,8 @@ class HeapHistogramTab extends AbstractTableTab {
         frame.renderWidget(
                 Paragraph.builder()
                         .text(Text.from(lines))
-                        
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
+                        
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                
.borderStyle(ctx.paneBorder(false)).title(title).build())
                         .build(),
                 area);
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/JfrTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/JfrTab.java
index d22fabb37db0..11a221df6dfb 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/JfrTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/JfrTab.java
@@ -319,6 +319,7 @@ class JfrTab extends AbstractTab {
         frame.renderWidget(
                 Paragraph.builder().text(Text.from(lines))
                         
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                .borderStyle(ctx.paneBorder(false))
                                 .title(" JFR ").build())
                         .build(),
                 area);
@@ -333,6 +334,7 @@ class JfrTab extends AbstractTab {
                     Paragraph.builder()
                             .text(Text.from(Line.from(Span.styled(msg, 
LABEL))))
                             
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                    .borderStyle(ctx.paneBorder(true))
                                     .title(" " + activeView.label + " 
").build())
                             .build(),
                     area);
@@ -397,7 +399,7 @@ class JfrTab extends AbstractTab {
                         Constraint.length(10), Constraint.length(10), 
Constraint.length(10))
                 .highlightStyle(Theme.selectionBg())
                 .highlightSpacing(Table.HighlightSpacing.ALWAYS)
-                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).borderStyle(ctx.paneBorder(true))
                         .title(" Routes (" + data.size() + ") ").build())
                 .build();
         lastTableArea = chunks.get(0);
@@ -415,6 +417,7 @@ class JfrTab extends AbstractTab {
                     Paragraph.builder()
                             .text(Text.from(Line.from(Span.styled("No routes", 
Style.EMPTY.dim()))))
                             
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                    .borderStyle(ctx.paneBorder(false))
                                     .title(" Processors ").build())
                             .build(),
                     chunks.get(1));
@@ -467,7 +470,7 @@ class JfrTab extends AbstractTab {
                         Constraint.length(10), Constraint.length(10), 
Constraint.length(10))
                 .highlightStyle(Theme.selectionBg())
                 .highlightSpacing(Table.HighlightSpacing.ALWAYS)
-                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).borderStyle(ctx.paneBorder(false))
                         .title(" Processors [" + routeId + "] (" + data.size() 
+ ") ").build())
                 .build();
         frame.renderStatefulWidget(table, area, processorTableState);
@@ -521,7 +524,7 @@ class JfrTab extends AbstractTab {
                         Constraint.length(10), Constraint.length(10), 
Constraint.length(10))
                 .highlightStyle(Theme.selectionBg())
                 .highlightSpacing(Table.HighlightSpacing.ALWAYS)
-                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).borderStyle(ctx.paneBorder(true))
                         .title(" Processors (" + data.size() + ") ").build())
                 .build();
         lastTableArea = area;
@@ -572,7 +575,7 @@ class JfrTab extends AbstractTab {
                         Constraint.length(10), Constraint.length(10), 
Constraint.length(10))
                 .highlightStyle(Theme.selectionBg())
                 .highlightSpacing(Table.HighlightSpacing.ALWAYS)
-                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).borderStyle(ctx.paneBorder(true))
                         .title(" Endpoints (" + data.size() + ") ").build())
                 .build();
         lastTableArea = area;
@@ -608,7 +611,7 @@ class JfrTab extends AbstractTab {
                         Constraint.length(28), Constraint.fill())
                 .highlightStyle(Theme.selectionBg())
                 .highlightSpacing(Table.HighlightSpacing.ALWAYS)
-                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).borderStyle(ctx.paneBorder(true))
                         .title(" Failures (" + failureData.size() + ") 
").build())
                 .build();
         lastTableArea = area;
@@ -641,7 +644,7 @@ class JfrTab extends AbstractTab {
                         Constraint.length(8), Constraint.length(8))
                 .highlightStyle(Theme.selectionBg())
                 .highlightSpacing(Table.HighlightSpacing.ALWAYS)
-                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).borderStyle(ctx.paneBorder(true))
                         .title(" Redeliveries (" + redeliveryData.size() + ") 
").build())
                 .build();
         lastTableArea = area;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
index b533dc6c4025..1c841f510c32 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
@@ -357,8 +357,8 @@ class RoutesTab extends AbstractDiagramTab {
             frame.renderWidget(
                     Paragraph.builder()
                             .text(Text.from(Line.from(Span.styled("No routes", 
Style.EMPTY.dim()))))
-                            
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title("
 Processors ")
-                                    .build())
+                            
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                    
.borderStyle(ctx.paneBorder(false)).title(" Processors ").build())
                             .build(),
                     chunks.get(1));
         }
@@ -536,7 +536,7 @@ class RoutesTab extends AbstractDiagramTab {
                 .highlightStyle(Theme.selectionBg())
                 .highlightSpacing(Table.HighlightSpacing.ALWAYS)
                 
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                        .title(" Routes ").build())
+                        .borderStyle(ctx.paneBorder(true)).title(" Routes 
").build())
                 .build();
     }
 
@@ -682,7 +682,7 @@ class RoutesTab extends AbstractDiagramTab {
                 .highlightStyle(Theme.selectionBg())
                 .highlightSpacing(Table.HighlightSpacing.ALWAYS)
                 
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                        .title(" Routes ").build())
+                        .borderStyle(ctx.paneBorder(true)).title(" Routes 
").build())
                 .build();
     }
 
@@ -990,7 +990,7 @@ class RoutesTab extends AbstractDiagramTab {
                     .header(Row.from(header))
                     .widths(widths.toArray(Constraint[]::new))
                     
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                            .title(" Processors [" + route.routeId + "] ")
+                            .borderStyle(ctx.paneBorder(false)).title(" 
Processors [" + route.routeId + "] ")
                             .build())
                     .build();
         } else {
@@ -1125,7 +1125,7 @@ class RoutesTab extends AbstractDiagramTab {
                 .header(Row.from(header))
                 .widths(widths.toArray(Constraint[]::new))
                 
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
-                        .title(" Processors [" + route.routeId + "] ")
+                        .borderStyle(ctx.paneBorder(false)).title(" Processors 
[" + route.routeId + "] ")
                         .build())
                 .build();
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTab.java
index 3de12c0904b1..ac381233f9ac 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTab.java
@@ -424,12 +424,13 @@ class TimelineTab extends AbstractTableTab {
         routeStrip = room.strip();
         lastTableArea = new Rect(top.x(), routeTop, top.width(), Math.max(1, 
routeHeight));
         Padding padding = room.roomy() ? Padding.uniform(1) : Padding.NONE;
-        
frame.renderWidget(Paragraph.builder().text(Text.from(lines)).block(block(title(info),
 padding)).build(), top);
+        frame.renderWidget(
+                
Paragraph.builder().text(Text.from(lines)).block(block(title(info), padding, 
!drill.isOpen())).build(), top);
 
         if (drill.isOpen()) {
             List<Timeline.Kept> kept = keptAtCursor();
             
frame.renderWidget(Paragraph.builder().text(Text.from(drill.lines(kept, 
detailHeight - 2 - 2 * gap)))
-                    .block(block(drill.title(kept, ctx.timeline.done(info.pid, 
drill.from(), drill.to())), padding))
+                    .block(block(drill.title(kept, ctx.timeline.done(info.pid, 
drill.from(), drill.to())), padding, true))
                     .build(),
                     rows.get(2));
             return;
@@ -705,7 +706,7 @@ class TimelineTab extends AbstractTableTab {
         Padding padding = room.roomy() ? Padding.uniform(1) : Padding.NONE;
         if (r == null) {
             
frame.renderWidget(Paragraph.builder().text(Text.from(Line.from(Span.raw(""))))
-                    .block(block(" Route ", padding)).build(), area);
+                    .block(block(" Route ", padding, false)).build(), area);
             return;
         }
         List<long[]> values = List.of(cols(r.completed(), false), 
cols(r.recovered(), false),
@@ -788,7 +789,7 @@ class TimelineTab extends AbstractTableTab {
         addTotal(totals, r.total(r.failed()), "failed", Theme.error());
         lines.add(Line.from(totals));
         String title = " " + r.route() + " ─ per second ";
-        
frame.renderWidget(Paragraph.builder().text(Text.from(lines)).block(block(title,
 padding)).build(), area);
+        
frame.renderWidget(Paragraph.builder().text(Text.from(lines)).block(block(title,
 padding, false)).build(), area);
     }
 
     private static void addTotal(List<Span> spans, long count, String words, 
Style style) {
@@ -917,8 +918,10 @@ class TimelineTab extends AbstractTableTab {
         return s.length() >= width ? s : " ".repeat(width - s.length()) + s;
     }
 
-    private static Block block(String title, Padding padding) {
-        return 
Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).padding(padding).build();
+    /** A panel: the one the keys go to has the focus border (the routes, or 
the exchanges of a second when open). */
+    private Block block(String title, Padding padding, boolean focused) {
+        return 
Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).borderStyle(ctx.paneBorder(focused))
+                .title(title).padding(padding).build();
     }
 
     @Override
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutViewTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutViewTest.java
index e867bb9cc6b4..a2b942acd9f6 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutViewTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AboutViewTest.java
@@ -28,6 +28,7 @@ import java.util.concurrent.atomic.AtomicReference;
 
 import dev.tamboui.buffer.Buffer;
 import dev.tamboui.layout.Rect;
+import dev.tamboui.style.Color;
 import dev.tamboui.terminal.Frame;
 import dev.tamboui.tui.event.KeyCode;
 import dev.tamboui.tui.event.KeyEvent;
@@ -281,6 +282,14 @@ class AboutViewTest {
         return ep;
     }
 
+    /** The colours of the borders of In and Routes. */
+    private static List<Color> borders(AboutView about, IntegrationInfo info) {
+        Rect area = new Rect(0, 0, 160, 34);
+        Buffer buffer = Buffer.empty(area);
+        about.render(Frame.forTesting(buffer), area, info);
+        return List.of(TuiTestHelper.borderColor(buffer, " In "), 
TuiTestHelper.borderColor(buffer, " Routes "));
+    }
+
     @Test
     void aboutAnIntegrationWithWhatHasHappened() throws Exception {
         Files.writeString(dir.resolve("shop.camel.yaml"), """
@@ -356,9 +365,12 @@ class AboutViewTest {
         about.moveCursor(1);
         render(about, info);
         assertThat(about.selected()).isEqualTo(new AboutView.Target("orders", 
null));
+        // the panel the cursor is in has the focus border
+        assertThat(borders(about, info)).containsExactly(Theme.accent(), 
Theme.muted().fg().orElseThrow());
         about.moveCursor(2);
         render(about, info);
         assertThat(about.selected()).isEqualTo(new AboutView.Target("stock", 
null));
+        assertThat(borders(about, 
info)).containsExactly(Theme.muted().fg().orElseThrow(), Theme.accent());
         assertThat(about.selectionContext().items()).contains("in: 
timer:orders (period=500) → orders", "route: orders",
                 "route: stock");
         assertThat(about.selectionContext().selectedIndex()).isEqualTo(2);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTabRenderTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTabRenderTest.java
index ee381eead6d1..7a3a3cbf862c 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTabRenderTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineTabRenderTest.java
@@ -22,6 +22,7 @@ import java.util.List;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
 
+import dev.tamboui.buffer.Buffer;
 import dev.tamboui.layout.Rect;
 import dev.tamboui.text.Span;
 import dev.tamboui.tui.event.KeyCode;
@@ -384,6 +385,21 @@ class TimelineTabRenderTest {
         
assertThat(tab.getTableDataAsJson().getInteger("selectedIndex")).isEqualTo(2);
     }
 
+    @Test
+    void thePanelTheKeysGoToHasTheFocusBorder() {
+        TimelineTab tab = new TimelineTab(ctx, now::get);
+        Buffer screen = TuiTestHelper.renderToBuffer(tab, 160, 40);
+        // ↑↓ select a route: the timeline has the focus
+        assertThat(TuiTestHelper.borderColor(screen, "Timeline ─ 
shop")).isEqualTo(Theme.accent());
+        assertThat(TuiTestHelper.borderColor(screen, "orders ─ per 
second")).isNotEqualTo(Theme.accent());
+
+        // Enter: ↑↓ go over the exchanges of the second
+        tab.handleKeyEvent(KeyEvent.ofKey(KeyCode.ENTER));
+        screen = TuiTestHelper.renderToBuffer(tab, 160, 40);
+        assertThat(TuiTestHelper.borderColor(screen, "Timeline ─ 
shop")).isNotEqualTo(Theme.accent());
+        assertThat(TuiTestHelper.borderColor(screen, "Exchanges 
at")).isEqualTo(Theme.accent());
+    }
+
     private static int indexOf(List<String> lines, String text) {
         for (int i = 0; i < lines.size(); i++) {
             if (lines.get(i).contains(text)) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiTestHelper.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiTestHelper.java
index a02465bd76ee..cdfffd78fa4c 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiTestHelper.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiTestHelper.java
@@ -74,4 +74,29 @@ final class TuiTestHelper {
         }
         return false;
     }
+
+    /** Renders a tab into a virtual buffer, for the styles of its cells. */
+    static Buffer renderToBuffer(MonitorTab tab, int width, int height) {
+        Rect area = new Rect(0, 0, width, height);
+        Buffer buffer = Buffer.empty(area);
+        tab.render(Frame.forTesting(buffer), area);
+        return buffer;
+    }
+
+    /** The colour of the border of the panel with this title, from its top 
left corner, or null when none. */
+    static Color borderColor(Buffer buffer, String title) {
+        for (int y = 0; y < buffer.height(); y++) {
+            StringBuilder row = new StringBuilder();
+            for (int x = 0; x < buffer.width(); x++) {
+                String sym = buffer.get(x, y).symbol();
+                row.append(sym.isEmpty() ? " " : sym.substring(0, 1));
+            }
+            int at = row.indexOf(title);
+            if (at >= 0) {
+                int corner = row.lastIndexOf("╭", at);
+                return corner >= 0 ? buffer.get(corner, 
y).style().fg().orElse(null) : null;
+            }
+        }
+        return null;
+    }
 }

Reply via email to