Skip to content

Commit 4e67819

Browse files
committed
Rename Theme to ColorScheme in GUI theming
Aligns the theming API with Qt's ColorScheme naming by renaming the enum, ThemeManager class, and related methods, signals, and attributes.
1 parent c32827d commit 4e67819

6 files changed

Lines changed: 135 additions & 134 deletions

File tree

src/ert/gui/theming/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from __future__ import annotations
22

3-
from .manager import ThemeManager
4-
from .theme import Theme
3+
from .manager import ColorSchemeManager
4+
from .theme import ColorScheme
55

66
__all__ = [
7-
"Theme",
8-
"ThemeManager",
7+
"ColorScheme",
8+
"ColorSchemeManager",
99
]

src/ert/gui/theming/manager.py

Lines changed: 32 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77
from PyQt6.QtGui import QGuiApplication, QStyleHints
88
from PyQt6.QtWidgets import QApplication
99

10-
from .theme import Theme, load_qss
10+
from .theme import ColorScheme, load_qss
1111

1212
logger = logging.getLogger(__name__)
1313

1414
_STYLE_HINTS_MISSING = "styleHints() unavailable; is a QGuiApplication running?"
1515
_QAPPLICATION_MISSING = (
16-
"QApplication instance not found; construct a QApplication before applying a theme."
16+
"QApplication instance not found; construct a QApplication before applying "
17+
"a colour scheme."
1718
)
1819

1920
_DARK_BASE_VALUE_THRESHOLD = 70
@@ -34,87 +35,78 @@ def _require_style_hints() -> QStyleHints:
3435
return hints
3536

3637

37-
def detect_system_theme() -> Theme:
38-
"""Return the OS-reported colour scheme, or fall back to a palette heuristic.
38+
def detect_system_color_scheme() -> ColorScheme:
3939

40-
Uses ``QGuiApplication.styleHints().colorScheme()`` on Qt 6.5+. When the
41-
style hint returns ``Unknown`` (or the platform integration does not
42-
report a value), the base-colour brightness of the current application
43-
palette is inspected instead.
44-
45-
A running ``QApplication`` (or at least a ``QGuiApplication``) must exist
46-
before calling this function.
47-
"""
4840
hints = _require_style_hints()
4941
scheme = hints.colorScheme()
5042
if scheme == Qt.ColorScheme.Dark:
51-
return Theme.DARK
43+
return ColorScheme.DARK
5244
if scheme == Qt.ColorScheme.Light:
53-
return Theme.LIGHT
45+
return ColorScheme.LIGHT
5446
return _palette_fallback()
5547

5648

57-
def _palette_fallback() -> Theme:
49+
def _palette_fallback() -> ColorScheme:
5850
app = cast(QApplication | None, QApplication.instance())
5951
if app is None:
60-
return Theme.LIGHT
52+
return ColorScheme.LIGHT
6153
return (
62-
Theme.DARK
54+
ColorScheme.DARK
6355
if app.palette().base().color().value() < _DARK_BASE_VALUE_THRESHOLD
64-
else Theme.LIGHT
56+
else ColorScheme.LIGHT
6557
)
6658

6759

68-
class ThemeManager(QObject):
69-
theme_changed = pyqtSignal(Theme)
60+
class ColorSchemeManager(QObject):
61+
color_scheme_changed = pyqtSignal(ColorScheme)
7062

7163
def __init__(self, parent: QObject | None = None) -> None:
7264
super().__init__(parent)
73-
self._follows_system_theme: bool = True
74-
self._current_theme: Theme = detect_system_theme()
65+
self._follows_system_color_scheme: bool = True
66+
self._current_color_scheme: ColorScheme = detect_system_color_scheme()
7567
hints = _require_style_hints()
7668
hints.colorSchemeChanged.connect(self._on_system_scheme_changed)
7769
self.apply_stylesheet_from_qss()
7870

7971
@property
80-
def current_theme(self) -> Theme:
81-
return self._current_theme
72+
def current_color_scheme(self) -> ColorScheme:
73+
return self._current_color_scheme
8274

8375
@property
8476
def follows_system(self) -> bool:
85-
return self._follows_system_theme
77+
return self._follows_system_color_scheme
8678

87-
def set_theme(self, theme: Theme) -> None:
88-
"""Pin the manager to ``theme`` and stop following the OS scheme."""
89-
self._follows_system_theme = False
90-
self._set_theme_internal(theme)
79+
def set_color_scheme(self, color_scheme: ColorScheme) -> None:
80+
"""Pin the manager to ``color_scheme`` and stop following the OS scheme."""
81+
self._follows_system_color_scheme = False
82+
self._set_color_scheme_internal(color_scheme)
9183

9284
def follow_system(self) -> None:
9385
"""Resume following the OS colour scheme; re-syncs immediately."""
94-
self._follows_system_theme = True
95-
self._set_theme_internal(detect_system_theme())
86+
self._follows_system_color_scheme = True
87+
self._set_color_scheme_internal(detect_system_color_scheme())
9688

9789
def apply_stylesheet_from_qss(self) -> None:
9890
app = cast(QApplication | None, QApplication.instance())
9991
if app is None:
10092
raise RuntimeError(_QAPPLICATION_MISSING)
10193
try:
102-
stylesheet = load_qss(self._current_theme)
94+
stylesheet = load_qss(self._current_color_scheme)
10395
except (OSError, UnicodeDecodeError):
10496
logger.exception(
105-
"Failed to load QSS for theme '%s'; keeping previous styling.",
106-
self._current_theme.value,
97+
"Failed to load QSS for colour scheme '%s'; keeping previous styling.",
98+
self._current_color_scheme.value,
10799
)
108100
return
109101
app.setStyleSheet(stylesheet)
110102

111103
def _on_system_scheme_changed(self, _scheme: Qt.ColorScheme) -> None:
112-
if self._follows_system_theme:
113-
self._set_theme_internal(detect_system_theme())
104+
if self._follows_system_color_scheme:
105+
self._set_color_scheme_internal(detect_system_color_scheme())
114106

115-
def _set_theme_internal(self, theme: Theme) -> None:
116-
if theme == self._current_theme:
107+
def _set_color_scheme_internal(self, color_scheme: ColorScheme) -> None:
108+
if color_scheme == self._current_color_scheme:
117109
return
118-
self._current_theme = theme
110+
self._current_color_scheme = color_scheme
119111
self.apply_stylesheet_from_qss()
120-
self.theme_changed.emit(theme)
112+
self.color_scheme_changed.emit(color_scheme)

src/ert/gui/theming/theme.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
_THEMES_SUBPATH = "resources/gui/themes"
77

88

9-
class Theme(Enum):
10-
"""Identifier for a visual theme shipped with the ERT GUI.
9+
class ColorScheme(Enum):
10+
"""Identifier for a visual colour scheme shipped with the ERT GUI.
1111
1212
The value is used as the base filename of the corresponding QSS file
1313
under ``src/ert/gui/resources/gui/themes/``.
@@ -17,10 +17,10 @@ class Theme(Enum):
1717
DARK = "dark"
1818

1919

20-
def load_qss(theme: Theme) -> str:
21-
resource = files("ert.gui").joinpath(f"{_THEMES_SUBPATH}/{theme.value}.qss")
20+
def load_qss(color_scheme: ColorScheme) -> str:
21+
resource = files("ert.gui").joinpath(f"{_THEMES_SUBPATH}/{color_scheme.value}.qss")
2222
if not resource.is_file():
2323
raise FileNotFoundError(
24-
f"QSS file for theme '{theme.value}' not found at {resource}"
24+
f"QSS file for colour scheme '{color_scheme.value}' not found at {resource}"
2525
)
2626
return resource.read_text(encoding="utf-8")

tests/ert/unit_tests/gui/theming/test_qss_loading.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import pytest
44

5-
from ert.gui.theming import Theme
5+
from ert.gui.theming import ColorScheme
66
from ert.gui.theming import theme as theme_module
77
from ert.gui.theming.theme import load_qss
88

@@ -20,17 +20,21 @@ def joinpath(self, _name: str) -> _MissingResource:
2020
return _MissingResource()
2121

2222

23-
@pytest.mark.parametrize("theme", list(Theme))
24-
def test_that_qss_file_is_loaded_for_each_theme(theme: Theme) -> None:
25-
content = load_qss(theme)
26-
assert content.strip(), f"QSS for {theme.value} theme must not be empty"
23+
@pytest.mark.parametrize("color_scheme", list(ColorScheme))
24+
def test_that_qss_file_is_loaded_for_each_color_scheme(
25+
color_scheme: ColorScheme,
26+
) -> None:
27+
content = load_qss(color_scheme)
28+
assert content.strip(), (
29+
f"QSS for {color_scheme.value} colour scheme must not be empty"
30+
)
2731
assert "QWidget" in content, (
28-
f"QSS for {theme.value} theme should style QWidget as a baseline"
32+
f"QSS for {color_scheme.value} colour scheme should style QWidget as a baseline"
2933
)
3034

3135

3236
def test_that_dark_and_light_qss_differ() -> None:
33-
assert load_qss(Theme.DARK) != load_qss(Theme.LIGHT)
37+
assert load_qss(ColorScheme.DARK) != load_qss(ColorScheme.LIGHT)
3438

3539

3640
def test_that_load_qss_raises_file_not_found_when_theme_file_is_missing(
@@ -39,4 +43,4 @@ def test_that_load_qss_raises_file_not_found_when_theme_file_is_missing(
3943
monkeypatch.setattr(theme_module, "files", lambda _pkg: _MissingPackage())
4044

4145
with pytest.raises(FileNotFoundError, match="dark"):
42-
load_qss(Theme.DARK)
46+
load_qss(ColorScheme.DARK)

tests/ert/unit_tests/gui/theming/test_theme_detection.py

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
from PyQt6.QtCore import Qt
55
from PyQt6.QtGui import QColor, QGuiApplication, QPalette
66

7-
from ert.gui.theming import Theme
7+
from ert.gui.theming import ColorScheme
88
from ert.gui.theming import manager as manager_module
9-
from ert.gui.theming.manager import _DARK_BASE_VALUE_THRESHOLD, detect_system_theme
9+
from ert.gui.theming.manager import (
10+
_DARK_BASE_VALUE_THRESHOLD,
11+
detect_system_color_scheme,
12+
)
1013

1114

1215
def _palette_with_base_value(value: int) -> QPalette:
@@ -23,7 +26,7 @@ def test_that_detection_returns_dark_when_style_hints_report_dark(
2326
"colorScheme",
2427
lambda: Qt.ColorScheme.Dark,
2528
)
26-
assert detect_system_theme() == Theme.DARK
29+
assert detect_system_color_scheme() == ColorScheme.DARK
2730

2831

2932
def test_that_detection_returns_light_when_style_hints_report_light(
@@ -34,7 +37,7 @@ def test_that_detection_returns_light_when_style_hints_report_light(
3437
"colorScheme",
3538
lambda: Qt.ColorScheme.Light,
3639
)
37-
assert detect_system_theme() == Theme.LIGHT
40+
assert detect_system_color_scheme() == ColorScheme.LIGHT
3841

3942

4043
def test_that_detection_falls_back_to_palette_when_scheme_is_unknown(
@@ -48,7 +51,7 @@ def test_that_detection_falls_back_to_palette_when_scheme_is_unknown(
4851
# With the default Qt palette used in tests the base colour is white
4952
# (value 255), which is above ``_DARK_BASE_VALUE_THRESHOLD`` in
5053
# ``_palette_fallback``, so the fallback path must resolve to LIGHT.
51-
assert detect_system_theme() == Theme.LIGHT
54+
assert detect_system_color_scheme() == ColorScheme.LIGHT
5255

5356

5457
def test_that_require_style_hints_raises_when_style_hints_are_unavailable(
@@ -67,34 +70,32 @@ def test_that_palette_fallback_returns_light_when_no_qapplication_exists(
6770
monkeypatch.setattr(
6871
manager_module.QApplication, "instance", staticmethod(lambda: None)
6972
)
70-
assert manager_module._palette_fallback() == Theme.LIGHT
71-
72-
73-
def test_that_palette_fallback_returns_dark_when_base_value_is_below_threshold(
74-
qtbot, monkeypatch
75-
) -> None:
76-
app = manager_module.QApplication.instance()
77-
monkeypatch.setattr(
78-
app, "palette", lambda: _palette_with_base_value(_DARK_BASE_VALUE_THRESHOLD - 1)
79-
)
80-
assert manager_module._palette_fallback() == Theme.DARK
81-
82-
83-
def test_that_palette_fallback_returns_light_when_base_value_is_above_threshold(
84-
qtbot, monkeypatch
73+
assert manager_module._palette_fallback() == ColorScheme.LIGHT
74+
75+
76+
@pytest.mark.parametrize(
77+
("base_value", "expected_scheme"),
78+
[
79+
pytest.param(
80+
_DARK_BASE_VALUE_THRESHOLD - 1,
81+
ColorScheme.DARK,
82+
id="returns-dark-when-base-value-is-below-threshold",
83+
),
84+
pytest.param(
85+
_DARK_BASE_VALUE_THRESHOLD + 1,
86+
ColorScheme.LIGHT,
87+
id="returns-light-when-base-value-is-above-threshold",
88+
),
89+
pytest.param(
90+
_DARK_BASE_VALUE_THRESHOLD,
91+
ColorScheme.LIGHT,
92+
id="returns-light-when-base-value-equals-threshold",
93+
),
94+
],
95+
)
96+
def test_that_palette_fallback_resolves_scheme_from_base_value_threshold(
97+
qtbot, monkeypatch, base_value, expected_scheme
8598
) -> None:
8699
app = manager_module.QApplication.instance()
87-
monkeypatch.setattr(
88-
app, "palette", lambda: _palette_with_base_value(_DARK_BASE_VALUE_THRESHOLD + 1)
89-
)
90-
assert manager_module._palette_fallback() == Theme.LIGHT
91-
92-
93-
def test_that_palette_fallback_returns_light_when_base_value_equals_threshold(
94-
qtbot, monkeypatch
95-
) -> None:
96-
app = manager_module.QApplication.instance()
97-
monkeypatch.setattr(
98-
app, "palette", lambda: _palette_with_base_value(_DARK_BASE_VALUE_THRESHOLD)
99-
)
100-
assert manager_module._palette_fallback() == Theme.LIGHT
100+
monkeypatch.setattr(app, "palette", lambda: _palette_with_base_value(base_value))
101+
assert manager_module._palette_fallback() == expected_scheme

0 commit comments

Comments
 (0)