Theme updates (#1475)

This commit is contained in:
Veronica Berglyd Olsen
2023-07-19 17:43:57 +02:00
committed by GitHub
8 changed files with 139 additions and 93 deletions
+1 -2
View File
@@ -1,4 +1,3 @@
[Main]
name = Default Theme
name = Qt Default Theme
description = Qt standard colours
icontheme = typicons_light
+5 -4
View File
@@ -10,21 +10,22 @@ icontheme = typicons_dark
[Palette]
window = 54, 54, 54
windowtext = 174, 174, 174
windowtext = 204, 204, 204
base = 62, 62, 62
alternatebase = 78, 78, 78
text = 174, 174, 174
text = 204, 204, 204
tooltipbase = 255, 255, 192
tooltiptext = 21, 21, 13
button = 62, 62, 62
buttontext = 174, 174, 174
brighttext = 174, 174, 174
buttontext = 204, 204, 204
brighttext = 62, 62, 62
highlight = 44, 152, 247
highlightedtext = 255, 255, 255
link = 44, 152, 247
linkvisited = 44, 152, 247
[GUI]
helptext = 164, 164, 164
statusnone = 150, 152, 150
statussaved = 39, 135, 78
statusunsaved = 138, 32, 32
@@ -0,0 +1,31 @@
[Main]
name = Default Light Theme
description = The novelWriter standard light theme
author = Veronica Berglyd Olsen
credit = Veronica Berglyd Olsen
url = https://github.com/vkbo/novelWriter
license = CC BY-SA 4.0
licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
icontheme = typicons_light
[Palette]
window = 239, 239, 239
windowtext = 0, 0, 0
base = 255, 255, 255
alternatebase = 239, 239, 239
text = 0, 0, 0
tooltipbase = 255, 255, 220
tooltiptext = 0, 0, 0
button = 239, 239, 239
buttontext = 0, 0, 0
brighttext = 255, 255, 255
highlight = 48, 135, 198
highlightedtext = 255, 255, 255
link = 0, 84, 255
linkvisited = 0, 84, 255
[GUI]
helptext = 92, 92, 92
statusnone = 120, 120, 120
statussaved = 200, 15, 39
statusunsaved = 2, 133, 37
@@ -17,13 +17,14 @@ tooltipbase = 133, 153, 0
tooltiptext = 0, 43, 54
button = 7, 54, 66
buttontext = 253, 246, 227
brighttext = 253, 246, 227
brighttext = 7, 54, 66
highlight = 42, 161, 152
highlightedtext = 0, 43, 54
link = 38, 139, 210
linkvisited = 38, 139, 210
[GUI]
helptext = 166, 161, 149
statusnone = 88, 110, 117
statussaved = 42, 161, 152
statusunsaved = 203, 75, 22
@@ -17,13 +17,14 @@ tooltipbase = 133, 153, 0
tooltiptext = 0, 43, 54
button = 238, 232, 213
buttontext = 0, 43, 54
brighttext = 0, 43, 54
brighttext = 253, 246, 227
highlight = 42, 161, 152
highlightedtext = 253, 246, 227
link = 38, 139, 210
linkvisited = 38, 139, 210
[GUI]
helptext = 78, 91, 95
statusnone = 88, 110, 117
statussaved = 42, 161, 152
statusunsaved = 203, 75, 22
+3 -3
View File
@@ -135,7 +135,7 @@ class NPagedToolButton(QToolButton):
def paintEvent(self, event):
"""Overload the paint event to draw a simple, left aligned text
label, with a highlight when selected and alternative base
label, with a highlight when selected and a transparent base
colour when hovered.
"""
opt = QStyleOptionToolButton()
@@ -150,9 +150,9 @@ class NPagedToolButton(QToolButton):
palette = self.palette()
if opt.state & QStyle.State_MouseOver == QStyle.State_MouseOver:
backCol = palette.alternateBase()
backCol = palette.base()
paint.setBrush(backCol)
paint.setOpacity(0.5)
paint.setOpacity(0.75)
paint.drawRoundedRect(0, 0, width, height, self._cR, self._cR)
if self.isChecked():
+79 -78
View File
@@ -23,10 +23,12 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
import logging
from math import ceil
from pathlib import Path
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import qApp
@@ -111,10 +113,10 @@ class GuiTheme:
# Load Themes
self._guiPalette = QPalette()
self._themeList = []
self._syntaxList = []
self._availThemes = {}
self._availSyntax = {}
self._themeList: list[tuple[str, str]] = []
self._syntaxList: list[tuple[str, str]] = []
self._availThemes: dict[str, Path] = {}
self._availSyntax: dict[str, Path] = {}
self._listConf(self._availSyntax, CONFIG.assetPath("syntax"))
self._listConf(self._availThemes, CONFIG.assetPath("themes"))
@@ -166,22 +168,22 @@ class GuiTheme:
# Methods
##
def getTextWidth(self, theText, theFont=None):
"""Returns the width needed to contain a given piece of text.
def getTextWidth(self, text: str, font: QFont | None = None) -> int:
"""Returns the width needed to contain a given piece of text in
pixels.
"""
if isinstance(theFont, QFont):
qMetrics = QFontMetrics(theFont)
if isinstance(font, QFont):
qMetrics = QFontMetrics(font)
else:
qMetrics = QFontMetrics(self.guiFont)
return int(ceil(qMetrics.boundingRect(theText).width()))
return int(ceil(qMetrics.boundingRect(text).width()))
##
# Theme Methods
##
def loadTheme(self):
"""Load the currently specified GUI theme.
"""
"""Load the currently specified GUI theme."""
guiTheme = CONFIG.guiTheme
if guiTheme not in self._availThemes:
logger.error("Could not find GUI theme '%s'", guiTheme)
@@ -195,70 +197,72 @@ class GuiTheme:
# Config File
logger.info("Loading GUI theme '%s'", guiTheme)
confParser = NWConfigParser()
parser = NWConfigParser()
try:
with open(themeFile, mode="r", encoding="utf-8") as inFile:
confParser.read_file(inFile)
parser.read_file(inFile)
except Exception:
logger.error("Could not load theme settings from: %s", themeFile)
logException()
return False
# Main
cnfSec = "Main"
if confParser.has_section(cnfSec):
self.themeName = confParser.rdStr(cnfSec, "name", "")
self.themeDescription = confParser.rdStr(cnfSec, "description", "N/A")
self.themeAuthor = confParser.rdStr(cnfSec, "author", "N/A")
self.themeCredit = confParser.rdStr(cnfSec, "credit", "N/A")
self.themeUrl = confParser.rdStr(cnfSec, "url", "")
self.themeLicense = confParser.rdStr(cnfSec, "license", "N/A")
self.themeLicenseUrl = confParser.rdStr(cnfSec, "licenseurl", "")
self.themeIcons = confParser.rdStr(cnfSec, "icontheme", "")
sec = "Main"
if parser.has_section(sec):
self.themeName = parser.rdStr(sec, "name", "")
self.themeDescription = parser.rdStr(sec, "description", "N/A")
self.themeAuthor = parser.rdStr(sec, "author", "N/A")
self.themeCredit = parser.rdStr(sec, "credit", "N/A")
self.themeUrl = parser.rdStr(sec, "url", "")
self.themeLicense = parser.rdStr(sec, "license", "N/A")
self.themeLicenseUrl = parser.rdStr(sec, "licenseurl", "")
self.themeIcons = parser.rdStr(sec, "icontheme", "")
# Palette
cnfSec = "Palette"
if confParser.has_section(cnfSec):
self._setPalette(confParser, cnfSec, "window", QPalette.Window)
self._setPalette(confParser, cnfSec, "windowtext", QPalette.WindowText)
self._setPalette(confParser, cnfSec, "base", QPalette.Base)
self._setPalette(confParser, cnfSec, "alternatebase", QPalette.AlternateBase)
self._setPalette(confParser, cnfSec, "text", QPalette.Text)
self._setPalette(confParser, cnfSec, "tooltipbase", QPalette.ToolTipBase)
self._setPalette(confParser, cnfSec, "tooltiptext", QPalette.ToolTipText)
self._setPalette(confParser, cnfSec, "button", QPalette.Button)
self._setPalette(confParser, cnfSec, "buttontext", QPalette.ButtonText)
self._setPalette(confParser, cnfSec, "brighttext", QPalette.BrightText)
self._setPalette(confParser, cnfSec, "highlight", QPalette.Highlight)
self._setPalette(confParser, cnfSec, "highlightedtext", QPalette.HighlightedText)
self._setPalette(confParser, cnfSec, "link", QPalette.Link)
self._setPalette(confParser, cnfSec, "linkvisited", QPalette.LinkVisited)
sec = "Palette"
if parser.has_section(sec):
self._setPalette(parser, sec, "window", QPalette.ColorRole.Window)
self._setPalette(parser, sec, "windowtext", QPalette.ColorRole.WindowText)
self._setPalette(parser, sec, "base", QPalette.ColorRole.Base)
self._setPalette(parser, sec, "alternatebase", QPalette.ColorRole.AlternateBase)
self._setPalette(parser, sec, "text", QPalette.ColorRole.Text)
self._setPalette(parser, sec, "tooltipbase", QPalette.ColorRole.ToolTipBase)
self._setPalette(parser, sec, "tooltiptext", QPalette.ColorRole.ToolTipText)
self._setPalette(parser, sec, "button", QPalette.ColorRole.Button)
self._setPalette(parser, sec, "buttontext", QPalette.ColorRole.ButtonText)
self._setPalette(parser, sec, "brighttext", QPalette.ColorRole.BrightText)
self._setPalette(parser, sec, "highlight", QPalette.ColorRole.Highlight)
self._setPalette(parser, sec, "highlightedtext", QPalette.ColorRole.HighlightedText)
self._setPalette(parser, sec, "link", QPalette.ColorRole.Link)
self._setPalette(parser, sec, "linkvisited", QPalette.ColorRole.LinkVisited)
else:
self._guiPalette = qApp.style().standardPalette()
# GUI
cnfSec = "GUI"
if confParser.has_section(cnfSec):
self.statNone = self._parseColour(confParser, cnfSec, "statusnone")
self.statUnsaved = self._parseColour(confParser, cnfSec, "statusunsaved")
self.statSaved = self._parseColour(confParser, cnfSec, "statussaved")
# Icons
self.iconCache.loadTheme(self.themeIcons or "typicons_light")
sec = "GUI"
if parser.has_section(sec):
self.helpText = self._parseColour(parser, sec, "helptext")
self.statNone = self._parseColour(parser, sec, "statusnone")
self.statUnsaved = self._parseColour(parser, sec, "statusunsaved")
self.statSaved = self._parseColour(parser, sec, "statussaved")
# Update Dependant Colours
backCol = self._guiPalette.window().color()
textCol = self._guiPalette.windowText().color()
backLCol = backCol.lightnessF()
textLCol = textCol.lightnessF()
backLNess = backCol.lightnessF()
textLNess = textCol.lightnessF()
if backLCol > textLCol:
helpLCol = textLCol + 0.65*(backLCol - textLCol)
else:
helpLCol = backLCol + 0.65*(textLCol - backLCol)
if self.helpText == [0, 0, 0]:
if backLNess > textLNess:
helpLCol = textLNess + 0.35*(backLNess - textLNess)
else:
helpLCol = backLNess + 0.65*(textLNess - backLNess)
self.helpText = [int(255*helpLCol)]*3
self.helpText = [int(255*helpLCol)]*3
# Icons
defaultIcons = "typicons_light" if backLNess >= 0.5 else "typicons_dark"
self.iconCache.loadTheme(self.themeIcons or defaultIcons)
# Apply Styles
qApp.setPalette(self._guiPalette)
@@ -266,8 +270,7 @@ class GuiTheme:
return True
def loadSyntax(self):
"""Load the currently specified syntax highlighter theme.
"""
"""Load the currently specified syntax highlighter theme."""
guiSyntax = CONFIG.guiSyntax
if guiSyntax not in self._availSyntax:
logger.error("Could not find syntax theme '%s'", guiSyntax)
@@ -323,9 +326,8 @@ class GuiTheme:
return True
def listThemes(self):
"""Scan the GUI themes folder and list all themes.
"""
def listThemes(self) -> list[tuple[str, str]]:
"""Scan the GUI themes folder and list all themes."""
if self._themeList:
return self._themeList
@@ -340,9 +342,8 @@ class GuiTheme:
return self._themeList
def listSyntax(self):
"""Scan the syntax themes folder and list all themes.
"""
def listSyntax(self) -> list[tuple[str, str]]:
"""Scan the syntax themes folder and list all themes."""
if self._syntaxList:
return self._syntaxList
@@ -362,8 +363,7 @@ class GuiTheme:
##
def _setGuiFont(self):
"""Update the GUI's font style from settings.
"""
"""Update the GUI's font style from settings."""
theFont = QFont()
fontDB = QFontDatabase()
if CONFIG.guiFont not in fontDB.families():
@@ -383,9 +383,8 @@ class GuiTheme:
return
def _listConf(self, targetDict, checkDir):
"""Scan for theme config files and populate the dictionary.
"""
def _listConf(self, targetDict: dict, checkDir: Path) -> bool:
"""Scan for theme config files and populate the dictionary."""
if not checkDir.is_dir():
return False
@@ -395,29 +394,31 @@ class GuiTheme:
return True
def _parseColour(self, confParser, cnfSec, cnfName):
"""Parse a colour value from a config string.
"""
if confParser.has_option(cnfSec, cnfName):
values = confParser.get(cnfSec, cnfName).split(",")
def _parseColour(
self, parser: NWConfigParser, section: str, name: str
) -> list[int]:
"""Parse a colour value from a config string."""
if parser.has_option(section, name):
values = parser.get(section, name).split(",")
result = []
try:
result.append(minmax(int(values[0]), 0, 255))
result.append(minmax(int(values[1]), 0, 255))
result.append(minmax(int(values[2]), 0, 255))
except Exception:
logger.error("Could not load theme colours for '%s' from config file", cnfName)
logger.error("Could not load theme colours for '%s' from config file", name)
result = [0, 0, 0]
else:
logger.warning("Could not find theme colours for '%s' in config file", cnfName)
logger.warning("Could not find theme colours for '%s' in config file", name)
result = [0, 0, 0]
return result
def _setPalette(self, confParser, cnfSec, cnfName, paletteVal):
"""Set a palette colour value from a config string.
"""
def _setPalette(
self, parser: NWConfigParser, section: str, name: str, value: QPalette.ColorRole
):
"""Set a palette colour value from a config string."""
self._guiPalette.setColor(
paletteVal, QColor(*self._parseColour(confParser, cnfSec, cnfName))
value, QColor(*self._parseColour(parser, section, name))
)
return
+16 -4
View File
@@ -122,8 +122,7 @@ def testGuiTheme_Main(qtbot, nwGUI, tstPaths):
@pytest.mark.gui
def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI):
"""Test the theme part of the class.
"""
"""Test the theme part of the class."""
mainTheme: GuiTheme = nwGUI.mainTheme
# List Themes
@@ -137,7 +136,8 @@ def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI):
# Load the theme info
themesList = mainTheme.listThemes()
assert themesList[0] == ("default_dark", "Default Dark Theme")
assert themesList[1] == ("default", "Default Theme")
assert themesList[1] == ("default_light", "Default Light Theme")
assert themesList[2] == ("default", "Qt Default Theme")
# A second call should returned the cached list
assert mainTheme.listThemes() == mainTheme._themeList
@@ -169,6 +169,18 @@ def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI):
wCol = QApplication.style().standardPalette().color(QPalette.Window).getRgb()
assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == wCol
# Load Default Light Theme
# ========================
CONFIG.guiTheme = "default_light"
assert mainTheme.loadTheme() is True
# Check a few values
assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (239, 239, 239, 255)
assert mainTheme._guiPalette.color(QPalette.WindowText).getRgb() == (0, 0, 0, 255)
assert mainTheme._guiPalette.color(QPalette.Base).getRgb() == (255, 255, 255, 255)
assert mainTheme._guiPalette.color(QPalette.AlternateBase).getRgb() == (239, 239, 239, 255)
# Load Default Dark Theme
# =======================
@@ -177,7 +189,7 @@ def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI):
# Check a few values
assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (54, 54, 54, 255)
assert mainTheme._guiPalette.color(QPalette.WindowText).getRgb() == (174, 174, 174, 255)
assert mainTheme._guiPalette.color(QPalette.WindowText).getRgb() == (204, 204, 204, 255)
assert mainTheme._guiPalette.color(QPalette.Base).getRgb() == (62, 62, 62, 255)
assert mainTheme._guiPalette.color(QPalette.AlternateBase).getRgb() == (78, 78, 78, 255)