🐛 fix: sub-step default expanded, real-time checkbox, startup freeze, color sync
1. SubStepFrame defaults to expanded (▼), not collapsed 2. erase_all checkbox has command=_on_erase_toggle → immediate config update 3. _deferred_init runs _check_vitis/_refresh_ports in background thread Fixes UI freeze during startup (subprocess.run for tool versions) 4. Sub-step colors now match StepHeader: - pending: gray ACCENT_PENDING (#888888) - running: blue CIRCLE_RUNNING (#0078D4) with pulse animation - complete: green ACCENT_COMPLETE (#107C10) - error: red ACCENT_ERROR (#D13438) - Running sub-step pulses between light/dark blue like StepHeader
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
bootloader_bin_path: /home/ly0kos/work/Verify/FW/tftp_app/bootloader/bootloader_z100_800M.bin
|
bootloader_bin_path: /home/ly0kos/work/Verify/FW/tftp_app/bootloader/bootloader_z100_800M.bin
|
||||||
bootloader_bit_path: /home/ly0kos/work/Verify/FW/tftp_app/bootloader/pl_arm_wrapper_hw_platform_0/pl_arm_wrapper.bit
|
bootloader_bit_path: /home/ly0kos/work/Verify/FW/tftp_app/bootloader/pl_arm_wrapper_hw_platform_0/pl_arm_wrapper.bit
|
||||||
bootloader_elf_path: /home/ly0kos/work/Verify/FW/tftp_app/bootloader/app.elf
|
bootloader_elf_path: /home/ly0kos/work/Verify/FW/tftp_app/bootloader/app.elf
|
||||||
erase_all: true
|
erase_all: false
|
||||||
firmware_bin_path: /home/ly0kos/work/Verify/FW/V1.3.1.3.3_06_01_pmu_cpld_check/V1.3.1.3.3_06_01_pmu_cpld_check.bin
|
firmware_bin_path: /home/ly0kos/work/Verify/FW/V1.3.1.3.3_06_01_pmu_cpld_check/V1.3.1.3.3_06_01_pmu_cpld_check.bin
|
||||||
flash_model: s25fl256s1
|
flash_model: s25fl256s1
|
||||||
flash_type: qspi-x4-single
|
flash_type: qspi-x4-single
|
||||||
|
|||||||
@@ -97,9 +97,11 @@ class MainWindow(ctk.CTk):
|
|||||||
self.after(100, self._deferred_init)
|
self.after(100, self._deferred_init)
|
||||||
|
|
||||||
def _deferred_init(self) -> None:
|
def _deferred_init(self) -> None:
|
||||||
"""Run blocking operations after the window is shown."""
|
"""Run blocking operations in background thread after window shown."""
|
||||||
|
def _bg_init():
|
||||||
self._check_vitis()
|
self._check_vitis()
|
||||||
self._refresh_ports_initial()
|
self._refresh_ports_initial()
|
||||||
|
threading.Thread(target=_bg_init, daemon=True).start()
|
||||||
|
|
||||||
# ── Config ─────────────────────────────────────────────────
|
# ── Config ─────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -212,6 +214,11 @@ class MainWindow(ctk.CTk):
|
|||||||
self._log_message(f" ✗ Save failed: {e}")
|
self._log_message(f" ✗ Save failed: {e}")
|
||||||
self._status.set_status(f"Save failed: {e}", "error")
|
self._status.set_status(f"Save failed: {e}", "error")
|
||||||
|
|
||||||
|
def _on_erase_toggle(self) -> None:
|
||||||
|
"""Update config.erase_all immediately when checkbox toggles."""
|
||||||
|
if self._config:
|
||||||
|
self._config.erase_all = self._erase_cb_var.get()
|
||||||
|
|
||||||
# ── UI Construction ────────────────────────────────────────
|
# ── UI Construction ────────────────────────────────────────
|
||||||
|
|
||||||
def _build_ui(self) -> None:
|
def _build_ui(self) -> None:
|
||||||
@@ -485,6 +492,7 @@ class MainWindow(ctk.CTk):
|
|||||||
text="整片Flash擦除 (Erase entire flash)",
|
text="整片Flash擦除 (Erase entire flash)",
|
||||||
variable=self._erase_cb_var,
|
variable=self._erase_cb_var,
|
||||||
font=FONT_SMALL,
|
font=FONT_SMALL,
|
||||||
|
command=self._on_erase_toggle,
|
||||||
)
|
)
|
||||||
self._erase_cb.grid(row=0, column=0, sticky="w", padx=PADDING_SMALL)
|
self._erase_cb.grid(row=0, column=0, sticky="w", padx=PADDING_SMALL)
|
||||||
|
|
||||||
|
|||||||
+61
-22
@@ -637,17 +637,16 @@ class LogDisplay(ctk.CTkFrame):
|
|||||||
class SubStepFrame(ctk.CTkFrame):
|
class SubStepFrame(ctk.CTkFrame):
|
||||||
"""Collapsible frame showing flash operation sub-steps with status.
|
"""Collapsible frame showing flash operation sub-steps with status.
|
||||||
|
|
||||||
Three sub-steps: Erase → Program → Verify.
|
Colors match StepHeader: gray pending, blue running (+pulse),
|
||||||
Simple white-text dots and labels.
|
green complete, red error. Default expanded.
|
||||||
Collapsed by default.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, parent: ctk.CTkFrame) -> None:
|
def __init__(self, parent: ctk.CTkFrame) -> None:
|
||||||
super().__init__(parent, fg_color="transparent")
|
super().__init__(parent, fg_color="transparent")
|
||||||
|
|
||||||
self._collapsed = True
|
self._collapsed = False
|
||||||
self._toggle_btn = ctk.CTkButton(
|
self._toggle_btn = ctk.CTkButton(
|
||||||
self, text="▶", width=22, height=22,
|
self, text="▼", width=22, height=22,
|
||||||
font=(FONT_SMALL[0], 9), command=self._toggle,
|
font=(FONT_SMALL[0], 9), command=self._toggle,
|
||||||
)
|
)
|
||||||
self._toggle_btn.grid(row=0, column=0, padx=(4, 4), pady=(2, 2), sticky="w")
|
self._toggle_btn.grid(row=0, column=0, padx=(4, 4), pady=(2, 2), sticky="w")
|
||||||
@@ -661,18 +660,16 @@ class SubStepFrame(ctk.CTkFrame):
|
|||||||
self._sub_items: list[dict] = []
|
self._sub_items: list[dict] = []
|
||||||
names = ["Erase ", "Program", "Verify "]
|
names = ["Erase ", "Program", "Verify "]
|
||||||
for i, name in enumerate(names):
|
for i, name in enumerate(names):
|
||||||
dot = ctk.CTkLabel(self, text="○", font=FONT_BODY, anchor="w")
|
dot = ctk.CTkLabel(self, text="○", font=FONT_BODY, text_color=ACCENT_PENDING, anchor="w")
|
||||||
label = ctk.CTkLabel(self, text=name, font=FONT_BODY, anchor="w")
|
label = ctk.CTkLabel(self, text=name, font=FONT_BODY, text_color=ACCENT_PENDING, anchor="w")
|
||||||
pct = ctk.CTkLabel(self, text="", font=FONT_BODY, anchor="e")
|
pct = ctk.CTkLabel(self, text="", font=FONT_BODY, text_color=ACCENT_PENDING, anchor="e")
|
||||||
dot.grid(row=i + 1, column=0, padx=(16, 2), pady=(2, 2), sticky="w")
|
dot.grid(row=i + 1, column=0, padx=(16, 2), pady=(2, 2), sticky="w")
|
||||||
label.grid(row=i + 1, column=1, padx=(2, 4), pady=(2, 2), sticky="w")
|
label.grid(row=i + 1, column=1, padx=(2, 4), pady=(2, 2), sticky="w")
|
||||||
pct.grid(row=i + 1, column=2, padx=(4, 8), pady=(2, 2), sticky="e")
|
pct.grid(row=i + 1, column=2, padx=(4, 8), pady=(2, 2), sticky="e")
|
||||||
dot.grid_remove()
|
|
||||||
label.grid_remove()
|
|
||||||
pct.grid_remove()
|
|
||||||
self._sub_items.append({"dot": dot, "label": label, "pct": pct})
|
self._sub_items.append({"dot": dot, "label": label, "pct": pct})
|
||||||
|
|
||||||
self.grid_columnconfigure(1, weight=1)
|
self.grid_columnconfigure(1, weight=1)
|
||||||
|
self._pulse_job: str | None = None
|
||||||
|
|
||||||
def _toggle(self) -> None:
|
def _toggle(self) -> None:
|
||||||
self._collapsed = not self._collapsed
|
self._collapsed = not self._collapsed
|
||||||
@@ -692,33 +689,75 @@ class SubStepFrame(ctk.CTkFrame):
|
|||||||
self._toggle()
|
self._toggle()
|
||||||
|
|
||||||
def set_phase(self, phase: str, pct: int = -1) -> None:
|
def set_phase(self, phase: str, pct: int = -1) -> None:
|
||||||
|
_C_PENDING = ACCENT_PENDING
|
||||||
|
_C_RUNNING = CIRCLE_RUNNING
|
||||||
|
_C_DONE = ACCENT_COMPLETE
|
||||||
|
|
||||||
idx = {"erase": 0, "program": 1, "verify": 2}.get(phase, -1)
|
idx = {"erase": 0, "program": 1, "verify": 2}.get(phase, -1)
|
||||||
if idx < 0:
|
if idx < 0:
|
||||||
if phase == "done":
|
if phase == "done":
|
||||||
|
self._stop_pulse()
|
||||||
for i in self._sub_items:
|
for i in self._sub_items:
|
||||||
i["dot"].configure(text="●")
|
i["dot"].configure(text="●", text_color=_C_DONE)
|
||||||
i["pct"].configure(text="OK")
|
i["label"].configure(text_color=_C_DONE)
|
||||||
|
i["pct"].configure(text="OK", text_color=_C_DONE)
|
||||||
elif phase == "error":
|
elif phase == "error":
|
||||||
|
self._stop_pulse()
|
||||||
for i in self._sub_items:
|
for i in self._sub_items:
|
||||||
i["dot"].configure(text="✗")
|
i["dot"].configure(text="✗", text_color=ACCENT_ERROR)
|
||||||
|
i["label"].configure(text_color=ACCENT_ERROR)
|
||||||
|
i["pct"].configure(text="", text_color=ACCENT_ERROR)
|
||||||
return
|
return
|
||||||
|
|
||||||
pct_text = f"{pct}%" if pct >= 0 else ""
|
pct_text = f"{pct}%" if pct >= 0 else ""
|
||||||
for i, item in enumerate(self._sub_items):
|
for i, item in enumerate(self._sub_items):
|
||||||
if i < idx:
|
if i < idx:
|
||||||
item["dot"].configure(text="●")
|
item["dot"].configure(text="●", text_color=_C_DONE)
|
||||||
item["pct"].configure(text="OK")
|
item["label"].configure(text_color=_C_DONE)
|
||||||
|
item["pct"].configure(text="OK", text_color=_C_DONE)
|
||||||
elif i == idx:
|
elif i == idx:
|
||||||
item["dot"].configure(text="◉")
|
item["dot"].configure(text="◉", text_color=_C_RUNNING)
|
||||||
item["pct"].configure(text=pct_text)
|
item["label"].configure(text_color=_C_RUNNING)
|
||||||
|
item["pct"].configure(text=pct_text, text_color=_C_RUNNING)
|
||||||
else:
|
else:
|
||||||
item["dot"].configure(text="○")
|
item["dot"].configure(text="○", text_color=_C_PENDING)
|
||||||
item["pct"].configure(text="")
|
item["label"].configure(text_color=_C_PENDING)
|
||||||
|
item["pct"].configure(text="", text_color=_C_PENDING)
|
||||||
|
|
||||||
|
if phase in ("erase", "program", "verify") and not self._pulse_job:
|
||||||
|
self._start_pulse(idx)
|
||||||
|
|
||||||
|
def _start_pulse(self, idx: int) -> None:
|
||||||
|
"""Start pulsing the running sub-step to match StepHeader animation."""
|
||||||
|
_LIGHT = RUNNING_FADE_LIGHT
|
||||||
|
_DARK = RUNNING_FADE_DARK
|
||||||
|
|
||||||
|
def _pulse(state=[True]):
|
||||||
|
if idx >= len(self._sub_items):
|
||||||
|
return
|
||||||
|
item = self._sub_items[idx]
|
||||||
|
c = _LIGHT if state[0] else _DARK
|
||||||
|
item["dot"].configure(text_color=c)
|
||||||
|
item["label"].configure(text_color=c)
|
||||||
|
item["pct"].configure(text_color=c)
|
||||||
|
state[0] = not state[0]
|
||||||
|
self._pulse_job = self.after(PULSE_DURATION, lambda: _pulse(state))
|
||||||
|
|
||||||
|
_pulse()
|
||||||
|
|
||||||
|
def _stop_pulse(self) -> None:
|
||||||
|
if self._pulse_job:
|
||||||
|
self.after_cancel(self._pulse_job)
|
||||||
|
self._pulse_job = None
|
||||||
|
|
||||||
def reset(self) -> None:
|
def reset(self) -> None:
|
||||||
|
self._stop_pulse()
|
||||||
|
_C = ACCENT_PENDING
|
||||||
for i in self._sub_items:
|
for i in self._sub_items:
|
||||||
i["dot"].configure(text="○")
|
i["dot"].configure(text="○", text_color=_C)
|
||||||
i["pct"].configure(text="")
|
i["label"].configure(text_color=_C)
|
||||||
|
i["pct"].configure(text="", text_color=_C)
|
||||||
if not self._collapsed:
|
if not self._collapsed:
|
||||||
self._toggle()
|
self._toggle()
|
||||||
|
self._toggle() # collapse then expand to reset
|
||||||
self._toggle()
|
self._toggle()
|
||||||
|
|||||||
Reference in New Issue
Block a user