diff --git a/bec_widgets/utils/bec_widget.py b/bec_widgets/utils/bec_widget.py index a1c7e176..02f86975 100644 --- a/bec_widgets/utils/bec_widget.py +++ b/bec_widgets/utils/bec_widget.py @@ -66,7 +66,7 @@ class BECWidget(BECConnector): if hasattr(qapp, "theme_signal"): qapp.theme_signal.theme_updated.connect(self._update_theme) - def _update_theme(self, theme: str): + def _update_theme(self, theme: str | None = None): """Update the theme.""" if theme is None: qapp = QApplication.instance() diff --git a/bec_widgets/utils/colors.py b/bec_widgets/utils/colors.py index 0cc911b2..e07d0aed 100644 --- a/bec_widgets/utils/colors.py +++ b/bec_widgets/utils/colors.py @@ -1,6 +1,5 @@ from __future__ import annotations -import itertools import re from typing import TYPE_CHECKING, Literal @@ -71,15 +70,64 @@ def apply_theme(theme: Literal["dark", "light"]): Apply the theme to all pyqtgraph widgets. Do not use this function directly. Use set_theme instead. """ app = QApplication.instance() - # go through all pyqtgraph widgets and set background - children = itertools.chain.from_iterable( - top.findChildren(pg.GraphicsLayoutWidget) for top in app.topLevelWidgets() - ) - pg.setConfigOptions( - foreground="d" if theme == "dark" else "k", background="k" if theme == "dark" else "w" - ) - for pg_widget in children: - pg_widget.setBackground("k" if theme == "dark" else "w") + graphic_layouts = [ + child + for top in app.topLevelWidgets() + for child in top.findChildren(pg.GraphicsLayoutWidget) + ] + + plot_items = [ + item + for gl in graphic_layouts + for item in gl.ci.items.keys() # ci is internal pg.GraphicsLayout that hosts all items + if isinstance(item, pg.PlotItem) + ] + + histograms = [ + item + for gl in graphic_layouts + for item in gl.ci.items.keys() # ci is internal pg.GraphicsLayout that hosts all items + if isinstance(item, pg.HistogramLUTItem) + ] + + # Update background color based on the theme + if theme == "light": + background_color = "#e9ecef" # Subtle contrast for light mode + foreground_color = "#141414" + label_color = "#000000" + axis_color = "#666666" + else: + background_color = "#141414" # Dark mode + foreground_color = "#e9ecef" + label_color = "#FFFFFF" + axis_color = "#CCCCCC" + + # update GraphicsLayoutWidget + pg.setConfigOptions(foreground=foreground_color, background=background_color) + for pg_widget in graphic_layouts: + pg_widget.setBackground(background_color) + + # update PlotItems + for plot_item in plot_items: + for axis in ["left", "right", "top", "bottom"]: + plot_item.getAxis(axis).setPen(pg.mkPen(color=axis_color)) + plot_item.getAxis(axis).setTextPen(pg.mkPen(color=label_color)) + + # Change title color + plot_item.titleLabel.setText(plot_item.titleLabel.text, color=label_color) + + # Change legend color + if hasattr(plot_item, "legend") and plot_item.legend is not None: + plot_item.legend.setLabelTextColor(label_color) + # if legend is in plot item and theme is changed, has to be like that because of pg opt logic + for sample, label in plot_item.legend.items: + label_text = label.text + label.setText(label_text, color=label_color) + + # update HistogramLUTItem + for histogram in histograms: + histogram.axis.setPen(pg.mkPen(color=axis_color)) + histogram.axis.setTextPen(pg.mkPen(color=label_color)) # now define stylesheet according to theme and apply it style = bec_qthemes.load_stylesheet(theme) diff --git a/tests/unit_tests/test_color_validation.py b/tests/unit_tests/test_color_utils.py similarity index 65% rename from tests/unit_tests/test_color_validation.py rename to tests/unit_tests/test_color_utils.py index eda47163..3644285d 100644 --- a/tests/unit_tests/test_color_validation.py +++ b/tests/unit_tests/test_color_utils.py @@ -1,9 +1,15 @@ +import pyqtgraph as pg import pytest from pydantic import ValidationError from qtpy.QtGui import QColor +from qtpy.QtWidgets import QVBoxLayout, QWidget -from bec_widgets.utils import Colors +from bec_widgets.utils import Colors, ConnectionConfig +from bec_widgets.utils.bec_widget import BECWidget +from bec_widgets.utils.colors import apply_theme from bec_widgets.widgets.containers.figure.plots.waveform.waveform_curve import CurveConfig +from tests.unit_tests.client_mocks import mocked_client +from tests.unit_tests.conftest import create_widget def test_color_validation_CSS(): @@ -110,3 +116,55 @@ def test_golder_angle_colors(num): assert all(color.isValid() for color in colors_qcolor) assert all(color.startswith("#") for color in colors_hex) + + +################################################## +# Testing of the ExamplePlotWidget theme change +################################################## + + +class ExamplePlotWidget(BECWidget, QWidget): + def __init__( + self, + parent: QWidget | None = None, + config: ConnectionConfig | None = None, + client=None, + gui_id: str | None = None, + ) -> None: + if config is None: + config = ConnectionConfig(widget_class=self.__class__.__name__) + super().__init__(client=client, gui_id=gui_id, config=config) + QWidget.__init__(self, parent=parent) + + self.layout = QVBoxLayout(self) + self.glw = pg.GraphicsLayoutWidget() + self.pi = pg.PlotItem() + + self.layout.addWidget(self.glw) + self.glw.addItem(self.pi) + self.pi.plot([1, 2, 3, 4, 5], pen="r") + + +def test_apply_theme(qtbot, mocked_client): + widget = create_widget(qtbot, ExamplePlotWidget, client=mocked_client) + apply_theme("dark") + + # Get the default state of dark theme + dark_bg = widget.glw.backgroundBrush().color().name() + dark_axis_color = widget.pi.getAxis("left").pen().color().name() + dark_label_color = widget.pi.getAxis("left").textPen().color().name() + + assert dark_bg == "#141414" + assert dark_axis_color == "#cccccc" + assert dark_label_color == "#ffffff" + + apply_theme("light") + + # Get the default state of light theme + light_bg = widget.glw.backgroundBrush().color().name() + light_axis_color = widget.pi.getAxis("left").pen().color().name() + light_label_color = widget.pi.getAxis("left").textPen().color().name() + + assert light_bg == "#e9ecef" + assert light_axis_color == "#666666" + assert light_label_color == "#000000" diff --git a/tests/unit_tests/test_multi_waveform_widget.py b/tests/unit_tests/test_multi_waveform_widget.py index 757c5ce6..5a331f87 100644 --- a/tests/unit_tests/test_multi_waveform_widget.py +++ b/tests/unit_tests/test_multi_waveform_widget.py @@ -271,7 +271,8 @@ def test_multi_waveform_widget_theme_update(qtbot, multi_waveform_widget): palette = get_theme_palette() waveform_color_dark = multi_waveform_widget.waveform.plot_item.getAxis("left").pen().color() bg_color = multi_waveform_widget.fig.backgroundBrush().color() - assert bg_color == QColor("black") + + assert bg_color == QColor(20, 20, 20) assert waveform_color_dark == palette.text().color() # Set the theme to light @@ -279,7 +280,7 @@ def test_multi_waveform_widget_theme_update(qtbot, multi_waveform_widget): palette = get_theme_palette() waveform_color_light = multi_waveform_widget.waveform.plot_item.getAxis("left").pen().color() bg_color = multi_waveform_widget.fig.backgroundBrush().color() - assert bg_color == QColor("white") + assert bg_color == QColor(233, 236, 239) assert waveform_color_light == palette.text().color() assert waveform_color_dark != waveform_color_light @@ -291,5 +292,5 @@ def test_multi_waveform_widget_theme_update(qtbot, multi_waveform_widget): waveform_color = multi_waveform_widget.waveform.plot_item.getAxis("left").pen().color() bg_color = multi_waveform_widget.fig.backgroundBrush().color() - assert bg_color == QColor("black") + assert bg_color == QColor(20, 20, 20) assert waveform_color == waveform_color_dark diff --git a/tests/unit_tests/test_waveform_widget.py b/tests/unit_tests/test_waveform_widget.py index 607a2897..d40a0f4b 100644 --- a/tests/unit_tests/test_waveform_widget.py +++ b/tests/unit_tests/test_waveform_widget.py @@ -484,7 +484,7 @@ def test_waveform_widget_theme_update(qtbot, waveform_widget): palette = get_theme_palette() waveform_color_dark = waveform_widget.waveform.plot_item.getAxis("left").pen().color() bg_color = waveform_widget.fig.backgroundBrush().color() - assert bg_color == QColor("black") + assert bg_color == QColor(20, 20, 20) assert waveform_color_dark == palette.text().color() # Set the theme to light; equivalent to clicking the light mode button @@ -493,7 +493,7 @@ def test_waveform_widget_theme_update(qtbot, waveform_widget): palette = get_theme_palette() waveform_color_light = waveform_widget.waveform.plot_item.getAxis("left").pen().color() bg_color = waveform_widget.fig.backgroundBrush().color() - assert bg_color == QColor("white") + assert bg_color == QColor(233, 236, 239) assert waveform_color_light == palette.text().color() assert waveform_color_dark != waveform_color_light @@ -509,7 +509,7 @@ def test_waveform_widget_theme_update(qtbot, waveform_widget): # we compare the waveform color to the dark theme color waveform_color = waveform_widget.waveform.plot_item.getAxis("left").pen().color() bg_color = waveform_widget.fig.backgroundBrush().color() - assert bg_color == QColor("black") + assert bg_color == QColor(20, 20, 20) assert waveform_color == waveform_color_dark