From 43d3d1020119e72fe3c99d91a3f89dc8d6fb7018 Mon Sep 17 00:00:00 2001 From: Pierre Raybaut <1311787+PierreRaybaut@users.noreply.github.com> Date: Fri, 9 Oct 2026 11:00:36 +0200 Subject: [PATCH] fix(welcome): follow color mode changes in subdued texts Assisted-by: Claude Opus 5.5 --- datalab/gui/main.py | 4 ++- .../features/common/welcome_unit_test.py | 34 +++++++++++++++++++ datalab/widgets/expandabletext.py | 30 ++++++++++++---- 3 files changed, 61 insertions(+), 7 deletions(-) diff --git a/datalab/gui/main.py b/datalab/gui/main.py index 0e159a839..9bfa7a9fa 100644 --- a/datalab/gui/main.py +++ b/datalab/gui/main.py @@ -91,6 +91,7 @@ from datalab.webapi.actions import WebApiActions from datalab.widgets import instconfviewer from datalab.widgets import status as dl_status +from datalab.widgets.expandabletext import refresh_subdued_colors if TYPE_CHECKING: from typing import Literal @@ -2390,9 +2391,10 @@ def _about(self) -> None: # pragma: no cover ) def _update_extra_color_mode(self) -> None: - """Update the macro panel color mode""" + """Update the macro panel color mode and the subdued text colors""" if self.macropanel is not None: self.macropanel.update_color_mode() + refresh_subdued_colors() # Settings changes are intentionally dispatched in one place because each # option may trigger a specific live UI update or panel refresh. diff --git a/datalab/tests/features/common/welcome_unit_test.py b/datalab/tests/features/common/welcome_unit_test.py index e81b1df8e..348e7707d 100644 --- a/datalab/tests/features/common/welcome_unit_test.py +++ b/datalab/tests/features/common/welcome_unit_test.py @@ -9,6 +9,8 @@ from types import SimpleNamespace import numpy as np +from guidata import qthelpers as guidata_qth +from plotpy import config as plotpy_config from qtpy import QtCore as QC from qtpy import QtGui as QG from qtpy import QtWidgets as QW @@ -120,6 +122,38 @@ def create_gaussian(menu: QW.QMenu, *_args) -> None: Conf.welcome_on_startup.set(initial) +def test_welcome_page_follows_color_mode(monkeypatch) -> None: + """Subdued texts remain legible when the color mode is switched at runtime""" + # Switching the color mode changes these globals: restore them at teardown + monkeypatch.setenv(guidata_qth.ENV_COLOR_MODE, guidata_qth.get_color_mode()) + monkeypatch.setattr(guidata_qth, "CURRENT_THEME", guidata_qth.CURRENT_THEME) + initial_mode = Conf.color_mode.get() + description = _("Generate a 1D signal or 2D image from a template.") + with datalab_test_app_context(console=False) as win: + page = win.welcomepanel + labels = [page.start_title] + [ + label + for label in page.entries["create"].findChildren(QW.QLabel) + if label.text() == description + ] + assert len(labels) == 2 + try: + # Light to dark, then dark to light + for mode in ("light", "dark", "light"): + Conf.color_mode.set(mode) + win._update_color_mode() # pylint: disable=protected-access + palette = QW.QApplication.instance().palette() + window = palette.color(QG.QPalette.Window).lightness() + for label in labels: + text = label.palette().color(QG.QPalette.WindowText) + alpha = text.alphaF() + lightness = alpha * text.lightness() + (1 - alpha) * window + assert abs(lightness - window) > 64, (mode, label.text()) + finally: + Conf.color_mode.set(initial_mode) + plotpy_config.set_plotpy_color_mode(guidata_qth.LIGHT) + + def _application_plugin( plugin_id: str, tiles: tuple[WelcomeTile, ...] | Exception, diff --git a/datalab/widgets/expandabletext.py b/datalab/widgets/expandabletext.py index b14912a5e..63fdc04fa 100644 --- a/datalab/widgets/expandabletext.py +++ b/datalab/widgets/expandabletext.py @@ -33,6 +33,9 @@ #: Default left indent in pixels DEFAULT_INDENT: int = 20 +#: Dynamic property marking the widgets with a subdued text color +SUBDUED_PROPERTY: str = "datalab_subdued" + # --- Palette helpers (public — reused by other modules) -------------------------- @@ -52,18 +55,33 @@ def apply_palette_color(widget: QW.QWidget, color: QG.QColor) -> None: def apply_subdued_color(widget: QW.QWidget) -> None: """Apply a subdued/secondary text color on *widget*. - Uses the ``QPalette.PlaceholderText`` role (Qt 5.12+) which provides a - theme-native "dimmed" color. Sets both ``WindowText`` (for ``QLabel``) - and ``Text`` (for ``QTextBrowser`` / ``QTextEdit``) roles. + The application ``WindowText`` (for ``QLabel``) and ``Text`` (for + ``QTextBrowser`` / ``QTextEdit``) colors are dimmed to half opacity, like Qt + placeholder texts. The widget is marked so that :func:`refresh_subdued_colors` + can follow color mode changes. Args: widget: Target widget """ - subdued = QW.QApplication.instance().palette().color(QG.QPalette.PlaceholderText) + app_palette = QW.QApplication.instance().palette() palette = widget.palette() - palette.setColor(QG.QPalette.WindowText, subdued) - palette.setColor(QG.QPalette.Text, subdued) + for role in (QG.QPalette.WindowText, QG.QPalette.Text): + color = QG.QColor(app_palette.color(role)) + color.setAlpha(128) + palette.setColor(role, color) widget.setPalette(palette) + widget.setProperty(SUBDUED_PROPERTY, True) + + +def refresh_subdued_colors() -> None: + """Update the subdued text colors after an application palette change. + + Subdued colors are set explicitly on widgets, so they do not follow the + application palette by themselves. + """ + for widget in QW.QApplication.allWidgets(): + if widget.property(SUBDUED_PROPERTY): + apply_subdued_color(widget) # --- Internal helpers ------------------------------------------------------------