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>
329 lines
9.3 KiB
Python
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}; }}
|
|
"""
|