wongyiekheng c92fbe7548 Second Commit
Major refactor of Ameba Control Panel v3.1.0:
- Three-column layout: icon sidebar, config+history, log view
- Dracula PRO theme with light/dark toggle
- DTR/RTS GPIO control (replaces ASCII commands)
- Multi-CDC firmware support for AmebaSmart control device
- Dynamic DUT tabs with +/- management
- NN Model flash image support
- Settings dialog (Font, Serial, Flash, Command tabs)
- Background port scanning, debounced session store
- Adaptive log flush rate, format cache optimization
- Smooth sidebar animation, deferred startup
- pytest test framework with session/log/settings tests
- Thread safety fixes: _alive guards, parented timers, safe baud parsing
- Find highlight: needle-only highlighting with focused match color
- Partial line buffering for table output
- PyInstaller packaging with version stamp and module exclusions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 13:01:12 +08:00

329 lines
9.3 KiB
Python

"""Dracula PRO (Van Helsing) inspired themes."""
from __future__ import annotations
from dataclasses import dataclass
@dataclass(frozen=True)
class Palette:
name: str
bg: str
bg_secondary: str
bg_input: str
bg_button: str
bg_button_hover: str
bg_button_pressed: str
border: str
border_focus: str
text: str
text_dim: str
text_bright: str
accent: str
accent_dim: str
selection: str
green: str
green_bright: str
orange: str
pink: str
purple: str
red: str
yellow: str
tab_active: str
tab_inactive: str
scroll_handle: str
scroll_hover: str
log_rx: str
log_tx: str
log_info: str
# Dracula PRO Van Helsing palette
_D = dict(
cyan="#80ffea", green="#8aff80", orange="#ffca80",
pink="#ff80bf", purple="#9580ff", red="#ff9580",
yellow="#ffff80", comment="#7970a9",
)
DARK = Palette(
name="dark",
bg="#1e2029",
bg_secondary="#282a36",
bg_input="#313442",
bg_button="#414458",
bg_button_hover="#515570",
bg_button_pressed="#363949",
border="#414458",
border_focus=_D["cyan"],
text="#f8f8f2",
text_dim=_D["comment"],
text_bright="#ffffff",
accent=_D["cyan"],
accent_dim="#2a4a50",
selection="#44475a",
green=_D["green"],
green_bright="#50fa7b",
orange=_D["orange"],
pink=_D["pink"],
purple=_D["purple"],
red=_D["red"],
yellow=_D["yellow"],
tab_active="#1e2029",
tab_inactive="#282a36",
scroll_handle="#44475a",
scroll_hover="#515570",
log_rx=_D["green"],
log_tx=_D["cyan"],
log_info=_D["comment"],
)
LIGHT = Palette(
name="light",
bg="#ffffff",
bg_secondary="#f4f4f8",
bg_input="#ffffff",
bg_button="#eeeef2",
bg_button_hover="#dddde4",
bg_button_pressed="#ccccd4",
border="#d4d4dc",
border_focus="#6c5ce7",
text="#282a36",
text_dim="#7970a9",
text_bright="#1e2029",
accent="#6c5ce7",
accent_dim="#eae7fd",
selection="#ddd8fd",
green="#2ebc50",
green_bright="#27ae60",
orange="#e67e22",
pink="#e84393",
purple="#6c5ce7",
red="#e74c3c",
yellow="#f1c40f",
tab_active="#ffffff",
tab_inactive="#f4f4f8",
scroll_handle="#c4c4cc",
scroll_hover="#a4a4ac",
log_rx="#1a8a3d",
log_tx="#2944a8",
log_info="#7970a9",
)
def build_stylesheet(p: Palette) -> str:
return f"""
/* ── Dracula PRO Theme ────────────────────────────── */
* {{ outline: none; }}
QWidget {{
background-color: {p.bg};
color: {p.text};
font-family: "Segoe UI", "SF Pro Text", sans-serif;
font-size: 10pt;
}}
QMainWindow {{ background-color: {p.bg}; }}
/* ── Menu ─────────────────────────────────────────── */
QMenuBar {{
background-color: {p.bg_secondary};
color: {p.text};
border-bottom: 1px solid {p.border};
padding: 2px;
}}
QMenuBar::item {{ padding: 5px 12px; border-radius: 4px; }}
QMenuBar::item:selected {{ background-color: {p.selection}; }}
QMenu {{
background-color: {p.bg_secondary};
color: {p.text};
border: 1px solid {p.border};
border-radius: 8px;
padding: 4px;
}}
QMenu::item {{ padding: 6px 28px 6px 16px; border-radius: 4px; }}
QMenu::item:selected {{ background-color: {p.accent}; color: {p.bg}; }}
QMenu::separator {{ height: 1px; background: {p.border}; margin: 4px 8px; }}
/* ── Tabs ─────────────────────────────────────────── */
QTabWidget::pane {{
border: 1px solid {p.border};
background: {p.bg};
top: -1px;
}}
QTabBar {{ background: {p.bg_secondary}; qproperty-drawBase: 0; }}
QTabBar::tab {{
background: {p.tab_inactive};
color: {p.text_dim};
padding: 7px 18px;
border: none;
border-bottom: 2px solid transparent;
margin-right: 1px;
}}
QTabBar::tab:selected {{
background: {p.tab_active};
color: {p.accent};
border-bottom: 2px solid {p.accent};
}}
QTabBar::tab:hover:!selected {{ color: {p.text}; background: {p.bg_button}; }}
/* ── GroupBox (Collapsible Card) ──────────────────── */
QGroupBox {{
background-color: {p.bg_secondary};
border: 1px solid {p.border};
border-radius: 8px;
margin-top: 14px;
padding: 10px 8px 8px 8px;
font-weight: 600;
}}
QGroupBox::title {{
subcontrol-origin: margin;
subcontrol-position: top left;
padding: 2px 10px;
color: {p.accent};
font-size: 10pt;
}}
QGroupBox::indicator {{
width: 0px; height: 0px;
margin: 0px; padding: 0px;
border: none;
}}
/* ── Buttons ──────────────────────────────────────── */
QPushButton {{
background-color: {p.bg_button};
color: {p.text};
border: 1px solid {p.border};
border-radius: 6px;
padding: 5px 14px;
min-height: 24px;
font-weight: 500;
}}
QPushButton:hover {{
background-color: {p.bg_button_hover};
border-color: {p.accent};
}}
QPushButton:pressed {{ background-color: {p.bg_button_pressed}; }}
QPushButton:disabled {{ color: {p.text_dim}; }}
QPushButton[checkable="true"]:checked {{
background-color: {p.green};
color: {p.bg};
border-color: {p.green};
font-weight: bold;
}}
QPushButton[checkable="true"]:checked:hover {{ background-color: {p.green_bright}; }}
/* ── Inputs ───────────────────────────────────────── */
QLineEdit {{
background-color: {p.bg_input};
color: {p.text};
border: 1px solid {p.border};
border-radius: 6px;
padding: 4px 8px;
selection-background-color: {p.selection};
selection-color: {p.text_bright};
}}
QLineEdit:focus {{ border: 1.5px solid {p.accent}; }}
QComboBox {{
background-color: {p.bg_input};
color: {p.text};
border: 1px solid {p.border};
border-radius: 6px;
padding: 4px 8px;
min-height: 24px;
}}
QComboBox:hover {{ border-color: {p.accent}; }}
QComboBox::drop-down {{ border: none; width: 22px; }}
QComboBox QAbstractItemView {{
background-color: {p.bg_secondary};
color: {p.text};
border: 1px solid {p.border};
border-radius: 6px;
selection-background-color: {p.accent};
selection-color: {p.bg};
padding: 4px;
outline: none;
}}
QSpinBox {{
background-color: {p.bg_input};
color: {p.text};
border: 1px solid {p.border};
border-radius: 6px;
padding: 4px 8px;
}}
/* ── CheckBox ─────────────────────────────────────── */
QCheckBox {{ color: {p.text}; spacing: 6px; }}
QCheckBox::indicator {{
width: 14px; height: 14px;
border: 2px solid {p.text_dim};
border-radius: 4px;
background: transparent;
}}
QCheckBox::indicator:checked {{
background: {p.accent};
border-color: {p.accent};
}}
/* ── List ─────────────────────────────────────────── */
QListWidget {{
background-color: {p.bg_input};
color: {p.text};
border: 1px solid {p.border};
border-radius: 6px;
outline: none;
padding: 2px;
}}
QListWidget::item {{ padding: 3px 8px; border-radius: 4px; }}
QListWidget::item:selected {{ background-color: {p.selection}; color: {p.text_bright}; }}
QListWidget::item:hover:!selected {{ background-color: {p.bg_button}; }}
/* ── ScrollBar ────────────────────────────────────── */
QScrollBar:vertical {{
background: transparent; width: 8px; margin: 2px 0;
}}
QScrollBar::handle:vertical {{
background: {p.scroll_handle}; border-radius: 4px; min-height: 30px;
}}
QScrollBar::handle:vertical:hover {{ background: {p.scroll_hover}; }}
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {{ height: 0; }}
QScrollBar:horizontal {{
background: transparent; height: 8px; margin: 0 2px;
}}
QScrollBar::handle:horizontal {{
background: {p.scroll_handle}; border-radius: 4px; min-width: 30px;
}}
QScrollBar::handle:horizontal:hover {{ background: {p.scroll_hover}; }}
QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {{ width: 0; }}
/* ── Misc ─────────────────────────────────────────── */
QScrollArea {{ background: transparent; border: none; }}
QSplitter::handle {{ background: {p.border}; width: 2px; }}
QTextEdit {{
background-color: {p.bg_input};
color: {p.text};
border: 1px solid {p.border};
border-radius: 6px;
selection-background-color: {p.selection};
padding: 4px;
}}
QToolTip {{
background-color: {p.bg_secondary};
color: {p.text};
border: 1px solid {p.border};
border-radius: 6px;
padding: 6px 10px;
}}
QDialog {{ background-color: {p.bg}; }}
QDialogButtonBox QPushButton {{ min-width: 80px; }}
QLabel {{ color: {p.text}; background: transparent; }}
QToolButton {{
background: transparent;
color: {p.text};
border: none;
border-radius: 6px;
padding: 4px 8px;
font-size: 14pt;
}}
QToolButton:hover {{ background-color: {p.bg_button_hover}; }}
"""