feat: cross-fade and palette flip on theme switch
Freeze the old look in a click-through screenshot overlay and fade it out over the restyled window — QSS has no transitions and animating the palette would re-polish every widget per frame. Sunset also flips the app-palette text roles so native primitives QSS can't recolor (spin and combo arrow glyphs) follow the theme. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,15 +15,25 @@ from aarecommon.models.models import (
|
||||
SessionsStateEnum,
|
||||
TokenData,
|
||||
)
|
||||
from PySide6.QtCore import QEvent, QSettings, Qt, QTimer, Signal, Slot
|
||||
from PySide6.QtGui import QAction, QActionGroup, QColor, QCursor, QGuiApplication, QKeySequence
|
||||
from PySide6.QtCore import QEvent, QPropertyAnimation, QSettings, Qt, QTimer, Signal, Slot
|
||||
from PySide6.QtGui import (
|
||||
QAction,
|
||||
QActionGroup,
|
||||
QColor,
|
||||
QCursor,
|
||||
QGuiApplication,
|
||||
QKeySequence,
|
||||
QPalette,
|
||||
)
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication,
|
||||
QCheckBox,
|
||||
QDockWidget,
|
||||
QFrame,
|
||||
QGraphicsColorizeEffect,
|
||||
QGraphicsOpacityEffect,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QMainWindow,
|
||||
QMessageBox,
|
||||
QPushButton,
|
||||
@@ -72,10 +82,13 @@ from aare.gui.scan_logic.rotation_scan_manager import RotationScanManager
|
||||
from aare.gui.scan_logic.sample_mount_logic import SampleMountLogic
|
||||
from aare.gui.styles import (
|
||||
APP_BACKGROUND,
|
||||
DARK_TEXT,
|
||||
DOCK_CONTENT_LEFT_PAD,
|
||||
THEME_FADE_MS,
|
||||
THEME_ORIGINAL,
|
||||
THEME_PORTRAIT,
|
||||
build_app_stylesheet,
|
||||
qcolor,
|
||||
)
|
||||
|
||||
# Threads
|
||||
@@ -1682,9 +1695,46 @@ class MainWindow(QMainWindow):
|
||||
self.tutorial_manager.start("manual_workflow_demo")
|
||||
|
||||
def _apply_theme(self) -> None:
|
||||
# Screenshot cross-fade (THEME_FADE_MS in styles.py): freeze the old
|
||||
# look in a click-through overlay, restyle beneath it in one normal
|
||||
# repolish, fade the overlay out. QSS has no transitions, and
|
||||
# animating the palette would re-polish every widget per frame.
|
||||
old_look = self.grab() if self.isVisible() else None
|
||||
# Native primitives QSS can't recolor (spin/combo arrow glyphs) draw
|
||||
# from the app palette — flip its text roles with the theme.
|
||||
app = QApplication.instance()
|
||||
if not hasattr(self, "_default_palette"):
|
||||
self._default_palette = app.palette()
|
||||
if self._theme_mode == THEME_PORTRAIT:
|
||||
palette = QPalette(self._default_palette)
|
||||
for role in (
|
||||
QPalette.ColorRole.ButtonText,
|
||||
QPalette.ColorRole.Text,
|
||||
QPalette.ColorRole.WindowText,
|
||||
):
|
||||
palette.setColor(role, qcolor(DARK_TEXT))
|
||||
app.setPalette(palette)
|
||||
else:
|
||||
app.setPalette(self._default_palette)
|
||||
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)
|
||||
if old_look is None:
|
||||
return
|
||||
overlay = QLabel(self)
|
||||
overlay.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, True)
|
||||
overlay.setPixmap(old_look)
|
||||
overlay.setGeometry(self.rect())
|
||||
overlay.raise_()
|
||||
overlay.show()
|
||||
effect = QGraphicsOpacityEffect(overlay)
|
||||
overlay.setGraphicsEffect(effect)
|
||||
fade = QPropertyAnimation(effect, b"opacity", overlay)
|
||||
fade.setDuration(THEME_FADE_MS)
|
||||
fade.setStartValue(1.0)
|
||||
fade.setEndValue(0.0)
|
||||
fade.finished.connect(overlay.deleteLater)
|
||||
fade.start(QPropertyAnimation.DeletionPolicy.DeleteWhenStopped)
|
||||
|
||||
def _restore_theme_settings(self) -> None:
|
||||
settings = QSettings("PSI", "AareGUI")
|
||||
|
||||
@@ -99,6 +99,9 @@ DOCK_CONTENT_LEFT_PAD = 10
|
||||
# lives in MainWindow.event(); the QSS :hover part picks the one separator.
|
||||
SEPARATOR_HINT = "rgba(168, 178, 192, 50%)" # scrollbar-track grey @50%
|
||||
|
||||
# Theme-switch screenshot cross-fade duration (int ms, used in code).
|
||||
THEME_FADE_MS = 250
|
||||
|
||||
# Tab face fill: the selected Dewar/Auxiliary tab AND the unchecked
|
||||
# All/Queued/Flagged/Measured filter buttons share this background.
|
||||
TAB_FACE_BG = "#ffffff"
|
||||
|
||||
Reference in New Issue
Block a user