From f3f7133961580869ee4d54a051e036dbf9e8ff08 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 15 Jan 2025 23:06:36 +0100 Subject: [PATCH] Remove status led colours in theme and use icon colours instead --- novelwriter/dialogs/about.py | 2 +- novelwriter/gui/statusbar.py | 6 +- novelwriter/gui/theme.py | 155 +++++++++++++++---------------- novelwriter/tools/welcome.py | 4 +- tests/test_gui/test_gui_theme.py | 58 ++++++------ 5 files changed, 113 insertions(+), 112 deletions(-) diff --git a/novelwriter/dialogs/about.py b/novelwriter/dialogs/about.py index 0d5ca304..cb132ce1 100644 --- a/novelwriter/dialogs/about.py +++ b/novelwriter/dialogs/about.py @@ -52,7 +52,7 @@ class GuiAbout(NDialog): self.resize(700, 500) # Logo and Banner - self.nwImage = SHARED.theme.loadDecoration("nw-text", h=36) + self.nwImage = SHARED.theme.getDecoration("nw-text", h=36) self.nwLogo = QLabel(self) self.nwLogo.setPixmap(SHARED.theme.getPixmap("novelwriter", (128, 128))) diff --git a/novelwriter/gui/statusbar.py b/novelwriter/gui/statusbar.py index 6dd75830..258c0a67 100644 --- a/novelwriter/gui/statusbar.py +++ b/novelwriter/gui/statusbar.py @@ -133,9 +133,9 @@ class GuiMainStatus(QStatusBar): self.idlePixmap = SHARED.theme.getPixmap("timer_off", (iPx, iPx)) self.timeIcon.setPixmap(self.timePixmap) - colNone = SHARED.theme.statNone - colSaved = SHARED.theme.statSaved - colUnsaved = SHARED.theme.statUnsaved + colNone = SHARED.theme.getIconColor("default").darker(150) + colSaved = SHARED.theme.getIconColor("green").darker(150) + colUnsaved = SHARED.theme.getIconColor("red").darker(150) self.docIcon.setColors(colNone, colSaved, colUnsaved) self.projIcon.setColors(colNone, colSaved, colUnsaved) diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index ae00c12a..70156dc7 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -42,7 +42,7 @@ from novelwriter.config import DEF_GUI, DEF_ICONS, DEF_SYNTAX from novelwriter.constants import nwLabels from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType from novelwriter.error import logException -from novelwriter.types import QtPaintAntiAlias, QtTransparent +from novelwriter.types import QtBlack, QtPaintAntiAlias, QtTransparent logger = logging.getLogger(__name__) @@ -94,20 +94,17 @@ class GuiTheme: __slots__ = ( # Attributes - "iconCache", "themeMeta", "isDarkTheme", "statNone", "statUnsaved", - "statSaved", "helpText", "fadedText", "errorText", "syntaxMeta", - "syntaxTheme", "guiFont", "guiFontB", "guiFontBU", "guiFontSmall", - "fontPointSize", "fontPixelSize", "baseIconHeight", "baseButtonHeight", - "textNHeight", "textNWidth", "baseIconSize", "buttonIconSize", - "guiFontFixed", + "iconCache", "themeMeta", "isDarkTheme", "helpText", "fadedText", "errorText", + "syntaxMeta", "syntaxTheme", "guiFont", "guiFontB", "guiFontBU", "guiFontSmall", + "fontPointSize", "fontPixelSize", "baseIconHeight", "baseButtonHeight", "textNHeight", + "textNWidth", "baseIconSize", "buttonIconSize", "guiFontFixed", # Functions - "getIcon", "getPixmap", "getItemIcon", "getToggleIcon", - "loadDecoration", "getHeaderDecoration", "getHeaderDecorationNarrow", + "getIcon", "getPixmap", "getItemIcon", "getIconColor", "getToggleIcon", "getDecoration", + "getHeaderDecoration", "getHeaderDecorationNarrow", # Internal - "_guiPalette", "_themeList", "_syntaxList", "_availThemes", - "_availSyntax", "_styleSheets", + "_guiPalette", "_themeList", "_syntaxList", "_availThemes", "_availSyntax", "_styleSheets", ) def __init__(self) -> None: @@ -118,12 +115,10 @@ class GuiTheme: self.themeMeta = ThemeMeta() self.isDarkTheme = False - self.statNone = QColor(0, 0, 0) - self.statUnsaved = QColor(0, 0, 0) - self.statSaved = QColor(0, 0, 0) - self.helpText = QColor(0, 0, 0) - self.fadedText = QColor(0, 0, 0) - self.errorText = QColor(255, 0, 0) + # Special Text Colours + self.helpText = QColor(0, 0, 0) + self.fadedText = QColor(0, 0, 0) + self.errorText = QColor(255, 0, 0) # Syntax Theme self.syntaxMeta = ThemeMeta() @@ -149,8 +144,9 @@ class GuiTheme: self.getIcon = self.iconCache.getIcon self.getPixmap = self.iconCache.getPixmap self.getItemIcon = self.iconCache.getItemIcon + self.getIconColor = self.iconCache.getIconColor self.getToggleIcon = self.iconCache.getToggleIcon - self.loadDecoration = self.iconCache.loadDecoration + self.getDecoration = self.iconCache.getDecoration self.getHeaderDecoration = self.iconCache.getHeaderDecoration self.getHeaderDecorationNarrow = self.iconCache.getHeaderDecorationNarrow @@ -296,12 +292,9 @@ class GuiTheme: # GUI sec = "GUI" if parser.has_section(sec): - self.helpText = self._parseColour(parser, sec, "helptext") - self.fadedText = self._parseColour(parser, sec, "fadedtext") - self.errorText = self._parseColour(parser, sec, "errortext") - self.statNone = self._parseColour(parser, sec, "statusnone") - self.statUnsaved = self._parseColour(parser, sec, "statusunsaved") - self.statSaved = self._parseColour(parser, sec, "statussaved") + self.helpText = self._parseColour(parser, sec, "helptext") + self.fadedText = self._parseColour(parser, sec, "fadedtext") + self.errorText = self._parseColour(parser, sec, "errortext") # Update Dependant Colours # Based on: https://github.com/qt/qtbase/blob/dev/src/gui/kernel/qplatformtheme.cpp @@ -483,7 +476,6 @@ class GuiTheme: # Reset GUI Palette faded = QColor(128, 128, 128) dimmed = QColor(130, 130, 130) if isDark else QColor(190, 190, 190) - grey = QColor(120, 120, 120) if isDark else QColor(140, 140, 140) red = QColor(242, 119, 122) if isDark else QColor(240, 40, 41) orange = QColor(249, 145, 57) if isDark else QColor(245, 135, 31) yellow = QColor(255, 204, 102) if isDark else QColor(234, 183, 0) @@ -492,12 +484,10 @@ class GuiTheme: blue = QColor(102, 153, 204) if isDark else QColor(66, 113, 174) purple = QColor(204, 153, 204) if isDark else QColor(137, 89, 168) - self.statNone = grey - self.statUnsaved = red - self.statSaved = green - self.helpText = dimmed - self.fadedText = faded - self.errorText = red + # Text Colours + self.helpText = dimmed + self.fadedText = faded + self.errorText = red self._guiPalette = palette @@ -575,9 +565,9 @@ class GuiIcons: """ __slots__ = ( - "mainTheme", "themeMeta", "_svgData", "_svgColours", "_qIcons", - "_headerDec", "_headerDecNarrow", "_availThemes", "_themeList", - "_noIcon", + "mainTheme", "themeMeta", "_svgData", "_svgColors", "_qColors", + "_qIcons", "_headerDec", "_headerDecNarrow", "_availThemes", + "_themeList", "_noIcon", ) TOGGLE_ICON_KEYS: dict[str, tuple[str, str]] = { @@ -596,7 +586,8 @@ class GuiIcons: # Storage self._svgData: dict[str, bytes] = {} - self._svgColours: dict[str, bytes] = {} + self._svgColors: dict[str, bytes] = {} + self._qColors: dict[str, QColor] = {} self._qIcons: dict[str, QIcon] = {} self._headerDec: list[QPixmap] = [] self._headerDecNarrow: list[QPixmap] = [] @@ -616,7 +607,8 @@ class GuiIcons: def clear(self) -> None: """Clear the icon cache.""" self._svgData = {} - self._svgColours = {} + self._svgColors = {} + self._qColors = {} self._qIcons = {} self._headerDec = [] self._headerDecNarrow = [] @@ -666,52 +658,31 @@ class GuiIcons: # Set colour overrides for project item icons if (override := CONFIG.iconColTree) != "theme": - color = self._svgColours.get(override, b"#000000") - self._svgColours["root"] = color - self._svgColours["folder"] = color + color = self._svgColors.get(override, b"#000000") + self._svgColors["root"] = color + self._svgColors["folder"] = color if not CONFIG.iconColDocs: - self._svgColours["file"] = color - self._svgColours["title"] = color - self._svgColours["chapter"] = color - self._svgColours["scene"] = color - self._svgColours["note"] = color + self._svgColors["file"] = color + self._svgColors["title"] = color + self._svgColors["chapter"] = color + self._svgColors["scene"] = color + self._svgColors["note"] = color return True def setIconColor(self, key: str, color: QColor) -> None: """Set an icon colour for a named colour.""" - self._svgColours[key] = color.name(QColor.NameFormat.HexRgb).encode("utf-8") + self._qColors[key] = QColor(color) + self._svgColors[key] = color.name(QColor.NameFormat.HexRgb).encode("utf-8") return ## # Access Functions ## - def loadDecoration(self, name: str, w: int | None = None, h: int | None = None) -> QPixmap: - """Load graphical decoration element based on the decoration - map or the icon map. This function always returns a QPixmap. - """ - if name in self.IMAGE_MAP: - idx = int(self.mainTheme.isDarkTheme) - imgPath = CONFIG.assetPath("images") / self.IMAGE_MAP[name][idx] - else: - logger.error("Decoration with name '%s' does not exist", name) - return QPixmap() - - if not imgPath.is_file(): - logger.error("Asset not found: %s", imgPath) - return QPixmap() - - pixmap = QPixmap(str(imgPath)) - tMode = Qt.TransformationMode.SmoothTransformation - if w is not None and h is not None: - return pixmap.scaled(w, h, Qt.AspectRatioMode.IgnoreAspectRatio, tMode) - elif w is None and h is not None: - return pixmap.scaledToHeight(h, tMode) - elif w is not None and h is None: - return pixmap.scaledToWidth(w, tMode) - - return pixmap + def getIconColor(self, name: str) -> QColor: + """Return an icon color.""" + return QColor(self._qColors.get(name) or QtBlack) def getIcon(self, name: str, color: str | None = None, w: int = 24, h: int = 24) -> QIcon: """Return an icon from the icon buffer, or load it.""" @@ -735,13 +706,6 @@ class GuiIcons: return icon return self._noIcon - def getPixmap(self, name: str, size: tuple[int, int], color: str | None = None) -> QPixmap: - """Return an icon from the icon buffer as a QPixmap. If it - doesn't exist, return an empty QPixmap. - """ - w, h = size - return self.getIcon(name, color, w, h).pixmap(w, h, QIcon.Mode.Normal) - def getItemIcon( self, tType: nwItemType, tClass: nwItemClass, tLayout: nwItemLayout, hLevel: str = "H0" ) -> QIcon: @@ -778,6 +742,39 @@ class GuiIcons: return self.getIcon(name, color) + def getPixmap(self, name: str, size: tuple[int, int], color: str | None = None) -> QPixmap: + """Return an icon from the icon buffer as a QPixmap. If it + doesn't exist, return an empty QPixmap. + """ + w, h = size + return self.getIcon(name, color, w, h).pixmap(w, h, QIcon.Mode.Normal) + + def getDecoration(self, name: str, w: int | None = None, h: int | None = None) -> QPixmap: + """Load graphical decoration element based on the decoration + map or the icon map. This function always returns a QPixmap. + """ + if name in self.IMAGE_MAP: + idx = int(self.mainTheme.isDarkTheme) + imgPath = CONFIG.assetPath("images") / self.IMAGE_MAP[name][idx] + else: + logger.error("Decoration with name '%s' does not exist", name) + return QPixmap() + + if not imgPath.is_file(): + logger.error("Asset not found: %s", imgPath) + return QPixmap() + + pixmap = QPixmap(str(imgPath)) + tMode = Qt.TransformationMode.SmoothTransformation + if w is not None and h is not None: + return pixmap.scaled(w, h, Qt.AspectRatioMode.IgnoreAspectRatio, tMode) + elif w is None and h is not None: + return pixmap.scaledToHeight(h, tMode) + elif w is not None and h is None: + return pixmap.scaledToWidth(w, tMode) + + return pixmap + def getHeaderDecoration(self, hLevel: int) -> QPixmap: """Get the decoration for a specific heading level.""" if not self._headerDec: @@ -835,7 +832,7 @@ class GuiIcons: return QIcon(str(CONFIG.assetPath("icons") / "x-novelwriter-project.svg")) if svg := self._svgData.get(name, b""): - if fill := self._svgColours.get(color or "default"): + if fill := self._svgColors.get(color or "default"): svg = svg.replace(b"#000000", fill) pixmap = QPixmap(w, h) pixmap.fill(QtTransparent) @@ -857,7 +854,7 @@ class GuiIcons: painter = QPainter(pixmap) painter.setRenderHint(QtPaintAntiAlias) - if fill := self._svgColours.get(color or "default"): + if fill := self._svgColors.get(color or "default"): painter.fillPath(path, QColor(fill.decode(encoding="utf-8"))) painter.end() diff --git a/novelwriter/tools/welcome.py b/novelwriter/tools/welcome.py index 32791caf..354b206d 100644 --- a/novelwriter/tools/welcome.py +++ b/novelwriter/tools/welcome.py @@ -75,8 +75,8 @@ class GuiWelcome(NDialog): # Elements # ======== - self.bgImage = SHARED.theme.loadDecoration("welcome") - self.nwImage = SHARED.theme.loadDecoration("nw-text", h=36) + self.bgImage = SHARED.theme.getDecoration("welcome") + self.nwImage = SHARED.theme.getDecoration("nw-text", h=36) self.bgColor = QColor(54, 54, 54) if SHARED.theme.isDarkTheme else QColor(255, 255, 255) self.nwLogo = QLabel(self) diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py index d45b500e..825a85ee 100644 --- a/tests/test_gui/test_gui_theme.py +++ b/tests/test_gui/test_gui_theme.py @@ -184,13 +184,17 @@ def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, tstPaths): # Check a few values assert mainTheme._guiPalette.color( - QPalette.ColorRole.Window).getRgb() == (239, 239, 239, 255) + QPalette.ColorRole.Window + ).getRgb() == (239, 239, 239, 255) assert mainTheme._guiPalette.color( - QPalette.ColorRole.WindowText).getRgb() == (0, 0, 0, 255) + QPalette.ColorRole.WindowText + ).getRgb() == (0, 0, 0, 255) assert mainTheme._guiPalette.color( - QPalette.ColorRole.Base).getRgb() == (255, 255, 255, 255) + QPalette.ColorRole.Base + ).getRgb() == (255, 255, 255, 255) assert mainTheme._guiPalette.color( - QPalette.ColorRole.AlternateBase).getRgb() == (239, 239, 239, 255) + QPalette.ColorRole.AlternateBase + ).getRgb() == (224, 224, 224, 255) # Load Default Dark Theme # ======================= @@ -298,29 +302,29 @@ def testGuiTheme_IconThemes(qtbot, caplog, monkeypatch, nwGUI, tstPaths): assert iconCache.themeMeta.name == "Material Symbols - Rounded Medium" # Load with project colour override - purple = iconCache._svgColours["purple"] - assert iconCache._svgColours["root"] != purple - assert iconCache._svgColours["folder"] != purple - assert iconCache._svgColours["file"] != purple - assert iconCache._svgColours["title"] != purple - assert iconCache._svgColours["chapter"] != purple - assert iconCache._svgColours["scene"] != purple - assert iconCache._svgColours["note"] != purple + purple = iconCache._svgColors["purple"] + assert iconCache._svgColors["root"] != purple + assert iconCache._svgColors["folder"] != purple + assert iconCache._svgColors["file"] != purple + assert iconCache._svgColors["title"] != purple + assert iconCache._svgColors["chapter"] != purple + assert iconCache._svgColors["scene"] != purple + assert iconCache._svgColors["note"] != purple CONFIG.iconColTree = "purple" assert iconCache.loadTheme("material_rounded_normal") is True - assert iconCache._svgColours["root"] == purple - assert iconCache._svgColours["folder"] == purple - assert iconCache._svgColours["file"] == purple - assert iconCache._svgColours["title"] == purple - assert iconCache._svgColours["chapter"] == purple - assert iconCache._svgColours["scene"] == purple - assert iconCache._svgColours["note"] == purple + assert iconCache._svgColors["root"] == purple + assert iconCache._svgColors["folder"] == purple + assert iconCache._svgColors["file"] == purple + assert iconCache._svgColors["title"] == purple + assert iconCache._svgColors["chapter"] == purple + assert iconCache._svgColors["scene"] == purple + assert iconCache._svgColors["note"] == purple # Change some colours iconCache.setIconColor("root", QColor(255, 255, 255)) - assert iconCache._svgColours["root"] != purple - assert iconCache._svgColours["root"] == b"#ffffff" + assert iconCache._svgColors["root"] != purple + assert iconCache._svgColors["root"] == b"#ffffff" # List Themes # =========== @@ -459,31 +463,31 @@ def testGuiTheme_LoadDecorations(qtbot, monkeypatch, nwGUI): # ================ # Invalid name should return empty pixmap - qPix = iconCache.loadDecoration("stuff") + qPix = iconCache.getDecoration("stuff") assert qPix.isNull() is True # Load an image - qPix = iconCache.loadDecoration("welcome") + qPix = iconCache.getDecoration("welcome") assert qPix.isNull() is False # Fail finding the file with monkeypatch.context() as mp: mp.setattr("pathlib.Path.is_file", lambda *a: False) - qPix = iconCache.loadDecoration("welcome") + qPix = iconCache.getDecoration("welcome") assert qPix.isNull() is True # Test image sizes - qPix = iconCache.loadDecoration("welcome", w=100, h=None) + qPix = iconCache.getDecoration("welcome", w=100, h=None) assert qPix.isNull() is False assert qPix.width() == 100 assert qPix.height() > 50 - qPix = iconCache.loadDecoration("welcome", w=None, h=100) + qPix = iconCache.getDecoration("welcome", w=None, h=100) assert qPix.isNull() is False assert qPix.width() > 100 assert qPix.height() == 100 - qPix = iconCache.loadDecoration("welcome", w=100, h=100) + qPix = iconCache.getDecoration("welcome", w=100, h=100) assert qPix.isNull() is False assert qPix.width() == 100 assert qPix.height() == 100