|
|
@@ -86,9 +86,6 @@ public class MainView extends BorderPane {
|
|
|
/** 标记由代码触发的输入框文本同步,避免无限递归 */
|
|
|
private boolean syncingPlateField = false;
|
|
|
|
|
|
- private final CheckBox triggerGeo = new CheckBox("地感");
|
|
|
- private final CheckBox triggerGate = new CheckBox("道闸");
|
|
|
-
|
|
|
private final Spinner<Integer> heartbeatSec = intSpinner(5, 600, 60, 80);
|
|
|
private Button heartbeatBtn;
|
|
|
|
|
|
@@ -500,21 +497,19 @@ public class MainView extends BorderPane {
|
|
|
|
|
|
right.getChildren().add(card("通道 · Channel", buildChannelSwitcher()));
|
|
|
|
|
|
- // 单条触发 + 心跳合并到一张卡片
|
|
|
- Region heartbeatDivider = new Region();
|
|
|
- heartbeatDivider.getStyleClass().add("divider");
|
|
|
- VBox singleCard = card("单条触发 · Single & Heartbeat",
|
|
|
- buildSingleActions(),
|
|
|
- heartbeatDivider,
|
|
|
- buildHeartbeatActions());
|
|
|
+ // 单条触发:3 个大按钮 + [地感 / 心跳 / 道闸] 三等分行
|
|
|
+ VBox singleCard = card("单条触发 · Single & Heartbeat", buildSingleActions());
|
|
|
right.getChildren().add(singleCard);
|
|
|
|
|
|
right.getChildren().add(card("批量入场 · Batch", buildBatchActions()));
|
|
|
|
|
|
- // 运行指标卡片(占满剩余空间,内含可滚动的最近发送列表)
|
|
|
+ // 运行指标卡片:占满剩余空间,内部最近发送 / 报文左右各占 1/2。
|
|
|
+ // minHeight 要保留一个底线,但不能太大——太大会把上方卡片挤出可视区。
|
|
|
+ // 实际显示高度由 vgrow 自动撑开,窗口越高它就越高。
|
|
|
TelemetryPanel tp = new TelemetryPanel(telemetry);
|
|
|
VBox.setVgrow(tp, Priority.ALWAYS);
|
|
|
VBox telemetryCard = card("运行 · Telemetry", tp);
|
|
|
+ telemetryCard.setMinHeight(280);
|
|
|
VBox.setVgrow(telemetryCard, Priority.ALWAYS);
|
|
|
right.getChildren().add(telemetryCard);
|
|
|
return right;
|
|
|
@@ -575,11 +570,6 @@ public class MainView extends BorderPane {
|
|
|
((Region) n).setMaxWidth(Double.MAX_VALUE);
|
|
|
}
|
|
|
|
|
|
- Button sendGeo = primaryButton("发送");
|
|
|
- Button sendGate = primaryButton("发送");
|
|
|
- HBox geoRow = trigRow(triggerGeo, sendGeo);
|
|
|
- HBox gateRow = trigRow(triggerGate, sendGate);
|
|
|
-
|
|
|
btnRandom.setOnAction(e -> {
|
|
|
String plate = sampleRandomPlate();
|
|
|
sendIvsResult(plate);
|
|
|
@@ -596,17 +586,81 @@ public class MainView extends BorderPane {
|
|
|
return;
|
|
|
}
|
|
|
sendIvsResult(plate);
|
|
|
- if (triggerGeo.isSelected()) sendGpioInPulse();
|
|
|
- if (triggerGate.isSelected()) sendGateOpened();
|
|
|
});
|
|
|
+
|
|
|
+ VBox v = new VBox(10, topRow, buildTriggersAndHeartbeatRow());
|
|
|
+ return v;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 三等分单行:[地感|发送] [心跳|间隔|启动] [道闸|发送]
|
|
|
+ * <ul>
|
|
|
+ * <li>每个 cell 占 1/3 宽度,title 左对齐、按钮右对齐</li>
|
|
|
+ * <li>地感 / 道闸 不再使用 CheckBox 联动,单独按钮直接触发对应消息</li>
|
|
|
+ * <li>心跳标题去掉英文 "Heartbeat",更紧凑</li>
|
|
|
+ * </ul>
|
|
|
+ */
|
|
|
+ private Node buildTriggersAndHeartbeatRow() {
|
|
|
+ Button sendGeo = primaryButton("发送");
|
|
|
+ sendGeo.setMinWidth(64);
|
|
|
+ sendGeo.setPrefWidth(64);
|
|
|
sendGeo.setOnAction(e -> sendGpioInPulse());
|
|
|
+
|
|
|
+ Button sendGate = primaryButton("发送");
|
|
|
+ sendGate.setMinWidth(64);
|
|
|
+ sendGate.setPrefWidth(64);
|
|
|
sendGate.setOnAction(e -> sendGateOpened());
|
|
|
|
|
|
- VBox v = new VBox(10, topRow, geoRow, gateRow);
|
|
|
- return v;
|
|
|
+ heartbeatBtn = dangerButton("启动");
|
|
|
+ heartbeatBtn.setOnAction(e -> toggleHeartbeat());
|
|
|
+ heartbeatBtn.setMinWidth(72);
|
|
|
+ heartbeatBtn.setPrefWidth(72);
|
|
|
+ activeChannel.addListener((o, a, b) -> refreshHeartbeatBtn());
|
|
|
+
|
|
|
+ Label hbUnit = new Label("s");
|
|
|
+ hbUnit.getStyleClass().add("field-label");
|
|
|
+
|
|
|
+ HBox geoCell = inlineCell(makeInlineTitle("地感"), sendGeo);
|
|
|
+ HBox gateCell = inlineCell(makeInlineTitle("道闸"), sendGate);
|
|
|
+ HBox hbCell = inlineCell(makeInlineTitle("心跳"),
|
|
|
+ heartbeatSec, hbUnit, heartbeatBtn);
|
|
|
+
|
|
|
+ // 等宽:每格 1/3
|
|
|
+ for (HBox cell : new HBox[]{geoCell, gateCell, hbCell}) {
|
|
|
+ HBox.setHgrow(cell, Priority.ALWAYS);
|
|
|
+ cell.setMaxWidth(Double.MAX_VALUE);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 顺序:地感(左) → 道闸(中) → 心跳(右)
|
|
|
+ HBox row = new HBox(10, geoCell, gateCell, hbCell);
|
|
|
+ row.setAlignment(Pos.CENTER_LEFT);
|
|
|
+ return row;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 一个 inline 单元:左侧标题 → spacer → 右侧 trailing 控件序列。
|
|
|
+ * 每个 cell 自带轻微背景 + 圆角边框({@code .inline-cell}),让三联行视觉上彼此分明。
|
|
|
+ */
|
|
|
+ private HBox inlineCell(Label title, Node... trailing) {
|
|
|
+ Region grow = new Region();
|
|
|
+ HBox.setHgrow(grow, Priority.ALWAYS);
|
|
|
+ HBox cell = new HBox(8);
|
|
|
+ cell.getStyleClass().add("inline-cell");
|
|
|
+ cell.getChildren().add(title);
|
|
|
+ cell.getChildren().add(grow);
|
|
|
+ for (Node n : trailing) cell.getChildren().add(n);
|
|
|
+ cell.setAlignment(Pos.CENTER_LEFT);
|
|
|
+ return cell;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Label makeInlineTitle(String text) {
|
|
|
+ Label l = new Label(text);
|
|
|
+ l.getStyleClass().add("inline-title");
|
|
|
+ return l;
|
|
|
}
|
|
|
|
|
|
private Node buildBatchActions() {
|
|
|
+ // 单行:[前缀 …] [数量 …] [间隔 … ms] ⟶ [▶ 开始]
|
|
|
Label l1 = new Label("数量");
|
|
|
l1.getStyleClass().add("field-label");
|
|
|
Label l2 = new Label("间隔");
|
|
|
@@ -616,43 +670,19 @@ public class MainView extends BorderPane {
|
|
|
Label l4 = new Label("前缀");
|
|
|
l4.getStyleClass().add("field-label");
|
|
|
|
|
|
- batchPrefix.setPrefColumnCount(6);
|
|
|
+ batchPrefix.setPrefColumnCount(5);
|
|
|
|
|
|
- HBox row1 = new HBox(8, l4, batchPrefix, l1, batchCount, l2, batchGapMs, l3);
|
|
|
- row1.setAlignment(Pos.CENTER_LEFT);
|
|
|
-
|
|
|
- batchBtn = primaryButton("▶ 开始批量入场");
|
|
|
- batchBtn.setMaxWidth(Double.MAX_VALUE);
|
|
|
+ batchBtn = primaryButton("▶ 开始");
|
|
|
+ batchBtn.setMinWidth(96);
|
|
|
+ batchBtn.setPrefWidth(96);
|
|
|
batchBtn.setOnAction(e -> toggleBatch());
|
|
|
|
|
|
- VBox v = new VBox(10, row1, batchBtn);
|
|
|
- return v;
|
|
|
- }
|
|
|
-
|
|
|
- private Node buildHeartbeatActions() {
|
|
|
- Label tag = new Label("心跳 · Heartbeat");
|
|
|
- tag.getStyleClass().add("subsection-title");
|
|
|
-
|
|
|
- Label l1 = new Label("间隔");
|
|
|
- l1.getStyleClass().add("field-label");
|
|
|
- Label l2 = new Label("s");
|
|
|
- l2.getStyleClass().add("field-label");
|
|
|
-
|
|
|
- heartbeatBtn = dangerButton("启动");
|
|
|
- heartbeatBtn.setOnAction(e -> toggleHeartbeat());
|
|
|
- heartbeatBtn.setPrefWidth(86);
|
|
|
-
|
|
|
- activeChannel.addListener((o, a, b) -> refreshHeartbeatBtn());
|
|
|
-
|
|
|
- HBox row = new HBox(8, l1, heartbeatSec, l2, spacer(), heartbeatBtn);
|
|
|
- row.setAlignment(Pos.CENTER_LEFT);
|
|
|
-
|
|
|
- return new VBox(6, tag, row);
|
|
|
- }
|
|
|
-
|
|
|
- private HBox trigRow(CheckBox cb, Button send) {
|
|
|
- send.setMinWidth(86);
|
|
|
- HBox row = new HBox(10, cb, spacer(), send);
|
|
|
+ HBox row = new HBox(8,
|
|
|
+ l4, batchPrefix,
|
|
|
+ l1, batchCount,
|
|
|
+ l2, batchGapMs, l3,
|
|
|
+ spacer(),
|
|
|
+ batchBtn);
|
|
|
row.setAlignment(Pos.CENTER_LEFT);
|
|
|
return row;
|
|
|
}
|
|
|
@@ -892,7 +922,7 @@ public class MainView extends BorderPane {
|
|
|
private void toggleBatch() {
|
|
|
if (batchSender.isRunning()) {
|
|
|
batchSender.stop();
|
|
|
- batchBtn.setText("▶ 开始批量入场");
|
|
|
+ batchBtn.setText("▶ 开始");
|
|
|
return;
|
|
|
}
|
|
|
if (!requireConnected()) return;
|
|
|
@@ -902,13 +932,13 @@ public class MainView extends BorderPane {
|
|
|
String prefix = batchPrefix.getText() == null ? "" : batchPrefix.getText().trim();
|
|
|
int colorCode = currentPlateColorCode();
|
|
|
|
|
|
- batchBtn.setText("■ 停止批量");
|
|
|
+ batchBtn.setText("■ 停止");
|
|
|
batchSender.start(ch, total, gap,
|
|
|
() -> randomPlateForBatch(prefix),
|
|
|
colorCode,
|
|
|
DeviceMessageBuilder.TRIGGER_TYPE_NORMAL_PASS,
|
|
|
msg -> Platform.runLater(() -> console.log("[" + ch.key().displayName + "] " + msg)),
|
|
|
- ok -> Platform.runLater(() -> batchBtn.setText("▶ 开始批量入场")));
|
|
|
+ ok -> Platform.runLater(() -> batchBtn.setText("▶ 开始")));
|
|
|
}
|
|
|
|
|
|
private int currentPlateColorCode() {
|