♻️ refactor(gui): improve left panel readability with better fonts, colors, and layout

- 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
This commit is contained in:
Jeremy Shen
2026-06-09 17:12:41 +08:00
parent 5277c88e3d
commit bda64d5f46
3 changed files with 139 additions and 78 deletions
+15 -14
View File
@@ -42,6 +42,7 @@ from gui.styles import (
DANGER_COLOR, DANGER_COLOR,
INFO_COLOR, INFO_COLOR,
CONNECTOR_COLOR, CONNECTOR_COLOR,
STEP_ICON_SIZE,
get_theme, get_theme,
) )
from gui.widgets import ( from gui.widgets import (
@@ -190,16 +191,17 @@ class MainWindow(ctk.CTk):
row=0, column=0, sticky="nsew", row=0, column=0, sticky="nsew",
padx=PADDING, pady=PADDING, padx=PADDING, pady=PADDING,
) )
panel.grid_rowconfigure(9, weight=1) panel.grid_rowconfigure(10, weight=1)
# ── Title ──
title = ctk.CTkLabel( title = ctk.CTkLabel(
panel, panel,
text="Workflow", text="Workflow",
font=FONT_HEADING, 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 = [ step_defs = [
{"num": 1, "title": "Check Environment", "desc": "Verify tools & connectivity"}, {"num": 1, "title": "Check Environment", "desc": "Verify tools & connectivity"},
{"num": 2, "title": "Program & Verify Flash", "desc": "Wipe, program, and verify flash"}, {"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"}, {"num": 4, "title": "Load Main Program", "desc": "TFTP upload, reboot, verify boot"},
] ]
# Create step headers with callbacks
self._steps = [] self._steps = []
self._step_statuses = {} self._step_statuses = {}
for i, defn in enumerate(step_defs): for i, defn in enumerate(step_defs):
@@ -217,24 +218,24 @@ class MainWindow(ctk.CTk):
title=defn["title"], title=defn["title"],
description=defn["desc"], description=defn["desc"],
callback=lambda idx=i: self._execute_step(idx), 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._steps.append(step)
self._step_statuses[i] = "pending" self._step_statuses[i] = "pending"
# Add connector line between steps # Connector line between steps
if i > 0: if i > 0:
connector = ctk.CTkFrame( connector = ctk.CTkFrame(
panel, panel,
width=2, width=3,
height=8, height=12,
fg_color=CONNECTOR_COLOR, fg_color=CONNECTOR_COLOR,
corner_radius=1, corner_radius=2,
) )
connector.grid( connector.grid(
row=i * 2, column=0, row=i * 2, column=0,
sticky="n", sticky="n",
padx=(PADDING_SMALL + 24 + PADDING_SMALL, PADDING_SMALL), padx=(PADDING_SMALL + STEP_ICON_SIZE + PADDING_SMALL, PADDING_SMALL),
) )
# Position step # Position step
@@ -245,16 +246,16 @@ class MainWindow(ctk.CTk):
pady=PADDING_SMALL, pady=PADDING_SMALL,
) )
# Progress indicator # ── Progress indicator ──
self._progress = ProgressIndicator(panel) self._progress = ProgressIndicator(panel)
self._progress.grid( self._progress.grid(
row=8, column=0, row=8, column=0,
sticky="nsew", sticky="nsew",
padx=PADDING, padx=PADDING,
pady=PADDING, pady=(PADDING, PADDING_SMALL),
) )
# Run button # ── Run All button ──
self._run_btn = ctk.CTkButton( self._run_btn = ctk.CTkButton(
panel, panel,
text="▶ Run All Steps", text="▶ Run All Steps",
@@ -267,7 +268,7 @@ class MainWindow(ctk.CTk):
row=9, column=0, row=9, column=0,
sticky="nsew", sticky="nsew",
padx=PADDING, padx=PADDING,
pady=PADDING, pady=PADDING_SMALL,
) )
def _build_log_panel(self, parent: ctk.CTkFrame) -> None: def _build_log_panel(self, parent: ctk.CTkFrame) -> None:
+64 -32
View File
@@ -10,22 +10,22 @@ import darkdetect
# ── Color Theme ────────────────────────────────────────────── # ── Color Theme ──────────────────────────────────────────────
PRIMARY_COLOR: str = "#0066CC" PRIMARY_COLOR: str = "#0078D4"
SECONDARY_COLOR: str = "#00A86B" SECONDARY_COLOR: str = "#107C10"
DANGER_COLOR: str = "#CC3333" DANGER_COLOR: str = "#D13438"
WARNING_COLOR: str = "#E6A817" WARNING_COLOR: str = "#FFB900"
SUCCESS_COLOR: str = "#00A86B" SUCCESS_COLOR: str = "#107C10"
INFO_COLOR: str = "#0066CC" INFO_COLOR: str = "#0078D4"
DARK_BG: str = "#2B2B2B" DARK_BG: str = "#1E1E1E"
DARK_FG: str = "#E0E0E0" DARK_FG: str = "#F0F0F0"
DARK_CARD: str = "#3A3A3A" DARK_CARD: str = "#2D2D30"
DARK_BORDER: str = "#555555" DARK_BORDER: str = "#3F3F46"
LIGHT_BG: str = "#F5F5F5" LIGHT_BG: str = "#FAFAFA"
LIGHT_FG: str = "#333333" LIGHT_FG: str = "#1A1A1A"
LIGHT_CARD: str = "#FFFFFF" LIGHT_CARD: str = "#FFFFFF"
LIGHT_BORDER: str = "#DDDDDD" LIGHT_BORDER: str = "#E0E0E0"
def get_theme() -> dict: def get_theme() -> dict:
@@ -54,12 +54,29 @@ def get_theme() -> dict:
# ── Typography ─────────────────────────────────────────────── # ── Typography ───────────────────────────────────────────────
# Cross-platform font stack: Windows → macOS → Linux fallbacks
FONT_TITLE: tuple[str, int, str] = ("Segoe UI", 18, "bold") def _font(name: str, size: int, weight: str = "") -> tuple[str, int, str]:
FONT_HEADING: tuple[str, int, str] = ("Segoe UI", 14, "bold") """Build a cross-platform font tuple.
FONT_BODY: tuple[str, int, str] = ("Segoe UI", 11)
FONT_SMALL: tuple[str, int, str] = ("Segoe UI", 9) Args:
FONT_MONO: tuple[str, int, str] = ("Consolas", 10) 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 ────────────────────────────────────────────────── # ── Spacing ──────────────────────────────────────────────────
@@ -83,34 +100,49 @@ LOG_HEIGHT: int = 200
# ── Step UI Enhancements ───────────────────────────────────── # ── Step UI Enhancements ─────────────────────────────────────
# Accent bar colors for step states # Accent bar colors for step states
ACCENT_PENDING: str = "#666666" ACCENT_PENDING: str = "#888888"
ACCENT_RUNNING: str = PRIMARY_COLOR ACCENT_RUNNING: str = PRIMARY_COLOR
ACCENT_COMPLETE: str = SUCCESS_COLOR ACCENT_COMPLETE: str = SUCCESS_COLOR
ACCENT_ERROR: str = DANGER_COLOR ACCENT_ERROR: str = DANGER_COLOR
ACCENT_DISABLED: str = "#444444" ACCENT_DISABLED: str = "#555555"
# Connector line color # Connector line color
CONNECTOR_COLOR: str = "#555555" CONNECTOR_COLOR: str = "#3F3F46"
CONNECTOR_COLOR_LIGHT: str = "#CCCCCC" CONNECTOR_COLOR_LIGHT: str = "#CCCCCC"
# Button hover states # Button hover states
STEP_RUN_BG: str = "#0055AA" STEP_RUN_BG: str = "#0078D4"
STEP_RUN_BG_HOVER: str = "#0077DD" STEP_RUN_BG_HOVER: str = "#1A8AE6"
STEP_RUNNER_BG: str = "#555555" STEP_RUNNER_BG: str = "#666666"
STEP_RUNNER_BG_HOVER: str = "#777777" STEP_RUNNER_BG_HOVER: str = "#888888"
# Animation # Animation
PULSE_DURATION: int = 500 # ms between pulse flashes PULSE_DURATION: int = 600 # ms between pulse flashes
RUNNING_FADE_LIGHT: str = "#0066CC" RUNNING_FADE_LIGHT: str = "#0078D4"
RUNNING_FADE_DARK: str = "#003366" RUNNING_FADE_DARK: str = "#005A9E"
# Step card styling # Step card styling
STEP_CARD_BG: str = "#333333" STEP_CARD_BG: str = "#2D2D30"
STEP_CARD_BG_HOVER: str = "#3D3D3D" STEP_CARD_BG_HOVER: str = "#353539"
STEP_CARD_BG_LIGHT: str = "#FFFFFF" STEP_CARD_BG_LIGHT: str = "#FFFFFF"
STEP_CARD_BG_HOVER_LIGHT: str = "#F0F0F0" STEP_CARD_BG_HOVER_LIGHT: str = "#F0F0F0"
STEP_DISABLED_ALPHA: float = 0.5 STEP_DISABLED_ALPHA: float = 0.5
# Icon sizes # Icon sizes
STEP_ICON_SIZE: int = 28 STEP_ICON_SIZE: int = 32
STEP_BUTTON_SIZE: int = 28 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"
+60 -32
View File
@@ -42,9 +42,33 @@ from gui.styles import (
STEP_CARD_BG, STEP_CARD_BG,
STEP_CARD_BG_HOVER, STEP_CARD_BG_HOVER,
STEP_DISABLED_ALPHA, 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: def _is_dark() -> bool:
"""Check if dark mode is active. """Check if dark mode is active.
@@ -115,9 +139,9 @@ class StepHeader(ctk.CTkFrame):
# Accent bar (left edge) # Accent bar (left edge)
self._accent_bar = ctk.CTkFrame( self._accent_bar = ctk.CTkFrame(
self, self,
width=4, width=5,
height=40, height=48,
corner_radius=2, corner_radius=3,
fg_color=ACCENT_PENDING, fg_color=ACCENT_PENDING,
) )
self._accent_bar.grid( self._accent_bar.grid(
@@ -130,12 +154,12 @@ class StepHeader(ctk.CTkFrame):
self._num_label = ctk.CTkLabel( self._num_label = ctk.CTkLabel(
self, self,
text=str(self._step_number), text=str(self._step_number),
font=("Segoe UI", 13, "bold"), font=_font(14, "bold"),
width=STEP_ICON_SIZE, width=STEP_ICON_SIZE,
height=STEP_ICON_SIZE, height=STEP_ICON_SIZE,
corner_radius=STEP_ICON_SIZE // 2, corner_radius=STEP_ICON_SIZE // 2,
fg_color="transparent", fg_color=CIRCLE_PENDING,
text_color=DARK_FG if _is_dark() else LIGHT_FG, text_color="white",
) )
self._num_label.grid( self._num_label.grid(
row=0, column=1, row=0, column=1,
@@ -157,14 +181,14 @@ class StepHeader(ctk.CTkFrame):
self._title_label = ctk.CTkLabel( self._title_label = ctk.CTkLabel(
content_frame, content_frame,
text=self._title, text=self._title,
font=("Segoe UI", 12, "bold"), font=_font(13, "bold"),
anchor="w", anchor="w",
text_color=DARK_FG if _is_dark() else LIGHT_FG, text_color=DARK_FG if _is_dark() else LIGHT_FG,
) )
self._title_label.grid( self._title_label.grid(
row=0, column=0, row=0, column=0,
sticky="nw", sticky="nw",
pady=(0, 2), pady=(0, 3),
) )
self._desc_label = ctk.CTkLabel( self._desc_label = ctk.CTkLabel(
@@ -172,7 +196,7 @@ class StepHeader(ctk.CTkFrame):
text=self._description, text=self._description,
font=FONT_SMALL, font=FONT_SMALL,
anchor="w", anchor="w",
text_color="#999999", text_color=DESC_TEXT_COLOR if _is_dark() else DESC_TEXT_COLOR_LIGHT,
wraplength=400, wraplength=400,
) )
self._desc_label.grid( self._desc_label.grid(
@@ -195,10 +219,10 @@ class StepHeader(ctk.CTkFrame):
self._run_btn = ctk.CTkButton( self._run_btn = ctk.CTkButton(
btn_frame, btn_frame,
text="", text="",
font=("Segoe UI", 12, "bold"), font=_font(14, "bold"),
width=STEP_BUTTON_SIZE, width=STEP_BUTTON_SIZE,
height=STEP_BUTTON_SIZE, height=STEP_BUTTON_SIZE,
corner_radius=6, corner_radius=8,
fg_color=STEP_RUN_BG, fg_color=STEP_RUN_BG,
hover_color=STEP_RUN_BG_HOVER, hover_color=STEP_RUN_BG_HOVER,
text_color="white", text_color="white",
@@ -211,10 +235,10 @@ class StepHeader(ctk.CTkFrame):
self._rerun_btn = ctk.CTkButton( self._rerun_btn = ctk.CTkButton(
btn_frame, btn_frame,
text="", text="",
font=("Segoe UI", 12, "bold"), font=_font(14, "bold"),
width=STEP_BUTTON_SIZE, width=STEP_BUTTON_SIZE,
height=STEP_BUTTON_SIZE, height=STEP_BUTTON_SIZE,
corner_radius=6, corner_radius=8,
fg_color=STEP_RUNNER_BG, fg_color=STEP_RUNNER_BG,
hover_color=STEP_RUNNER_BG_HOVER, hover_color=STEP_RUNNER_BG_HOVER,
text_color="white", text_color="white",
@@ -223,14 +247,18 @@ class StepHeader(ctk.CTkFrame):
) )
self._rerun_btn.grid(row=0, column=0, padx=(0, PADDING_SMALL)) self._rerun_btn.grid(row=0, column=0, padx=(0, PADDING_SMALL))
# Status icon # Status icon — separate from buttons
self._status_label = ctk.CTkLabel( self._status_label = ctk.CTkLabel(
btn_frame, self,
text="", text="",
font=("Segoe UI", 14), font=_font(16),
anchor="e", 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 # Hover effect bindings
self.bind("<Enter>", self._on_hover_enter) self.bind("<Enter>", self._on_hover_enter)
@@ -276,16 +304,16 @@ class StepHeader(ctk.CTkFrame):
# Number circle # Number circle
circle_colors = { circle_colors = {
"pending": "#666666", "pending": CIRCLE_PENDING,
"running": RUNNING_FADE_LIGHT, "running": CIRCLE_RUNNING,
"complete": ACCENT_COMPLETE, "complete": CIRCLE_COMPLETE,
"error": ACCENT_ERROR, "error": CIRCLE_ERROR,
"disabled": ACCENT_DISABLED, "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) self._num_label.configure(fg_color=circle_color)
# Status icon # Status icon — separate from buttons
icons = { icons = {
"pending": "", "pending": "",
"running": "", "running": "",
@@ -313,10 +341,10 @@ class StepHeader(ctk.CTkFrame):
self._run_btn.configure(state="hidden") self._run_btn.configure(state="hidden")
self._rerun_btn.configure(state="hidden") self._rerun_btn.configure(state="hidden")
self._stop_pulse() self._stop_pulse()
self.configure(fg_color="#2A2A2A") self.configure(fg_color=DARK_CARD)
self._num_label.configure(fg_color=ACCENT_DISABLED) self._num_label.configure(fg_color=CIRCLE_DISABLED)
self._title_label.configure(text_color="#777777") self._title_label.configure(text_color=DISABLED_TITLE_COLOR)
self._desc_label.configure(text_color="#666666") self._desc_label.configure(text_color=DISABLED_DESC_COLOR)
elif status == "complete": elif status == "complete":
self._run_btn.configure(state="hidden") self._run_btn.configure(state="hidden")
self._rerun_btn.configure(state="normal") self._rerun_btn.configure(state="normal")
@@ -333,11 +361,11 @@ class StepHeader(ctk.CTkFrame):
self._rerun_btn.configure(state="hidden") self._rerun_btn.configure(state="hidden")
self._stop_pulse() self._stop_pulse()
# Update description # Update description — concise status text
desc_map = { desc_map = {
"pending": self._description or "Not started", "pending": self._description or "Not started",
"running": "Running...", "running": "Running…",
"complete": "Completed successfully", "complete": "Done",
"error": "Failed — click ↻ to retry", "error": "Failed — click ↻ to retry",
"disabled": "Waiting for dependencies", "disabled": "Waiting for dependencies",
} }