界面改用「精密仪器工作台」布局:顶栏压缩、视图切布局、标定与回放面板不再裁切

- What: 品牌条 40→28px 并加 实时监测/历史回放/标定 导航;导航改为切换主界面三套布局(实时=2D图像+曲线、回放=自动全程浏览、标定=标定面板 880px+曲线,2D 图像整体缩小);传感器选择(传感器 / 传感器配置 两个带 tooltip 的下拉 + 产品徽标)移到状态轨最左侧,记录模式移入设置区;新增 RecordStatusChip(记录中时长、丢帧、写入异常)与「同一时刻只有一个实心按钮」(新增 dangerOutline 描边危险样式);三轴读数移到曲线上方(16pt 等宽)、只保留最下方时间轴、网格淡化、全程浏览条可拖动;三轴配色统一为蓝/橙/绿并把语义色对比度审计扩展到 toolbar_bg;箭头页脚显示放大倍率与有效区域,深度模式才显示色条与范围;左侧面板改名「2D 图像」且固定显示图像宽高比;标定面板加纵向滚动区、九按钮分两行、六列表头 ResizeToContents;PlaybackDialog 补上从未调用的 retranslate;main() 在事件循环结束后 flush 日志并 os._exit,避免设备 SDK 原生线程把进程挂在解释器收尾。
- Why: 评审提出的 7 点:顶部占高与装饰过多、记录状态不突出、配置切换看不懂且藏得太深、切视图应换主界面布局、标定与回放面板文字被裁、关窗后命令行不退出、2D 图像无固定宽高比且名字不符。
- Verification: ui_check.py(430 项,新增 top area / instrument panels / clean shutdown 段,含中英×1280/1440 的轨不挤压检查)、test_ui_smoke.py(57 项,新增视图切换、标定不裁切、2D 宽高比、回放按钮文字、传感器选择器等用例)、drive_shared.py(146 项)、locale_studio.py(261 键齐全、zh==en、prefs 对齐)、reproduce.py、ui_snapshot.py 四张复核图。
- Not in this change: 录制文件格式、CSV/MP4 写入路径与记录通道数据流不变;三维面板仍是占位符;回放对话框(含离线深度重算)本身只修了按钮文字。
This commit is contained in:
ykcheungab
2026-09-13 18:54:31 +08:00
parent 0f4552cd4e
commit 35e86ec1f7
26 changed files with 2790 additions and 304 deletions
+75 -13
View File
@@ -9,6 +9,7 @@ Examples
python tools/ui_snapshot.py --theme dark --out out_dark.png
python tools/ui_snapshot.py --theme light --sensor finger \
--panels cal,3d --demo --size 1600x900 --out out_light.png
python tools/ui_snapshot.py --demo --scene recording --out out_recording.png
"""
from __future__ import annotations
@@ -34,6 +35,61 @@ if sys.platform == "win32" and "QT_QPA_FONTDIR" not in os.environ:
os.chdir(APP_ROOT)
def _run_scene(window, app, scene: str):
"""Drive the window into a scene that would otherwise need hardware.
``recording`` mocks the record channel's stats through the same single seam
the app reads them from (``_record_stats``), so nothing is written to disk
and no device is opened: the render is of the real widgets, not of a mockup.
"""
from ui_check import _fake_packet, _StuckWorker
stuck = _StuckWorker()
window._ensure_workers = lambda: None
window._box_worker = stuck
window._finger_worker = stuck
window.toolbar.btn_run.setChecked(True)
for _ in range(4):
app.processEvents()
if scene != "recording":
return
# Real packets so the phase reaches ``running`` and the rail holds real
# values; the recording itself is the only mocked part.
for index in range(300):
stuck.emit(_fake_packet(index))
window._on_run_tick()
for _ in range(4):
app.processEvents()
window._run_timer.stop()
from orisys_app_shared.record.experiment import RecordStats
from orisys_studio.ui import record_status
directory = "20240101T101500Z_session0001_a1b2c3d4e5f6"
window.toolbar.set_record_checked(True)
window.toolbar.set_record_status(directory)
window._record_stats = lambda: (
record_status.RECORDING,
RecordStats(
duration_s=12.4,
frames=372,
segments=1,
dropped_data=3,
bytes_written=48_000_000,
),
directory,
)
window._refresh_record_state()
# A visible window inside the session, like the mock's 60-65 s of 0-65 s.
first, last = window.session_store.extent()
if last > first:
window.force_plots.set_window(max(first, last - 6.0), last)
for _ in range(4):
app.processEvents()
def _demo_data(window) -> None:
"""Fill the plots, the calibration table and the arrow canvas."""
plots = window.force_plots
@@ -95,12 +151,22 @@ def main() -> int:
)
parser.add_argument("--size", default="1440x880", help="WxH window size")
parser.add_argument("--view", default="arrows_pure")
parser.add_argument(
"--preset",
choices=("custom", "live", "replay", "calibration"),
default="custom",
help="top-level view to render (the same layouts the brand rail switches)",
)
parser.add_argument("--demo", action="store_true", help="inject demo plot/table data")
parser.add_argument(
"--scene",
choices=("idle", "connecting"),
choices=("idle", "connecting", "recording"),
default="idle",
help="connecting renders the Stop/cancel state of a hung device open",
help=(
"connecting renders the Stop/cancel state of a hung device open; "
"recording renders the record strip of a running recording "
"(mocked stats: no file is written, no device is opened)"
),
)
parser.add_argument("--out", required=True, help="output PNG path")
args = parser.parse_args()
@@ -130,27 +196,23 @@ def main() -> int:
"show_finger_3d": "3d" in panels,
"show_fps": True,
"image_view_mode": args.view,
"view_preset": args.preset,
}
window = StudioMainWindow(prefs=prefs)
window.resize(width, height)
window.show()
app.processEvents()
if args.demo:
_demo_data(window)
for _ in range(4):
app.processEvents()
if args.scene == "connecting":
# A device open that never returns: the state the Stop button must
# survive. ``ui_check._StuckWorker`` drives the same window state.
from ui_check import _StuckWorker
_run_scene(window, app, "connecting")
stuck = _StuckWorker()
window._ensure_workers = lambda: None
window._box_worker = stuck
window._finger_worker = stuck
window.toolbar.btn_run.setChecked(True)
if args.scene == "recording":
_run_scene(window, app, "recording")
if args.demo:
_demo_data(window)
for _ in range(4):
app.processEvents()