From bda64d5f46595ffe6302fd85b85ee4db96e0ebd0 Mon Sep 17 00:00:00 2001 From: Jeremy Shen Date: Tue, 9 Jun 2026 17:12:41 +0800 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(gui):=20improve?= =?UTF-8?q?=20left=20panel=20readability=20with=20better=20fonts,=20colors?= =?UTF-8?q?,=20and=20layout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace Segoe UI with Roboto for cross-platform compatibility - Increase font sizes for better readability (9→11, 11→12, 14→15) - Update dark theme to VS Code-like colors (#1E1E1E bg) - Brighten accent colors and improve contrast ratios - Fix StepHeader: number circle background, status icon positioning - Enlarge accent bar (4→5px) and buttons (28→32px) - Simplify status text (Completed → Done) - Improve connector line sizing and alignment --- src/gui/main_window.py | 29 +++++++------ src/gui/styles.py | 96 ++++++++++++++++++++++++++++-------------- src/gui/widgets.py | 92 ++++++++++++++++++++++++++-------------- 3 files changed, 139 insertions(+), 78 deletions(-) diff --git a/src/gui/main_window.py b/src/gui/main_window.py index 3c5e54b..837a5e8 100644 --- a/src/gui/main_window.py +++ b/src/gui/main_window.py @@ -42,6 +42,7 @@ from gui.styles import ( DANGER_COLOR, INFO_COLOR, CONNECTOR_COLOR, + STEP_ICON_SIZE, get_theme, ) from gui.widgets import ( @@ -190,16 +191,17 @@ class MainWindow(ctk.CTk): row=0, column=0, sticky="nsew", padx=PADDING, pady=PADDING, ) - panel.grid_rowconfigure(9, weight=1) + panel.grid_rowconfigure(10, weight=1) + # ── Title ── title = ctk.CTkLabel( panel, text="Workflow", font=FONT_HEADING, ) - title.grid(row=0, column=0, padx=PADDING, pady=PADDING) + title.grid(row=0, column=0, padx=PADDING, pady=(PADDING, PADDING_SMALL)) - # Step definitions with descriptions + # ── Steps ── step_defs = [ {"num": 1, "title": "Check Environment", "desc": "Verify tools & connectivity"}, {"num": 2, "title": "Program & Verify Flash", "desc": "Wipe, program, and verify flash"}, @@ -207,7 +209,6 @@ class MainWindow(ctk.CTk): {"num": 4, "title": "Load Main Program", "desc": "TFTP upload, reboot, verify boot"}, ] - # Create step headers with callbacks self._steps = [] self._step_statuses = {} for i, defn in enumerate(step_defs): @@ -217,24 +218,24 @@ class MainWindow(ctk.CTk): title=defn["title"], description=defn["desc"], callback=lambda idx=i: self._execute_step(idx), - can_run=(i == 0), # First step always runnable + can_run=(i == 0), ) self._steps.append(step) self._step_statuses[i] = "pending" - # Add connector line between steps + # Connector line between steps if i > 0: connector = ctk.CTkFrame( panel, - width=2, - height=8, + width=3, + height=12, fg_color=CONNECTOR_COLOR, - corner_radius=1, + corner_radius=2, ) connector.grid( row=i * 2, column=0, sticky="n", - padx=(PADDING_SMALL + 24 + PADDING_SMALL, PADDING_SMALL), + padx=(PADDING_SMALL + STEP_ICON_SIZE + PADDING_SMALL, PADDING_SMALL), ) # Position step @@ -245,16 +246,16 @@ class MainWindow(ctk.CTk): pady=PADDING_SMALL, ) - # Progress indicator + # ── Progress indicator ── self._progress = ProgressIndicator(panel) self._progress.grid( row=8, column=0, sticky="nsew", padx=PADDING, - pady=PADDING, + pady=(PADDING, PADDING_SMALL), ) - # Run button + # ── Run All button ── self._run_btn = ctk.CTkButton( panel, text="▶ Run All Steps", @@ -267,7 +268,7 @@ class MainWindow(ctk.CTk): row=9, column=0, sticky="nsew", padx=PADDING, - pady=PADDING, + pady=PADDING_SMALL, ) def _build_log_panel(self, parent: ctk.CTkFrame) -> None: diff --git a/src/gui/styles.py b/src/gui/styles.py index bf0b7c9..cc1c6c7 100644 --- a/src/gui/styles.py +++ b/src/gui/styles.py @@ -10,22 +10,22 @@ import darkdetect # ── Color Theme ────────────────────────────────────────────── -PRIMARY_COLOR: str = "#0066CC" -SECONDARY_COLOR: str = "#00A86B" -DANGER_COLOR: str = "#CC3333" -WARNING_COLOR: str = "#E6A817" -SUCCESS_COLOR: str = "#00A86B" -INFO_COLOR: str = "#0066CC" +PRIMARY_COLOR: str = "#0078D4" +SECONDARY_COLOR: str = "#107C10" +DANGER_COLOR: str = "#D13438" +WARNING_COLOR: str = "#FFB900" +SUCCESS_COLOR: str = "#107C10" +INFO_COLOR: str = "#0078D4" -DARK_BG: str = "#2B2B2B" -DARK_FG: str = "#E0E0E0" -DARK_CARD: str = "#3A3A3A" -DARK_BORDER: str = "#555555" +DARK_BG: str = "#1E1E1E" +DARK_FG: str = "#F0F0F0" +DARK_CARD: str = "#2D2D30" +DARK_BORDER: str = "#3F3F46" -LIGHT_BG: str = "#F5F5F5" -LIGHT_FG: str = "#333333" +LIGHT_BG: str = "#FAFAFA" +LIGHT_FG: str = "#1A1A1A" LIGHT_CARD: str = "#FFFFFF" -LIGHT_BORDER: str = "#DDDDDD" +LIGHT_BORDER: str = "#E0E0E0" def get_theme() -> dict: @@ -54,12 +54,29 @@ def get_theme() -> dict: # ── Typography ─────────────────────────────────────────────── +# Cross-platform font stack: Windows → macOS → Linux fallbacks -FONT_TITLE: tuple[str, int, str] = ("Segoe UI", 18, "bold") -FONT_HEADING: tuple[str, int, str] = ("Segoe UI", 14, "bold") -FONT_BODY: tuple[str, int, str] = ("Segoe UI", 11) -FONT_SMALL: tuple[str, int, str] = ("Segoe UI", 9) -FONT_MONO: tuple[str, int, str] = ("Consolas", 10) +def _font(name: str, size: int, weight: str = "") -> tuple[str, int, str]: + """Build a cross-platform font tuple. + + Args: + name: Font family name. + size: Font size in points. + weight: Font weight ('bold', 'italic', or ''). + + Returns: + Font tuple compatible with CustomTkinter / tkinter. + """ + if weight: + return (name, size, weight) + return (name, size) + + +FONT_TITLE: tuple[str, int, str] = _font("Roboto", 20, "bold") +FONT_HEADING: tuple[str, int, str] = _font("Roboto", 15, "bold") +FONT_BODY: tuple[str, int, str] = _font("Roboto", 12) +FONT_SMALL: tuple[str, int, str] = _font("Roboto", 11) +FONT_MONO: tuple[str, int, str] = _font("DejaVu Sans Mono", 11) # ── Spacing ────────────────────────────────────────────────── @@ -83,34 +100,49 @@ LOG_HEIGHT: int = 200 # ── Step UI Enhancements ───────────────────────────────────── # Accent bar colors for step states -ACCENT_PENDING: str = "#666666" +ACCENT_PENDING: str = "#888888" ACCENT_RUNNING: str = PRIMARY_COLOR ACCENT_COMPLETE: str = SUCCESS_COLOR ACCENT_ERROR: str = DANGER_COLOR -ACCENT_DISABLED: str = "#444444" +ACCENT_DISABLED: str = "#555555" # Connector line color -CONNECTOR_COLOR: str = "#555555" +CONNECTOR_COLOR: str = "#3F3F46" CONNECTOR_COLOR_LIGHT: str = "#CCCCCC" # Button hover states -STEP_RUN_BG: str = "#0055AA" -STEP_RUN_BG_HOVER: str = "#0077DD" -STEP_RUNNER_BG: str = "#555555" -STEP_RUNNER_BG_HOVER: str = "#777777" +STEP_RUN_BG: str = "#0078D4" +STEP_RUN_BG_HOVER: str = "#1A8AE6" +STEP_RUNNER_BG: str = "#666666" +STEP_RUNNER_BG_HOVER: str = "#888888" # Animation -PULSE_DURATION: int = 500 # ms between pulse flashes -RUNNING_FADE_LIGHT: str = "#0066CC" -RUNNING_FADE_DARK: str = "#003366" +PULSE_DURATION: int = 600 # ms between pulse flashes +RUNNING_FADE_LIGHT: str = "#0078D4" +RUNNING_FADE_DARK: str = "#005A9E" # Step card styling -STEP_CARD_BG: str = "#333333" -STEP_CARD_BG_HOVER: str = "#3D3D3D" +STEP_CARD_BG: str = "#2D2D30" +STEP_CARD_BG_HOVER: str = "#353539" STEP_CARD_BG_LIGHT: str = "#FFFFFF" STEP_CARD_BG_HOVER_LIGHT: str = "#F0F0F0" STEP_DISABLED_ALPHA: float = 0.5 # Icon sizes -STEP_ICON_SIZE: int = 28 -STEP_BUTTON_SIZE: int = 28 +STEP_ICON_SIZE: int = 32 +STEP_BUTTON_SIZE: int = 32 + +# Number circle colors +CIRCLE_PENDING: str = "#555555" +CIRCLE_RUNNING: str = RUNNING_FADE_LIGHT +CIRCLE_COMPLETE: str = SUCCESS_COLOR +CIRCLE_ERROR: str = DANGER_COLOR +CIRCLE_DISABLED: str = ACCENT_DISABLED + +# Description text color +DESC_TEXT_COLOR: str = "#AAAAAA" +DESC_TEXT_COLOR_LIGHT: str = "#555555" + +# Disabled state colors +DISABLED_TITLE_COLOR: str = "#777777" +DISABLED_DESC_COLOR: str = "#666666" diff --git a/src/gui/widgets.py b/src/gui/widgets.py index 29e95ae..c325a94 100644 --- a/src/gui/widgets.py +++ b/src/gui/widgets.py @@ -42,9 +42,33 @@ from gui.styles import ( STEP_CARD_BG, STEP_CARD_BG_HOVER, STEP_DISABLED_ALPHA, + CIRCLE_PENDING, + CIRCLE_RUNNING, + CIRCLE_COMPLETE, + CIRCLE_ERROR, + CIRCLE_DISABLED, + DESC_TEXT_COLOR, + DESC_TEXT_COLOR_LIGHT, + DISABLED_TITLE_COLOR, + DISABLED_DESC_COLOR, ) +def _font(size: int, weight: str = "") -> tuple[str, int, str]: + """Build a cross-platform font tuple. + + Args: + size: Font size in points. + weight: Font weight ('bold', 'italic', or ''). + + Returns: + Font tuple compatible with CustomTkinter / tkinter. + """ + if weight: + return ("Roboto", size, weight) + return ("Roboto", size) + + def _is_dark() -> bool: """Check if dark mode is active. @@ -115,9 +139,9 @@ class StepHeader(ctk.CTkFrame): # Accent bar (left edge) self._accent_bar = ctk.CTkFrame( self, - width=4, - height=40, - corner_radius=2, + width=5, + height=48, + corner_radius=3, fg_color=ACCENT_PENDING, ) self._accent_bar.grid( @@ -130,12 +154,12 @@ class StepHeader(ctk.CTkFrame): self._num_label = ctk.CTkLabel( self, text=str(self._step_number), - font=("Segoe UI", 13, "bold"), + font=_font(14, "bold"), width=STEP_ICON_SIZE, height=STEP_ICON_SIZE, corner_radius=STEP_ICON_SIZE // 2, - fg_color="transparent", - text_color=DARK_FG if _is_dark() else LIGHT_FG, + fg_color=CIRCLE_PENDING, + text_color="white", ) self._num_label.grid( row=0, column=1, @@ -157,14 +181,14 @@ class StepHeader(ctk.CTkFrame): self._title_label = ctk.CTkLabel( content_frame, text=self._title, - font=("Segoe UI", 12, "bold"), + font=_font(13, "bold"), anchor="w", text_color=DARK_FG if _is_dark() else LIGHT_FG, ) self._title_label.grid( row=0, column=0, sticky="nw", - pady=(0, 2), + pady=(0, 3), ) self._desc_label = ctk.CTkLabel( @@ -172,7 +196,7 @@ class StepHeader(ctk.CTkFrame): text=self._description, font=FONT_SMALL, anchor="w", - text_color="#999999", + text_color=DESC_TEXT_COLOR if _is_dark() else DESC_TEXT_COLOR_LIGHT, wraplength=400, ) self._desc_label.grid( @@ -195,10 +219,10 @@ class StepHeader(ctk.CTkFrame): self._run_btn = ctk.CTkButton( btn_frame, text="▶", - font=("Segoe UI", 12, "bold"), + font=_font(14, "bold"), width=STEP_BUTTON_SIZE, height=STEP_BUTTON_SIZE, - corner_radius=6, + corner_radius=8, fg_color=STEP_RUN_BG, hover_color=STEP_RUN_BG_HOVER, text_color="white", @@ -211,10 +235,10 @@ class StepHeader(ctk.CTkFrame): self._rerun_btn = ctk.CTkButton( btn_frame, text="↻", - font=("Segoe UI", 12, "bold"), + font=_font(14, "bold"), width=STEP_BUTTON_SIZE, height=STEP_BUTTON_SIZE, - corner_radius=6, + corner_radius=8, fg_color=STEP_RUNNER_BG, hover_color=STEP_RUNNER_BG_HOVER, text_color="white", @@ -223,14 +247,18 @@ class StepHeader(ctk.CTkFrame): ) self._rerun_btn.grid(row=0, column=0, padx=(0, PADDING_SMALL)) - # Status icon + # Status icon — separate from buttons self._status_label = ctk.CTkLabel( - btn_frame, + self, text="", - font=("Segoe UI", 14), - anchor="e", + font=_font(16), + anchor="w", + ) + self._status_label.grid( + row=0, column=3, + sticky="e", + padx=PADDING_SMALL, ) - self._status_label.grid(row=0, column=0) # Hover effect bindings self.bind("", self._on_hover_enter) @@ -276,16 +304,16 @@ class StepHeader(ctk.CTkFrame): # Number circle circle_colors = { - "pending": "#666666", - "running": RUNNING_FADE_LIGHT, - "complete": ACCENT_COMPLETE, - "error": ACCENT_ERROR, - "disabled": ACCENT_DISABLED, + "pending": CIRCLE_PENDING, + "running": CIRCLE_RUNNING, + "complete": CIRCLE_COMPLETE, + "error": CIRCLE_ERROR, + "disabled": CIRCLE_DISABLED, } - circle_color = circle_colors.get(status, "#666666") + circle_color = circle_colors.get(status, CIRCLE_PENDING) self._num_label.configure(fg_color=circle_color) - # Status icon + # Status icon — separate from buttons icons = { "pending": "", "running": "◌", @@ -313,10 +341,10 @@ class StepHeader(ctk.CTkFrame): self._run_btn.configure(state="hidden") self._rerun_btn.configure(state="hidden") self._stop_pulse() - self.configure(fg_color="#2A2A2A") - self._num_label.configure(fg_color=ACCENT_DISABLED) - self._title_label.configure(text_color="#777777") - self._desc_label.configure(text_color="#666666") + self.configure(fg_color=DARK_CARD) + self._num_label.configure(fg_color=CIRCLE_DISABLED) + self._title_label.configure(text_color=DISABLED_TITLE_COLOR) + self._desc_label.configure(text_color=DISABLED_DESC_COLOR) elif status == "complete": self._run_btn.configure(state="hidden") self._rerun_btn.configure(state="normal") @@ -333,11 +361,11 @@ class StepHeader(ctk.CTkFrame): self._rerun_btn.configure(state="hidden") self._stop_pulse() - # Update description + # Update description — concise status text desc_map = { "pending": self._description or "Not started", - "running": "Running...", - "complete": "Completed successfully", + "running": "Running…", + "complete": "Done", "error": "Failed — click ↻ to retry", "disabled": "Waiting for dependencies", }