diff --git a/src/aare/gui/styles.py b/src/aare/gui/styles.py index 4628e432..83b3adb1 100644 --- a/src/aare/gui/styles.py +++ b/src/aare/gui/styles.py @@ -1,8 +1,312 @@ from __future__ import annotations +from string import Template + THEME_ORIGINAL = "original" THEME_PORTRAIT = "portrait" +# --------------------------------------------------------------------------- +# Color palette. Change values HERE to try a different look — the QSS below +# and the widgets that import these use them everywhere. Every UPPERCASE +# constant is exposed to the QSS templates as $lowercase_name (see _palette), +# so no color literals live inside the stylesheets themselves. +# Any value can be "transparent" (e.g. to hide a border or a fill). +# string.Template ($name) instead of f-strings so the QSS braces stay literal. + +# -- Light theme ------------------------------------------------------------ +BACKGROUND = "#e2e7ee" +BANNER = "#a2b7e4" +BANNER_TEXT = "#263043" # must contrast with BANNER +TEXT = "#263043" +SURFACE = "#f2f2f2" # input fields, cards, group boxes + +# Borders (all can be "transparent" to hide the line): +BORDER = "transparent" # main dividers, e.g. the beamline state bar top line +CARD_BORDER = "transparent" # cards / group boxes (Local Contact, automation) +COMPACT_BORDER = "transparent" # compact-automation cards and buttons + +# Compact-automation page: +COMPACT_CARD_BG = "#e6eefc" +COMPACT_TITLE = "#17324d" # section titles + menu/secondary button text +COMPACT_HINT = "#51657d" # secondary text: hints, queue titles +COMPACT_VALUE = "#10263a" +COMPACT_MENU_BG = "#cbdcf8" +COMPACT_MENU_BG_HOVER = "#bfd4f6" +PRIMARY = "#2563eb" # main action button +PRIMARY_HOVER = "#1d4ed8" +PRIMARY_TEXT = "white" +SECONDARY_BG = "#dfe9fb" +SECONDARY_BG_HOVER = "#d3e1f8" + +# Alert banners (alertKind: error / success / waiting=warning): +ERROR_BG = "#fbe4e6" +ERROR_BORDER = "#d97a84" +ERROR_TEXT = "#8f1d2c" +SUCCESS_BG = "#e7f6ea" +SUCCESS_BORDER = "#7bbf8e" +SUCCESS_TEXT = "#1f6a3a" +WARNING_BG = "#fff8e1" +WARNING_BORDER = "#ffb300" +WARNING_TEXT = "#e65100" + +# Axis video status + beamline state bar: +STATUS_IDLE_BG = "#d9e2f2" +STATUS_LABEL_TEXT = "#2f3b52" +STATE_TOGGLE_TEXT = "white" # collapse glyph sitting on the BANNER strip +STATE_CURRENT_TEXT = "#1e293b" # same slate as HEADING_TEXT, separate knob +STATE_TELL_TEXT = "#374357" # also the status-bar TELL line + +# -- Portrait (dark) theme: Catppuccin Macchiato ---------------------------- +# https://catppuccin.com/palette — token names in comments. DARK_BG was set to +# Macchiato mantle by hand, so the rest follows that flavor. +DARK_BG = "#1e2030" # mantle +DARK_TEXT = "#cad3f5" # text +DARK_SURFACE = "#24273a" # base — cards, state panel, scrollbar track +DARK_ELEVATED = "#363a4f" # surface0 — buttons, title strip, idle status pill +DARK_BORDER = "#494d64" # surface1 — card borders, scrollbar handle, button hover fill +DARK_ACCENT = "#8bd5ca" # teal +DARK_ACCENT_HOVER = "#a2ddd5" # teal +20% white — palette has no lighter teal step +DARK_MUTED = "#a5adcb" # subtext0 — secondary text +# Alert banners: full-strength color for border/text, 25%-over-DARK_BG tint +# for bg (Catppuccin defines no alert backgrounds, so these are blends). +DARK_ERROR_BG = "#523a4a" # red 25% over mantle +DARK_ERROR_BORDER = "#ed8796" # red +DARK_ERROR_TEXT = "#ed8796" # red +DARK_SUCCESS_BG = "#404e49" # green 25% over mantle +DARK_SUCCESS_BORDER = "#a6da95" # green +DARK_SUCCESS_TEXT = "#a6da95" # green +DARK_WARNING_BG = "#524d4c" # yellow 25% over mantle +DARK_WARNING_BORDER = "#eed49f" # yellow +DARK_WARNING_TEXT = "#eed49f" # yellow + +# -- Shared chrome (light-theme widgets) ------------------------------------ +# Extracted from per-widget literals so the whole app is themeable from this +# file. The same hex may appear under two names when the roles differ — +# separate knobs on purpose. +WHITE = "#ffffff" +DEFAULT_TEXT = "#000000" # labels that reset to plain black +NOTE_TEXT = "#555555" # tutorial hints, TELL sample details +DIM_TEXT = "#666666" # baton dialog timers +HINT_TEXT = "#999999" # baton dialog fine print +HEADING_TEXT = "#1e293b" # card headings (slate-800) +SUBTLE_TEXT = "#334155" # card body text (slate-700) +MUTED_TEXT = "#475569" # neutral chip / idle step text (slate-600) +FAINT_TEXT = "#64748b" # pending/skipped step text (slate-500) +SHADOW = "#000000" # drop shadows & tutorial scrim; alpha stays at call site + +# -- Semantic action colors ------------------------------------------------- +GO_TEXT = "#4e9a06" # green start/run/measure button text +ABORT_TEXT = "#a40000" # abort button text +ALERT_TEXT = "#ff0000" # out-of-range motor labels +PATH_WARN_TEXT = "#c80000" # file-exists warning in path panel +DANGER_ACCENT = "#d9534f" # invalid p-group border + message text + +# -- Status chips (local contact status) — "good" reuses SUCCESS_BG/TEXT ---- +CHIP_WARN_BG = "#fff3cd" +CHIP_WARN_TEXT = "#7a4b00" +CHIP_BAD_BG = "#fdeaea" +CHIP_BAD_TEXT = "#8b1e1e" +CHIP_NEUTRAL_BG = "#e9eef5" # text uses MUTED_TEXT +CHIP_INFO_BG = "#e8f1ff" +CHIP_INFO_TEXT = "#12406a" + +# -- Status cards (beamline recovery, local contact error frame) ------------ +WARN_CARD_BORDER = "#f0c36d" +BAD_CARD_BORDER = "#e6a8a8" +INFO_CARD_BG = "#eef6ff" +INFO_CARD_BORDER = "#a8c7e6" +PENDING_CARD_BG = "#fff7db" +PENDING_CARD_BORDER = "#e7cb73" + +# -- Log panel -------------------------------------------------------------- +LOG_BORDER = "#8a8a8a" +LOG_PANEL_BG = "#fff4f4" +LOG_ERROR_BG = "#fff1f1" +LOG_ERROR_BORDER = "#dd6666" +LOG_WARN_BG = "#fff8e8" +LOG_WARN_BORDER = "#d7aa42" +LOG_SUCCESS_BG = "#eefaf0" +LOG_SUCCESS_BORDER = "#6cb37a" +LOG_INFO_BG = "#eef5ff" +LOG_INFO_BORDER = "#6b9bd6" + +# -- Automation panel + progress steps -------------------------------------- +AUTOMATION_TITLE_TEXT = "#1f2937" +AUTOMATION_HINT_TEXT = "#374151" +STEP_RUNNING_TEXT = "#2563eb" # same blue as PRIMARY, separate knob +STEP_SUCCESS_TEXT = "#15803d" +STEP_FAILED_TEXT = "#b91c1c" +STEP_PAUSED_TEXT = "#c2410c" +STEP_DONE_BG = "#ecfdf3" +STEP_DONE_TEXT = "#166534" +STEP_DONE_BORDER = "#a7f3d0" +STEP_ACTIVE_BG = "#eff6ff" +STEP_ACTIVE_TEXT = "#1d4ed8" +STEP_ACTIVE_BORDER = "#bfdbfe" +STEP_FAILED_BG = "#fef2f2" +STEP_FAILED_BORDER = "#fecaca" +STEP_PAUSED_BG = "#fff7ed" +STEP_PAUSED_BORDER = "#fed7aa" +STEP_IDLE_BG = "#f8fafc" +STEP_IDLE_BORDER = "#e2e8f0" + +# -- Baton request dialog --------------------------------------------------- +BATON_OK_BG = "#4caf50" +BATON_OK_HOVER = "#45a049" +BATON_OK_PRESSED = "#3d8b40" +BATON_DANGER_BG = "#f44336" +BATON_DANGER_HOVER = "#da190b" +BATON_DANGER_PRESSED = "#c41000" +BATON_WARN = "#ff9800" +BATON_INFO = "#2196f3" +LIGHT_BORDER = "#cccccc" +PROGRESS_TRACK_BG = "#f0f0f0" + +# -- Splash screen ---------------------------------------------------------- +SPLASH_BG = "#222222" +SPLASH_BORDER = "#444444" +SPLASH_ACCENT = "#0078d7" + +# -- Numeric inputs --------------------------------------------------------- +INPUT_BG = "#ffffff" +INPUT_INVALID_BG = "#ffd5d5" +INPUT_DISABLED_BG = "#f0f0f0" +INPUT_DISABLED_INVALID_BG = "#f0e1e1" + +# -- Status bar flags (hex equivalents of the old CSS named colors) --------- +STATUS_OK = "#008000" # closed / idle / owned / tell ok (was "green") +STATUS_ALERT = "#ff0000" # open / busy / other-owner / hot cryo (was "red") +STATUS_WARN = "#ffa500" # baton waiting / warming cryo / tell busy (was "orange") +STATUS_INFO = "#0000ff" # cold cryo (was "blue") +STATUS_VACANT = "#ffff00" # baton vacant (was "yellow") +STATUS_REQUEST = "#00ffff" # baton request (was "cyan") + +# -- Beamline state panel --------------------------------------------------- +STATE_AVAILABLE = "#ed8936" +STATE_UNAVAILABLE = "#8c96a5" +STATE_MSG_ERROR = "#c81e1e" +STATE_MSG_INFO = "#005caa" + +# -- Sample tables + raster grid -------------------------------------------- +SAMPLE_ROW_ACTIVE_BG = "#ff6600" +SAMPLE_ROW_QUEUED_BG = "#729fcf" +SAMPLE_ROW_HIGHLIGHT_BG = "#d8e4fd" +RASTER_GRID_LINE = "#729fcf" +TABLE_SHADE_BG = "#e0e0e0" + +# -- Camera / video overlay (painter colors, alpha at call site) ------------ +BEAM_OPEN = "#00ff00" # beam marker: shutter open +BEAM_IDLE = "#f57900" # beam marker: idle +BEAM_BUSY = "#ff0000" # beam marker: busy +BEAM_MARKING = "#663399" # beam marker: marking mode +MARKER_GREEN = "#32cd32" # loop-centering click marker +PATH_START = "#008000" # raster path gradient start + start circle +PATH_END = "#ff0000" # raster path gradient end + end circle +LEGEND_BG = "#141414" +LEGEND_TEXT = "#f0f0f0" +TOOLTIP_TEXT = "#e6e6e6" +MARK_TOOLTIP_GOLD = "#ffd700" +MARK_TOOLTIP_ORANGE = "#ffa500" +MARK_TOOLTIP_RED = "#ff0000" +MARK_BADGE_BG = "#b43c00" + +# Prediction class overlay colors. The chart variant historically used pure +# green (#00ff00) while the overlay used CSS green (#008000) — both kept. +CLASS_COLORS = { + "pin": "#ff0000", + "loop_all": "#008000", + "loop_face": "#ffff00", + "crystal": "#0000ff", + "needle": "#ff00ff", + "ice": "#00ffff", +} +CHART_CLASS_COLORS = { + "Pin": "#ff0000", + "Loop_all": "#00ff00", + "Loop_face": "#ffff00", + "Crystal": "#0000ff", + "Needle": "#ff00ff", + "Ice": "#00ffff", +} +TARGET_COLORS = {"Cyan": "#00ffff", "Dark Blue": "#0046a0", "Dark Red": "#8c1919"} +BOOKMARK_COLORS = { + "red": "#ff0000", + "green": "#008000", + "blue": "#0000ff", + "indigo": "#4b0082", + "lime": "#00ff00", +} + +# -- Busy overlay (per-source color coding) --------------------------------- +BUSY_YELLOW = "#f1c40f" +BUSY_YELLOW_BORDER = "#fff8d2" +BUSY_YELLOW_DOT = "#fff6bf" +BUSY_YELLOW_TEXT_DARK = "#3b2f00" +BUSY_PURPLE = "#8e44ad" +BUSY_PURPLE_BORDER = "#ebdcf5" +BUSY_PURPLE_DOT = "#f0dfff" +BUSY_RED_BADGE = "#d64545" +BUSY_RED_FILL = "#be2828" +BUSY_RED_BORDER = "#ffdcdc" +BUSY_RED_DOT = "#ffdddd" +BUSY_ORANGE = "#e67e22" +BUSY_ORANGE_BORDER = "#ffead6" +BUSY_ORANGE_DOT = "#fff0db" +BUSY_BLUE = "#3498db" +BUSY_BLUE_BORDER = "#dcf0ff" +BUSY_BLUE_DOT = "#dff2ff" +BUSY_PSI_RED = "#e04f39" +BUSY_PSI_RED_BORDER = "#ffe1dc" +BUSY_PSI_RED_DOT = "#ffd8d1" + +# -- Charts (prediction metrics, target stability, fluorescence) ------------ +CHART_BLUE = "#1f77b4" +CHART_BLUE_LIGHT = "#6baed6" +CHART_BLUE_PALE = "#9ecae1" +CHART_RED = "#d62728" +CHART_RED_LIGHT = "#ff9896" +CHART_RED_DARK = "#c43c39" +CHART_ORANGE = "#ff7f0e" +CHART_ORANGE_PALE = "#ffbb78" +CHART_GREEN = "#2ca02c" +CHART_GREEN_PALE = "#98df8a" +CHART_CYAN = "#17becf" +CHART_PURPLE = "#9467bd" +CHART_MUTED = "#888888" +CONFIDENCE_BIN_COLORS = [CHART_RED, CHART_ORANGE, CHART_ORANGE_PALE, CHART_GREEN_PALE, CHART_GREEN] +SPECTRUM_LINE = "#cc0000" + +# -- Generic panels (developer help, raster table) -------------------------- +PANEL_BG_SOFT = "#f6f6f6" +PANEL_BG_FAINT = "#fafafa" +PANEL_BORDER = "#d0d0d0" +PANEL_BORDER_DARK = "#bdbdbd" +PANEL_BORDER_LIGHT = "#e0e0e0" +TUTORIAL_BORDER = "#555555" +TUTORIAL_HIGHLIGHT = "#ffff00" # widget spotlight pen (was Qt.yellow) +# --------------------------------------------------------------------------- + + +def _palette() -> dict[str, str]: + # Why: one substitution mapping instead of ~40 kwargs per stylesheet, and + # a renamed/missing constant fails loudly (KeyError) instead of silently. + return {k.lower(): v for k, v in globals().items() if k.isupper() and isinstance(v, str)} + + +def qcolor(color: str, alpha: int | None = None): + """QColor from a palette hex string, optional 0-255 alpha. + + Hue lives here (theme), alpha stays at the call site (behavior). Lazy Qt + import so this module — and its self-check — stay importable without + PySide6. + """ + from PySide6.QtGui import QColor + + c = QColor(color) + if alpha is not None: + c.setAlpha(alpha) + return c + def build_app_stylesheet(theme: str) -> str: if theme == THEME_PORTRAIT: @@ -11,24 +315,24 @@ def build_app_stylesheet(theme: str) -> str: def _original_stylesheet() -> str: - return """ + return Template(""" QMainWindow, QWidget { - background-color: rgb(216, 228, 253); - color: rgb(30, 41, 59); + background-color: $background; + color: $text; } QWidget#mainContentRoot, QWidget#standardMainPage, QWidget#compactAutomationPage { - background-color: rgb(216, 228, 253); + background-color: $background; } QWidget#portraitModePage { - background-color: #071018; + background-color: $dark_bg; } QFrame#compactAutomationPanel { - background: #d8e4fd; + background: $background; border: none; border-radius: 18px; } @@ -38,42 +342,42 @@ def _original_stylesheet() -> str: QFrame#compactProgressCard, QFrame#compactQueueCard, QFrame#compactQueueItem { - background: #e6eefc; - border: 1px solid #b9ccee; + background: $compact_card_bg; + border: 1px solid $compact_border; border-radius: 16px; } QLabel#compactSectionTitle { background: transparent; - color: #17324d; + color: $compact_title; font-size: 14px; font-weight: 700; } QLabel#compactSectionHint { background: transparent; - color: #51657d; + color: $compact_hint; font-size: 12px; } QLabel#compactQueueTitle { background: transparent; - color: #51657d; + color: $compact_hint; font-size: 11px; font-weight: 700; } QLabel#compactQueueValue { background: transparent; - color: #10263a; + color: $compact_value; font-size: 14px; font-weight: 700; } QToolButton#compactMenuButton { - background: #cbdcf8; - color: #17324d; - border: 1px solid #9fb9e5; + background: $compact_menu_bg; + color: $compact_title; + border: 1px solid $compact_border; border-radius: 14px; padding: 10px 14px; font-size: 18px; @@ -81,12 +385,12 @@ def _original_stylesheet() -> str: } QToolButton#compactMenuButton:hover { - background: #bfd4f6; + background: $compact_menu_bg_hover; } QPushButton#compactPrimaryButton { - background: #2563eb; - color: white; + background: $primary; + color: $primary_text; border: none; border-radius: 14px; padding: 14px 18px; @@ -95,14 +399,14 @@ def _original_stylesheet() -> str: } QPushButton#compactPrimaryButton:hover { - background: #1d4ed8; + background: $primary_hover; } QPushButton#compactSecondaryButton, QToolButton#compactSecondaryButton { - background: #dfe9fb; - color: #17324d; - border: 1px solid #b2c7eb; + background: $secondary_bg; + color: $compact_title; + border: 1px solid $compact_border; border-radius: 14px; padding: 14px 18px; font-size: 14px; @@ -111,26 +415,26 @@ def _original_stylesheet() -> str: QPushButton#compactSecondaryButton:hover, QToolButton#compactSecondaryButton:hover { - background: #d3e1f8; + background: $secondary_bg_hover; } QFrame#alertBanner[alertKind="error"] { - background-color: #fbe4e6; - border: 2px solid #d97a84; + background-color: $error_bg; + border: 2px solid $error_border; border-radius: 12px; margin: 8px 12px 8px 12px; } QFrame#alertBanner[alertKind="success"] { - background-color: #e7f6ea; - border: 2px solid #7bbf8e; + background-color: $success_bg; + border: 2px solid $success_border; border-radius: 12px; margin: 8px 12px 8px 12px; } QFrame#alertBanner[alertKind="waiting"] { - background-color: #fff8e1; - border: 2px solid #ffb300; + background-color: $warning_bg; + border: 2px solid $warning_border; border-radius: 12px; margin: 8px 12px 8px 12px; } @@ -142,20 +446,20 @@ def _original_stylesheet() -> str: } QFrame#alertBanner[alertKind="error"] QLabel { - color: #8f1d2c; + color: $error_text; } QFrame#alertBanner[alertKind="success"] QLabel { - color: #1f6a3a; + color: $success_text; } QFrame#alertBanner[alertKind="waiting"] QLabel { - color: #e65100; + color: $warning_text; } QWidget#axisVideoStatusContainer[busyState="idle"] { border-radius: 12px; - background-color: #d9e2f2; + background-color: $status_idle_bg; } QWidget#axisVideoStatusContainer[busyState="active"] { @@ -173,18 +477,33 @@ def _original_stylesheet() -> str: QLabel#axisVideoStatusLabel { background-color: transparent; - color: #2f3b52; + color: $status_label_text; font-weight: bold; } + QTabWidget::pane { + border: 1px solid $border; + } + + QMainWindow::separator { + background: $border; + width: 4px; + height: 4px; + } + + QFrame#beamlineControls, + QFrame#dataCollectionSettings { + border: 1px solid $border; + } + QFrame#beamlineStatePanel { - background: rgb(216, 228, 253); - border-top: 1px solid rgb(185, 204, 238); + background: $background; + border-top: 1px solid $border; } QLabel#beamlineStateTitle { - background-color: #4B0082; - color: #ffffff; + background-color: $banner; + color: $banner_text; font-size: 16px; font-weight: 700; } @@ -193,13 +512,13 @@ def _original_stylesheet() -> str: QPushButton#beamlineStateToggleButton { border: none; background: transparent; - color: white; + color: $state_toggle_text; font-size: 14px; font-weight: 700; } QLabel#beamlineStateCurrentLabel { - color: rgb(30, 41, 59); + color: $state_current_text; font-size: 18px; font-weight: 700; padding-left: 4px; @@ -207,7 +526,7 @@ def _original_stylesheet() -> str: } QLabel#beamlineStateTellLabel { - color: rgb(55, 67, 87); + color: $state_tell_text; font-size: 15px; font-weight: 600; padding-left: 4px; @@ -216,8 +535,8 @@ def _original_stylesheet() -> str: QWidget#portraitRoot, QWidget#portraitRoot QWidget { - background: #071018; - color: #F5F7FA; + background: $dark_bg; + color: $dark_text; font-family: 'Inter', 'SF Pro Display', Arial, sans-serif; font-size: 14px; } @@ -228,13 +547,13 @@ def _original_stylesheet() -> str: } QWidget#portraitRoot QScrollBar:vertical { - background: #0E1A26; + background: $dark_surface; width: 4px; border-radius: 2px; } QWidget#portraitRoot QScrollBar::handle:vertical { - background: #1A3A36; + background: $dark_border; border-radius: 2px; min-height: 20px; } @@ -243,33 +562,33 @@ def _original_stylesheet() -> str: QWidget#portraitRoot QScrollBar::sub-line:vertical { height: 0px; } - """ + """).substitute(_palette()) def _portrait_stylesheet() -> str: - return """ + return Template(""" QMainWindow, QWidget { - background: #071018; - color: #F5F7FA; + background: $dark_bg; + color: $dark_text; } QWidget#mainContentRoot, QWidget#standardMainPage, QWidget#compactAutomationPage, QWidget#portraitModePage { - background: #071018; + background: $dark_bg; } QTabWidget::pane, QScrollArea, QDockWidget, QDockWidget > QWidget { - background: #071018; - color: #F5F7FA; + background: $dark_bg; + color: $dark_text; } QFrame#compactAutomationPanel { - background: #071018; + background: $dark_bg; border: none; border-radius: 18px; } @@ -279,42 +598,42 @@ def _portrait_stylesheet() -> str: QFrame#compactProgressCard, QFrame#compactQueueCard, QFrame#compactQueueItem { - background: #0E1A26; - border: 1px solid #1A3A36; + background: $dark_surface; + border: 1px solid $dark_border; border-radius: 16px; } QLabel#compactSectionTitle { background: transparent; - color: #62D8C8; + color: $dark_accent; font-size: 14px; font-weight: 700; } QLabel#compactSectionHint { background: transparent; - color: #8A9BB0; + color: $dark_muted; font-size: 12px; } QLabel#compactQueueTitle { background: transparent; - color: #8A9BB0; + color: $dark_muted; font-size: 11px; font-weight: 700; } QLabel#compactQueueValue { background: transparent; - color: #F5F7FA; + color: $dark_text; font-size: 14px; font-weight: 700; } QToolButton#compactMenuButton { - background: #132131; - color: #62D8C8; - border: 1px solid #1A3A36; + background: $dark_elevated; + color: $dark_accent; + border: 1px solid $dark_border; border-radius: 14px; padding: 10px 14px; font-size: 18px; @@ -322,12 +641,12 @@ def _portrait_stylesheet() -> str: } QToolButton#compactMenuButton:hover { - background: #1A3A36; + background: $dark_border; } QPushButton#compactPrimaryButton { - background: #62D8C8; - color: #071018; + background: $dark_accent; + color: $dark_bg; border: none; border-radius: 14px; padding: 14px 18px; @@ -336,14 +655,14 @@ def _portrait_stylesheet() -> str: } QPushButton#compactPrimaryButton:hover { - background: #7ce6d8; + background: $dark_accent_hover; } QPushButton#compactSecondaryButton, QToolButton#compactSecondaryButton { - background: #132131; - color: #F5F7FA; - border: 1px solid #1A3A36; + background: $dark_elevated; + color: $dark_text; + border: 1px solid $dark_border; border-radius: 14px; padding: 14px 18px; font-size: 14px; @@ -352,26 +671,26 @@ def _portrait_stylesheet() -> str: QPushButton#compactSecondaryButton:hover, QToolButton#compactSecondaryButton:hover { - background: #1A3A36; + background: $dark_border; } QFrame#alertBanner[alertKind="error"] { - background: #1A0E0E; - border: 2px solid #8f1d2c; + background: $dark_error_bg; + border: 2px solid $dark_error_border; border-radius: 12px; margin: 8px 12px 8px 12px; } QFrame#alertBanner[alertKind="success"] { - background: #0E1A12; - border: 2px solid #2a7a44; + background: $dark_success_bg; + border: 2px solid $dark_success_border; border-radius: 12px; margin: 8px 12px 8px 12px; } QFrame#alertBanner[alertKind="waiting"] { - background: #2B2208; - border: 2px solid #ffb300; + background: $dark_warning_bg; + border: 2px solid $dark_warning_border; border-radius: 12px; margin: 8px 12px 8px 12px; } @@ -383,20 +702,20 @@ def _portrait_stylesheet() -> str: } QFrame#alertBanner[alertKind="error"] QLabel { - color: #ffb3bc; + color: $dark_error_text; } QFrame#alertBanner[alertKind="success"] QLabel { - color: #a8f0c0; + color: $dark_success_text; } QFrame#alertBanner[alertKind="waiting"] QLabel { - color: #ffd166; + color: $dark_warning_text; } QWidget#axisVideoStatusContainer[busyState="idle"] { border-radius: 12px; - background-color: #132131; + background-color: $dark_elevated; } QWidget#axisVideoStatusContainer[busyState="active"] { @@ -414,18 +733,18 @@ def _portrait_stylesheet() -> str: QLabel#axisVideoStatusLabel { background-color: transparent; - color: #8A9BB0; + color: $dark_muted; font-weight: bold; } QFrame#beamlineStatePanel { - background: #0E1A26; - border-top: 1px solid #1A3A36; + background: $dark_surface; + border-top: 1px solid $dark_border; } QLabel#beamlineStateTitle { - background-color: #132131; - color: #F5F7FA; + background-color: $dark_elevated; + color: $dark_text; font-size: 16px; font-weight: 700; } @@ -434,13 +753,13 @@ def _portrait_stylesheet() -> str: QPushButton#beamlineStateToggleButton { border: none; background: transparent; - color: #F5F7FA; + color: $dark_text; font-size: 14px; font-weight: 700; } QLabel#beamlineStateCurrentLabel { - color: #F5F7FA; + color: $dark_text; font-size: 18px; font-weight: 700; padding-left: 4px; @@ -448,7 +767,7 @@ def _portrait_stylesheet() -> str: } QLabel#beamlineStateTellLabel { - color: #8A9BB0; + color: $dark_muted; font-size: 15px; font-weight: 600; padding-left: 4px; @@ -457,8 +776,8 @@ def _portrait_stylesheet() -> str: QWidget#portraitRoot, QWidget#portraitRoot QWidget { - background: #071018; - color: #F5F7FA; + background: $dark_bg; + color: $dark_text; font-family: 'Inter', 'SF Pro Display', Arial, sans-serif; font-size: 14px; } @@ -469,13 +788,13 @@ def _portrait_stylesheet() -> str: } QWidget#portraitRoot QScrollBar:vertical { - background: #0E1A26; + background: $dark_surface; width: 4px; border-radius: 2px; } QWidget#portraitRoot QScrollBar::handle:vertical { - background: #1A3A36; + background: $dark_border; border-radius: 2px; min-height: 20px; } @@ -484,4 +803,11 @@ def _portrait_stylesheet() -> str: QWidget#portraitRoot QScrollBar::sub-line:vertical { height: 0px; } - """ + """).substitute(_palette()) + + +if __name__ == "__main__": + # ponytail: smallest check that fails if a $name has no matching constant + for _theme in (THEME_ORIGINAL, THEME_PORTRAIT): + assert "$" not in build_app_stylesheet(_theme) + print("gude")