style: add typography, tooltip, scrollbar, and card tokens to the palette

The QSS gains a role-named font-size ladder (FONT_HERO..FONT_FINE),
borderless QToolTip styling for both themes, soft rounded scrollbars
without end arrows, and a card_style() helper with one shared
CARD_RADIUS so card-shaped frames keep uniform geometry. Banner text
flips to near-white with a shadow constant for the painted emboss.
Recovery-card and console-log colors are marked TODO for a later
retheme.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-07 11:54:23 +02:00
co-authored by Claude Fable 5
parent ae7bb02a9a
commit 219bcd9b35
+224 -40
View File
@@ -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 {