Improve welcome dialog and add more theme colour options (#2380)

This commit is contained in:
Veronica Berglyd Olsen
2025-06-04 22:27:07 +02:00
committed by GitHub
24 changed files with 173 additions and 155 deletions
+5 -2
View File
@@ -104,7 +104,7 @@ A colour theme ``.conf`` file consists of the following settings:
text = #000000
link = blue
headertext = green
headertag = green:160
headertag = green:L135
emphasis = orange
dialog = blue
altdialog = blue
@@ -149,12 +149,15 @@ There are several ways to enter colour values:
.. csv-table:: Colour Formats
:header: "Syntax", "Description"
:widths: 15, 85
:class: "tight-table"
"``#RRGGBB``", "A CSS style hexadecimal values, like ``#ff0000`` for red."
"``#RRGGBBAA``", "A CSS style hexadecimal values with transparency, like ``#ff00007f`` for half-transparent red."
"``name``", "A name referring to one of the colours already specified under the ``[Base]`` section, like ``red``. Note that you should not use named colours in the ``[Base]`` section itself as that may have unintended results."
"``name:alpha``", "A name referring to one of the colours already specified under the ``[Base]`` section, with a transparency value added. The alpha value must be in the range ``0`` to ``255``, like ``red:127`` for half-transparent red."
"``name:255``", "A name referring to one of the colours already specified under the ``[Base]`` section, with a transparency value added. The value must be in the range ``0`` to ``255``, like ``red:127`` for half-transparent red."
"``name:L100``", "A name referring to one of the colours already specified under the ``[Base]`` section, where the L-number is a percentage value that makes it lighter. The value must be greater than ``0``. ``L100`` means no change."
"``name:D100``", "A name referring to one of the colours already specified under the ``[Base]`` section, where the D-number is a percentage value that makes it darker. The value must be greater than ``0``. ``D100`` means no change."
"``r, g, b``", "A set of red, green and blue numbers in the range ``0`` to ``255``, like ``255, 0, 0`` for red."
"``r, g, b, a``", "A set of red, green, blue and alpha numbers in the range ``0`` to ``255``, like ``255, 0, 0, 127`` for half-transparent red."
Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

+1 -1
View File
@@ -54,7 +54,7 @@ background = #363636
text = default
link = blue
headertext = green
headertag = green:160
headertag = green:D150
emphasis = orange
dialog = blue
altdialog = blue
+1 -1
View File
@@ -54,7 +54,7 @@ background = #ffffff
text = #000000
link = blue
headertext = green
headertag = green:160
headertag = green:L135
emphasis = orange
dialog = blue
altdialog = blue
+2 -2
View File
@@ -70,7 +70,7 @@ background = #282a36
text = #f8f8f2
link = #ff79c6
headertext = purple
headertag = #bd93f9a0
headertag = purple:D150
emphasis = orange
dialog = green
altdialog = yellow
@@ -85,4 +85,4 @@ spellcheckline = red
errorline = green
replacetag = yellow
modifier = cyan
texthighlight = #f1fa8c60
texthighlight = yellow:96
+17 -17
View File
@@ -8,7 +8,7 @@ license = CC BY-SA 4.0
licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
[Base]
default = #cccccc
default = #c8c8c8
faded = #949494
red = #f2777a
orange = #f99139
@@ -44,28 +44,28 @@ link = blue
linkvisited = blue
[GUI]
helptext = #a4a4a4
helptext = default:D120
fadedtext = faded
errortext = red
[Syntax]
background = #363636
text = #c8c8c8
link = #c8c8c8
headertext = #e1e1e1
headertag = #e1e1e1a0
emphasis = #c8c8c8
dialog = #c8c8c8
altdialog = #c8c8c8
note = #c8c8c8
text = default
link = default
headertext = default:L115
headertag = default:D125
emphasis = default
dialog = default
altdialog = default
note = default
hidden = faded
shortcode = #e1e1e1
keyword = #e1e1e1
tag = #c8c8c8
value = #c8c8c8
optional = #e1e1e1
shortcode = default:L115
keyword = default:L115
tag = default
value = default
optional = default:L115
spellcheckline = red
errorline = green
replacetag = #e1e1e1
modifier = #e1e1e1
replacetag = default:L115
modifier = default:L115
texthighlight = #ffffff40
+17 -17
View File
@@ -8,7 +8,7 @@ license = CC BY-SA 4.0
licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
[Base]
default = #303030
default = #282828
faded = #6c6c6c
red = #a62a2d
orange = #b36829
@@ -44,28 +44,28 @@ link = blue
linkvisited = blue
[GUI]
helptext = #5c5c5c
helptext = default:L225
fadedtext = faded
errortext = red
[Syntax]
background = #ffffff
text = #141414
link = #141414
headertext = #000000
headertag = #000000a0
emphasis = #141414
dialog = #141414
altdialog = #141414
note = #141414
text = default
link = default
headertext = default:D200
headertag = default:L400
emphasis = default
dialog = default
altdialog = default
note = default
hidden = faded
shortcode = #000000
keyword = #000000
tag = #141414
value = #141414
optional = #000000
shortcode = default:D200
keyword = default:D200
tag = default
value = default
optional = default:D200
spellcheckline = red
errorline = green
replacetag = #000000
modifier = #000000
replacetag = default:D200
modifier = default:D200
texthighlight = #00000040
+1 -1
View File
@@ -77,7 +77,7 @@ headertag = blue:160
emphasis = orange
dialog = green
altdialog = yellow
note = #80764b
note = yellow:D175
hidden = faded
shortcode = blue
keyword = red
+2 -2
View File
@@ -32,7 +32,7 @@ default = #d6deeb
faded = #637777
red = #f78c6c
orange = #ecc48d
yellow = #addb67
yellow = #ffeb95
green = #addb67
cyan = #7fdbca
blue = #82aaff
@@ -77,7 +77,7 @@ headertag = blue:160
emphasis = orange
dialog = green
altdialog = yellow
note = #fff9ca
note = yellow:L115
hidden = faded
shortcode = blue
keyword = red
+4 -4
View File
@@ -66,12 +66,12 @@ background = #fafbfc
text = default
link = blue
headertext = green
headertag = #2dae58a0
headertag = green:D125
emphasis = purple
dialog = blue
altdialog = yellow
note = #78bbb9
hidden = #9194a2
note = cyan
hidden = purple:D150
shortcode = purple
keyword = blue
tag = green
@@ -81,4 +81,4 @@ spellcheckline = red
errorline = green
replacetag = cyan
modifier = purple
texthighlight = #cf9c0060
texthighlight = yellow:96
@@ -58,12 +58,12 @@ button = #073642
buttontext = default
brighttext = #073642
highlight = cyan
highlightedtext = #002b36
highlightedtext = default
link = blue
linkvisited = blue
[GUI]
helptext = purple
helptext = cyan
fadedtext = faded
errortext = red
@@ -71,18 +71,18 @@ errortext = red
background = #073642
text = default
link = blue
headertext = default:196
headertext = default:D125
headertag = cyan
emphasis = blue
dialog = cyan
altdialog = cyan
note = cyan:196
hidden = default:196
note = cyan:L115
hidden = default:D150
shortcode = yellow
keyword = green
tag = orange
value = orange
optional = cyan:196
optional = cyan:L115
spellcheckline = orange
errorline = red
replacetag = green
@@ -59,12 +59,12 @@ button = #eee8d5
buttontext = default
brighttext = #eee8d5
highlight = cyan
highlightedtext = #fdf6e3
highlightedtext = default
link = blue
linkvisited = blue
[GUI]
helptext = purple
helptext = cyan
fadedtext = faded
errortext = red
@@ -72,18 +72,18 @@ errortext = red
background = #eee8d5
text = default
link = blue
headertext = default:196
headertext = default:L125
headertag = cyan
emphasis = blue
dialog = cyan
altdialog = cyan
note = cyan:196
hidden = default:196
note = cyan:D125
hidden = default:L200
shortcode = yellow
keyword = green
tag = orange
value = orange
optional = cyan:196
optional = cyan:D125
spellcheckline = orange
errorline = red
replacetag = green
+4 -4
View File
@@ -41,20 +41,20 @@ note = yellow
window = #555753
windowtext = default
base = #2e3436
alternatebase = #888a85
alternatebase = #454e51
text = default
tooltipbase = #ffffc0
tooltiptext = #15150d
button = #555753
buttontext = default
brighttext = #555753
highlight = #2c98f7
highlight = blue
highlightedtext = #ffffff
link = blue
linkvisited = blue
[GUI]
helptext = #e9b96e
helptext = orange
fadedtext = faded
errortext = red
@@ -67,7 +67,7 @@ headertag = blue:160
emphasis = orange
dialog = green
altdialog = yellow
note = #fcf2a4
note = yellow:L115
hidden = faded
shortcode = blue
keyword = red
+3 -3
View File
@@ -48,13 +48,13 @@ tooltiptext = #15150d
button = #d3d7cf
buttontext = default
brighttext = #d3d7cf
highlight = #3087c6
highlight = blue:L200
highlightedtext = #ffffff
link = blue
linkvisited = blue
[GUI]
helptext = #8f5902
helptext = orange
fadedtext = faded
errortext = red
@@ -67,7 +67,7 @@ headertag = blue:160
emphasis = orange
dialog = green
altdialog = yellow
note = #a78800
note = yellow:D125
hidden = faded
shortcode = blue
keyword = red
+1 -1
View File
@@ -73,7 +73,7 @@ background = #ffffff
text = #4d4d4c
link = blue
headertext = blue
headertag = blue:160
headertag = blue:L135
emphasis = orange
dialog = green
altdialog = yellow
@@ -73,7 +73,7 @@ background = #1d1f21
text = default
link = blue
headertext = blue
headertag = blue:160
headertag = blue:D150
emphasis = orange
dialog = green
altdialog = yellow
@@ -73,7 +73,7 @@ background = #002451
text = default
link = blue
headertext = blue
headertag = blue:160
headertag = blue:D150
emphasis = orange
dialog = green
altdialog = yellow
@@ -73,7 +73,7 @@ background = #000000
text = default
link = blue
headertext = blue
headertag = blue:160
headertag = blue:D150
emphasis = orange
dialog = green
altdialog = yellow
@@ -73,7 +73,7 @@ background = #2d2d2d
text = default
link = blue
headertext = blue
headertag = blue:160
headertag = blue:D150
emphasis = orange
dialog = green
altdialog = yellow
+33 -28
View File
@@ -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
@@ -634,7 +639,7 @@ class GuiIcons:
"unfold": ("unfold-show", "unfold-hide"),
}
IMAGE_MAP: Final[dict[str, tuple[str, str]]] = {
"welcome": ("welcome-light.jpg", "welcome-dark.jpg"),
"welcome": ("welcome.webp", "welcome.webp"),
"nw-text": ("novelwriter-text-light.svg", "novelwriter-text-dark.svg"),
}
+2 -3
View File
@@ -32,7 +32,7 @@ from PyQt6.QtCore import (
QAbstractListModel, QModelIndex, QObject, QPoint, QSize, Qt, pyqtSignal,
pyqtSlot
)
from PyQt6.QtGui import QAction, QCloseEvent, QColor, QFont, QPainter, QPaintEvent, QPen, QShortcut
from PyQt6.QtGui import QAction, QCloseEvent, QFont, QPainter, QPaintEvent, QPen, QShortcut
from PyQt6.QtWidgets import (
QApplication, QFileDialog, QFormLayout, QHBoxLayout, QLabel, QLineEdit,
QListView, QMenu, QPushButton, QScrollArea, QStackedWidget,
@@ -77,7 +77,6 @@ class GuiWelcome(NDialog):
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)
self.nwLogo.setPixmap(SHARED.theme.getPixmap("novelwriter", (128, 128)))
@@ -178,8 +177,8 @@ class GuiWelcome(NDialog):
hPix = min(hWin, 600)
tMode = Qt.TransformationMode.SmoothTransformation
painter = QPainter(self)
painter.fillRect(self.rect(), self.bgColor)
painter.drawPixmap(0, hWin - hPix, self.bgImage.scaledToHeight(hPix, tMode))
painter.end()
super().paintEvent(event)
return
+26 -15
View File
@@ -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