From 0ab33e184371ddc7c764cbabe2568370fcf60901 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 4 Jun 2025 21:09:35 +0200 Subject: [PATCH] Allow adjusting lighter and darker as well in theme files --- novelwriter/gui/theme.py | 59 +++++++++++++++++--------------- tests/test_gui/test_gui_theme.py | 41 ++++++++++++++-------- 2 files changed, 58 insertions(+), 42 deletions(-) diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 46adebbb..89d3fecd 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -244,32 +244,6 @@ class GuiTheme: window = palette.window().color() return text.lightnessF() > window.lightnessF() - def parseColor(self, value: str, default: QColor = QtBlack) -> QColor: - """Parse a string as a colour value.""" - if value in self._qColors: - # Named colour - return self._qColors[value] - elif value.startswith("#") and len(value) == 7: - # Assume #RRGGBB - return QColor.fromString(value) - elif value.startswith("#") and len(value) == 9: - # Assume #RRGGBBAA and convert to #AARRGGBB - return QColor.fromString(f"#{value[7:9]}{value[1:7]}") - elif ":" in value: - # Colour name and alpha - name, _, alpha = value.partition(":") - color = QColor(self._qColors.get(name.strip(), default)) - color.setAlpha(checkInt(alpha, 255)) - return color - elif "," in value: - # Integer red, green, blue, alpha - data = value.split(",") - result = [0, 0, 0, 255] - for i in range(min(len(data), 4)): - result[i] = checkInt(data[i].strip(), result[i]) - return QColor(*result) - return default - def loadTheme(self, force: bool = False) -> bool: """Load the currently specified GUI theme. The boolean return can be used to determine if the GUI needs refreshing. @@ -489,6 +463,37 @@ class GuiTheme: # Internal Functions ## + def _parseColor(self, value: str, default: QColor = QtBlack) -> QColor: + """Parse a string as a colour value.""" + if value in self._qColors: + # Named colour + return self._qColors[value] + elif value.startswith("#") and len(value) == 7: + # Assume #RRGGBB + return QColor.fromString(value) + elif value.startswith("#") and len(value) == 9: + # Assume #RRGGBBAA and convert to #AARRGGBB + return QColor.fromString(f"#{value[7:9]}{value[1:7]}") + elif ":" in value: + # Colour name and alpha + name, _, adjust = value.partition(":") + color = QColor(self._qColors.get(name.strip(), default)) + if adjust.startswith("L"): + color = color.lighter(checkInt(adjust[1:], 100)) + elif adjust.startswith("D"): + color = color.darker(checkInt(adjust[1:], 100)) + else: + color.setAlpha(checkInt(adjust, 255)) + return color + elif "," in value: + # Integer red, green, blue, alpha + data = value.split(",") + result = [0, 0, 0, 255] + for i in range(min(len(data), 4)): + result[i] = checkInt(data[i].strip(), result[i]) + return QColor(*result) + return default + def _setBaseColor(self, key: str, color: QColor) -> None: """Set the colour for a named colour.""" self._qColors[key] = QColor(color) @@ -544,7 +549,7 @@ class GuiTheme: def _readColor(self, parser: ConfigParser, section: str, name: str) -> QColor: """Parse a colour value from a config string.""" - return self.parseColor(parser.get(section, name, fallback="default")) + return self._parseColor(parser.get(section, name, fallback="default")) def _setPalette( self, parser: ConfigParser, section: str, name: str, value: QPalette.ColorRole diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py index d14304d1..4c834d38 100644 --- a/tests/test_gui/test_gui_theme.py +++ b/tests/test_gui/test_gui_theme.py @@ -52,29 +52,40 @@ def testGuiTheme_ParseColor(): theme._qColors["red"] = QColor(255, 0, 0) theme._qColors["green"] = QColor(0, 255, 0) theme._qColors["blue"] = QColor(0, 0, 255) + theme._qColors["grey"] = QColor(127, 127, 127) # By Name - assert theme.parseColor("red").getRgb() == (255, 0, 0, 255) - assert theme.parseColor("green").getRgb() == (0, 255, 0, 255) - assert theme.parseColor("blue").getRgb() == (0, 0, 255, 255) - assert theme.parseColor("bob").getRgb() == (0, 0, 0, 255) + assert theme._parseColor("red").getRgb() == (255, 0, 0, 255) + assert theme._parseColor("green").getRgb() == (0, 255, 0, 255) + assert theme._parseColor("blue").getRgb() == (0, 0, 255, 255) + assert theme._parseColor("bob").getRgb() == (0, 0, 0, 255) # CSS Format - assert theme.parseColor("#ff0000").getRgb() == (255, 0, 0, 255) - assert theme.parseColor("#ff00007f").getRgb() == (255, 0, 0, 127) - assert theme.parseColor("#ff00").getRgb() == (0, 0, 0, 255) # Too short -> ignored - assert theme.parseColor("#ff00007f15").getRgb() == (0, 0, 0, 255) # Too long -> ignored + assert theme._parseColor("#ff0000").getRgb() == (255, 0, 0, 255) + assert theme._parseColor("#ff00007f").getRgb() == (255, 0, 0, 127) + assert theme._parseColor("#ff00").getRgb() == (0, 0, 0, 255) # Too short -> ignored + assert theme._parseColor("#ff00007f15").getRgb() == (0, 0, 0, 255) # Too long -> ignored # Name + Alpha - assert theme.parseColor("red:255").getRgb() == (255, 0, 0, 255) - assert theme.parseColor("red:127").getRgb() == (255, 0, 0, 127) - assert theme.parseColor("red:512").getRgb() == (255, 0, 0, 255) # Value truncated + assert theme._parseColor("red:255").getRgb() == (255, 0, 0, 255) + assert theme._parseColor("red:127").getRgb() == (255, 0, 0, 127) + assert theme._parseColor("red:512").getRgb() == (255, 0, 0, 255) # Value truncated + + # Name + Lighter + assert theme._parseColor("grey:L100").getRgb() == (127, 127, 127, 255) + assert theme._parseColor("grey:L150").getRgb() == (190, 190, 190, 255) + assert theme._parseColor("grey:L50").getRgb() == (63, 63, 63, 255) + + # Name + Darker + assert theme._parseColor("grey:D100").getRgb() == (127, 127, 127, 255) + assert theme._parseColor("grey:D150").getRgb() == (85, 85, 85, 255) + assert theme._parseColor("grey:D50").getRgb() == (254, 254, 254, 255) # Values - assert theme.parseColor("255, 0, 0").getRgb() == (255, 0, 0, 255) - assert theme.parseColor("255, 0, 0, 255").getRgb() == (255, 0, 0, 255) - assert theme.parseColor("255, 0, 0, 127").getRgb() == (255, 0, 0, 127) - assert theme.parseColor("255, 0, 0, 127, 42").getRgb() == (255, 0, 0, 127) # Truncated + assert theme._parseColor("255, 0, 0").getRgb() == (255, 0, 0, 255) + assert theme._parseColor("255, 0, 0, 255").getRgb() == (255, 0, 0, 255) + assert theme._parseColor("255, 0, 0, 127").getRgb() == (255, 0, 0, 127) + assert theme._parseColor("255, 0, 0, 127, 42").getRgb() == (255, 0, 0, 127) # Truncated @pytest.mark.gui