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 ae27d8e5a630 chore: camel-jbang - clicks on About, Heap Histogram, the
JFR views and the Timeline seconds, and the wheel scrolls the panel under the
mouse (#29468)
ae27d8e5a630 is described below
commit ae27d8e5a63098d108ff747a0b517649be97e01e
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 9 17:13:21 2026 +0100
chore: camel-jbang - clicks on About, Heap Histogram, the JFR views and the
Timeline seconds, and the wheel scrolls the panel under the mouse (#29468)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../dsl/jbang/core/commands/tui/AboutView.java | 58 ++++++++++++++++++----
.../jbang/core/commands/tui/AbstractTableTab.java | 48 +++++++++++++++++-
.../dsl/jbang/core/commands/tui/BeansTab.java | 20 +++++---
.../jbang/core/commands/tui/ConfigurationTab.java | 20 +++++---
.../dsl/jbang/core/commands/tui/EndpointsTab.java | 1 -
.../dsl/jbang/core/commands/tui/ErrorsTab.java | 49 +++++++-----------
.../jbang/core/commands/tui/HeapHistogramTab.java | 1 +
.../camel/dsl/jbang/core/commands/tui/JfrTab.java | 20 ++++++++
.../dsl/jbang/core/commands/tui/OverviewTab.java | 4 ++
.../dsl/jbang/core/commands/tui/ThreadsTab.java | 20 +++++---
.../jbang/core/commands/tui/TimelineExchanges.java | 28 ++++++++++-
.../dsl/jbang/core/commands/tui/TimelineTab.java | 27 +++++++++-
.../src/main/resources/tui/help/beans.md | 1 +
.../src/main/resources/tui/help/configuration.md | 1 +
.../src/main/resources/tui/help/errors.md | 1 +
.../src/main/resources/tui/help/jfr.md | 2 +-
.../src/main/resources/tui/help/overview.md | 2 +-
.../src/main/resources/tui/help/threads.md | 1 +
.../src/main/resources/tui/help/timeline.md | 2 +
.../dsl/jbang/core/commands/tui/AboutViewTest.java | 24 +++++++++
.../commands/tui/ConfigurationTabRenderTest.java | 43 ++++++++++++++++
.../core/commands/tui/ErrorsTabRenderTest.java | 45 +++++++++++++++++
.../jbang/core/commands/tui/JfrTabRenderTest.java | 20 ++++++++
.../core/commands/tui/TimelineTabRenderTest.java | 44 ++++++++++++++++
24 files changed, 413 insertions(+), 69 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 4aba7b02e35f..61a3aede2c51 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
@@ -27,6 +27,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
@@ -100,6 +101,9 @@ final class AboutView {
private int focus = -1;
private volatile List<Target> targets = List.of();
private volatile List<String> targetLabels = List.of();
+ // what In, Routes and Out show and where their lines are on the screen,
for a click
+ private Columns shown;
+ private final Rect[] columnAreas = new Rect[3];
/**
* What a line of In, Routes or Out is about, for Enter: a route (opened
on the Diagram) or an endpoint a route
@@ -357,6 +361,42 @@ final class AboutView {
}
}
+ /**
+ * A click on In, Routes or Out puts the cursor on the entry clicked: its
name, or the line under it (its exchanges,
+ * or the route that sends to it). False when the click was on none.
+ */
+ boolean click(int x, int y) {
+ Columns c = shown;
+ if (c == null) {
+ return false;
+ }
+ List<List<Line>> lines = List.of(c.in(), c.routes(), c.out());
+ List<List<Target>> columns = List.of(c.inTargets(), c.routeTargets(),
c.outTargets());
+ int before = 0;
+ for (int i = 0; i < columns.size(); i++) {
+ List<Target> col = columns.get(i);
+ Rect r = columnAreas[i];
+ if (r != null && r.contains(x, y)) {
+ int at = y - r.y();
+ if (at > 0 && at < col.size() && col.get(at) == null &&
col.get(at - 1) != null
+ && !lines.get(i).get(at).rawContent().isBlank()) {
+ at--;
+ }
+ if (at >= col.size() || col.get(at) == null) {
+ return false;
+ }
+ cursor = before + count(col.subList(0, at));
+ return true;
+ }
+ before += count(col);
+ }
+ return false;
+ }
+
+ private static int count(List<Target> targets) {
+ return (int) targets.stream().filter(Objects::nonNull).count();
+ }
+
/** The line the cursor is on, or null. */
Target selected() {
List<Target> t = targets;
@@ -516,14 +556,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, focus == 0)).build(),
- cols.get(0));
- 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, focus == 2)).build(),
- cols.get(2));
+ List<List<Line>> texts = List.of(columns.in(), columns.routes(),
columns.out());
+ String[] titles = { " In ", " Routes ", " Out " };
+ for (int i = 0; i < titles.length; i++) {
+ Block b = block(titles[i], padding, focus == i);
+ columnAreas[i] = b.inner(cols.get(i));
+
frame.renderWidget(Paragraph.builder().text(Text.from(texts.get(i))).block(b).build(),
cols.get(i));
+ }
}
/** What the integration is, and its purpose: the comment at the top of
its main file. */
@@ -573,12 +612,13 @@ final class AboutView {
}
targets = all;
targetLabels = labels;
+ shown = c;
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();
+ int n = count(columns.get(i));
if (cursor < before + n) {
focus = i;
break;
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractTableTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractTableTab.java
index b839273fadbd..ef93a08b84fc 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractTableTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractTableTab.java
@@ -25,6 +25,7 @@ import dev.tamboui.text.Span;
import dev.tamboui.tui.event.KeyCode;
import dev.tamboui.tui.event.KeyEvent;
import dev.tamboui.tui.event.MouseEvent;
+import dev.tamboui.tui.event.MouseEventKind;
import dev.tamboui.widgets.scrollbar.ScrollbarState;
import dev.tamboui.widgets.table.Table;
import dev.tamboui.widgets.table.TableState;
@@ -36,6 +37,11 @@ abstract class AbstractTableTab extends AbstractTab {
protected final TableState tableState = new TableState();
protected final ScrollbarState tableScrollState = new ScrollbarState();
protected Rect lastTableArea;
+ // the detail panel of the selected row, for the mouse: null when the tab
has none
+ protected Rect lastDetailArea;
+
+ // the lines a turn of the mouse wheel scrolls a detail panel
+ protected static final int WHEEL_LINES = 3;
private final String[] sortColumns;
protected String sort;
@@ -113,9 +119,49 @@ abstract class AbstractTableTab extends AbstractTab {
tableState.selectNext(getRowCount());
}
+ /**
+ * A click on a row selects it. On a tab with a detail panel ({@link
#lastDetailArea}), the panel the mouse clicks
+ * or scrolls gets the keys, and the wheel scrolls the panel under the
mouse: the rows of the table, the lines of
+ * the detail.
+ */
@Override
public boolean handleMouseEvent(MouseEvent me, Rect area) {
- return handleTableClick(me, lastTableArea, tableState, getRowCount());
+ boolean up = me.kind() == MouseEventKind.SCROLL_UP;
+ boolean wheel = up || me.kind() == MouseEventKind.SCROLL_DOWN;
+ if (lastDetailArea != null && lastDetailArea.contains(me.x(), me.y())
&& (wheel || me.isClick())) {
+ focusDetail(true);
+ if (wheel) {
+ scrollDetail(up ? -WHEEL_LINES : WHEEL_LINES);
+ }
+ return true;
+ }
+ if (lastDetailArea != null && wheel && lastTableArea != null &&
lastTableArea.contains(me.x(), me.y())) {
+ focusDetail(false);
+ if (up) {
+ navigateUp();
+ } else {
+ navigateDown();
+ }
+ return true;
+ }
+ if (handleTableClick(me, lastTableArea, tableState, getRowCount())) {
+ focusDetail(false);
+ rowClicked();
+ return true;
+ }
+ return false;
+ }
+
+ /** The mouse gave the keys to the detail panel, or back to the table; a
tab where Tab moves the keys keeps it. */
+ protected void focusDetail(boolean focused) {
+ }
+
+ /** The wheel over the detail panel: scrolls it by the lines, up when
negative. */
+ protected void scrollDetail(int lines) {
+ }
+
+ /** A click selected a row: the detail of another row starts at its top. */
+ protected void rowClicked() {
}
@Override
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BeansTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BeansTab.java
index 426f77ec21de..b1589490e721 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BeansTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BeansTab.java
@@ -31,7 +31,6 @@ import dev.tamboui.text.Span;
import dev.tamboui.text.Text;
import dev.tamboui.tui.event.KeyCode;
import dev.tamboui.tui.event.KeyEvent;
-import dev.tamboui.tui.event.MouseEvent;
import dev.tamboui.widgets.block.Block;
import dev.tamboui.widgets.block.BorderType;
import dev.tamboui.widgets.block.Borders;
@@ -163,12 +162,18 @@ class BeansTab extends AbstractTableTab {
}
@Override
- public boolean handleMouseEvent(MouseEvent me, Rect area) {
- boolean handled = super.handleMouseEvent(me, area);
- if (handled) {
- detailScroll = 0;
- }
- return handled;
+ protected void focusDetail(boolean focused) {
+ detailFocused = focused;
+ }
+
+ @Override
+ protected void scrollDetail(int lines) {
+ detailScroll = Math.max(0, detailScroll + lines);
+ }
+
+ @Override
+ protected void rowClicked() {
+ detailScroll = 0;
}
@Override
@@ -237,6 +242,7 @@ class BeansTab extends AbstractTableTab {
}
private void renderDetail(Frame frame, Rect area, List<BeanData> visible) {
+ lastDetailArea = area;
Style detailBorderStyle = ctx.paneBorder(detailFocused);
Style detailTitleStyle = detailFocused ? Theme.title() :
Style.EMPTY.fg(Theme.accent());
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTab.java
index 2cbe60b01b1e..444218243f6d 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTab.java
@@ -33,7 +33,6 @@ import dev.tamboui.text.Span;
import dev.tamboui.text.Text;
import dev.tamboui.tui.event.KeyCode;
import dev.tamboui.tui.event.KeyEvent;
-import dev.tamboui.tui.event.MouseEvent;
import dev.tamboui.widgets.block.Block;
import dev.tamboui.widgets.block.BorderType;
import dev.tamboui.widgets.block.Borders;
@@ -129,12 +128,18 @@ class ConfigurationTab extends AbstractTableTab {
}
@Override
- public boolean handleMouseEvent(MouseEvent me, Rect area) {
- boolean handled = super.handleMouseEvent(me, area);
- if (handled) {
- detailScroll = 0;
- }
- return handled;
+ protected void focusDetail(boolean focused) {
+ detailFocused = focused;
+ }
+
+ @Override
+ protected void scrollDetail(int lines) {
+ detailScroll = Math.max(0, detailScroll + lines);
+ }
+
+ @Override
+ protected void rowClicked() {
+ detailScroll = 0;
}
@Override
@@ -232,6 +237,7 @@ class ConfigurationTab extends AbstractTableTab {
}
private void renderDetail(Frame frame, Rect area, List<ConfigProperty>
props) {
+ lastDetailArea = area;
Style detailBorderStyle = ctx.paneBorder(detailFocused);
Style detailTitleStyle = detailFocused ? Theme.title() :
Style.EMPTY.fg(Theme.accent());
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
index 71672ff5813c..a1d28ce58da3 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
@@ -80,7 +80,6 @@ class EndpointsTab extends AbstractTableTab {
private int panelMode = PANEL_CHART;
private int detailScroll;
private boolean detailFocused;
- private Rect lastDetailArea;
private int chartPanelHeight = 16;
private final DragSplit vSplit = new DragSplit();
private int flowPanelWidth = 38;
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 4a6e16d1b2c3..4386fcfa9304 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
@@ -37,7 +37,6 @@ import dev.tamboui.text.Text;
import dev.tamboui.tui.event.KeyCode;
import dev.tamboui.tui.event.KeyEvent;
import dev.tamboui.tui.event.MouseEvent;
-import dev.tamboui.tui.event.MouseEventKind;
import dev.tamboui.widgets.block.Block;
import dev.tamboui.widgets.block.BorderType;
import dev.tamboui.widgets.block.Borders;
@@ -66,8 +65,6 @@ class ErrorsTab extends AbstractTableTab {
private boolean showHeaders = true;
private boolean showBody = true;
- private static final int MOUSE_SCROLL_LINES = 3;
-
private static final int INFO_NARROW = 0;
private static final int INFO_WIDE = 1;
private static final int INFO_FULL = 2;
@@ -269,41 +266,30 @@ class ErrorsTab extends AbstractTableTab {
@Override
public boolean handleMouseEvent(MouseEvent me, Rect area) {
if (!diagram.isShowDiagram()) {
- if (handleTableClick(me, lastTableArea, tableState,
filteredSize())) {
- detailScroll = 0;
- return true;
- }
+ // the wheel scrolls the errors over the table, the detail over
the detail
+ return super.handleMouseEvent(me, area);
}
- if (diagram.isShowDiagram()) {
- if (diagram.handleMouseScroll(me)) {
- return true;
- }
- if (me.isClick()) {
- if (diagram.isHistoryMode() &&
diagram.isHistoryTopologyMode()) {
- int clicked = diagram.handleNodeClick(me);
- if (clicked >= 0) {
- return true;
- }
- } else {
- int clicked = diagram.handleEipNodeClick(me);
- if (clicked >= 0) {
- return true;
- }
- }
- }
- return false;
- }
- if (me.kind() == MouseEventKind.SCROLL_UP) {
- detailScroll = Math.max(0, detailScroll - MOUSE_SCROLL_LINES);
+ if (diagram.handleMouseScroll(me)) {
return true;
}
- if (me.kind() == MouseEventKind.SCROLL_DOWN) {
- detailScroll += MOUSE_SCROLL_LINES;
- return true;
+ if (me.isClick()) {
+ int clicked = diagram.isHistoryMode() &&
diagram.isHistoryTopologyMode()
+ ? diagram.handleNodeClick(me) :
diagram.handleEipNodeClick(me);
+ return clicked >= 0;
}
return false;
}
+ @Override
+ protected void scrollDetail(int lines) {
+ detailScroll = Math.max(0, detailScroll + lines);
+ }
+
+ @Override
+ protected void rowClicked() {
+ detailScroll = 0;
+ }
+
@Override
public void navigateUp() {
detailScroll = 0;
@@ -419,6 +405,7 @@ class ErrorsTab extends AbstractTableTab {
.build();
lastTableArea = chunks.get(0);
+ lastDetailArea = showDetail ? chunks.get(1) : null;
frame.renderStatefulWidget(table, chunks.get(0), tableState);
renderScrollbar(frame, table, filteredSize());
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 629c81905ec2..4a2acf52d232 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
@@ -174,6 +174,7 @@ class HeapHistogramTab extends AbstractTableTab {
.title(title).build())
.build();
+ lastTableArea = area;
frame.renderStatefulWidget(table, area, tableState);
}
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 d03e1ffb0540..93810fd2a9e9 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
@@ -93,6 +93,9 @@ class JfrTab extends AbstractTab {
private final TableState processorTableState = new TableState();
private final ScrollbarState processorScrollState = new ScrollbarState();
private Rect lastTableArea;
+ // where the names of the views are on the screen, for a click: the row,
and the columns of each name
+ private int viewsRow = -1;
+ private final int[] viewColumns = new int[View.values().length];
private int topPanelHeight = -1;
// sort state per view
@@ -228,6 +231,16 @@ class JfrTab extends AbstractTab {
@Override
public boolean handleMouseEvent(MouseEvent me, Rect area) {
+ if (me.isClick() && me.y() == viewsRow) {
+ // a click on the name of a view shows it, as Space does
+ View[] views = View.values();
+ for (int i = 0; i < views.length; i++) {
+ if (me.x() >= viewColumns[i] && me.x() < viewColumns[i] +
views[i].label.length()) {
+ switchView(views[i]);
+ return true;
+ }
+ }
+ }
return handleTableClick(me, lastTableArea, activeTableState(),
getRowCount());
}
@@ -258,6 +271,7 @@ class JfrTab extends AbstractTab {
public void render(Frame frame, Rect area) {
IntegrationInfo info = ctx.findSelectedIntegration();
if (info == null) {
+ viewsRow = -1;
renderNoSelection(frame, area);
return;
}
@@ -304,10 +318,16 @@ class JfrTab extends AbstractTab {
lines.add(Line.from(line1));
List<Span> line2 = new ArrayList<>();
+ // the second line inside the border
+ viewsRow = area.y() + 2;
+ int x = area.x() + 1;
for (View v : View.values()) {
if (v.ordinal() > 0) {
line2.add(Span.styled(" | ", LABEL));
+ x += 3;
}
+ viewColumns[v.ordinal()] = x;
+ x += v.label.length();
boolean active = v == activeView;
line2.add(Span.styled(v.label, active ? VALUE : LABEL));
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
index 2774d4fd5e72..8d8e42995e13 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
@@ -289,6 +289,10 @@ class OverviewTab extends AbstractTab {
@Override
public boolean handleMouseEvent(MouseEvent me, Rect area) {
+ if (showsAbout()) {
+ // About has no table: a click puts its cursor on an entry of In,
Routes or Out, the wheel moves it
+ return me.isClick() && about.click(me.x(), me.y());
+ }
if (vSplit.handleMouse(me, me.y())) {
if (vSplit.isDragging() && me.kind() == MouseEventKind.DRAG) {
bottomPanelHeight = Math.max(5, Math.min(area.y() +
area.height() - me.y(), area.height() - 5));
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThreadsTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThreadsTab.java
index dc0b3b88c9a6..a3c114d2ce40 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThreadsTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThreadsTab.java
@@ -31,7 +31,6 @@ import dev.tamboui.text.Span;
import dev.tamboui.text.Text;
import dev.tamboui.tui.event.KeyCode;
import dev.tamboui.tui.event.KeyEvent;
-import dev.tamboui.tui.event.MouseEvent;
import dev.tamboui.widgets.block.Block;
import dev.tamboui.widgets.block.BorderType;
import dev.tamboui.widgets.block.Borders;
@@ -125,12 +124,18 @@ class ThreadsTab extends AbstractTableTab {
}
@Override
- public boolean handleMouseEvent(MouseEvent me, Rect area) {
- boolean handled = super.handleMouseEvent(me, area);
- if (handled) {
- traceScroll = 0;
- }
- return handled;
+ protected void focusDetail(boolean focused) {
+ detailFocused = focused;
+ }
+
+ @Override
+ protected void scrollDetail(int lines) {
+ traceScroll = Math.max(0, traceScroll + lines);
+ }
+
+ @Override
+ protected void rowClicked() {
+ traceScroll = 0;
}
@Override
@@ -236,6 +241,7 @@ class ThreadsTab extends AbstractTableTab {
}
private void renderTrace(Frame frame, Rect area, List<ThreadData> visible)
{
+ lastDetailArea = area;
Style detailBorderStyle = detailFocused ?
Style.EMPTY.fg(Theme.accent()) : Theme.muted();
Style detailTitleStyle = detailFocused ? Theme.title() :
Style.EMPTY.fg(Theme.accent());
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineExchanges.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineExchanges.java
index 63640fc969fc..22ffe20e9c93 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineExchanges.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TimelineExchanges.java
@@ -91,6 +91,31 @@ final class TimelineExchanges {
selected = Math.max(0, Math.min(Math.max(0, count - 1), selected +
delta));
}
+ /**
+ * A click on a line of the list selects the exchange on it; true when it
was on one.
+ *
+ * @param line the line clicked, from the top of the list
+ * @param count the exchanges kept
+ * @param rows the lines the list has
+ */
+ boolean click(int line, int count, int rows) {
+ if (!open || detail || line < 0 || line >= rows) {
+ return false;
+ }
+ int i = first(count, rows) + line;
+ if (i >= count) {
+ return false;
+ }
+ selected = i;
+ return true;
+ }
+
+ /** The first exchange the list shows: the list scrolls to keep the
selected one in view. */
+ private int first(int count, int rows) {
+ int sel = Math.min(selected, count - 1);
+ return Math.max(0, Math.min(sel - rows + 1, count - rows));
+ }
+
Timeline.Kept selected(List<Timeline.Kept> kept) {
return kept.isEmpty() ? null : kept.get(Math.min(selected, kept.size()
- 1));
}
@@ -114,8 +139,7 @@ final class TimelineExchanges {
return detailLines(selected(kept), rows);
}
int sel = Math.min(selected, kept.size() - 1);
- int first = Math.max(0, Math.min(sel - rows + 1, kept.size() - rows));
- for (int i = first; i < kept.size() && lines.size() < rows; i++) {
+ for (int i = first(kept.size(), rows); i < kept.size() && lines.size()
< rows; i++) {
lines.add(row(kept.get(i), i == sel));
}
return lines;
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 641d1231a954..6afbb89d9f13 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
@@ -84,6 +84,9 @@ class TimelineTab extends AbstractTableTab {
private int routeTop = -1;
private int routePitch = 1;
private int routeStrip = 1;
+ // the column of the screen the seconds start at, and the lines of the
exchanges of a second, for the clicks
+ private int stripLeft = -1;
+ private Rect drillArea;
private final TimelineExchanges drill = new TimelineExchanges();
TimelineTab(MonitorContext ctx) {
@@ -296,7 +299,13 @@ class TimelineTab extends AbstractTableTab {
@Override
public boolean handleMouseEvent(MouseEvent me, Rect area) {
- if (routeTop < 0 || !me.isClick() || lastTableArea == null ||
!lastTableArea.contains(me.x(), me.y())) {
+ if (!me.isClick()) {
+ return false;
+ }
+ if (drill.isOpen() && drillArea != null && drillArea.contains(me.x(),
me.y())) {
+ return drill.click(me.y() - drillArea.y(), keptAtCursor().size(),
drillArea.height());
+ }
+ if (routeTop < 0 || lastTableArea == null ||
!lastTableArea.contains(me.x(), me.y())) {
return false;
}
// a click on a strip of a route selects it, not on the blank line
between two routes
@@ -304,6 +313,14 @@ class TimelineTab extends AbstractTableTab {
int i = dy / routePitch;
if (dy >= 0 && dy % routePitch < routeStrip && i < getRowCount()) {
tableState.select(i);
+ // paused, the cursor goes to the second clicked, and the
exchanges open show that second
+ int column = me.x() >= stripLeft ? (me.x() - stripLeft) / width :
-1;
+ if (pausedAt >= 0 && column >= 0 && column < columnCount() &&
column != cursor) {
+ cursor = column;
+ if (drill.isOpen()) {
+ drill.open(cursorFrom(), cursorTo());
+ }
+ }
return true;
}
return false;
@@ -423,13 +440,19 @@ class TimelineTab extends AbstractTableTab {
routePitch = room.strip() + (room.spaced() ? 1 : 0);
routeStrip = room.strip();
lastTableArea = new Rect(top.x(), routeTop, top.width(), Math.max(1,
routeHeight));
+ stripLeft = top.x() + 1 + gap + label;
+ drillArea = null;
Padding padding = room.roomy() ? Padding.uniform(1) : Padding.NONE;
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)))
+ Rect d = rows.get(2);
+ drillArea = new Rect(
+ d.x() + 1 + gap, d.y() + 1 + gap, Math.max(0, d.width() -
2 - 2 * gap),
+ Math.max(0, detailHeight - 2 - 2 * gap));
+
frame.renderWidget(Paragraph.builder().text(Text.from(drill.lines(kept,
drillArea.height())))
.block(block(drill.title(kept, ctx.timeline.done(info.pid,
drill.from(), drill.to())), padding, true))
.build(),
rows.get(2));
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/beans.md
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/beans.md
index 13df2295d85b..b1339720846c 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/beans.md
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/beans.md
@@ -38,6 +38,7 @@ Press `f` to cycle through filter modes:
## Keys
- `Tab` — switch focus between table and detail panel
+- Mouse — a click or the wheel gives the panel under the mouse the focus; the
wheel scrolls it
- `Up/Down` — navigate in focused panel
- `PgUp/PgDn` — page in focused panel
- `s` — cycle sort column
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/configuration.md
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/configuration.md
index 7f833cce4951..b21d2556fc32 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/configuration.md
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/configuration.md
@@ -39,6 +39,7 @@ detail panel shows:
- `Up/Down` — select property
- `Enter` — view property detail / documentation
- `Tab` — switch focus between the table and the detail panel
+- Mouse — a click or the wheel gives the panel under the mouse the focus; the
wheel scrolls it
- `PgUp/PgDn` — scroll the detail panel
- `s` — cycle sort column
- `S` — reverse sort order
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/errors.md
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/errors.md
index aa22d1fdf428..160d865a4fa4 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/errors.md
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/errors.md
@@ -128,3 +128,4 @@ Press `Esc` to navigate back one route in drill-down mode.
- `S` — reverse sort order
- `PgUp/PgDn` — scroll the detail panel
- `Home/End` — jump to the top/end of the detail panel
+- Mouse — a click selects an error; the wheel scrolls the errors over the
table and the detail over the detail panel
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/jfr.md
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/jfr.md
index 17a5a38a303d..6e925adece3b 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/jfr.md
+++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/jfr.md
@@ -18,7 +18,7 @@ data is aggregated into five views:
## Controls
- `F5` — refresh (take new JFR snapshot)
-- `Space` — cycle view
+- `Space` — cycle view, or click the name of a view
- `s` / `S` — cycle sort column / reverse sort direction
- `Esc` — back
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/overview.md
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/overview.md
index 8dcd6594f3c9..95bbac06a9b2 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/overview.md
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/overview.md
@@ -43,7 +43,7 @@ happened to it, with no AI:
Keys of About:
-- **Up/Down** — Select a line of In, Routes or Out
+- **Up/Down** — Select a line of In, Routes or Out, or click it
- **Enter** — Open the selected line: a route drilled down on the
Diagram, an endpoint a route sends to with its details on the Endpoint
tab
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/threads.md
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/threads.md
index cccdc8f33859..260aa052136f 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/threads.md
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/threads.md
@@ -66,6 +66,7 @@ watch BLOCKED/WAITED counters change over time without manual
action.
- `Up/Down` — select thread (detail updates automatically)
- `PgUp/PgDn` — scroll stack trace detail
- `Tab` — switch focus between the table and the stack trace
+- Mouse — a click or the wheel gives the panel under the mouse the focus; the
wheel scrolls it
- `s` — cycle sort column
- `S` — reverse sort order
- `f` — toggle filter (camel / all)
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/timeline.md
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/timeline.md
index 59ce27f2c213..2bd6c270c465 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/timeline.md
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/timeline.md
@@ -92,6 +92,8 @@ were done then, of which these were kept.
the window moves
- **Shift+Left/Right** — While paused, go back and forth in the 5
minutes kept
+- **Click** — Selects the route of a strip; paused, the cursor goes to the
+ second clicked. A click on an exchange of the list selects it
- **Enter** — The exchanges of the second at the cursor (it pauses
first), or the selected exchange in detail
- **Esc** — Close the exchange, then the list, then follow now again
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 a2b942acd9f6..e70c554163ad 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
@@ -379,5 +379,29 @@ class AboutViewTest {
assertThat(about.selected()).isEqualTo(new AboutView.Target("orders",
"http:localhost:8765/ship"));
assertThat(about.handleKeyEvent(KeyEvent.ofKey(KeyCode.ENTER),
info)).isTrue();
assertThat(opened).containsExactly(new AboutView.Target("orders",
"http:localhost:8765/ship"));
+
+ // a click puts the cursor on an entry: a route, an endpoint by the
line under it, an entry of In
+ List<String> screen = render(about, info).lines().toList();
+ assertThat(click(about, screen, "↳ stock")).isTrue();
+ assertThat(about.selected()).isEqualTo(new AboutView.Target("stock",
null));
+ assertThat(click(about, screen, "timer:orders")).isTrue();
+ assertThat(about.selectionContext().selectedIndex()).isZero();
+ assertThat(click(about, screen, "from orders")).isTrue();
+ assertThat(about.selected()).isEqualTo(new AboutView.Target("orders",
"http:localhost:8765/ship"));
+ // the head and What happened are no entries
+ assertThat(click(about, screen, "Every order is checked")).isFalse();
+ assertThat(click(about, screen, "What happened")).isFalse();
+ assertThat(about.selected()).isEqualTo(new AboutView.Target("orders",
"http:localhost:8765/ship"));
+ }
+
+ /** Clicks on the first line that has the text, on its second character. */
+ private static boolean click(AboutView about, List<String> screen, String
text) {
+ for (int y = 0; y < screen.size(); y++) {
+ int x = screen.get(y).indexOf(text);
+ if (x >= 0) {
+ return about.click(x + 1, y);
+ }
+ }
+ throw new AssertionError(text + " is not on the screen");
}
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTabRenderTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTabRenderTest.java
index 19a6e9875089..e579279daf72 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTabRenderTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTabRenderTest.java
@@ -16,6 +16,7 @@
*/
package org.apache.camel.dsl.jbang.core.commands.tui;
+import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
@@ -23,6 +24,9 @@ import dev.tamboui.buffer.Buffer;
import dev.tamboui.layout.Rect;
import dev.tamboui.style.Color;
import dev.tamboui.terminal.Frame;
+import dev.tamboui.text.Span;
+import dev.tamboui.tui.event.MouseButton;
+import dev.tamboui.tui.event.MouseEvent;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -181,6 +185,45 @@ class ConfigurationTabRenderTest {
assertEquals("", ConfigurationTab.sourceOf(prop));
}
+ @Test
+ void thePanelTheMouseClicksOrScrollsGetsTheKeys() {
+ addProperty("camel.main.name", "my-app", "application.properties");
+ addProperty("camel.main.shutdownTimeout", "30",
"application.properties");
+ addProperty("camel.main.tracing", "false", "application.properties");
+ ConfigurationTab tab = new ConfigurationTab(ctx);
+ Rect area = new Rect(0, 0, 120, 30);
+ String rendered = TuiTestHelper.renderToString(tab, 120, 30);
+ int detail =
rendered.lines().toList().indexOf(rendered.lines().filter(l ->
l.contains("Property Detail"))
+ .findFirst().orElseThrow());
+ int timeout =
rendered.lines().toList().indexOf(rendered.lines().filter(l ->
l.contains("shutdownTimeout"))
+ .findFirst().orElseThrow());
+
+ // a click on a row selects it, and the keys stay with the table
+ tab.handleMouseEvent(MouseEvent.press(MouseButton.LEFT, 20, timeout),
area);
+ assertEquals(1, tab.tableState.selected());
+ assertTrue(footer(tab).contains("detail"), footer(tab));
+
+ // the wheel over the detail gives it the keys, and leaves the row
selected
+ tab.handleMouseEvent(MouseEvent.scrollDown(20, detail + 2), area);
+ assertEquals(1, tab.tableState.selected());
+ assertTrue(footer(tab).contains("table"), footer(tab));
+
+ // the wheel over the table selects the next row, and gives the keys
back to the table
+ tab.handleMouseEvent(MouseEvent.scrollDown(20, timeout), area);
+ assertEquals(2, tab.tableState.selected());
+ assertTrue(footer(tab).contains("detail"), footer(tab));
+
+ // a click on the detail gives it the keys
+ tab.handleMouseEvent(MouseEvent.press(MouseButton.LEFT, 20, detail +
2), area);
+ assertTrue(footer(tab).contains("table"), footer(tab));
+ }
+
+ private static String footer(ConfigurationTab tab) {
+ List<Span> spans = new ArrayList<>();
+ tab.renderFooter(spans);
+ return spans.stream().map(Span::content).reduce("", String::concat);
+ }
+
private ConfigurationTab.ConfigProperty addProperty(String key, String
value, String source) {
ConfigurationTab.ConfigProperty prop = new
ConfigurationTab.ConfigProperty();
prop.key = key;
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTabRenderTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTabRenderTest.java
index f102251cebe1..924d60898a9d 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTabRenderTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTabRenderTest.java
@@ -28,6 +28,8 @@ import dev.tamboui.terminal.Frame;
import dev.tamboui.text.Span;
import dev.tamboui.tui.event.KeyEvent;
import dev.tamboui.tui.event.KeyModifiers;
+import dev.tamboui.tui.event.MouseButton;
+import dev.tamboui.tui.event.MouseEvent;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -150,6 +152,49 @@ class ErrorsTabRenderTest {
assertTrue(rendered.contains(ErrorsTab.shortExchangeId("ID-001")),
rendered);
}
+ @Test
+ void theWheelScrollsThePanelUnderTheMouse() {
+ for (int i = 1; i <= 3; i++) {
+ addError("ID-00" + i, "route1", "to1", "java.io.IOException",
"fail " + i, false);
+ }
+ StringBuilder trace = new StringBuilder("java.io.IOException: fail 1");
+ for (int i = 0; i < 60; i++) {
+ trace.append("\n\tat
org.example.Frame").append(i).append(".call(Frame.java:").append(i).append(')');
+ }
+ info.errors.get(0).stackTrace = trace.toString();
+ ErrorsTab tab = new ErrorsTab(ctx);
+ Rect area = new Rect(0, 0, 160, 40);
+ String before = renderToString(tab, 160, 40);
+ int detail = row(before, " Detail ");
+ assertTrue(detail > 0, before);
+
+ // over the detail: its lines scroll, the error stays selected
+ tab.handleMouseEvent(MouseEvent.scrollDown(40, detail + 5), area);
+ tab.handleMouseEvent(MouseEvent.scrollDown(40, detail + 5), area);
+ String scrolled = renderToString(tab, 160, 40);
+ assertEquals(0, tab.tableState.selected());
+ assertFalse(scrolled.contains("Exchange: ID-001"), scrolled);
+
+ // over the table: the next error
+ tab.handleMouseEvent(MouseEvent.scrollDown(40, 6), area);
+ assertEquals(1, tab.tableState.selected());
+
+ // a click on a row selects it, and its detail starts at the top
+ String rows = renderToString(tab, 160, 40);
+ tab.handleMouseEvent(MouseEvent.press(MouseButton.LEFT, 40, row(rows,
ErrorsTab.shortExchangeId("ID-003"))), area);
+ assertEquals(2, tab.tableState.selected());
+ }
+
+ private static int row(String rendered, String text) {
+ String[] lines = rendered.split("\n");
+ for (int y = 0; y < lines.length; y++) {
+ if (lines[y].contains(text)) {
+ return y;
+ }
+ }
+ return -1;
+ }
+
@Test
void renderMultipleErrorsAllAppear() {
addError("ID-AAA", "route-a", "to1", "IOException", "fail1", false);
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/JfrTabRenderTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/JfrTabRenderTest.java
index c6a18163c332..bf4ca7633284 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/JfrTabRenderTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/JfrTabRenderTest.java
@@ -21,10 +21,13 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
+import dev.tamboui.layout.Rect;
import dev.tamboui.text.Span;
import dev.tamboui.tui.event.KeyCode;
import dev.tamboui.tui.event.KeyEvent;
import dev.tamboui.tui.event.KeyModifiers;
+import dev.tamboui.tui.event.MouseButton;
+import dev.tamboui.tui.event.MouseEvent;
import org.apache.camel.util.json.JsonArray;
import org.apache.camel.util.json.JsonObject;
import org.junit.jupiter.api.BeforeEach;
@@ -217,6 +220,23 @@ class JfrTabRenderTest {
});
}
+ @Test
+ void aClickOnTheNameOfAViewShowsIt() {
+ JfrTab tab = new JfrTab(ctx);
+ Rect area = new Rect(0, 0, 140, 30);
+ List<String> lines = TuiTestHelper.renderToString(tab, 140,
30).lines().toList();
+ int y = 0;
+ while (!lines.get(y).contains("Redeliveries")) {
+ y++;
+ }
+ int x = lines.get(y).indexOf("Failures");
+
+ assertThat(tab.handleMouseEvent(MouseEvent.press(MouseButton.LEFT, x +
2, y), area)).isTrue();
+ assertThat(TuiTestHelper.renderToString(tab, 140, 30)).contains("╭
Failures ");
+ // the space between two names is no view
+ assertThat(tab.handleMouseEvent(MouseEvent.press(MouseButton.LEFT, x -
2, y), area)).isFalse();
+ }
+
@Test
void promptToSnapshotWhenNoData() {
JfrTab tab = new JfrTab(ctx);
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 c5b277032565..d55de400d4c6 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
@@ -403,6 +403,50 @@ class TimelineTabRenderTest {
assertThat(tab.getTableDataAsJson().getInteger("selectedIndex")).isEqualTo(2);
}
+ @Test
+ void pausedAClickOnASecondPutsTheCursorThere() {
+ TimelineTab tab = new TimelineTab(ctx, now::get);
+ Rect area = new Rect(0, 0, 160, 40);
+ List<String> lines = TuiTestHelper.renderToString(tab, 160,
40).lines().toList();
+ int shipping = indexOf(lines, " shipping");
+ // the seconds start where the time axis does
+ int left = lines.get(indexOf(lines, "-60s")).indexOf("-60s");
+
+ // following now, a click selects the route and leaves the timeline
following
+ assertThat(tab.handleMouseEvent(MouseEvent.press(MouseButton.LEFT,
left + 30, shipping), area)).isTrue();
+ assertThat(tab.isPaused()).isFalse();
+
+ tab.handleKeyEvent(KeyEvent.ofChar(' '));
+ assertThat(tab.handleMouseEvent(MouseEvent.press(MouseButton.LEFT,
left + 30, shipping), area)).isTrue();
+ assertThat(tab.cursorFrom()).isEqualTo(T0 / 1000 + 30);
+
assertThat(tab.getTableDataAsJson().getInteger("selectedIndex")).isEqualTo(1);
+ // the label of the route is no second
+ assertThat(tab.handleMouseEvent(MouseEvent.press(MouseButton.LEFT,
left - 3, shipping), area)).isTrue();
+ assertThat(tab.cursorFrom()).isEqualTo(T0 / 1000 + 30);
+ }
+
+ @Test
+ void aClickOnAnExchangeOfTheSecondsSelectsIt() {
+ // narrow: a column is two seconds, and has two exchanges
+ TimelineTab tab = new TimelineTab(ctx, now::get);
+ Rect area = new Rect(0, 0, 100, 40);
+ TuiTestHelper.renderToString(tab, 100, 40);
+ tab.handleKeyEvent(KeyEvent.ofChar(' '));
+ while (tab.cursorFrom() > T0 / 1000 + 30) {
+ tab.handleKeyEvent(KeyEvent.ofKey(KeyCode.LEFT));
+ }
+ assertThat(tab.cursorTo()).isEqualTo(T0 / 1000 + 31);
+ tab.handleKeyEvent(KeyEvent.ofKey(KeyCode.ENTER));
+ List<String> lines = TuiTestHelper.renderToString(tab, 100,
40).lines().toList();
+
+ assertThat(tab.handleMouseEvent(MouseEvent.press(MouseButton.LEFT, 20,
indexOf(lines, "H31")), area)).isTrue();
+ // below the last exchange is none
+ assertThat(tab.handleMouseEvent(MouseEvent.press(MouseButton.LEFT, 20,
indexOf(lines, "H31") + 1), area))
+ .isFalse();
+ tab.handleKeyEvent(KeyEvent.ofKey(KeyCode.ENTER));
+ assertThat(TuiTestHelper.renderToString(tab, 100,
40)).contains("H31").doesNotContain("H30");
+ }
+
@Test
void thePanelTheKeysGoToHasTheFocusBorder() {
TimelineTab tab = new TimelineTab(ctx, now::get);