move controller connect to popup dialog

This commit is contained in:
ykcheungab
2026-09-24 21:36:24 +08:00
parent 4dad413d29
commit 6effe74172
13 changed files with 773 additions and 99 deletions
+133 -55
View File
@@ -63,7 +63,10 @@ BRAND_H = 36
STRIP_H = 44
RUNBAR_H = 40
STATUS_H = 26
RAIL_LEFT = 330
#: Matches ``MAIN_SPLITTER_SIZES`` in the window: the rail narrowed when the two
#: start-up panels moved into the device-setup dialog, and the mockup has to show the
#: same proportions or it stops being a preview of anything.
RAIL_LEFT = 300
WORKSPACE_W = PAGE_W - RAIL_LEFT - 3 * MARGIN
PREVIEW_W, PREVIEW_H = 640, 340
@@ -110,6 +113,23 @@ STRINGS = {
"nodes": "节点数量",
"axes_count": "轴数量",
"busdata_group": "总线数据 (SDO / RTEX) ▶",
"setup_open": "设备设置",
"setup_title": "设备设置",
"setup_hint": "连接与总线初始化每次开机只需确认一次,因此收进这里;顶栏状态条持续显示结果,设置窗口可以一直开着。",
"setup_tabs": ["连接", "总线初始化"],
"setup_rows": [
["连接方式", "以太网 ▾"],
["IP", "192.168.0.11"],
["状态", "已连接"],
["BAS 文件", "…\\bas\\ECAT.bas"],
["初始化状态", "初始化成功"],
["总线类型", "EtherCAT"],
["节点数量", "5"],
["轴数量", "5"],
],
"setup_connect": "连接",
"setup_disconnect": "断开连接",
"setup_close": "关闭",
"tabs": ["轴调试", "测试序列", "手指调试", "传感器", "表面分布", "标定点"],
"readout_title": "轴状态",
"enabled": "已使能",
@@ -247,6 +267,23 @@ STRINGS = {
"nodes": "Nodes",
"axes_count": "Axes",
"busdata_group": "Bus data (SDO / RTEX) ▶",
"setup_open": "Device setup",
"setup_title": "Device setup",
"setup_hint": "Connecting and bringing the bus up are checked once per machine start-up, so they live here. The status strip keeps reporting the result.",
"setup_tabs": ["Connection", "Bus initialisation"],
"setup_rows": [
["Connection", "Ethernet ▾"],
["IP", "192.168.0.11"],
["State", "Connected"],
["BAS file", "…\\bas\\ECAT.bas"],
["Init state", "Initialised"],
["Bus type", "EtherCAT"],
["Nodes", "5"],
["Axes", "5"],
],
"setup_connect": "Connect",
"setup_disconnect": "Disconnect",
"setup_close": "Close",
"tabs": ["Axis debug", "Test sequence", "Finger debug", "Sensor", "Surface map", "Calibration points"],
"readout_title": "Axis state",
"enabled": "Enabled",
@@ -543,13 +580,16 @@ def draw_status_strip(painter: QPainter, palette: Palette, s: dict, top: int) ->
_text(
painter,
QRect(PAGE_W - 600, top, 380, STRIP_H),
QRect(PAGE_W - 600, top, 300, STRIP_H),
"上次动作: 已使能轴0 / last action: enable axis 0",
palette,
size=10,
muted=True,
align=Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter,
)
# The device-setup button, before the run-screen buttons: connection and bus
# initialisation live behind it now instead of in the rail.
_button(painter, QRect(PAGE_W - 316, top + 10, 100, 24), s["setup_open"], palette)
_button(painter, QRect(PAGE_W - 210, top + 10, 96, 24), s["pause"], palette)
_button(painter, QRect(PAGE_W - 106, top + 10, 92, 24), s["stop_all"], palette, danger=True)
@@ -565,53 +605,13 @@ def draw_status_bar(painter: QPainter, palette: Palette, s: dict, top: int) -> N
def draw_left_rail(painter: QPainter, palette: Palette, s: dict, rect: QRect) -> None:
conn = QRect(rect.x(), rect.y(), rect.width(), 210)
inner = _panel(painter, conn, palette)
_box_title(painter, conn, s["conn_title"], palette)
y = inner.y() + 6
_text(painter, QRect(inner.x(), y, 76, 22), s["conn_mode"], palette, size=10, muted=True)
_field(painter, QRect(inner.x() + 78, y, inner.width() - 78, 22), s["conn_mode_value"] + " ▾", palette)
y += 30
_text(painter, QRect(inner.x(), y, 30, 22), s["conn_ip"], palette, size=10, muted=True)
_field(painter, QRect(inner.x() + 32, y, 140, 22), "192.168.0.11", palette)
_button(painter, QRect(inner.x() + 180, y, 88, 22), s["scan"], palette)
y += 32
_button(painter, QRect(inner.x(), y, 104, 26), s["connect"], palette, primary=True)
_button(painter, QRect(inner.x() + 112, y, 108, 26), s["disconnect"], palette)
y += 36
_text(painter, QRect(inner.x(), y, inner.width(), 20), s["connected"], palette, size=10, muted=True)
y += 24
_text(painter, QRect(inner.x(), y, inner.width(), 20), "demo (simulated platform)", palette, size=9, muted=True)
"""The rail as it is now: only what is read while working.
bus = QRect(rect.x(), rect.y() + 222, rect.width(), 250)
inner = _panel(painter, bus, palette)
_box_title(painter, bus, s["bus_title"], palette)
y = inner.y() + 6
_text(painter, QRect(inner.x(), y, 60, 22), s["bus_file"], palette, size=10, muted=True)
_field(painter, QRect(inner.x() + 62, y, 140, 22), "…\\bas\\ECAT.bas", palette)
_button(painter, QRect(inner.x() + 208, y, 66, 22), s["choose"], palette)
y += 30
_text(painter, QRect(inner.x(), y, 74, 20), s["bus_state"], palette, size=10, muted=True)
_text(painter, QRect(inner.x() + 76, y, 180, 20), s["bus_ok"], palette, size=10)
y += 24
_text(painter, QRect(inner.x(), y, 74, 20), s["bus_type"], palette, size=10, muted=True)
_text(painter, QRect(inner.x() + 76, y, 180, 20), s["chip_bus"], palette, size=10)
y += 24
_text(painter, QRect(inner.x(), y, 90, 20), s["nodes"], palette, size=10, muted=True)
_text(painter, QRect(inner.x() + 92, y, 40, 20), "5", palette, size=10)
_text(painter, QRect(inner.x() + 146, y, 70, 20), s["axes_count"], palette, size=10, muted=True)
_text(painter, QRect(inner.x() + 218, y, 40, 20), "5", palette, size=10)
y += 32
_button(painter, QRect(inner.x(), y, 146, 26), s["download"], palette, primary=True)
_button(painter, QRect(inner.x() + 154, y, 104, 26), s["reinit"], palette)
group = QRect(rect.x(), rect.y() + 484, rect.width(), 32)
_panel(painter, group.adjusted(0, 0, 0, -8), palette)
_text(painter, QRect(group.x() + 12, group.y(), group.width() - 24, 30), s["busdata_group"], palette, size=10, muted=True)
# The axis readout lives in this rail now, not in a second one on the right:
# two thin rails cost the workspace 240 px at every window size.
readout = QRect(rect.x(), rect.y() + 526, rect.width(), 268)
Connection and bus initialisation are in the device-setup dialog, so the rail
starts with the axis readout instead of burying it under ~460 px of start-up
controls.
"""
readout = QRect(rect.x(), rect.y(), rect.width(), 268)
inner = _panel(painter, readout, palette)
_box_title(painter, readout, s["readout_title"], palette)
y = inner.y() + 6
@@ -631,6 +631,54 @@ def draw_left_rail(painter: QPainter, palette: Palette, s: dict, rect: QRect) ->
_button(painter, QRect(inner.x(), y + 40, inner.width(), 28), s["stop_all"], palette, danger=True)
_button(painter, QRect(inner.x(), y + 76, inner.width(), 28), s["clear_alarms"], palette)
group = QRect(rect.x(), rect.y() + 280, rect.width(), 32)
_panel(painter, group.adjusted(0, 0, 0, -8), palette)
_text(painter, QRect(group.x() + 12, group.y(), group.width() - 24, 30), s["busdata_group"], palette, size=10, muted=True)
def draw_setup_dialog(painter: QPainter, palette: Palette, s: dict, rect: QRect) -> None:
"""The device-setup dialog: connection on one tab, bus initialisation on the other.
Drawn as its own panel so the proposal can be reviewed in the same image as the
page it came out of.
"""
dialog = QRect(rect.x(), rect.y(), rect.width(), 400)
inner = _panel(painter, dialog, palette)
_box_title(painter, dialog, s["setup_title"], palette)
_text(
painter,
QRect(inner.x(), inner.y(), inner.width(), 34),
s["setup_hint"],
palette,
size=9,
muted=True,
)
tabs = QRect(inner.x(), inner.y() + 38, inner.width(), inner.height() - 74)
tab_w = 130
for index, label in enumerate(s["setup_tabs"]):
tab = QRect(tabs.x() + index * tab_w, tabs.y(), tab_w, 26)
active = index == 0
painter.fillRect(tab, palette.window if active else palette.panel)
painter.setPen(QPen(palette.border, 1))
painter.drawRect(tab.adjusted(0, 0, -1, -1))
if active:
painter.setPen(QPen(palette.accent, 2))
painter.drawLine(tab.bottomLeft(), tab.bottomRight())
_text(painter, tab, label, palette, size=10, bold=active, align=Qt.AlignmentFlag.AlignCenter)
# The rows are both tabs' contents stacked, in order: the connection fields and
# the bus readouts, which is what the two tabs hold between them.
body = _panel(painter, tabs.adjusted(0, 26, 0, 0), palette)
y = body.y() + 6
for label, value in s["setup_rows"]:
_text(painter, QRect(body.x(), y, 96, 22), label, palette, size=10, muted=True)
_text(painter, QRect(body.x() + 104, y, body.width() - 104, 22), value, palette, size=10)
y += 24
_button(painter, QRect(body.x(), y + 6, 110, 26), s["setup_connect"], palette, primary=True)
_button(painter, QRect(body.x() + 118, y + 6, 118, 26), s["setup_disconnect"], palette)
_button(painter, QRect(dialog.right() - 110, dialog.bottom() - 34, 96, 26), s["setup_close"], palette)
def draw_tabs(painter: QPainter, palette: Palette, s: dict, workspace: QRect, active: str) -> QRect:
tab_h = 30
@@ -1256,6 +1304,22 @@ TAB_PAINTERS = {
#: Tab index -> painter key, for the tab strip labels.
TAB_KEYS = ("axes", "sequence", "finger", "sensor", "surface", "points")
#: Pages that are not workspace tabs. The device setup is a dialog, so it is drawn as
#: a page of its own: it has to be reviewable like everything else.
EXTRA_PAGES = ("setup",)
def draw_setup_page(painter: QPainter, palette: Palette, s: dict, rect: QRect) -> None:
"""The device-setup dialog over the page it was opened from."""
painter.fillRect(rect, palette.window)
draw_page(painter, palette, s, rect, active="axes", with_chrome=True, with_rail=True)
draw_setup_dialog(
painter,
palette,
s,
QRect(rect.center().x() - 300, rect.center().y() - 200, 600, 400),
)
def draw_page(
painter: QPainter,
@@ -1265,15 +1329,21 @@ def draw_page(
*,
active: str = "axes",
with_chrome: bool = True,
with_rail: bool = True,
) -> None:
"""The whole page: chrome, strip, run bar, rail, workspace, status bar."""
"""The whole page: chrome, strip, run bar, rail, workspace, status bar.
``with_rail`` only exists for the setup page, which draws this page as the
background behind the dialog and does not want to draw the rail twice.
"""
painter.fillRect(rect, palette.window)
body_top = rect.y() + MENU_H + BRAND_H + STRIP_H + RUNBAR_H
body_h = rect.height() - (MENU_H + BRAND_H + STRIP_H + RUNBAR_H) - STATUS_H
draw_left_rail(
painter, palette, s, QRect(rect.x() + MARGIN, body_top + MARGIN, RAIL_LEFT, body_h - 2 * MARGIN)
)
if with_rail:
draw_left_rail(
painter, palette, s, QRect(rect.x() + MARGIN, body_top + MARGIN, RAIL_LEFT, body_h - 2 * MARGIN)
)
workspace = QRect(
rect.x() + 2 * MARGIN + RAIL_LEFT,
@@ -1294,6 +1364,14 @@ def draw_page(
draw_status_bar(painter, palette, s, rect.bottom() - STATUS_H)
def draw_full_page(painter: QPainter, palette: Palette, s: dict, rect: QRect, key: str) -> None:
"""One page of the proposal, by key — a workspace tab or the setup dialog."""
if key in EXTRA_PAGES:
draw_setup_page(painter, palette, s, rect)
return
draw_page(painter, palette, s, rect, active=key)
def draw_run_bar(painter: QPainter, palette: Palette, s: dict, top: int) -> None:
"""The run strip: phase, progress and the three controls."""
painter.fillRect(QRect(0, top, PAGE_W, RUNBAR_H), palette.window)
@@ -1356,7 +1434,7 @@ def main() -> int:
default=TAB_KEYS[0],
# Derived, not listed: a literal here silently rejects any tab added to
# TAB_KEYS, which is how the finger page first went missing.
choices=(*TAB_KEYS, "all"),
choices=(*TAB_KEYS, *EXTRA_PAGES, "all"),
help="which page to draw; 'all' writes one full-size image per page",
)
args = parser.parse_args()
@@ -1367,7 +1445,7 @@ def main() -> int:
base = Path(args.out).resolve()
base.parent.mkdir(parents=True, exist_ok=True)
pages = TAB_KEYS if args.page == "all" else (args.page,)
pages = (*TAB_KEYS, *EXTRA_PAGES) if args.page == "all" else (args.page,)
failures = 0
for key in pages:
target = base if args.page != "all" else base.with_name(f"{base.stem}_{key}{base.suffix or '.png'}")
@@ -1376,7 +1454,7 @@ def main() -> int:
painter = QPainter(pixmap)
painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
try:
draw_page(painter, palette, s, QRect(0, 0, args.width, args.height), active=key)
draw_full_page(painter, palette, s, QRect(0, 0, args.width, args.height), key)
finally:
painter.end()
if pixmap.save(str(target)):