Remove status led colours in theme and use icon colours instead
This commit is contained in:
@@ -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)))
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
+76
-79
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user