feat: theme-aware beamline state panel

The panel paints availability/message colors in code per DAQ tick, so
QSS can't retint it — state_colors(theme) hands it a per-theme dict
(Catppuccin Mocha inks for Sunset) and _apply_theme pushes theme flips.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 10:35:54 +02:00
co-authored by Claude Fable 5
parent 1946430f0d
commit 7801025802
3 changed files with 53 additions and 16 deletions
+2
View File
@@ -1683,6 +1683,8 @@ class MainWindow(QMainWindow):
def _apply_theme(self) -> None:
self.setStyleSheet(build_app_stylesheet(self._theme_mode))
# State colors are painted in code per DAQ tick — QSS can't reach them.
self.beamline_state_panel.set_theme(self._theme_mode)
def _restore_theme_settings(self) -> None:
settings = QSettings("PSI", "AareGUI")
+26 -15
View File
@@ -5,13 +5,7 @@ from PySide6.QtCore import Qt, QTimer, Signal, Slot
from PySide6.QtGui import QCursor, QFont, QFontMetrics
from PySide6.QtWidgets import QFrame, QHBoxLayout, QLabel, QMenu, QPushButton, QSizePolicy, QToolTip
from aare.gui.styles import (
FONT_VALUE,
STATE_AVAILABLE,
STATE_MSG_ERROR,
STATE_MSG_INFO,
STATE_UNAVAILABLE,
)
from aare.gui.styles import FONT_VALUE, THEME_ORIGINAL, state_colors
# Shortcut transitions from the "Available transitions" menu in
# widgets/status_bar.py show_state_menu — these come ON TOP of the one-hop
@@ -146,8 +140,9 @@ class BeamlineStatePanel(QFrame):
self._hover_hint_timer.setInterval(3000)
self._hover_hint_timer.timeout.connect(self._show_hover_hint)
self._available_color = STATE_AVAILABLE
self._unavailable_color = STATE_UNAVAILABLE
# Per-theme colors (MainWindow._apply_theme calls set_theme).
self._colors = state_colors(THEME_ORIGINAL)
self._separators: list[QLabel] = []
layout = QHBoxLayout(self)
layout.setContentsMargins(10, 2, 10, 2)
@@ -159,9 +154,8 @@ class BeamlineStatePanel(QFrame):
for index, (state, label) in enumerate(self._ENTRIES):
if index:
separator = QLabel("", self)
separator.setStyleSheet(
f"color: {STATE_UNAVAILABLE}; background: transparent; border: none; font-size: {FONT_VALUE};"
)
self._style_separator(separator)
self._separators.append(separator)
layout.addWidget(separator)
button = HoverableButton(label, self)
button.setFlat(True)
@@ -310,6 +304,21 @@ class BeamlineStatePanel(QFrame):
elif state == BeamlineStateEnum.XrayFluorescence:
self.xray_fluorescence.emit()
def _style_separator(self, separator: QLabel) -> None:
separator.setStyleSheet(
f"color: {self._colors['unavailable']};"
f" background: transparent; border: none; font-size: {FONT_VALUE};"
)
def set_theme(self, theme: str) -> None:
"""Adopt the theme's state colors (MainWindow._apply_theme calls this
on every switch — the colors are painted in code, so the app QSS
alone cannot restyle them)."""
self._colors = state_colors(theme)
for separator in self._separators:
self._style_separator(separator)
self._apply_highlight()
def _apply_highlight(self) -> None:
available = self._available_targets()
for state, button in self._buttons.items():
@@ -325,14 +334,16 @@ class BeamlineStatePanel(QFrame):
# No backgrounds, no rounded corners.
if is_current or is_pending:
color = (
STATE_MSG_ERROR if state == BeamlineStateEnum.Maintenance else STATE_MSG_INFO
self._colors["error"]
if state == BeamlineStateEnum.Maintenance
else self._colors["info"]
)
bold = True
elif is_available:
color = self._available_color
color = self._colors["available"]
bold = False
else:
color = self._unavailable_color
color = self._colors["unavailable"]
bold = False
# Font set in code (not QSS) so _update_label_mode can measure
+25 -1
View File
@@ -333,11 +333,35 @@ STATUS_VACANT = "#ffff00" # baton vacant (was "yellow")
STATUS_REQUEST = "#00ffff" # baton request (was "cyan")
# -- Beamline state panel ---------------------------------------------------
# The panel paints these in code per DAQ tick (data-driven), not via QSS.
# The panel paints these in code per DAQ tick (data-driven), so it asks
# state_colors(theme) below instead of QSS. Light values unchanged; dark
# values are Catppuccin Mocha so they stay readable on the sunset sky.
STATE_AVAILABLE = "#ed8936"
STATE_UNAVAILABLE = "#8c96a5"
STATE_MSG_ERROR = "#c81e1e"
STATE_MSG_INFO = "#005caa"
DARK_STATE_AVAILABLE = "#fab387" # mocha peach
DARK_STATE_UNAVAILABLE = "#7f849c" # mocha overlay1
DARK_STATE_MSG_ERROR = "#f38ba8" # mocha red
DARK_STATE_MSG_INFO = "#89b4fa" # mocha blue
def state_colors(theme: str) -> dict[str, str]:
"""Beamline-state text colors for the given theme."""
if theme == THEME_PORTRAIT:
return {
"available": DARK_STATE_AVAILABLE,
"unavailable": DARK_STATE_UNAVAILABLE,
"error": DARK_STATE_MSG_ERROR,
"info": DARK_STATE_MSG_INFO,
}
return {
"available": STATE_AVAILABLE,
"unavailable": STATE_UNAVAILABLE,
"error": STATE_MSG_ERROR,
"info": STATE_MSG_INFO,
}
# -- Sample tables + raster grid --------------------------------------------
SAMPLE_ROW_ACTIVE_BG = "#ff6600"