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:
+224
-40
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user