diff --git a/src/aare/gui/styles.py b/src/aare/gui/styles.py index 83b3adb1..8a86223b 100644 --- a/src/aare/gui/styles.py +++ b/src/aare/gui/styles.py @@ -16,7 +16,8 @@ THEME_PORTRAIT = "portrait" # -- Light theme ------------------------------------------------------------ BACKGROUND = "#e2e7ee" BANNER = "#a2b7e4" -BANNER_TEXT = "#263043" # must contrast with BANNER +BANNER_TEXT = "#F8F8FC" # must contrast with BANNER +BANNER_TEXT_SHADOW = "#000000" # soft shadow under banner titles; alpha in TitleLabel TEXT = "#263043" SURFACE = "#f2f2f2" # input fields, cards, group boxes @@ -111,6 +112,8 @@ CHIP_INFO_BG = "#e8f1ff" CHIP_INFO_TEXT = "#12406a" # -- Status cards (beamline recovery, local contact error frame) ------------ +# TODO: recovery-card colors are inherited from the old ad-hoc design and +# stand out against the app palette — retheme them here when ready. WARN_CARD_BORDER = "#f0c36d" BAD_CARD_BORDER = "#e6a8a8" INFO_CARD_BG = "#eef6ff" @@ -119,6 +122,8 @@ PENDING_CARD_BG = "#fff7db" PENDING_CARD_BORDER = "#e7cb73" # -- Log panel -------------------------------------------------------------- +# TODO: console-log notification colors are inherited from the old ad-hoc +# design and stand out against the app palette — retheme them here when ready. LOG_BORDER = "#8a8a8a" LOG_PANEL_BG = "#fff4f4" LOG_ERROR_BG = "#fff1f1" @@ -204,7 +209,7 @@ 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" +TOOLTIP_TEXT = "#e6e6e6" # camera coords tooltip pen — NOT the QToolTip popup MARK_TOOLTIP_GOLD = "#ffd700" MARK_TOOLTIP_ORANGE = "#ffa500" MARK_TOOLTIP_RED = "#ff0000" @@ -284,6 +289,32 @@ PANEL_BORDER_DARK = "#bdbdbd" PANEL_BORDER_LIGHT = "#e0e0e0" TUTORIAL_BORDER = "#555555" TUTORIAL_HIGHLIGHT = "#ffff00" # widget spotlight pen (was Qt.yellow) + +# -- Typography ladder (role-named; tune sizes here, not in widgets) -------- +FONT_HERO = "28px" # portrait main action button +FONT_ALERT = "18px" # alert banner text +FONT_VALUE = "18px" # prominent values, big glyph buttons +FONT_TITLE = "16px" # section / panel titles +FONT_BODY_LG = "14px" # primary buttons, emphasized body text — always bold +FONT_BODY = "14px" # default UI text +FONT_LABEL = "14px" # form labels, secondary text — always bold +FONT_HINT = "12px" # hints +FONT_FINE = "11px" # fine print, queue titles + +# -- Hover tooltips (the QToolTip popup; styled borderless) ----------------- +TOOLTIP_BG = "#f7f9fc" +TOOLTIP_FG = "#263043" +DARK_TOOLTIP_BG = "#363a4f" # surface0 +DARK_TOOLTIP_FG = "#cad3f5" # text + +# -- Scrollbars (rounded, no arrows: grey track, darker draggable handle) --- +SCROLLBAR_TRACK = "#d8dde5" +SCROLLBAR_HANDLE = "#a8b2c0" +SCROLLBAR_HANDLE_HOVER = "#8794a6" + +# -- Cards ------------------------------------------------------------------ +CARD_RADIUS = "12px" # one radius for every card-shaped frame +FLAT_CARD_RADIUS = "0px" # recovery + console-log cards stay square # --------------------------------------------------------------------------- @@ -308,6 +339,27 @@ def qcolor(color: str, alpha: int | None = None): return c +def card_style( + bg: str, + border: str, + text: str | None = None, + *, + selector: str = "QFrame", + border_px: int = 1, + radius: str = CARD_RADIUS, + extra: str = "", +) -> str: + """One QSS rule for every card-shaped frame: geometry stays uniform + (CARD_RADIUS unless overridden), only the role colors vary. `extra` + appends declarations (padding, font-weight, …) inside the same rule.""" + color = f" color: {text};" if text else "" + tail = f" {extra}" if extra else "" + return ( + f"{selector} {{ background: {bg};{color}" + f" border: {border_px}px solid {border}; border-radius: {radius};{tail} }}" + ) + + def build_app_stylesheet(theme: str) -> str: if theme == THEME_PORTRAIT: return _portrait_stylesheet() @@ -331,6 +383,15 @@ def _original_stylesheet() -> str: background-color: $dark_bg; } + /* Borderless hover hints. The transparent border is required — QToolTip + only honours the stylesheet background once a border is set. */ + QToolTip { + background-color: $tooltip_bg; + color: $tooltip_fg; + border: 1px solid transparent; + padding: 4px 6px; + } + QFrame#compactAutomationPanel { background: $background; border: none; @@ -350,27 +411,27 @@ def _original_stylesheet() -> str: QLabel#compactSectionTitle { background: transparent; color: $compact_title; - font-size: 14px; + font-size: $font_body; font-weight: 700; } QLabel#compactSectionHint { background: transparent; color: $compact_hint; - font-size: 12px; + font-size: $font_hint; } QLabel#compactQueueTitle { background: transparent; color: $compact_hint; - font-size: 11px; + font-size: $font_fine; font-weight: 700; } QLabel#compactQueueValue { background: transparent; color: $compact_value; - font-size: 14px; + font-size: $font_body; font-weight: 700; } @@ -380,7 +441,7 @@ def _original_stylesheet() -> str: border: 1px solid $compact_border; border-radius: 14px; padding: 10px 14px; - font-size: 18px; + font-size: $font_value; font-weight: 700; } @@ -394,7 +455,7 @@ def _original_stylesheet() -> str: border: none; border-radius: 14px; padding: 14px 18px; - font-size: 15px; + font-size: $font_body_lg; font-weight: 700; } @@ -409,7 +470,7 @@ def _original_stylesheet() -> str: border: 1px solid $compact_border; border-radius: 14px; padding: 14px 18px; - font-size: 14px; + font-size: $font_body; font-weight: 700; } @@ -421,27 +482,27 @@ def _original_stylesheet() -> str: QFrame#alertBanner[alertKind="error"] { background-color: $error_bg; border: 2px solid $error_border; - border-radius: 12px; + border-radius: $card_radius; margin: 8px 12px 8px 12px; } QFrame#alertBanner[alertKind="success"] { background-color: $success_bg; border: 2px solid $success_border; - border-radius: 12px; + border-radius: $card_radius; margin: 8px 12px 8px 12px; } QFrame#alertBanner[alertKind="waiting"] { background-color: $warning_bg; border: 2px solid $warning_border; - border-radius: 12px; + border-radius: $card_radius; margin: 8px 12px 8px 12px; } QFrame#alertBanner QLabel { font-weight: 700; - font-size: 20px; + font-size: $font_alert; padding: 2px 6px 2px 6px; } @@ -458,12 +519,12 @@ def _original_stylesheet() -> str: } QWidget#axisVideoStatusContainer[busyState="idle"] { - border-radius: 12px; + border-radius: $card_radius; background-color: $status_idle_bg; } QWidget#axisVideoStatusContainer[busyState="active"] { - border-radius: 12px; + border-radius: $card_radius; } QLabel#axisVideoStatusDot { @@ -504,7 +565,7 @@ def _original_stylesheet() -> str: QLabel#beamlineStateTitle { background-color: $banner; color: $banner_text; - font-size: 16px; + font-size: $font_title; font-weight: 700; } @@ -513,13 +574,13 @@ def _original_stylesheet() -> str: border: none; background: transparent; color: $state_toggle_text; - font-size: 14px; + font-size: $font_body; font-weight: 700; } QLabel#beamlineStateCurrentLabel { color: $state_current_text; - font-size: 18px; + font-size: $font_value; font-weight: 700; padding-left: 4px; background: transparent; @@ -527,18 +588,75 @@ def _original_stylesheet() -> str: QLabel#beamlineStateTellLabel { color: $state_tell_text; - font-size: 15px; - font-weight: 600; + font-size: $font_body_lg; + font-weight: 700; padding-left: 4px; background: transparent; } + /* Plain scroll containers stay frameless; data views (tables, trees, + text/log views) keep their native frame — those borders are wanted. */ + QScrollArea { + border: none; + } + + /* Soft scrollbars: rounded track + draggable handle, no end arrows. */ + QScrollBar:vertical { + background: $scrollbar_track; + width: 10px; + border: none; + border-radius: 5px; + margin: 0px; + } + + QScrollBar:horizontal { + background: $scrollbar_track; + height: 10px; + border: none; + border-radius: 5px; + margin: 0px; + } + + QScrollBar::handle:vertical { + background: $scrollbar_handle; + border-radius: 5px; + min-height: 24px; + } + + QScrollBar::handle:horizontal { + background: $scrollbar_handle; + border-radius: 5px; + min-width: 24px; + } + + QScrollBar::handle:vertical:hover, + QScrollBar::handle:vertical:pressed, + QScrollBar::handle:horizontal:hover, + QScrollBar::handle:horizontal:pressed { + background: $scrollbar_handle_hover; + } + + QScrollBar::add-line, QScrollBar::sub-line { + width: 0px; + height: 0px; + } + + QScrollBar::add-page, QScrollBar::sub-page { + background: transparent; + } + + /* Kill the square filler where the two scrollbars meet. */ + QAbstractScrollArea::corner { + background: transparent; + border: none; + } + QWidget#portraitRoot, QWidget#portraitRoot QWidget { background: $dark_bg; color: $dark_text; font-family: 'Inter', 'SF Pro Display', Arial, sans-serif; - font-size: 14px; + font-size: $font_body; } QWidget#portraitRoot QScrollArea { @@ -587,6 +705,15 @@ def _portrait_stylesheet() -> str: color: $dark_text; } + /* Borderless hover hints. The transparent border is required — QToolTip + only honours the stylesheet background once a border is set. */ + QToolTip { + background-color: $dark_tooltip_bg; + color: $dark_tooltip_fg; + border: 1px solid transparent; + padding: 4px 6px; + } + QFrame#compactAutomationPanel { background: $dark_bg; border: none; @@ -606,27 +733,27 @@ def _portrait_stylesheet() -> str: QLabel#compactSectionTitle { background: transparent; color: $dark_accent; - font-size: 14px; + font-size: $font_body; font-weight: 700; } QLabel#compactSectionHint { background: transparent; color: $dark_muted; - font-size: 12px; + font-size: $font_hint; } QLabel#compactQueueTitle { background: transparent; color: $dark_muted; - font-size: 11px; + font-size: $font_fine; font-weight: 700; } QLabel#compactQueueValue { background: transparent; color: $dark_text; - font-size: 14px; + font-size: $font_body; font-weight: 700; } @@ -636,7 +763,7 @@ def _portrait_stylesheet() -> str: border: 1px solid $dark_border; border-radius: 14px; padding: 10px 14px; - font-size: 18px; + font-size: $font_value; font-weight: 700; } @@ -650,7 +777,7 @@ def _portrait_stylesheet() -> str: border: none; border-radius: 14px; padding: 14px 18px; - font-size: 15px; + font-size: $font_body_lg; font-weight: 700; } @@ -665,7 +792,7 @@ def _portrait_stylesheet() -> str: border: 1px solid $dark_border; border-radius: 14px; padding: 14px 18px; - font-size: 14px; + font-size: $font_body; font-weight: 700; } @@ -677,27 +804,27 @@ def _portrait_stylesheet() -> str: QFrame#alertBanner[alertKind="error"] { background: $dark_error_bg; border: 2px solid $dark_error_border; - border-radius: 12px; + border-radius: $card_radius; margin: 8px 12px 8px 12px; } QFrame#alertBanner[alertKind="success"] { background: $dark_success_bg; border: 2px solid $dark_success_border; - border-radius: 12px; + border-radius: $card_radius; margin: 8px 12px 8px 12px; } QFrame#alertBanner[alertKind="waiting"] { background: $dark_warning_bg; border: 2px solid $dark_warning_border; - border-radius: 12px; + border-radius: $card_radius; margin: 8px 12px 8px 12px; } QFrame#alertBanner QLabel { font-weight: 700; - font-size: 20px; + font-size: $font_alert; padding: 2px 6px 2px 6px; } @@ -714,12 +841,12 @@ def _portrait_stylesheet() -> str: } QWidget#axisVideoStatusContainer[busyState="idle"] { - border-radius: 12px; + border-radius: $card_radius; background-color: $dark_elevated; } QWidget#axisVideoStatusContainer[busyState="active"] { - border-radius: 12px; + border-radius: $card_radius; } QLabel#axisVideoStatusDot { @@ -745,7 +872,7 @@ def _portrait_stylesheet() -> str: QLabel#beamlineStateTitle { background-color: $dark_elevated; color: $dark_text; - font-size: 16px; + font-size: $font_title; font-weight: 700; } @@ -754,13 +881,13 @@ def _portrait_stylesheet() -> str: border: none; background: transparent; color: $dark_text; - font-size: 14px; + font-size: $font_body; font-weight: 700; } QLabel#beamlineStateCurrentLabel { color: $dark_text; - font-size: 18px; + font-size: $font_value; font-weight: 700; padding-left: 4px; background: transparent; @@ -768,18 +895,75 @@ def _portrait_stylesheet() -> str: QLabel#beamlineStateTellLabel { color: $dark_muted; - font-size: 15px; - font-weight: 600; + font-size: $font_body_lg; + font-weight: 700; padding-left: 4px; background: transparent; } + /* Plain scroll containers stay frameless; data views (tables, trees, + text/log views) keep their native frame — those borders are wanted. */ + QScrollArea { + border: none; + } + + /* Soft scrollbars: rounded track + draggable handle, no end arrows. */ + QScrollBar:vertical { + background: $dark_surface; + width: 10px; + border: none; + border-radius: 5px; + margin: 0px; + } + + QScrollBar:horizontal { + background: $dark_surface; + height: 10px; + border: none; + border-radius: 5px; + margin: 0px; + } + + QScrollBar::handle:vertical { + background: $dark_border; + border-radius: 5px; + min-height: 24px; + } + + QScrollBar::handle:horizontal { + background: $dark_border; + border-radius: 5px; + min-width: 24px; + } + + QScrollBar::handle:vertical:hover, + QScrollBar::handle:vertical:pressed, + QScrollBar::handle:horizontal:hover, + QScrollBar::handle:horizontal:pressed { + background: $dark_muted; + } + + QScrollBar::add-line, QScrollBar::sub-line { + width: 0px; + height: 0px; + } + + QScrollBar::add-page, QScrollBar::sub-page { + background: transparent; + } + + /* Kill the square filler where the two scrollbars meet. */ + QAbstractScrollArea::corner { + background: transparent; + border: none; + } + QWidget#portraitRoot, QWidget#portraitRoot QWidget { background: $dark_bg; color: $dark_text; font-family: 'Inter', 'SF Pro Display', Arial, sans-serif; - font-size: 14px; + font-size: $font_body; } QWidget#portraitRoot QScrollArea {