Remove status led colours in theme and use icon colours instead

This commit is contained in:
Veronica Berglyd Olsen
2025-01-15 23:06:36 +01:00
parent 785322e577
commit f3f7133961
5 changed files with 113 additions and 112 deletions
+1 -1
View File
@@ -52,7 +52,7 @@ class GuiAbout(NDialog):
self.resize(700, 500) self.resize(700, 500)
# Logo and Banner # 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 = QLabel(self)
self.nwLogo.setPixmap(SHARED.theme.getPixmap("novelwriter", (128, 128))) self.nwLogo.setPixmap(SHARED.theme.getPixmap("novelwriter", (128, 128)))
+3 -3
View File
@@ -133,9 +133,9 @@ class GuiMainStatus(QStatusBar):
self.idlePixmap = SHARED.theme.getPixmap("timer_off", (iPx, iPx)) self.idlePixmap = SHARED.theme.getPixmap("timer_off", (iPx, iPx))
self.timeIcon.setPixmap(self.timePixmap) self.timeIcon.setPixmap(self.timePixmap)
colNone = SHARED.theme.statNone colNone = SHARED.theme.getIconColor("default").darker(150)
colSaved = SHARED.theme.statSaved colSaved = SHARED.theme.getIconColor("green").darker(150)
colUnsaved = SHARED.theme.statUnsaved colUnsaved = SHARED.theme.getIconColor("red").darker(150)
self.docIcon.setColors(colNone, colSaved, colUnsaved) self.docIcon.setColors(colNone, colSaved, colUnsaved)
self.projIcon.setColors(colNone, colSaved, colUnsaved) self.projIcon.setColors(colNone, colSaved, colUnsaved)
+76 -79
View File
@@ -42,7 +42,7 @@ from novelwriter.config import DEF_GUI, DEF_ICONS, DEF_SYNTAX
from novelwriter.constants import nwLabels from novelwriter.constants import nwLabels
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException from novelwriter.error import logException
from novelwriter.types import QtPaintAntiAlias, QtTransparent from novelwriter.types import QtBlack, QtPaintAntiAlias, QtTransparent
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -94,20 +94,17 @@ class GuiTheme:
__slots__ = ( __slots__ = (
# Attributes # Attributes
"iconCache", "themeMeta", "isDarkTheme", "statNone", "statUnsaved", "iconCache", "themeMeta", "isDarkTheme", "helpText", "fadedText", "errorText",
"statSaved", "helpText", "fadedText", "errorText", "syntaxMeta", "syntaxMeta", "syntaxTheme", "guiFont", "guiFontB", "guiFontBU", "guiFontSmall",
"syntaxTheme", "guiFont", "guiFontB", "guiFontBU", "guiFontSmall", "fontPointSize", "fontPixelSize", "baseIconHeight", "baseButtonHeight", "textNHeight",
"fontPointSize", "fontPixelSize", "baseIconHeight", "baseButtonHeight", "textNWidth", "baseIconSize", "buttonIconSize", "guiFontFixed",
"textNHeight", "textNWidth", "baseIconSize", "buttonIconSize",
"guiFontFixed",
# Functions # Functions
"getIcon", "getPixmap", "getItemIcon", "getToggleIcon", "getIcon", "getPixmap", "getItemIcon", "getIconColor", "getToggleIcon", "getDecoration",
"loadDecoration", "getHeaderDecoration", "getHeaderDecorationNarrow", "getHeaderDecoration", "getHeaderDecorationNarrow",
# Internal # Internal
"_guiPalette", "_themeList", "_syntaxList", "_availThemes", "_guiPalette", "_themeList", "_syntaxList", "_availThemes", "_availSyntax", "_styleSheets",
"_availSyntax", "_styleSheets",
) )
def __init__(self) -> None: def __init__(self) -> None:
@@ -118,12 +115,10 @@ class GuiTheme:
self.themeMeta = ThemeMeta() self.themeMeta = ThemeMeta()
self.isDarkTheme = False self.isDarkTheme = False
self.statNone = QColor(0, 0, 0) # Special Text Colours
self.statUnsaved = QColor(0, 0, 0) self.helpText = QColor(0, 0, 0)
self.statSaved = QColor(0, 0, 0) self.fadedText = QColor(0, 0, 0)
self.helpText = QColor(0, 0, 0) self.errorText = QColor(255, 0, 0)
self.fadedText = QColor(0, 0, 0)
self.errorText = QColor(255, 0, 0)
# Syntax Theme # Syntax Theme
self.syntaxMeta = ThemeMeta() self.syntaxMeta = ThemeMeta()
@@ -149,8 +144,9 @@ class GuiTheme:
self.getIcon = self.iconCache.getIcon self.getIcon = self.iconCache.getIcon
self.getPixmap = self.iconCache.getPixmap self.getPixmap = self.iconCache.getPixmap
self.getItemIcon = self.iconCache.getItemIcon self.getItemIcon = self.iconCache.getItemIcon
self.getIconColor = self.iconCache.getIconColor
self.getToggleIcon = self.iconCache.getToggleIcon self.getToggleIcon = self.iconCache.getToggleIcon
self.loadDecoration = self.iconCache.loadDecoration self.getDecoration = self.iconCache.getDecoration
self.getHeaderDecoration = self.iconCache.getHeaderDecoration self.getHeaderDecoration = self.iconCache.getHeaderDecoration
self.getHeaderDecorationNarrow = self.iconCache.getHeaderDecorationNarrow self.getHeaderDecorationNarrow = self.iconCache.getHeaderDecorationNarrow
@@ -296,12 +292,9 @@ class GuiTheme:
# GUI # GUI
sec = "GUI" sec = "GUI"
if parser.has_section(sec): if parser.has_section(sec):
self.helpText = self._parseColour(parser, sec, "helptext") self.helpText = self._parseColour(parser, sec, "helptext")
self.fadedText = self._parseColour(parser, sec, "fadedtext") self.fadedText = self._parseColour(parser, sec, "fadedtext")
self.errorText = self._parseColour(parser, sec, "errortext") 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")
# Update Dependant Colours # Update Dependant Colours
# Based on: https://github.com/qt/qtbase/blob/dev/src/gui/kernel/qplatformtheme.cpp # Based on: https://github.com/qt/qtbase/blob/dev/src/gui/kernel/qplatformtheme.cpp
@@ -483,7 +476,6 @@ class GuiTheme:
# Reset GUI Palette # Reset GUI Palette
faded = QColor(128, 128, 128) faded = QColor(128, 128, 128)
dimmed = QColor(130, 130, 130) if isDark else QColor(190, 190, 190) 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) red = QColor(242, 119, 122) if isDark else QColor(240, 40, 41)
orange = QColor(249, 145, 57) if isDark else QColor(245, 135, 31) orange = QColor(249, 145, 57) if isDark else QColor(245, 135, 31)
yellow = QColor(255, 204, 102) if isDark else QColor(234, 183, 0) 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) blue = QColor(102, 153, 204) if isDark else QColor(66, 113, 174)
purple = QColor(204, 153, 204) if isDark else QColor(137, 89, 168) purple = QColor(204, 153, 204) if isDark else QColor(137, 89, 168)
self.statNone = grey # Text Colours
self.statUnsaved = red self.helpText = dimmed
self.statSaved = green self.fadedText = faded
self.helpText = dimmed self.errorText = red
self.fadedText = faded
self.errorText = red
self._guiPalette = palette self._guiPalette = palette
@@ -575,9 +565,9 @@ class GuiIcons:
""" """
__slots__ = ( __slots__ = (
"mainTheme", "themeMeta", "_svgData", "_svgColours", "_qIcons", "mainTheme", "themeMeta", "_svgData", "_svgColors", "_qColors",
"_headerDec", "_headerDecNarrow", "_availThemes", "_themeList", "_qIcons", "_headerDec", "_headerDecNarrow", "_availThemes",
"_noIcon", "_themeList", "_noIcon",
) )
TOGGLE_ICON_KEYS: dict[str, tuple[str, str]] = { TOGGLE_ICON_KEYS: dict[str, tuple[str, str]] = {
@@ -596,7 +586,8 @@ class GuiIcons:
# Storage # Storage
self._svgData: dict[str, bytes] = {} 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._qIcons: dict[str, QIcon] = {}
self._headerDec: list[QPixmap] = [] self._headerDec: list[QPixmap] = []
self._headerDecNarrow: list[QPixmap] = [] self._headerDecNarrow: list[QPixmap] = []
@@ -616,7 +607,8 @@ class GuiIcons:
def clear(self) -> None: def clear(self) -> None:
"""Clear the icon cache.""" """Clear the icon cache."""
self._svgData = {} self._svgData = {}
self._svgColours = {} self._svgColors = {}
self._qColors = {}
self._qIcons = {} self._qIcons = {}
self._headerDec = [] self._headerDec = []
self._headerDecNarrow = [] self._headerDecNarrow = []
@@ -666,52 +658,31 @@ class GuiIcons:
# Set colour overrides for project item icons # Set colour overrides for project item icons
if (override := CONFIG.iconColTree) != "theme": if (override := CONFIG.iconColTree) != "theme":
color = self._svgColours.get(override, b"#000000") color = self._svgColors.get(override, b"#000000")
self._svgColours["root"] = color self._svgColors["root"] = color
self._svgColours["folder"] = color self._svgColors["folder"] = color
if not CONFIG.iconColDocs: if not CONFIG.iconColDocs:
self._svgColours["file"] = color self._svgColors["file"] = color
self._svgColours["title"] = color self._svgColors["title"] = color
self._svgColours["chapter"] = color self._svgColors["chapter"] = color
self._svgColours["scene"] = color self._svgColors["scene"] = color
self._svgColours["note"] = color self._svgColors["note"] = color
return True return True
def setIconColor(self, key: str, color: QColor) -> None: def setIconColor(self, key: str, color: QColor) -> None:
"""Set an icon colour for a named colour.""" """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 return
## ##
# Access Functions # Access Functions
## ##
def loadDecoration(self, name: str, w: int | None = None, h: int | None = None) -> QPixmap: def getIconColor(self, name: str) -> QColor:
"""Load graphical decoration element based on the decoration """Return an icon color."""
map or the icon map. This function always returns a QPixmap. return QColor(self._qColors.get(name) or QtBlack)
"""
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 getIcon(self, name: str, color: str | None = None, w: int = 24, h: int = 24) -> QIcon: 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.""" """Return an icon from the icon buffer, or load it."""
@@ -735,13 +706,6 @@ class GuiIcons:
return icon return icon
return self._noIcon 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( def getItemIcon(
self, tType: nwItemType, tClass: nwItemClass, tLayout: nwItemLayout, hLevel: str = "H0" self, tType: nwItemType, tClass: nwItemClass, tLayout: nwItemLayout, hLevel: str = "H0"
) -> QIcon: ) -> QIcon:
@@ -778,6 +742,39 @@ class GuiIcons:
return self.getIcon(name, color) 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: def getHeaderDecoration(self, hLevel: int) -> QPixmap:
"""Get the decoration for a specific heading level.""" """Get the decoration for a specific heading level."""
if not self._headerDec: if not self._headerDec:
@@ -835,7 +832,7 @@ class GuiIcons:
return QIcon(str(CONFIG.assetPath("icons") / "x-novelwriter-project.svg")) return QIcon(str(CONFIG.assetPath("icons") / "x-novelwriter-project.svg"))
if svg := self._svgData.get(name, b""): 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) svg = svg.replace(b"#000000", fill)
pixmap = QPixmap(w, h) pixmap = QPixmap(w, h)
pixmap.fill(QtTransparent) pixmap.fill(QtTransparent)
@@ -857,7 +854,7 @@ class GuiIcons:
painter = QPainter(pixmap) painter = QPainter(pixmap)
painter.setRenderHint(QtPaintAntiAlias) 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.fillPath(path, QColor(fill.decode(encoding="utf-8")))
painter.end() painter.end()
+2 -2
View File
@@ -75,8 +75,8 @@ class GuiWelcome(NDialog):
# Elements # Elements
# ======== # ========
self.bgImage = SHARED.theme.loadDecoration("welcome") self.bgImage = SHARED.theme.getDecoration("welcome")
self.nwImage = SHARED.theme.loadDecoration("nw-text", h=36) 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.bgColor = QColor(54, 54, 54) if SHARED.theme.isDarkTheme else QColor(255, 255, 255)
self.nwLogo = QLabel(self) self.nwLogo = QLabel(self)
+31 -27
View File
@@ -184,13 +184,17 @@ def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, tstPaths):
# Check a few values # Check a few values
assert mainTheme._guiPalette.color( assert mainTheme._guiPalette.color(
QPalette.ColorRole.Window).getRgb() == (239, 239, 239, 255) QPalette.ColorRole.Window
).getRgb() == (239, 239, 239, 255)
assert mainTheme._guiPalette.color( assert mainTheme._guiPalette.color(
QPalette.ColorRole.WindowText).getRgb() == (0, 0, 0, 255) QPalette.ColorRole.WindowText
).getRgb() == (0, 0, 0, 255)
assert mainTheme._guiPalette.color( assert mainTheme._guiPalette.color(
QPalette.ColorRole.Base).getRgb() == (255, 255, 255, 255) QPalette.ColorRole.Base
).getRgb() == (255, 255, 255, 255)
assert mainTheme._guiPalette.color( assert mainTheme._guiPalette.color(
QPalette.ColorRole.AlternateBase).getRgb() == (239, 239, 239, 255) QPalette.ColorRole.AlternateBase
).getRgb() == (224, 224, 224, 255)
# Load Default Dark Theme # Load Default Dark Theme
# ======================= # =======================
@@ -298,29 +302,29 @@ def testGuiTheme_IconThemes(qtbot, caplog, monkeypatch, nwGUI, tstPaths):
assert iconCache.themeMeta.name == "Material Symbols - Rounded Medium" assert iconCache.themeMeta.name == "Material Symbols - Rounded Medium"
# Load with project colour override # Load with project colour override
purple = iconCache._svgColours["purple"] purple = iconCache._svgColors["purple"]
assert iconCache._svgColours["root"] != purple assert iconCache._svgColors["root"] != purple
assert iconCache._svgColours["folder"] != purple assert iconCache._svgColors["folder"] != purple
assert iconCache._svgColours["file"] != purple assert iconCache._svgColors["file"] != purple
assert iconCache._svgColours["title"] != purple assert iconCache._svgColors["title"] != purple
assert iconCache._svgColours["chapter"] != purple assert iconCache._svgColors["chapter"] != purple
assert iconCache._svgColours["scene"] != purple assert iconCache._svgColors["scene"] != purple
assert iconCache._svgColours["note"] != purple assert iconCache._svgColors["note"] != purple
CONFIG.iconColTree = "purple" CONFIG.iconColTree = "purple"
assert iconCache.loadTheme("material_rounded_normal") is True assert iconCache.loadTheme("material_rounded_normal") is True
assert iconCache._svgColours["root"] == purple assert iconCache._svgColors["root"] == purple
assert iconCache._svgColours["folder"] == purple assert iconCache._svgColors["folder"] == purple
assert iconCache._svgColours["file"] == purple assert iconCache._svgColors["file"] == purple
assert iconCache._svgColours["title"] == purple assert iconCache._svgColors["title"] == purple
assert iconCache._svgColours["chapter"] == purple assert iconCache._svgColors["chapter"] == purple
assert iconCache._svgColours["scene"] == purple assert iconCache._svgColors["scene"] == purple
assert iconCache._svgColours["note"] == purple assert iconCache._svgColors["note"] == purple
# Change some colours # Change some colours
iconCache.setIconColor("root", QColor(255, 255, 255)) iconCache.setIconColor("root", QColor(255, 255, 255))
assert iconCache._svgColours["root"] != purple assert iconCache._svgColors["root"] != purple
assert iconCache._svgColours["root"] == b"#ffffff" assert iconCache._svgColors["root"] == b"#ffffff"
# List Themes # List Themes
# =========== # ===========
@@ -459,31 +463,31 @@ def testGuiTheme_LoadDecorations(qtbot, monkeypatch, nwGUI):
# ================ # ================
# Invalid name should return empty pixmap # Invalid name should return empty pixmap
qPix = iconCache.loadDecoration("stuff") qPix = iconCache.getDecoration("stuff")
assert qPix.isNull() is True assert qPix.isNull() is True
# Load an image # Load an image
qPix = iconCache.loadDecoration("welcome") qPix = iconCache.getDecoration("welcome")
assert qPix.isNull() is False assert qPix.isNull() is False
# Fail finding the file # Fail finding the file
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("pathlib.Path.is_file", lambda *a: False) mp.setattr("pathlib.Path.is_file", lambda *a: False)
qPix = iconCache.loadDecoration("welcome") qPix = iconCache.getDecoration("welcome")
assert qPix.isNull() is True assert qPix.isNull() is True
# Test image sizes # 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.isNull() is False
assert qPix.width() == 100 assert qPix.width() == 100
assert qPix.height() > 50 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.isNull() is False
assert qPix.width() > 100 assert qPix.width() > 100
assert qPix.height() == 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.isNull() is False
assert qPix.width() == 100 assert qPix.width() == 100
assert qPix.height() == 100 assert qPix.height() == 100