Merge branch 'main' into unicode_fix

This commit is contained in:
Veronica Berglyd Olsen
2025-07-05 14:17:16 +02:00
170 changed files with 5273 additions and 2593 deletions
+22 -8
View File
@@ -55,19 +55,33 @@ The following libraries are dependencies of novelWriter:
* **Enchant** by Dom Lachowicz
* **PyEnchant** by Dimitri Merejkowsky
## Assets
## Icon Themes
Some of the assets bundled with novelWriter were adapted from the following sources:
The following icon themes are bundles with novelWriter:
* **Material Symbols** icons by Google Inc (Apache 2.0)
* **Remix** icons by RemixIcon (Apache 2.0)
* **Font Awesome** icons by Fonticons Inc (CC BY 4.0)
* **Tomorrow** syntax themes by Chris Kempson (MIT License)
* **Owl** syntax themes by Sarah Drasner (MIT License)
* **Solarized** themes by Ethan Schoonover (MIT License)
* **Cyberpunk Night** theme by Anders Lemvigh (CC BY-SA 4.0)
* **Dracula** theme by Zeno Rocha (MIT License)
* **Snazzy Light** theme by Florian Reuschel (MIT License)
## Colour Themes
Some colour themes included with novelWriter were adapted from the following sources:
* **Dracula** theme by Zeno Rocha
* **Espresso** theme by Sainnhe Park and Antoine Cotten
* **Noctis** themes by Liviu Schera, and contributors
* **Owl** themes by Sarah Drasner
* **Primer** themes by GitHub Inc and Myian
* **Snazzy Light** theme by Florian Reuschel
* **Solarized** themes by Ethan Schoonover
* **Tango** themes by the Tango Desktop Project
* **Tomorrow** themes by Chris Kempson
Other themes were created for novelWriter:
* **Blue Streak** themes by Veronica Berglyd Olsen
* **Cyberpunk Night** theme by Anders Lemvigh
* **LCARS** theme by Veronica Berglyd Olsen
## Fonts
+1
View File
@@ -101,6 +101,7 @@ Text Formatting Shortcuts
":kbd:`Ctrl+B`", "Format selected text, or word under cursor, with bold"
":kbd:`Ctrl+D`", "Format selected text, or word under cursor, with strike through"
":kbd:`Ctrl+I`", "Format selected text, or word under cursor, with italic"
":kbd:`Ctrl+M`", "Highlight selected text, or word under cursor"
":kbd:`Ctrl+Shift+/`", "Remove format for block or selected text"
":kbd:`Ctrl+Shift+D`", "Toggle ignored text format for block or selected text"
+1
View File
@@ -75,6 +75,7 @@ storage for robustness.
more/counting
more/typography
more/dictionaries
more/customise
more/handling_errors
more/project_format
+196 -170
View File
@@ -1,94 +1,35 @@
.. _docs_more_custom:
**************
Customisations
**************
*************
Custom Themes
*************
.. _Enchant: https://rrthomas.github.io/enchant/
.. _Free Desktop: https://cgit.freedesktop.org/libreoffice/dictionaries/tree/
There are a few ways you can customise novelWriter yourself. Currently, you can add new GUI themes,
your own syntax themes, and install additional dictionaries.
.. _docs_more_custom_dict:
Spell Check Dictionaries
========================
novelWriter uses Enchant_ as the spell checking tool. Depending on your operating system, it may or
may not load all installed spell check dictionaries automatically.
Linux and MacOS
---------------
On Linux and MacOS, you generally only have to install hunspell, aspell or myspell dictionaries on
your system like you do for other applications. See your distro or OS documentation for how to do
this. These dictionaries should show up as available spell check languages in novelWriter.
Windows
-------
For Windows, English is included with the installation. For other languages you have to download
and add dictionaries yourself.
**Install Tool**
A small tool to assist with this can be found under **Tools > Add Dictionaries**. It will import
spell checking dictionaries from Free Office or Libre Office extensions. The dictionaries are then
installed in the install location for the Enchant library and should thus work for any application
that uses Enchant for spell checking.
**Manual Install**
If you prefer to do this manually or want to use a different source than the ones mentioned above,
You need to get compatible dictionary files for your language. You need two files files ending with
``.aff`` and ``.dic``. These files must then be copied to the following location:
``C:\Users\<USER>\AppData\Local\enchant\hunspell``
This assumes your user profile is stored at ``C:\Users\<USER>``. The last one or two folders may
not exist, so you may need to create them.
You can find the various dictionaries on the `Free Desktop`_ website.
.. note::
The Free Desktop link points to a repository, and what may look like file links inside the
dictionary folder are actually links to web pages. If you right-click and download those, you
get HTML files, not dictionaries!
In order to download the actual dictionary files, right-click the "plain" label at the end of
each line and download that.
There are a few ways you can customise novelWriter yourself. Currently, you can relatively easily
add new GUI themes. You can also add new icon themes, although this is not as straightforward.
.. _docs_more_custom_theme:
Syntax and GUI Themes
=====================
Colour Themes
=============
Adding your own GUI and syntax themes is relatively easy, although it requires that you manually
edit config files with colour values. The themes are defined by simple plain text config files with
meta data and colour settings.
Adding your own colour themes is relatively easy, although it requires that you manually edit
config files with colour values. The themes are defined by simple plain text config files with meta
data and colour settings.
In order to make your own versions, first copy one of the existing files to your local computer and
modify it as you like.
* The existing syntax themes are stored in
`novelwriter/assets/syntax <https://github.com/vkbo/novelWriter/tree/main/novelwriter/assets/syntax>`_.
* The existing GUI themes are stored in
`novelwriter/assets/themes <https://github.com/vkbo/novelWriter/tree/main/novelwriter/assets/themes>`_.
* The existing icon themes are stored in
`novelwriter/assets/icons <https://github.com/vkbo/novelWriter/tree/main/novelwriter/assets/icons>`_.
The existing colour themes are stored in
`novelwriter/assets/themes <https://github.com/vkbo/novelWriter/tree/main/novelwriter/assets/themes>`_.
Remember to also change the name of your theme by modifying the ``name`` setting at the top of the
file, otherwise you may not be able to distinguish them in **Preferences**.
For novelWriter to be able to locate the custom theme files, you must copy them to the
:ref:`docs_technical_locations_data` location in your home or user area. There should be a folder there named
``syntax`` for syntax themes, just ``themes`` for GUI themes, and ``icons`` for icon themes. These
folders are created the first time you start novelWriter.
:ref:`docs_technical_locations_data` location in your home or user area. There should be a folder
there named ``themes`` for colour themes. These folders are created the first time you start
novelWriter.
Once the files are copied there, they should show up in **Preferences** with the label you
set as ``name`` inside the file.
@@ -99,71 +40,130 @@ set as ``name`` inside the file.
files up to date.
Custom GUI and Icons Theme
--------------------------
The Theme File Format
---------------------
A GUI theme ``.conf`` file consists of the following settings:
A colour theme ``.conf`` file consists of the following settings:
.. code-block:: cfg
:caption: The theme file for the "Default Light Theme"
[Main]
name = My Custom Theme
description = A description of my custom theme
author = Jane Doe
credit = John Doe
url = https://example.com
license = CC BY-SA 4.0
licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
name = Default Light Theme
mode = light
description = The novelWriter standard light theme
author = Veronica Berglyd Olsen
credit = Veronica Berglyd Olsen
url = https://github.com/vkbo/novelWriter
[Icons]
default = 100, 100, 100
faded = 100, 100, 100
red = 255, 0, 0
orange = 255, 128, 0
yellow = 255, 255, 0
green = 0, 255, 0
aqua = 0, 255, 255
blue = 0, 0, 255
purple = 255, 0, 255
[Base]
base = #fcfcfc
default = #303030
faded = #6c6c6c
red = #a62a2d
orange = #b36829
yellow = #a39c34
green = #296629
cyan = #269999
blue = #3a70a6
purple = #b35ab3
[Project]
root = 0, 255, 255
folder = 255, 255, 0
file = 100, 100, 100
title = 0, 255, 0
chapter = 255, 0, 0
scene = 0, 0, 255
note = 255, 255, 0
root = blue
folder = yellow
file = default
title = green
chapter = red
scene = blue
note = yellow
active = green
inactive = red
disabled = faded
[Palette]
window = 100, 100, 100
windowtext = 100, 100, 100
base = 100, 100, 100
alternatebase = 100, 100, 100
text = 100, 100, 100
tooltipbase = 100, 100, 100
tooltiptext = 100, 100, 100
button = 100, 100, 100
buttontext = 100, 100, 100
brighttext = 100, 100, 100
highlight = 100, 100, 100
highlightedtext = 100, 100, 100
link = 100, 100, 100
linkvisited = 100, 100, 100
window = base:D105
windowtext = default
base = base
alternatebase = #e0e0e0
text = default
tooltipbase = #ffffc0
tooltiptext = #15150d
button = #efefef
buttontext = default
brighttext = base
highlight = #3087c6
highlightedtext = base
link = blue
linkvisited = blue
accent = #3087c6
[GUI]
helptext = 0, 0, 0
fadedtext = 128, 128, 128
errortext = 255, 0, 0
helptext = #5c5c5c
fadedtext = #6c6c6c
errortext = red
In the Main section you must at least define the ``name`` settings.
[Syntax]
background = base
text = default
line = default:32
link = blue
headertext = green
headertag = green:L135
emphasis = orange
dialog = blue
altdialog = blue
note = yellow:D125
hidden = faded
shortcode = green
keyword = red
tag = green
value = blue
optional = green
spellcheckline = red
errorline = green
replacetag = green
modifier = green
texthighlight = yellow:72
The Palette values correspond to the Qt enum values for ``QPalette::ColorRole``, see the
`Qt documentation <https://doc.qt.io/qt-6/qpalette.html#ColorRole-enum>`_ for more details. The
colour values are RGB numbers on the format ``r, g, b`` where each is an integer from ``0`` to
``255``. Omitted values are not loaded and will use default values. If the ``helptext`` colour is
not defined, it is computed as a colour between the ``window`` and ``windowtext`` colour.
Additional shades of some of the colours are also computed. These are mainly used for 3D effects.
Theme Sections
--------------
.. _ColorRole: https://doc.qt.io/qt-6/qpalette.html#ColorRole-enum
The theme file is made up of different sections depending on what part of novelWriter the theme
affects.
.. csv-table:: Theme Sections Overview
:header: "Section", "Description"
:class: "tight-table"
"``[Main]``", "Meta data about the theme, You must at least set ``name``, ``mode`` and ``author``, and ``mode`` must be either ``light`` or ``dark``."
"``[Base]``", "The base colours of the theme. These are also selectable colours in various places inside the app, like for icon colours in **Preferences**."
"``[Project]``", "The colours used for icons and markers for the different project item types."
"``[Palette]``", "The colours used for styling the user interface. The values correspond to the ColorRole_ values in the Qt library."
"``[GUI]``", "The colours used for styling additional elements of the user interface."
"``[Syntax]``", "The colours used for syntax highlighting in documents."
Colour Value Formats
--------------------
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: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."
.. versionadded:: 2.5
The ``fadedtext`` and ``errortext`` theme colour entries were added.
@@ -172,60 +172,86 @@ Additional shades of some of the colours are also computed. These are mainly use
The ``icontheme`` setting was dropped as the icon theme is now its own setting.
The ``[Icons]`` and ``[Project]`` sections were added, and the ``status*`` settings removed.
.. versionadded:: 2.8
The ``[Syntax]`` section was moved into the main theme file. Previously, these settings were in
their own file. The ``[Icons]`` section was renamed to ``[Base]``. Added the ``line`` setting.
Dropped the ``license`` and ``licenseurl`` settings. The ``description`` and ``author`` fields
are now required if the theme is included in the app, but not for user themes.
Custom Syntax Theme
-------------------
A syntax theme ``.conf`` file consists of the following settings:
Icon Themes
===========
Icon themes are *not* straightforward to add, but if you want to make the effort, this section
describes how to do it.
The existing icon themes are stored in
`novelwriter/assets/icons <https://github.com/vkbo/novelWriter/tree/main/novelwriter/assets/icons>`_.
As with colour themes, remember to change the name of your theme by modifying the ``name`` setting
at the top of the file, otherwise you may not be able to distinguish them in **Preferences**.
For novelWriter to be able to locate the custom theme files, you must copy them to the
:ref:`docs_technical_locations_data` location in your home or user area. There should be a folder
there ``icons`` for icon themes. These folders are created the first time you start novelWriter.
The Icons File Format
---------------------
Icon themes are kept in files with the ``.icons`` file extension. The file format is a custom
format with entries on the form ``section:key = value``.
.. code-block:: cfg
:caption: The icons file for "Material Symbols - Rounded Medium" (truncated)
[Main]
name = My Syntax Theme
author = Jane Doe
credit = John Doe
url = https://example.com
license = CC BY-SA 4.0
licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
# Meta
meta:name = Material Symbols - Rounded Medium
meta:author = Google Inc
meta:license = Apache 2.0
[Syntax]
background = 255, 255, 255
text = 0, 0, 0
link = 0, 0, 0
headertext = 0, 0, 0
headertag = 0, 0, 0
emphasis = 0, 0, 0
dialog = 0, 0, 0
altdialog = 0, 0, 0
note = 0, 0, 0
hidden = 0, 0, 0
shortcode = 0, 0, 0
keyword = 0, 0, 0
tag = 0, 0, 0
value = 0, 0, 0
optional = 0, 0, 0
spellcheckline = 0, 0, 0
errorline = 0, 0, 0
replacetag = 0, 0, 0
modifier = 0, 0, 0
texthighlight = 255, 255, 255, 128
# Icons
icon:alert_error = <svg ...>
icon:alert_info = <svg ...>
icon:alert_question = <svg ...>
icon:alert_warn = <svg ...>
icon:cls_archive = <svg ...>
icon:cls_character = <svg ...>
icon:cls_custom = <svg ...>
icon:cls_entity = <svg ...>
icon:cls_none = <svg ...>
icon:cls_novel = <svg ...>
icon:cls_object = <svg ...>
icon:cls_plot = <svg ...>
icon:cls_template = <svg ...>
icon:cls_timeline = <svg ...>
icon:cls_trash = <svg ...>
icon:cls_world = <svg ...>
In the Main section, you must define at least the ``name`` setting. The Syntax colour values are
RGB(A) numbers of the format ``r, g, b, a`` where each is an integer from ``0`` to ``255``. The
fourth value is the alpha channel, which can be omitted.
The icon keys are associated with icon placement locations inside novelWriter, and the template for
them is defined in the script that generates the default icon themes.
Omitted syntax colours default to black, except ``background`` which defaults to white, and
``texthighlight`` which defaults to white with half transparency.
The script can be found under
`utils/icon_themes.py <https://github.com/vkbo/novelWriter/blob/main/utils/icon_themes.py>`__
in the source code.
.. versionadded:: 2.2
The ``shortcode`` syntax colour entry was added.
This file includes all the code needed to generate the themes that are included in novelWriter. The
icon keys are mapped to icon keys from the specific themes in JSON files in the ``icon_themes``
folder next to the script. This is the recommended way to generate these themes. Doing it manually
is not advisable.
.. versionadded:: 2.3
The ``optional`` syntax colour entry was added.
.. versionadded:: 2.4
The ``texthighlight`` syntax colour entry was added.
Icon Value Format
-----------------
.. versionadded:: 2.5
The ``dialog``, ``altdialog``, ``note`` and ``tag`` syntax colour entries were added.
``straightquotes``, ``doublequotes`` and ``singlequotes`` were removed.
As can be seen from the example, an icon is defined in the ``icon`` section with a key and an
in-line SVG XML block. The XML must fit on one line and obey the following rules:
#. It must be single colour, that is, the fill colour attribute must be able to colourise the
entire icon.
#. The fill colour attribute *must* be defined and must be set to: ``fill="#000000"``. This value
is replaced by the relevant theme colour when the icon is processed in novelWriter.
.. versionadded:: 2.7
The icon theme files were added. Previously, icons were stored as individual SVG files with a
config file mapping the file names to the internal icon keys.
+55
View File
@@ -0,0 +1,55 @@
.. _docs_more_dictionaries:
************************
Spell Check Dictionaries
************************
.. _Enchant: https://rrthomas.github.io/enchant/
.. _Free Desktop: https://cgit.freedesktop.org/libreoffice/dictionaries/tree/
Spell checking is provided by the Enchant_ library. Depending on your operating system, it may or
may not load all installed spell check dictionaries automatically.
Linux and MacOS
===============
On Linux and MacOS, you generally only have to install hunspell, aspell or myspell dictionaries on
your system like you do for other applications. See your distro or OS documentation for how to do
this. These dictionaries should show up as available spell check languages in novelWriter.
Windows
=======
For Windows, English is included with the installation. For other languages you have to download
and add dictionaries yourself.
**Install Tool**
A small tool to assist with this can be found under **Tools > Add Dictionaries**. It will import
spell checking dictionaries from Free Office or Libre Office extensions. The dictionaries are then
installed in the install location for the Enchant library and should thus work for any application
that uses Enchant for spell checking.
**Manual Install**
If you prefer to do this manually or want to use a different source than the ones mentioned above,
You need to get compatible dictionary files for your language. You need two files files ending with
``.aff`` and ``.dic``. These files must then be copied to the following location:
``C:\Users\<USER>\AppData\Local\enchant\hunspell``
This assumes your user profile is stored at ``C:\Users\<USER>``. The last one or two folders may
not exist, so you may need to create them.
You can find the various dictionaries on the `Free Desktop`_ website.
.. note::
The Free Desktop link points to a repository, and what may look like file links inside the
dictionary folder are actually links to web pages. If you right-click and download those, you
get HTML files, not dictionaries!
In order to download the actual dictionary files, right-click the "plain" label at the end of
each line and download that.
+1 -1
View File
@@ -55,7 +55,7 @@ edited from the **Project Word List** tool available from the **Tools** menu.
.. note::
Generally, spell checking dictionaries are collected from your operating system, but on Windows
they are not. See :ref:`docs_more_custom_dict` for how to add spell checking dictionaries on
they are not. See :ref:`docs_more_dictionaries` for how to add spell checking dictionaries on
Windows.
+5 -4
View File
@@ -49,9 +49,9 @@ __license__ = "GPLv3"
__author__ = "Veronica Berglyd Olsen"
__maintainer__ = "Veronica Berglyd Olsen"
__email__ = "code@vkbo.net"
__version__ = "2.7.2"
__hexversion__ = "0x020702f0"
__date__ = "2025-06-24"
__version__ = "2.8a1"
__hexversion__ = "0x020800a1"
__date__ = "2025-06-13"
__status__ = "Stable"
__domain__ = "novelwriter.io"
@@ -296,7 +296,8 @@ def _createApp(style: str) -> QApplication:
"""Create the app. This is done in a function to make it easier to
block app creation during testing.
"""
app = QApplication([CONFIG.appName, f"-style={style}"])
app = QApplication([CONFIG.appName])
app.setStyle(style)
app.setApplicationName(CONFIG.appName)
app.setApplicationVersion(__version__)
app.setOrganizationDomain(__domain__)
@@ -56,6 +56,9 @@ icon:sb_outline = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512
icon:sb_project = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="#000000" height="128" width="128"><path d="M471.6 21.7c-21.9-21.9-57.3-21.9-79.2 0L362.3 51.7l97.9 97.9 30.1-30.1c21.9-21.9 21.9-57.3 0-79.2L471.6 21.7zm-299.2 220c-6.1 6.1-10.8 13.6-13.5 21.9l-29.6 88.8c-2.9 8.6-.6 18.1 5.8 24.6s15.9 8.7 24.6 5.8l88.8-29.6c8.2-2.7 15.7-7.4 21.9-13.5L437.7 172.3 339.7 74.3 172.4 241.7zM96 64C43 64 0 107 0 160L0 416c0 53 43 96 96 96l256 0c53 0 96-43 96-96l0-96c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 96c0 17.7-14.3 32-32 32L96 448c-17.7 0-32-14.3-32-32l0-256c0-17.7 14.3-32 32-32l96 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L96 64z" /></svg>
icon:sb_search = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="#000000" height="128" width="128"><path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z" /></svg>
icon:sb_stats = <svg xmlns="http://www.w3.org/2000/svg" viewBox="-32 0 512 512" fill="#000000" height="128" width="128"><path d="M160 80c0-26.5 21.5-48 48-48l32 0c26.5 0 48 21.5 48 48l0 352c0 26.5-21.5 48-48 48l-32 0c-26.5 0-48-21.5-48-48l0-352zM0 272c0-26.5 21.5-48 48-48l32 0c26.5 0 48 21.5 48 48l0 160c0 26.5-21.5 48-48 48l-32 0c-26.5 0-48-21.5-48-48L0 272zM368 96l32 0c26.5 0 48 21.5 48 48l0 288c0 26.5-21.5 48-48 48l-32 0c-26.5 0-48-21.5-48-48l0-288c0-26.5 21.5-48 48-48z" /></svg>
icon:theme_light = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="#000000" height="128" width="128"><path d="M361.5 1.2c5 2.1 8.6 6.6 9.6 11.9L391 121l107.9 19.8c5.3 1 9.8 4.6 11.9 9.6s1.5 10.7-1.6 15.2L446.9 256l62.3 90.3c3.1 4.5 3.7 10.2 1.6 15.2s-6.6 8.6-11.9 9.6L391 391 371.1 498.9c-1 5.3-4.6 9.8-9.6 11.9s-10.7 1.5-15.2-1.6L256 446.9l-90.3 62.3c-4.5 3.1-10.2 3.7-15.2 1.6s-8.6-6.6-9.6-11.9L121 391 13.1 371.1c-5.3-1-9.8-4.6-11.9-9.6s-1.5-10.7 1.6-15.2L65.1 256 2.8 165.7c-3.1-4.5-3.7-10.2-1.6-15.2s6.6-8.6 11.9-9.6L121 121 140.9 13.1c1-5.3 4.6-9.8 9.6-11.9s10.7-1.5 15.2 1.6L256 65.1 346.3 2.8c4.5-3.1 10.2-3.7 15.2-1.6zM160 256a96 96 0 1 1 192 0 96 96 0 1 1 -192 0zm224 0a128 128 0 1 0 -256 0 128 128 0 1 0 256 0z" /></svg>
icon:theme_dark = <svg xmlns="http://www.w3.org/2000/svg" viewBox="-64 0 512 512" fill="#000000" height="128" width="128"><path d="M223.5 32C100 32 0 132.3 0 256S100 480 223.5 480c60.6 0 115.5-24.2 155.8-63.4c5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6c-96.9 0-175.5-78.8-175.5-176c0-65.8 36-123.1 89.3-153.3c6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z" /></svg>
icon:theme_auto = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="#000000" height="128" width="128"><path d="M448 256c0-106-86-192-192-192l0 384c106 0 192-86 192-192zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256z" /></svg>
icon:add = <svg xmlns="http://www.w3.org/2000/svg" viewBox="-32 0 512 512" fill="#000000" height="128" width="128"><path d="M256 80c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 144L48 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l144 0 0 144c0 17.7 14.3 32 32 32s32-14.3 32-32l0-144 144 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-144 0 0-144z" /></svg>
icon:bookmarks = <svg xmlns="http://www.w3.org/2000/svg" viewBox="-64 0 512 512" fill="#000000" height="128" width="128"><path d="M0 48V487.7C0 501.1 10.9 512 24.3 512c5 0 9.9-1.5 14-4.4L192 400 345.7 507.6c4.1 2.9 9 4.4 14 4.4c13.4 0 24.3-10.9 24.3-24.3V48c0-26.5-21.5-48-48-48H48C21.5 0 0 21.5 0 48z" /></svg>
icon:browse = <svg xmlns="http://www.w3.org/2000/svg" viewBox="32 0 512 512" fill="#000000" height="128" width="128"><path d="M88.7 223.8L0 375.8 0 96C0 60.7 28.7 32 64 32l117.5 0c17 0 33.3 6.7 45.3 18.7l26.5 26.5c12 12 28.3 18.7 45.3 18.7L416 96c35.3 0 64 28.7 64 64l0 32-336 0c-22.8 0-43.8 12.1-55.3 31.8zm27.6 16.1C122.1 230 132.6 224 144 224l400 0c11.5 0 22 6.1 27.7 16.1s5.7 22.2-.1 32.1l-112 192C453.9 474 443.4 480 432 480L32 480c-11.5 0-22-6.1-27.7-16.1s-5.7-22.2 .1-32.1l112-192z" /></svg>
@@ -56,6 +56,9 @@ icon:sb_outline = <svg xmlns="http://www.w3.org/2000/svg" height="128" view
icon:sb_project = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M556.61-113.78v-63.74q0-10.83 4.13-20.31 4.13-9.47 11.83-17.17L781-423q10.7-10.7 23.67-15.54 12.98-4.85 26.24-4.85 14.26 0 27.24 5.35 12.98 5.34 23.68 16.04l37 37q9.69 10.7 15.04 23.67 5.35 12.98 5.35 26.24 0 13.83-4.85 27.03-4.85 13.19-15.54 23.89l-208 208q-7.7 7.69-17.18 11.54-9.48 3.85-20.3 3.85h-63.74q-22.09 0-37.54-15.46-15.46-15.46-15.46-37.54Zm279.96-187.96 31.34-33.35-37-37-32.34 32.35 38 38ZM166.78-140.78q-44.3 0-75.15-30.85-30.85-30.85-30.85-75.15v-466.44q0-44.3 30.85-75.15 30.85-30.85 75.15-30.85h181q21.09 0 40.39 7.98 19.31 7.98 34.26 22.94L480-730.74h313.22q44.3 0 75.15 30.85 30.85 30.85 30.85 75.15v57q0 21.96-17.89 35.22-17.89 13.26-40.85 9.13-9.78-.57-19.35.28-9.57.85-18.78 3.68-21.79 2.82-41.76 12.82-19.98 10-36.59 26.61L507.52-263.52q-14.95 14.95-22.93 33.98-7.98 19.02-7.98 40.67v1.31q0 19.82-13.48 33.3-13.48 13.48-33.3 13.48H166.78Z" /></svg>
icon:sb_search = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M375.48-307q-114.09 0-193.55-79.46-79.45-79.45-79.45-193.54 0-114.09 79.45-193.54Q261.39-853 375.48-853q114.09 0 193.54 79.46 79.46 79.45 79.46 193.54 0 45.13-12.87 83.28T601-429.7l219.48 220.05q14.96 15.52 14.96 37.32 0 21.81-15.53 36.77-14.95 14.95-37.04 14.95t-37.04-14.95L526.91-354.48q-29.43 21.74-68.15 34.61Q420.04-307 375.48-307Zm0-106q69.91 0 118.45-48.54 48.55-48.55 48.55-118.46t-48.55-118.46Q445.39-747 375.48-747t-118.46 48.54Q208.48-649.91 208.48-580t48.54 118.46Q305.57-413 375.48-413Z" /></svg>
icon:sb_stats = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M694.7-140.78q-22.09 0-37.55-15.46-15.45-15.46-15.45-37.54v-201.13q0-22.09 15.45-37.55 15.46-15.45 37.55-15.45h71.52q22.08 0 37.54 15.45 15.46 15.46 15.46 37.55v201.13q0 22.08-15.46 37.54t-37.54 15.46H694.7Zm-250.18 0q-22.09 0-37.54-15.46-15.46-15.46-15.46-37.54v-572.44q0-22.08 15.46-37.54 15.45-15.46 37.54-15.46h70.96q22.09 0 37.54 15.46 15.46 15.46 15.46 37.54v572.44q0 22.08-15.46 37.54-15.45 15.46-37.54 15.46h-70.96Zm-250.74 0q-22.08 0-37.54-15.46t-15.46-37.54v-372.44q0-22.08 15.46-37.54t37.54-15.46h71.52q22.09 0 37.55 15.46 15.45 15.46 15.45 37.54v372.44q0 22.08-15.45 37.54-15.46 15.46-37.55 15.46h-71.52Z" /></svg>
icon:theme_light = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M480-280q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480q0 83-58.5 141.5T480-280ZM80-427q-22.09 0-37.54-15.46Q27-457.91 27-480t15.46-37.54Q57.91-533 80-533h80q22.09 0 37.54 15.46Q213-502.09 213-480t-15.46 37.54Q182.09-427 160-427H80Zm720 0q-22.09 0-37.54-15.46Q747-457.91 747-480t15.46-37.54Q777.91-533 800-533h80q22.09 0 37.54 15.46Q933-502.09 933-480t-15.46 37.54Q902.09-427 880-427h-80ZM480-747q-22.09 0-37.54-15.46Q427-777.91 427-800v-80q0-22.09 15.46-37.54Q457.91-933 480-933t37.54 15.46Q533-902.09 533-880v80q0 22.09-15.46 37.54Q502.09-747 480-747Zm0 720q-22.09 0-37.54-15.46Q427-57.91 427-80v-80q0-22.09 15.46-37.54Q457.91-213 480-213t37.54 15.46Q533-182.09 533-160v80q0 22.09-15.46 37.54Q502.09-27 480-27ZM216.96-668.39l-43-42Q158-725.35 158.5-747.43q.5-22.09 15.46-38.61Q189.35-802 211.43-802q22.09 0 37.61 15.96l42.57 43q14.96 15.39 14.67 36.76-.28 21.37-14.67 37.32-14.96 15.96-36.83 16.03-21.87.06-37.82-15.46Zm494 494.43-42.57-43q-14.96-15.39-14.96-36.97 0-21.59 14.96-37.11 14.96-15.96 36.83-15.46 21.87.5 37.82 15.46l43 41.43q15.96 14.96 15.46 37.04-.5 22.09-15.46 38.61Q770.65-158 748.57-158q-22.09 0-37.61-15.96Zm-42-494.43Q653-683.35 653.5-705.22q.5-21.87 15.46-37.82l41.43-43q14.96-15.96 37.04-15.46 22.09.5 38.61 15.46Q802-770.65 802-748.57q0 22.09-15.96 37.61l-43 42.57q-15.39 14.96-36.76 14.67-21.37-.28-37.32-14.67Zm-495 494.43Q158-189.35 158-211.43q0-22.09 15.96-37.61l43-42.57q15.39-14.96 36.97-14.96 21.59 0 37.11 14.96 15.96 14.96 15.46 36.83-.5 21.87-15.46 37.82l-41.43 43Q234.65-158 212.57-158.5q-22.09-.5-38.61-15.46Z" /></svg>
icon:theme_dark = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M480.57-111.52q-157.22 0-263.14-105.63Q111.52-322.78 111.52-480q0-128.39 77.57-229.89 77.56-101.5 214.17-129.24 18.65-5.39 33.74-1.59 15.09 3.81 25.04 13.37 10.52 9.57 14.13 23.83 3.61 14.26-1 30.35-9.65 23.74-15.6 47.08-5.96 23.35-5.96 48 0 93.96 66.11 159.79 66.11 65.82 160.63 65.82 24.78 0 49.63-5.32 24.85-5.33 46.02-14.55 15.52-4.17 28.72-.85 13.19 3.33 22.19 11.72 9.44 8.39 13.24 22.63 3.81 14.24-1.02 33.33Q814.96-276 713.72-193.76q-101.24 82.24-233.15 82.24Z" /></svg>
icon:theme_auto = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M479.95-60.78q-86.91 0-163.3-32.91-76.39-32.92-133.22-89.74-56.82-56.83-89.74-133.27-32.91-76.44-32.91-163.35t32.91-163.3q32.92-76.39 89.74-133.22 56.83-56.82 133.27-89.74 76.44-32.91 163.35-32.91t163.3 32.91q76.39 32.92 133.22 89.74 56.82 56.83 89.74 133.27 32.91 76.44 32.91 163.35t-32.91 163.3q-32.92 76.39-89.74 133.22-56.83 56.82-133.27 89.74-76.44 32.91-163.35 32.91Zm46.83-110.13Q641.26-188.17 717.24-274t75.98-206q0-118.63-75.98-204.94-75.98-86.32-190.46-103.58v617.61Z" /></svg>
icon:add = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M427-427H233.78q-22.08 0-37.54-15.46-15.46-15.45-15.46-37.54t15.46-37.54Q211.7-533 233.78-533H427v-193.22q0-22.08 15.46-37.54 15.45-15.46 37.54-15.46t37.54 15.46Q533-748.3 533-726.22V-533h193.22q22.08 0 37.54 15.46 15.46 15.45 15.46 37.54t-15.46 37.54Q748.3-427 726.22-427H533v193.22q0 22.08-15.46 37.54-15.45 15.46-37.54 15.46t-37.54-15.46Q427-211.7 427-233.78V-427Z" /></svg>
icon:bookmarks = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M387-150.39 241.04-86.87q-53 22.65-100.87-8.48-47.87-31.13-47.87-88.83v-461.34q0-44.3 30.85-75.15 30.85-30.85 75.15-30.85h377.39q44.31 0 75.16 30.85 30.85 30.85 30.85 75.15v461.34q0 57.7-47.87 88.83-47.87 31.13-100.88 8.48L387-150.39Zm427.7-27.13q-22.09 0-37.55-15.46-15.45-15.46-15.45-37.54v-601h-441q-22.09 0-37.55-15.46-15.45-15.45-15.45-37.54t15.45-37.55q15.46-15.45 37.55-15.45h441q44.3 0 75.15 30.85 30.85 30.84 30.85 75.15v601q0 22.08-15.46 37.54t-37.54 15.46Z" /></svg>
icon:browse = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M166.78-140.78q-44.3 0-75.15-30.85-30.85-30.85-30.85-75.15v-466.44q0-44.87 30.85-75.43 30.85-30.57 75.15-30.57h181q21.09 0 40.39 7.98 19.31 7.98 34.26 22.94L480-730.74h366.22q22.08 0 37.54 15.46 15.46 15.45 15.46 37.54t-15.46 37.54q-15.46 15.46-37.54 15.46H340.57q-69.35 0-121.57 43.52-52.22 43.53-52.22 110.31v226.95l70.52-226.82q10.83-33.35 38.55-53.65 27.72-20.31 62.76-20.31h471.91q54 0 85.7 43.52 31.69 43.53 15.61 94.53l-53.92 174.43q-14.78 45.78-46.17 68.63-31.39 22.85-78.31 22.85H166.78Z" /></svg>
@@ -56,6 +56,9 @@ icon:sb_outline = <svg xmlns="http://www.w3.org/2000/svg" height="128" view
icon:sb_project = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M560-120v-66q0-8 3-15.5t9-13.5l209-208q9-9 20-13t22-4q12 0 23 4.5t20 13.5l37 37q8 9 12.5 20t4.5 22q0 11-4 22.5T903-300L695-92q-6 6-13.5 9T666-80h-66q-17 0-28.5-11.5T560-120Zm263-184 37-39-37-37-38 38 38 38ZM160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h207q16 0 30.5 6t25.5 17l57 57h320q33 0 56.5 23.5T880-640v83q0 18-14.5 29t-33.5 8h-11q-28 0-52.5 10T724-480L503-259q-11 11-17 25.5t-6 30.5v3q0 17-11.5 28.5T440-160H160Z" /></svg>
icon:sb_search = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M380-320q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l224 224q11 11 11 28t-11 28q-11 11-28 11t-28-11L532-372q-30 24-69 38t-83 14Zm0-80q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z" /></svg>
icon:sb_stats = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M680-160q-17 0-28.5-11.5T640-200v-200q0-17 11.5-28.5T680-440h80q17 0 28.5 11.5T800-400v200q0 17-11.5 28.5T760-160h-80Zm-240 0q-17 0-28.5-11.5T400-200v-560q0-17 11.5-28.5T440-800h80q17 0 28.5 11.5T560-760v560q0 17-11.5 28.5T520-160h-80Zm-240 0q-17 0-28.5-11.5T160-200v-360q0-17 11.5-28.5T200-600h80q17 0 28.5 11.5T320-560v360q0 17-11.5 28.5T280-160h-80Z" /></svg>
icon:theme_light = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M480-280q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480q0 83-58.5 141.5T480-280ZM80-440q-17 0-28.5-11.5T40-480q0-17 11.5-28.5T80-520h80q17 0 28.5 11.5T200-480q0 17-11.5 28.5T160-440H80Zm720 0q-17 0-28.5-11.5T760-480q0-17 11.5-28.5T800-520h80q17 0 28.5 11.5T920-480q0 17-11.5 28.5T880-440h-80ZM480-760q-17 0-28.5-11.5T440-800v-80q0-17 11.5-28.5T480-920q17 0 28.5 11.5T520-880v80q0 17-11.5 28.5T480-760Zm0 720q-17 0-28.5-11.5T440-80v-80q0-17 11.5-28.5T480-200q17 0 28.5 11.5T520-160v80q0 17-11.5 28.5T480-40ZM226-678l-43-42q-12-11-11.5-28t11.5-29q12-12 29-12t28 12l42 43q11 12 11 28t-11 28q-11 12-27.5 11.5T226-678Zm494 495-42-43q-11-12-11-28.5t11-27.5q11-12 27.5-11.5T734-282l43 42q12 11 11.5 28T777-183q-12 12-29 12t-28-12Zm-42-495q-12-11-11.5-27.5T678-734l42-43q11-12 28-11.5t29 11.5q12 12 12 29t-12 28l-43 42q-12 11-28 11t-28-11ZM183-183q-12-12-12-29t12-28l43-42q12-11 28.5-11t27.5 11q12 11 11.5 27.5T282-226l-42 43q-11 12-28 11.5T183-183Z" /></svg>
icon:theme_dark = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M480-120q-151 0-255.5-104.5T120-480q0-138 90-239.5T440-838q13-2 23 3.5t16 14.5q6 9 6.5 21t-7.5 23q-17 26-25.5 55t-8.5 61q0 90 63 153t153 63q31 0 61.5-9t54.5-25q11-7 22.5-6.5T819-479q10 5 15.5 15t3.5 24q-14 138-117.5 229T480-120Z" /></svg>
icon:theme_auto = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm40-83q119-15 199.5-104.5T800-480q0-123-80.5-212.5T520-797v634Z" /></svg>
icon:add = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M440-440H240q-17 0-28.5-11.5T200-480q0-17 11.5-28.5T240-520h200v-200q0-17 11.5-28.5T480-760q17 0 28.5 11.5T520-720v200h200q17 0 28.5 11.5T760-480q0 17-11.5 28.5T720-440H520v200q0 17-11.5 28.5T480-200q-17 0-28.5-11.5T440-240v-200Z" /></svg>
icon:bookmarks = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M400-160 232-88q-40 17-76-6.5T120-161v-519q0-33 23.5-56.5T200-760h400q33 0 56.5 23.5T680-680v519q0 43-36 66.5T568-88l-168-72Zm400 0q-17 0-28.5-11.5T760-200v-640H280q-17 0-28.5-11.5T240-880q0-17 11.5-28.5T280-920h480q33 0 56.5 23.5T840-840v640q0 17-11.5 28.5T800-160Z" /></svg>
icon:browse = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h207q16 0 30.5 6t25.5 17l57 57h360q17 0 28.5 11.5T880-680q0 17-11.5 28.5T840-640H314q-62 0-108 39t-46 99v262l79-263q8-26 29.5-41.5T316-560h516q41 0 64.5 32.5T909-457l-72 240q-8 26-29.5 41.5T760-160H160Z" /></svg>
@@ -56,6 +56,9 @@ icon:sb_outline = <svg xmlns="http://www.w3.org/2000/svg" height="128" view
icon:sb_project = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M569.23-152.31v-42.92q0-6.46 2.23-12.04 2.23-5.58 7.46-10.81l203.62-202.61q5.92-5.16 12.31-7.62 6.38-2.46 12.77-2.46 6.61 0 13.38 2.58 6.77 2.57 11.92 7.73l37 37.77q4.93 5.92 7.5 12.31Q880-364 880-357.62q0 6.39-2.08 12.89-2.07 6.5-8 12.42L667.31-129.69q-5.23 5.23-10.81 7.46-5.58 2.23-12.04 2.23h-42.92q-13.92 0-23.12-9.19-9.19-9.19-9.19-23.12Zm238.39-167.07 37-38.24-37-37.76-38 38 38 38ZM184.62-200q-27.62 0-46.12-18.5Q120-237 120-264.62v-430.76q0-27.62 18.5-46.12Q157-760 184.62-760H357q12.92 0 25.12 5.23 12.19 5.23 20.88 13.92L463.85-680h311.53q27.62 0 46.12 18.5Q840-643 840-615.38v61.46q0 12.61-9.88 20.92-9.89 8.31-22.74 6.85H804.85q-22.62 0-44.43 8.46-21.8 8.46-40.27 24.61L493-266.69q-8.69 8.69-13.92 20.5t-5.23 24.73v.69q0 8.54-6.12 14.65-6.11 6.12-14.65 6.12H184.62Z" /></svg>
icon:sb_search = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M381.54-350.77q-95.92 0-162.58-66.65-66.65-66.66-66.65-162.58 0-95.92 66.65-162.58 66.66-66.65 162.58-66.65 95.92 0 162.58 66.65 66.65 66.66 66.65 162.58 0 41.69-14.77 80.69t-38.77 66.69l236.31 236.31q5.61 5.62 6 13.77.38 8.16-6 14.54-6.39 6.38-14.16 6.38-7.76 0-14.15-6.38L528.92-404.31q-30 25.54-69 39.54t-78.38 14Zm0-40q79.61 0 134.42-54.81 54.81-54.8 54.81-134.42 0-79.62-54.81-134.42-54.81-54.81-134.42-54.81-79.62 0-134.42 54.81-54.81 54.8-54.81 134.42 0 79.62 54.81 134.42 54.8 54.81 134.42 54.81Z" /></svg>
icon:sb_stats = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M672.31-200q-13.73 0-23.02-9.29T640-232.31V-360q0-13.73 9.29-23.02t23.02-9.29h55.38q13.73 0 23.02 9.29T760-360v127.69q0 13.73-9.29 23.02T727.69-200h-55.38Zm-220 0q-13.73 0-23.02-9.29T420-232.31v-495.38q0-13.73 9.29-23.02t23.02-9.29h55.38q13.73 0 23.02 9.29t9.29 23.02v495.38q0 13.73-9.29 23.02T507.69-200h-55.38Zm-220 0q-13.73 0-23.02-9.29T200-232.31v-302.7q0-14.3 9.29-23.49 9.29-9.19 23.02-9.19h55.38q13.73 0 23.02 9.29 9.29 9.28 9.29 23.02v302.7q0 14.3-9.29 23.49-9.29 9.19-23.02 9.19h-55.38Z" /></svg>
icon:theme_light = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M480-320q-66.85 0-113.42-46.58Q320-413.15 320-480t46.58-113.42Q413.15-640 480-640t113.42 46.58Q640-546.85 640-480t-46.58 113.42Q546.85-320 480-320ZM80-460q-8.5 0-14.25-5.76T60-480.03q0-8.51 5.75-14.24T80-500h100q8.5 0 14.25 5.76t5.75 14.27q0 8.51-5.75 14.24T180-460H80Zm700 0q-8.5 0-14.25-5.76T760-480.03q0-8.51 5.75-14.24T780-500h100q8.5 0 14.25 5.76t5.75 14.27q0 8.51-5.75 14.24T880-460H780ZM479.97-760q-8.51 0-14.24-5.75T460-780v-100q0-8.5 5.76-14.25t14.27-5.75q8.51 0 14.24 5.75T500-880v100q0 8.5-5.76 14.25T479.97-760Zm0 700q-8.51 0-14.24-5.75T460-80v-100q0-8.5 5.76-14.25t14.27-5.75q8.51 0 14.24 5.75T500-180v100q0 8.5-5.76 14.25T479.97-60ZM254.46-678.77l-57.61-55.85q-5.85-5.61-5.73-13.76.11-8.16 5.96-14.77 6.38-6.62 14.15-6.62 7.77 0 14.15 6.62L282-706.31q6.38 6.62 6.38 14.16 0 7.53-6 14.15t-13.65 6.12q-7.65-.5-14.27-6.89Zm480.16 481.92L678-253.69q-6.38-6.62-6.38-14.27 0-7.66 6.38-14.04 5.23-6.62 13.08-6.12 7.84.5 14.46 6.89l57.61 55.85q5.85 5.61 5.73 13.76-.11 8.16-5.96 14.77-6.38 6.62-14.15 6.62-7.77 0-14.15-6.62ZM678-677.62q-6.62-6-6.12-13.65t6.89-14.27l55.85-57.61q5.61-5.85 13.76-5.73 8.16.11 14.77 5.96 6.62 6.38 6.62 14.15 0 7.77-6.62 14.15L706.31-678q-6.62 6.38-14.16 6.38-7.53 0-14.15-6ZM196.85-196.68q-6.62-6.78-6.62-14.55 0-7.77 6.62-14.15L253.69-282q6.62-6.38 14.27-6.38 7.66 0 14.04 6.38 5.85 5.23 5.35 13.08-.5 7.84-6.12 14.46l-55.85 57.61q-6.38 6.62-14.15 6.5-7.77-.11-14.38-6.33Z" /></svg>
icon:theme_dark = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M482.31-160Q349-160 255.65-253.35 162.31-346.69 162.31-480q0-98.77 56.54-181.42 56.53-82.66 160.38-118.89 12.23-4.31 21.46-2.27 9.23 2.04 15.23 7.96 6 5.93 7.66 15.24 1.65 9.3-2.5 19.53-7.77 19.08-11.27 38.85-3.5 19.77-3.5 41 0 106.92 74.54 181.46Q555.38-404 662.31-404q27.92 0 51.11-5.92 23.2-5.93 39.5-9.62 8.7-1.61 15.96.42 7.27 2.04 11.66 7.04 4.61 5 6.27 12.31 1.65 7.31-1.89 16.69-28.61 98-112.11 160.54Q589.31-160 482.31-160Z" /></svg>
icon:theme_auto = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M480.13-120q-74.67 0-140.41-28.34-65.73-28.34-114.36-76.92-48.63-48.58-76.99-114.26Q120-405.19 120-479.87q0-74.67 28.34-140.41 28.34-65.73 76.92-114.36 48.58-48.63 114.26-76.99Q405.19-840 479.87-840q74.67 0 140.41 28.34 65.73 28.34 114.36 76.92 48.63 48.58 76.99 114.26Q840-554.81 840-480.13q0 74.67-28.34 140.41-28.34 65.73-76.92 114.36-48.58 48.63-114.26 76.99Q554.81-120 480.13-120ZM500-160.69q123.62-8.08 211.81-98.35T800-480q0-130.69-87.42-220.19-87.43-89.5-212.58-99.12v638.62Z" /></svg>
icon:add = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M460-460H260q-8.5 0-14.25-5.76T240-480.03q0-8.51 5.75-14.24T260-500h200v-200q0-8.5 5.76-14.25t14.27-5.75q8.51 0 14.24 5.75T500-700v200h200q8.5 0 14.25 5.76t5.75 14.27q0 8.51-5.75 14.24T700-460H500v200q0 8.5-5.76 14.25T479.97-240q-8.51 0-14.24-5.75T460-260v-200Z" /></svg>
icon:bookmarks = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="m420-203.08-149.54 64.31q-32.31 13.92-61.38-5.35Q180-163.38 180-197.92v-457.46q0-27.62 18.5-46.12Q217-720 244.62-720h350.76q27.62 0 46.12 18.5Q660-683 660-655.38v457.46q0 34.54-29.08 53.8-29.07 19.27-61.38 5.35L420-203.08ZM760-220q-8.54 0-14.27-5.73T740-240v-535.39q0-9.23-7.69-16.92-7.69-7.69-16.93-7.69H320q-8.54 0-14.27-5.73T300-820q0-8.54 5.73-14.27T320-840h395.38q27.62 0 46.12 18.5Q780-803 780-775.39V-240q0 8.54-5.73 14.27T760-220Z" /></svg>
icon:browse = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M180-200q-25.31 0-42.65-17.35Q120-234.69 120-260v-435.38q0-25.31 19.65-44.97Q159.31-760 184.62-760H357q12.92 0 25.12 5.23 12.19 5.23 20.88 13.92L463.85-680h349.23q8.54 0 14.27 5.73t5.73 14.27q0 8.54-5.73 14.27T813.08-640H297.85Q242-640 201-604.08q-41 35.93-41 90.54v248.92q0 8.47 4.23 13.85 4.23 5.39 11.15 9.23l76.7-256.84q6.46-20.62 23.73-33.43 17.27-12.81 38.65-12.81h512.92q32.54 0 52.2 25.97 19.65 25.96 10.19 57.03l-65.85 219.24q-5.69 19.07-21.8 30.73Q786-200 766.15-200H180Z" /></svg>
@@ -56,6 +56,9 @@ icon:sb_outline = <svg xmlns="http://www.w3.org/2000/svg" height="128" view
icon:sb_project = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M166.78-246.78v-466.44 466.44Zm0 106q-44.3 0-75.15-30.85-30.85-30.85-30.85-75.15v-466.44q0-44.3 30.85-75.15 30.85-30.85 75.15-30.85h181q21.23 0 40.46 7.98 19.24 7.98 34.19 22.94L480-730.74h313.22q44.3 0 75.15 30.85 30.85 30.85 30.85 75.15v51.17q0 22.09-15.46 37.55-15.46 15.45-37.54 15.45-22.09 0-37.55-15.45-15.45-15.46-15.45-37.55v-51.17H436.26l-88.48-88.48h-181v466.44h256.83q22.09 0 37.54 15.45 15.46 15.46 15.46 37.55 0 22.08-15.46 37.54-15.45 15.46-37.54 15.46H166.78Zm389.83 27v-63.74q0-10.83 4.13-20.31 4.13-9.47 11.83-17.17L781-423q10.7-10.8 23.77-15.59 13.07-4.8 26.14-4.8 14.26 0 27.27 5.35 13.01 5.34 23.65 16.04l37 37q9.59 10.7 14.99 23.77t5.4 26.14q0 13.83-4.85 27.03-4.85 13.19-15.54 23.89l-208 208q-7.62 7.69-17.14 11.54-9.52 3.85-20.34 3.85h-63.74q-22.09 0-37.54-15.46-15.46-15.46-15.46-37.54Zm311.3-221.31-37-37 37 37Zm-240 203h38l118.74-119.74-18-19-19-18-119.74 118.74v38Zm138.74-138.74-19-18 37 37-18-19Z" /></svg>
icon:sb_search = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M375.48-307q-114.09 0-193.55-79.46-79.45-79.45-79.45-193.54 0-114.09 79.45-193.54Q261.39-853 375.48-853q114.09 0 193.54 79.46 79.46 79.45 79.46 193.54 0 45.13-12.87 83.28T601-429.7l219.48 220.05q14.96 15.52 14.96 37.32 0 21.81-15.53 36.77-14.95 14.95-37.04 14.95t-37.04-14.95L526.91-354.48q-29.43 21.74-68.15 34.61Q420.04-307 375.48-307Zm0-106q69.91 0 118.45-48.54 48.55-48.55 48.55-118.46t-48.55-118.46Q445.39-747 375.48-747t-118.46 48.54Q208.48-649.91 208.48-580t48.54 118.46Q305.57-413 375.48-413Z" /></svg>
icon:sb_stats = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M694.7-140.78q-22.09 0-37.55-15.46-15.45-15.46-15.45-37.54v-201.13q0-22.09 15.45-37.55 15.46-15.45 37.55-15.45h71.52q22.08 0 37.54 15.45 15.46 15.46 15.46 37.55v201.13q0 22.08-15.46 37.54t-37.54 15.46H694.7Zm-250.18 0q-22.09 0-37.54-15.46-15.46-15.46-15.46-37.54v-572.44q0-22.08 15.46-37.54 15.45-15.46 37.54-15.46h70.96q22.09 0 37.54 15.46 15.46 15.46 15.46 37.54v572.44q0 22.08-15.46 37.54-15.45 15.46-37.54 15.46h-70.96Zm-250.74 0q-22.08 0-37.54-15.46t-15.46-37.54v-372.44q0-22.08 15.46-37.54t37.54-15.46h71.52q22.09 0 37.55 15.46 15.45 15.46 15.45 37.54v372.44q0 22.08-15.45 37.54-15.46 15.46-37.55 15.46h-71.52Z" /></svg>
icon:theme_light = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M480-386q39.26 0 66.63-27.37Q574-440.74 574-480q0-39.26-27.37-66.63Q519.26-574 480-574q-39.26 0-66.63 27.37Q386-519.26 386-480q0 39.26 27.37 66.63Q440.74-386 480-386Zm0 106q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480q0 83-58.5 141.5T480-280ZM80-427q-22.09 0-37.54-15.46Q27-457.91 27-480t15.46-37.54Q57.91-533 80-533h80q22.09 0 37.54 15.46Q213-502.09 213-480t-15.46 37.54Q182.09-427 160-427H80Zm720 0q-22.09 0-37.54-15.46Q747-457.91 747-480t15.46-37.54Q777.91-533 800-533h80q22.09 0 37.54 15.46Q933-502.09 933-480t-15.46 37.54Q902.09-427 880-427h-80ZM480-747q-22.09 0-37.54-15.46Q427-777.91 427-800v-80q0-22.09 15.46-37.54Q457.91-933 480-933t37.54 15.46Q533-902.09 533-880v80q0 22.09-15.46 37.54Q502.09-747 480-747Zm0 720q-22.09 0-37.54-15.46Q427-57.91 427-80v-80q0-22.09 15.46-37.54Q457.91-213 480-213t37.54 15.46Q533-182.09 533-160v80q0 22.09-15.46 37.54Q502.09-27 480-27ZM216.96-668.39l-43-42Q158-725.35 158.5-747.43q.5-22.09 15.46-38.61Q189.35-802 211.43-802q22.09 0 37.61 15.96l42.57 43q14.96 15.39 14.67 36.76-.28 21.37-14.67 37.32-14.96 15.96-36.83 16.03-21.87.06-37.82-15.46Zm494 494.43-42.57-43q-14.96-15.39-14.96-36.97 0-21.59 14.96-37.11 14.96-15.96 36.83-15.46 21.87.5 37.82 15.46l43 41.43q15.96 14.96 15.46 37.04-.5 22.09-15.46 38.61Q770.65-158 748.57-158q-22.09 0-37.61-15.96Zm-42-494.43Q653-683.35 653.5-705.22q.5-21.87 15.46-37.82l41.43-43q14.96-15.96 37.04-15.46 22.09.5 38.61 15.46Q802-770.65 802-748.57q0 22.09-15.96 37.61l-43 42.57q-15.39 14.96-36.76 14.67-21.37-.28-37.32-14.67Zm-495 494.43Q158-189.35 158-211.43q0-22.09 15.96-37.61l43-42.57q15.39-14.96 36.97-14.96 21.59 0 37.11 14.96 15.96 14.96 15.46 36.83-.5 21.87-15.46 37.82l-41.43 43Q234.65-158 212.57-158.5q-22.09-.5-38.61-15.46ZM480-480Z" /></svg>
icon:theme_dark = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M480.57-111.52q-157.22 0-263.14-105.63Q111.52-322.78 111.52-480q0-128.39 77.57-229.89 77.56-101.5 214.17-129.24 18.65-5.39 33.74-1.59 15.09 3.81 25.33 13.37 10.23 9.57 13.84 23.83 3.61 14.26-1 30.35-9.65 23.74-15.6 47.08-5.96 23.35-5.96 48 0 94.01 66.13 159.81t160.61 65.8q24.78 0 49.63-5.32 24.85-5.33 46.02-14.55 15.52-4.17 28.72-.85 13.19 3.33 21.93 11.72 9.7 8.39 13.5 22.63 3.81 14.24-1.02 33.33Q814.96-276 713.72-193.76q-101.24 82.24-233.15 82.24Zm0-106q73.3 0 133.97-37.2 60.68-37.19 93.24-98.8-14.35 2.74-28.41 4.04-14.07 1.31-27.85.18-120.74-9.61-205.82-93.29-85.09-83.67-95.83-208.93-.57-13.78.46-28.13 1.02-14.35 4.32-28.13-61.04 33.13-98.8 94.09-37.76 60.95-37.76 133.69 0 108.74 76.87 185.61 76.86 76.87 185.61 76.87Zm-17.35-245.13Z" /></svg>
icon:theme_auto = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M479.95-60.78q-86.91 0-163.3-32.91-76.39-32.92-133.22-89.74-56.82-56.83-89.74-133.27-32.91-76.44-32.91-163.35t32.91-163.3q32.92-76.39 89.74-133.22 56.83-56.82 133.27-89.74 76.44-32.91 163.35-32.91t163.3 32.91q76.39 32.92 133.22 89.74 56.82 56.83 89.74 133.27 32.91 76.44 32.91 163.35t-32.91 163.3q-32.92 76.39-89.74 133.22-56.83 56.82-133.27 89.74-76.44 32.91-163.35 32.91Zm46.83-110.13Q641.26-188.17 717.24-274t75.98-206q0-118.63-75.98-204.94-75.98-86.32-190.46-103.58v617.61Z" /></svg>
icon:add = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M427-427H233.78q-22.08 0-37.54-15.46-15.46-15.45-15.46-37.54t15.46-37.54Q211.7-533 233.78-533H427v-193.22q0-22.08 15.46-37.54 15.45-15.46 37.54-15.46t37.54 15.46Q533-748.3 533-726.22V-533h193.22q22.08 0 37.54 15.46 15.46 15.45 15.46 37.54t-15.46 37.54Q748.3-427 726.22-427H533v193.22q0 22.08-15.46 37.54-15.45 15.46-37.54 15.46t-37.54-15.46Q427-211.7 427-233.78V-427Z" /></svg>
icon:bookmarks = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M387-150.39 241.04-86.87q-53 22.65-100.87-8.48-47.87-31.13-47.87-88.83v-461.34q0-44.3 30.85-75.15 30.85-30.85 75.15-30.85h377.39q44.31 0 75.16 30.85 30.85 30.85 30.85 75.15v461.34q0 57.7-47.87 88.83-47.87 31.13-100.88 8.48L387-150.39Zm0-115.7 188.69 82.61v-462.04H198.3v462.04L387-266.09Zm427.7 88.57q-22.09 0-37.55-15.46-15.45-15.46-15.45-37.54v-601h-441q-22.09 0-37.55-15.46-15.45-15.45-15.45-37.54t15.45-37.55q15.46-15.45 37.55-15.45h441q44.3 0 75.15 30.85 30.85 30.84 30.85 75.15v601q0 22.08-15.46 37.54t-37.54 15.46Zm-427.7-468H198.3h377.39H387Z" /></svg>
icon:browse = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M166.78-140.78q-44.3 0-75.15-30.85-30.85-30.85-30.85-75.15v-466.44q0-44.87 30.85-75.43 30.85-30.57 75.15-30.57h181q21.23 0 40.46 7.98 19.24 7.98 34.19 22.94L480-730.74h366.22q22.08 0 37.54 15.46 15.46 15.45 15.46 37.54t-15.46 37.54q-15.46 15.46-37.54 15.46H436.26l-88.48-88.48h-181v469.26l70.52-226.82q10.83-33.35 38.55-53.65 27.72-20.31 62.76-20.31h471.91q54 0 85.7 43.52 31.69 43.53 15.61 94.53l-53.92 174.43q-14.78 45.78-46.17 68.63-31.39 22.85-78.31 22.85H166.78Zm112.83-106h471.35l59-191.96H338.61l-59 191.96ZM166.78-470.91v-242.31 242.31Zm112.83 224.13 59-191.96-59 191.96Z" /></svg>
@@ -56,6 +56,9 @@ icon:sb_outline = <svg xmlns="http://www.w3.org/2000/svg" height="128" view
icon:sb_project = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M160-240v-480 480Zm0 80q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h207q16 0 30.5 6t25.5 17l57 57h320q33 0 56.5 23.5T880-640v80q0 17-11.5 28.5T840-520q-17 0-28.5-11.5T800-560v-80H447l-80-80H160v480h280q17 0 28.5 11.5T480-200q0 17-11.5 28.5T440-160H160Zm400 40v-66q0-8 3-15.5t9-13.5l209-208q9-9 20-13t22-4q12 0 23 4.5t20 13.5l37 37q8 9 12.5 20t4.5 22q0 11-4 22.5T903-300L695-92q-6 6-13.5 9T666-80h-66q-17 0-28.5-11.5T560-120Zm300-223-37-37 37 37ZM620-140h38l121-122-18-19-19-18-122 121v38Zm141-141-19-18 37 37-18-19Z" /></svg>
icon:sb_search = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M380-320q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l224 224q11 11 11 28t-11 28q-11 11-28 11t-28-11L532-372q-30 24-69 38t-83 14Zm0-80q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z" /></svg>
icon:sb_stats = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M680-160q-17 0-28.5-11.5T640-200v-200q0-17 11.5-28.5T680-440h80q17 0 28.5 11.5T800-400v200q0 17-11.5 28.5T760-160h-80Zm-240 0q-17 0-28.5-11.5T400-200v-560q0-17 11.5-28.5T440-800h80q17 0 28.5 11.5T560-760v560q0 17-11.5 28.5T520-160h-80Zm-240 0q-17 0-28.5-11.5T160-200v-360q0-17 11.5-28.5T200-600h80q17 0 28.5 11.5T320-560v360q0 17-11.5 28.5T280-160h-80Z" /></svg>
icon:theme_light = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M480-360q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35Zm0 80q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480q0 83-58.5 141.5T480-280ZM80-440q-17 0-28.5-11.5T40-480q0-17 11.5-28.5T80-520h80q17 0 28.5 11.5T200-480q0 17-11.5 28.5T160-440H80Zm720 0q-17 0-28.5-11.5T760-480q0-17 11.5-28.5T800-520h80q17 0 28.5 11.5T920-480q0 17-11.5 28.5T880-440h-80ZM480-760q-17 0-28.5-11.5T440-800v-80q0-17 11.5-28.5T480-920q17 0 28.5 11.5T520-880v80q0 17-11.5 28.5T480-760Zm0 720q-17 0-28.5-11.5T440-80v-80q0-17 11.5-28.5T480-200q17 0 28.5 11.5T520-160v80q0 17-11.5 28.5T480-40ZM226-678l-43-42q-12-11-11.5-28t11.5-29q12-12 29-12t28 12l42 43q11 12 11 28t-11 28q-11 12-27.5 11.5T226-678Zm494 495-42-43q-11-12-11-28.5t11-27.5q11-12 27.5-11.5T734-282l43 42q12 11 11.5 28T777-183q-12 12-29 12t-28-12Zm-42-495q-12-11-11.5-27.5T678-734l42-43q11-12 28-11.5t29 11.5q12 12 12 29t-12 28l-43 42q-12 11-28 11t-28-11ZM183-183q-12-12-12-29t12-28l43-42q12-11 28.5-11t27.5 11q12 11 11.5 27.5T282-226l-42 43q-11 12-28 11.5T183-183Zm297-297Z" /></svg>
icon:theme_dark = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M480-120q-151 0-255.5-104.5T120-480q0-138 90-239.5T440-838q13-2 23 3.5t16 14.5q6 9 6.5 21t-7.5 23q-17 26-25.5 55t-8.5 61q0 90 63 153t153 63q31 0 61.5-9t54.5-25q11-7 22.5-6.5T819-479q10 5 15.5 15t3.5 24q-14 138-117.5 229T480-120Zm0-80q88 0 158-48.5T740-375q-20 5-40 8t-40 3q-123 0-209.5-86.5T364-660q0-20 3-40t8-40q-78 32-126.5 102T200-480q0 116 82 198t198 82Zm-10-270Z" /></svg>
icon:theme_auto = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm40-83q119-15 199.5-104.5T800-480q0-123-80.5-212.5T520-797v634Z" /></svg>
icon:add = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M440-440H240q-17 0-28.5-11.5T200-480q0-17 11.5-28.5T240-520h200v-200q0-17 11.5-28.5T480-760q17 0 28.5 11.5T520-720v200h200q17 0 28.5 11.5T760-480q0 17-11.5 28.5T720-440H520v200q0 17-11.5 28.5T480-200q-17 0-28.5-11.5T440-240v-200Z" /></svg>
icon:bookmarks = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M400-160 232-88q-40 17-76-6.5T120-161v-519q0-33 23.5-56.5T200-760h400q33 0 56.5 23.5T680-680v519q0 43-36 66.5T568-88l-168-72Zm0-88 200 86v-518H200v518l200-86Zm400 88q-17 0-28.5-11.5T760-200v-640H280q-17 0-28.5-11.5T240-880q0-17 11.5-28.5T280-920h480q33 0 56.5 23.5T840-840v640q0 17-11.5 28.5T800-160ZM400-680H200h400-200Z" /></svg>
icon:browse = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h207q16 0 30.5 6t25.5 17l57 57h360q17 0 28.5 11.5T880-680q0 17-11.5 28.5T840-640H447l-80-80H160v480l79-263q8-26 29.5-41.5T316-560h516q41 0 64.5 32.5T909-457l-72 240q-8 26-29.5 41.5T760-160H160Zm84-80h516l72-240H316l-72 240Zm-84-262v-218 218Zm84 262 72-240-72 240Z" /></svg>
@@ -56,6 +56,9 @@ icon:sb_outline = <svg xmlns="http://www.w3.org/2000/svg" height="128" view
icon:sb_project = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M160-240v-480 480Zm24.62 40q-27.62 0-46.12-18.5Q120-237 120-264.62v-430.76q0-27.62 18.5-46.12Q157-760 184.62-760H357q12.92 0 25.12 5.23 12.19 5.23 20.88 13.92L463.85-680h311.53q27.62 0 46.12 18.5Q840-643 840-615.38v67.69q0 8.54-5.73 14.27T820-527.69q-8.54 0-14.27-5.73T800-547.69v-67.69q0-9.24-7.69-16.93-7.69-7.69-16.93-7.69H447.77l-80-80H184.62q-10.77 0-17.7 6.92-6.92 6.93-6.92 17.7v430.76q0 9.24 7.69 16.93 7.69 7.69 16.93 7.69h269.23q8.53 0 14.27 5.73 5.73 5.73 5.73 14.27t-5.73 14.27q-5.74 5.73-14.27 5.73H184.62Zm384.61 47.69v-42.92q0-6.46 2.23-12.04 2.23-5.58 7.46-10.81l203.62-202.61q5.92-5.93 12.31-8 6.38-2.08 12.77-2.08 6.61 0 13.38 2.58 6.77 2.57 11.92 7.73l37 37.77q4.93 5.92 7.5 12.31Q880-364 880-357.62q0 6.39-2.46 12.89-2.46 6.5-7.62 12.42L667.31-129.69q-5.23 5.23-10.81 7.46-5.58 2.23-12.04 2.23h-42.92q-13.92 0-23.12-9.19-9.19-9.19-9.19-23.12Zm275.39-205.31-37-37.76 37 37.76Zm-240 202.24h38l138.69-138.93-18.77-19-18.23-19.54-139.69 139.47v38Zm157.92-157.93-18.23-19.54 37 38.54-18.77-19Z" /></svg>
icon:sb_search = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M381.54-350.77q-95.92 0-162.58-66.65-66.65-66.66-66.65-162.58 0-95.92 66.65-162.58 66.66-66.65 162.58-66.65 95.92 0 162.58 66.65 66.65 66.66 66.65 162.58 0 41.69-14.77 80.69t-38.77 66.69l236.31 236.31q5.61 5.62 6 13.77.38 8.16-6 14.54-6.39 6.38-14.16 6.38-7.76 0-14.15-6.38L528.92-404.31q-30 25.54-69 39.54t-78.38 14Zm0-40q79.61 0 134.42-54.81 54.81-54.8 54.81-134.42 0-79.62-54.81-134.42-54.81-54.81-134.42-54.81-79.62 0-134.42 54.81-54.81 54.8-54.81 134.42 0 79.62 54.81 134.42 54.8 54.81 134.42 54.81Z" /></svg>
icon:sb_stats = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M672.31-200q-13.73 0-23.02-9.29T640-232.31V-360q0-13.73 9.29-23.02t23.02-9.29h55.38q13.73 0 23.02 9.29T760-360v127.69q0 13.73-9.29 23.02T727.69-200h-55.38Zm-220 0q-13.73 0-23.02-9.29T420-232.31v-495.38q0-13.73 9.29-23.02t23.02-9.29h55.38q13.73 0 23.02 9.29t9.29 23.02v495.38q0 13.73-9.29 23.02T507.69-200h-55.38Zm-220 0q-13.73 0-23.02-9.29T200-232.31v-302.7q0-14.3 9.29-23.49 9.29-9.19 23.02-9.19h55.38q13.73 0 23.02 9.29 9.29 9.28 9.29 23.02v302.7q0 14.3-9.29 23.49-9.29 9.19-23.02 9.19h-55.38Z" /></svg>
icon:theme_light = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M480-360q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35Zm0 40q-66.85 0-113.42-46.58Q320-413.15 320-480t46.58-113.42Q413.15-640 480-640t113.42 46.58Q640-546.85 640-480t-46.58 113.42Q546.85-320 480-320ZM80-460q-8.5 0-14.25-5.76T60-480.03q0-8.51 5.75-14.24T80-500h100q8.5 0 14.25 5.76t5.75 14.27q0 8.51-5.75 14.24T180-460H80Zm700 0q-8.5 0-14.25-5.76T760-480.03q0-8.51 5.75-14.24T780-500h100q8.5 0 14.25 5.76t5.75 14.27q0 8.51-5.75 14.24T880-460H780ZM479.97-760q-8.51 0-14.24-5.75T460-780v-100q0-8.5 5.76-14.25t14.27-5.75q8.51 0 14.24 5.75T500-880v100q0 8.5-5.76 14.25T479.97-760Zm0 700q-8.51 0-14.24-5.75T460-80v-100q0-8.5 5.76-14.25t14.27-5.75q8.51 0 14.24 5.75T500-180v100q0 8.5-5.76 14.25T479.97-60ZM254.46-678.77l-57.61-55.85q-5.85-5.61-5.73-13.76.11-8.16 5.96-14.77 6.38-6.62 14.15-6.62 7.77 0 14.15 6.62L282-706.31q6.38 6.62 6.38 14.16 0 7.53-6 14.15t-13.65 6.12q-7.65-.5-14.27-6.89Zm480.16 481.92L678-253.69q-6.38-6.62-6.38-14.27 0-7.66 6.38-14.04 5.23-6.62 13.08-6.12 7.84.5 14.46 6.89l57.61 55.85q5.85 5.61 5.73 13.76-.11 8.16-5.96 14.77-6.38 6.62-14.15 6.62-7.77 0-14.15-6.62ZM678-677.62q-6.62-6-6.12-13.65t6.89-14.27l55.85-57.61q5.61-5.85 13.76-5.73 8.16.11 14.77 5.96 6.62 6.38 6.62 14.15 0 7.77-6.62 14.15L706.31-678q-6.62 6.38-14.16 6.38-7.53 0-14.15-6ZM196.85-196.68q-6.62-6.78-6.62-14.55 0-7.77 6.62-14.15L253.69-282q6.62-6.38 14.27-6.38 7.66 0 14.04 6.38 5.85 5.23 5.35 13.08-.5 7.84-6.12 14.46l-55.85 57.61q-6.38 6.62-14.15 6.5-7.77-.11-14.38-6.33ZM480-480Z" /></svg>
icon:theme_dark = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M482.31-160Q349-160 255.65-253.35 162.31-346.69 162.31-480q0-98.77 56.54-181.42 56.53-82.66 160.38-118.89 12.23-4.31 21.46-2.27 9.23 2.04 15.23 7.96 6 5.93 7.66 15.24 1.65 9.3-2.5 19.53-7.77 19.08-11.27 38.66t-3.5 41.19q0 106.67 74.66 181.33Q555.64-404 662.31-404q27.92 0 51.11-5.92 23.2-5.93 39.5-9.62 8.7-1.61 15.96.42 7.27 2.04 11.54 7.04 4.73 5 6.39 12.31 1.65 7.31-1.89 16.69-28.61 98-112.11 160.54Q589.31-160 482.31-160Zm0-40q88 0 158-48.5t102-126.5q-20 5-40 8t-40 3q-123 0-209.5-86.5T366.31-660q0-20 3-40t8-40q-78 32-126.5 102t-48.5 158q0 116 82 198t198 82Zm-10-270Z" /></svg>
icon:theme_auto = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M480.13-120q-74.67 0-140.41-28.34-65.73-28.34-114.36-76.92-48.63-48.58-76.99-114.26Q120-405.19 120-479.87q0-74.67 28.34-140.41 28.34-65.73 76.92-114.36 48.58-48.63 114.26-76.99Q405.19-840 479.87-840q74.67 0 140.41 28.34 65.73 28.34 114.36 76.92 48.63 48.58 76.99 114.26Q840-554.81 840-480.13q0 74.67-28.34 140.41-28.34 65.73-76.92 114.36-48.58 48.63-114.26 76.99Q554.81-120 480.13-120ZM500-160.69q123.62-8.08 211.81-98.35T800-480q0-130.69-87.42-220.19-87.43-89.5-212.58-99.12v638.62Z" /></svg>
icon:add = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M460-460H260q-8.5 0-14.25-5.76T240-480.03q0-8.51 5.75-14.24T260-500h200v-200q0-8.5 5.76-14.25t14.27-5.75q8.51 0 14.24 5.75T500-700v200h200q8.5 0 14.25 5.76t5.75 14.27q0 8.51-5.75 14.24T700-460H500v200q0 8.5-5.76 14.25T479.97-240q-8.51 0-14.24-5.75T460-260v-200Z" /></svg>
icon:bookmarks = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="m420-203.08-149.54 64.31q-32.31 13.92-61.38-5.35Q180-163.38 180-197.92v-457.46q0-27.62 18.5-46.12Q217-720 244.62-720h350.76q27.62 0 46.12 18.5Q660-683 660-655.38v457.46q0 34.54-29.08 53.8-29.07 19.27-61.38 5.35L420-203.08Zm0-44.92 165.38 71.38q12.31 5.39 23.47-2.3 11.15-7.7 11.15-20.77v-455.69q0-9.24-7.69-16.93-7.69-7.69-16.93-7.69H244.62q-9.24 0-16.93 7.69-7.69 7.69-7.69 16.93v455.69q0 13.07 11.15 20.77 11.16 7.69 23.47 2.3L420-248Zm340 28q-8.54 0-14.27-5.73T740-240v-535.39q0-9.23-7.69-16.92-7.69-7.69-16.93-7.69H320q-8.54 0-14.27-5.73T300-820q0-8.54 5.73-14.27T320-840h395.38q27.62 0 46.12 18.5Q780-803 780-775.39V-240q0 8.54-5.73 14.27T760-220ZM420-680H220h400-200Z" /></svg>
icon:browse = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M180-200q-25.31 0-42.65-17.35Q120-234.69 120-260v-435.38q0-25.31 19.65-44.97Q159.31-760 184.62-760H357q12.92 0 25.12 5.23 12.19 5.23 20.88 13.92L463.85-680h349.23q8.54 0 14.27 5.73t5.73 14.27q0 8.54-5.73 14.27T813.08-640H447.77l-80-80H184.62q-10.77 0-17.7 6.92-6.92 6.93-6.92 17.7V-240q0-10.77 4.23-8.08 4.23 2.7 11.15 6.54l76.7-256.84q6.46-20.62 23.73-33.43 17.27-12.81 38.65-12.81h512.92q32.54 0 52.2 25.97 19.65 25.96 10.19 57.03l-65.85 219.24q-5.69 19.07-21.8 30.73Q786-200 766.15-200H180Zm37.08-40h546q8.46 0 15-4.62 6.54-4.61 8.84-13.07l64.31-215.39q3.85-12.3-3.85-21.92-7.69-9.62-20-9.62H314.46q-8.46 0-15 4.62-6.54 4.62-8.84 13.08L217.08-240ZM160-498.15V-720v221.85ZM217.08-240l73.54-246.92q2.3-8.46 3.84-13.08l1.54-4.62-2.69 9.62q-2.69 9.62-6.54 21.92l-64.31 215.39q-2.31 8.46-3.84 13.07l-1.54 4.62Z" /></svg>
@@ -56,6 +56,9 @@ icon:sb_outline = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 2
icon:sb_project = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M21 15.2426V21.0082C21 21.556 20.5551 22 20.0066 22H3.9934C3.44476 22 3 21.5511 3 20.9925V9H9C9.55228 9 10 8.55228 10 8V2H20.0017C20.5531 2 21 2.45531 21 2.9918V6.75736L12.0012 15.7562L11.995 19.995L16.2414 20.0012L21 15.2426ZM21.7782 8.80761L23.1924 10.2218L15.4142 18L13.9979 17.9979L14 16.5858L21.7782 8.80761ZM3 7L8 2.00318V7H3Z" /></svg>
icon:sb_search = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M16 2L21 7V21.0082C21 21.556 20.5551 22 20.0066 22H3.9934C3.44476 22 3 21.5447 3 21.0082V2.9918C3 2.44405 3.44495 2 3.9934 2H16ZM13.529 14.4464L15.7395 16.6569L17.1537 15.2426L14.9432 13.0322C15.8492 11.4983 15.6432 9.48951 14.3252 8.17157C12.7631 6.60948 10.2305 6.60948 8.66839 8.17157C7.1063 9.73367 7.1063 12.2663 8.66839 13.8284C9.98633 15.1464 11.9951 15.3524 13.529 14.4464ZM12.911 12.4142C12.13 13.1953 10.8637 13.1953 10.0826 12.4142C9.30156 11.6332 9.30156 10.3668 10.0826 9.58579C10.8637 8.80474 12.13 8.80474 12.911 9.58579C13.6921 10.3668 13.6921 11.6332 12.911 12.4142Z" /></svg>
icon:sb_stats = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M3 12H7V21H3V12ZM17 8H21V21H17V8ZM10 2H14V21H10V2Z" /></svg>
icon:theme_light = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M12 18C8.68629 18 6 15.3137 6 12C6 8.68629 8.68629 6 12 6C15.3137 6 18 8.68629 18 12C18 15.3137 15.3137 18 12 18ZM11 1H13V4H11V1ZM11 20H13V23H11V20ZM3.51472 4.92893L4.92893 3.51472L7.05025 5.63604L5.63604 7.05025L3.51472 4.92893ZM16.9497 18.364L18.364 16.9497L20.4853 19.0711L19.0711 20.4853L16.9497 18.364ZM19.0711 3.51472L20.4853 4.92893L18.364 7.05025L16.9497 5.63604L19.0711 3.51472ZM5.63604 16.9497L7.05025 18.364L4.92893 20.4853L3.51472 19.0711L5.63604 16.9497ZM23 11V13H20V11H23ZM4 11V13H1V11H4Z" /></svg>
icon:theme_dark = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M11.3807 2.01886C9.91573 3.38768 9 5.3369 9 7.49999C9 11.6421 12.3579 15 16.5 15C18.6631 15 20.6123 14.0843 21.9811 12.6193C21.6613 17.8537 17.3149 22 12 22C6.47715 22 2 17.5228 2 12C2 6.68514 6.14629 2.33869 11.3807 2.01886Z" /></svg>
icon:theme_auto = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M12 21.9967C6.47715 21.9967 2 17.5196 2 11.9967C2 6.47386 6.47715 1.9967 12 1.9967C17.5228 1.9967 22 6.47386 22 11.9967C22 17.5196 17.5228 21.9967 12 21.9967ZM12 19.9967V3.9967C7.58172 3.9967 4 7.57843 4 11.9967C4 16.415 7.58172 19.9967 12 19.9967Z" /></svg>
icon:add = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M11 11V5H13V11H19V13H13V19H11V13H5V11H11Z" /></svg>
icon:bookmarks = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M5 2H19C19.5523 2 20 2.44772 20 3V22.1433C20 22.4194 19.7761 22.6434 19.5 22.6434C19.4061 22.6434 19.314 22.6168 19.2344 22.5669L12 18.0313L4.76559 22.5669C4.53163 22.7136 4.22306 22.6429 4.07637 22.4089C4.02647 22.3293 4 22.2373 4 22.1433V3C4 2.44772 4.44772 2 5 2Z" /></svg>
icon:browse = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M22 11V20C22 20.5523 21.5523 21 21 21H3C2.44772 21 2 20.5523 2 20V11H22ZM22 9H2V4C2 3.44772 2.44772 3 3 3H10.4142L12.4142 5H21C21.5523 5 22 5.44772 22 6V9Z" /></svg>
@@ -56,6 +56,9 @@ icon:sb_outline = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 2
icon:sb_project = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M21 6.75736L19 8.75736V4H10V9H5V20H19V17.2426L21 15.2426V21.0082C21 21.556 20.5551 22 20.0066 22H3.9934C3.44476 22 3 21.5501 3 20.9932V8L9.00319 2H19.9978C20.5513 2 21 2.45531 21 2.9918V6.75736ZM21.7782 8.80761L23.1924 10.2218L15.4142 18L13.9979 17.9979L14 16.5858L21.7782 8.80761Z" /></svg>
icon:sb_search = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M15 4H5V20H19V8H15V4ZM3 2.9918C3 2.44405 3.44749 2 3.9985 2H16L20.9997 7L21 20.9925C21 21.5489 20.5551 22 20.0066 22H3.9934C3.44476 22 3 21.5447 3 21.0082V2.9918ZM13.529 14.4464C11.9951 15.3524 9.98633 15.1464 8.66839 13.8284C7.1063 12.2663 7.1063 9.73367 8.66839 8.17157C10.2305 6.60948 12.7631 6.60948 14.3252 8.17157C15.6432 9.48951 15.8492 11.4983 14.9432 13.0322L17.1537 15.2426L15.7395 16.6569L13.529 14.4464ZM12.911 12.4142C13.6921 11.6332 13.6921 10.3668 12.911 9.58579C12.13 8.80474 10.8637 8.80474 10.0826 9.58579C9.30156 10.3668 9.30156 11.6332 10.0826 12.4142C10.8637 13.1953 12.13 13.1953 12.911 12.4142Z" /></svg>
icon:sb_stats = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M3 12H7V21H3V12ZM17 8H21V21H17V8ZM10 2H14V21H10V2Z" /></svg>
icon:theme_light = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M12 18C8.68629 18 6 15.3137 6 12C6 8.68629 8.68629 6 12 6C15.3137 6 18 8.68629 18 12C18 15.3137 15.3137 18 12 18ZM12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8C9.79086 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16ZM11 1H13V4H11V1ZM11 20H13V23H11V20ZM3.51472 4.92893L4.92893 3.51472L7.05025 5.63604L5.63604 7.05025L3.51472 4.92893ZM16.9497 18.364L18.364 16.9497L20.4853 19.0711L19.0711 20.4853L16.9497 18.364ZM19.0711 3.51472L20.4853 4.92893L18.364 7.05025L16.9497 5.63604L19.0711 3.51472ZM5.63604 16.9497L7.05025 18.364L4.92893 20.4853L3.51472 19.0711L5.63604 16.9497ZM23 11V13H20V11H23ZM4 11V13H1V11H4Z" /></svg>
icon:theme_dark = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M10 7C10 10.866 13.134 14 17 14C18.9584 14 20.729 13.1957 21.9995 11.8995C22 11.933 22 11.9665 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C12.0335 2 12.067 2 12.1005 2.00049C10.8043 3.27098 10 5.04157 10 7ZM4 12C4 16.4183 7.58172 20 12 20C15.0583 20 17.7158 18.2839 19.062 15.7621C18.3945 15.9187 17.7035 16 17 16C12.0294 16 8 11.9706 8 7C8 6.29648 8.08133 5.60547 8.2379 4.938C5.71611 6.28423 4 8.9417 4 12Z" /></svg>
icon:theme_auto = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M12 21.9967C6.47715 21.9967 2 17.5196 2 11.9967C2 6.47386 6.47715 1.9967 12 1.9967C17.5228 1.9967 22 6.47386 22 11.9967C22 17.5196 17.5228 21.9967 12 21.9967ZM12 19.9967C16.4183 19.9967 20 16.415 20 11.9967C20 7.57843 16.4183 3.9967 12 3.9967C7.58172 3.9967 4 7.57843 4 11.9967C4 16.415 7.58172 19.9967 12 19.9967ZM12 17.9967V5.9967C15.3137 5.9967 18 8.683 18 11.9967C18 15.3104 15.3137 17.9967 12 17.9967Z" /></svg>
icon:add = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M11 11V5H13V11H19V13H13V19H11V13H5V11H11Z" /></svg>
icon:bookmarks = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M5 2H19C19.5523 2 20 2.44772 20 3V22.1433C20 22.4194 19.7761 22.6434 19.5 22.6434C19.4061 22.6434 19.314 22.6168 19.2344 22.5669L12 18.0313L4.76559 22.5669C4.53163 22.7136 4.22306 22.6429 4.07637 22.4089C4.02647 22.3293 4 22.2373 4 22.1433V3C4 2.44772 4.44772 2 5 2ZM18 4H6V19.4324L12 15.6707L18 19.4324V4Z" /></svg>
icon:browse = <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#000000" height="128" width="128"><path d="M12.4142 5H21C21.5523 5 22 5.44772 22 6V20C22 20.5523 21.5523 21 21 21H3C2.44772 21 2 20.5523 2 20V4C2 3.44772 2.44772 3 3 3H10.4142L12.4142 5ZM20 11H4V19H20V11ZM20 9V7H11.5858L9.58579 5H4V9H20Z" /></svg>
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,28 +0,0 @@
[Main]
name = Cyberpunk Night
author = Anders Lemvigh
url = https://github.com/alemvigh
license = CC BY-SA 4.0
licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
[Syntax]
background = 0, 0, 0
text = 150, 150, 150
link = 77, 77, 255
headertext = 255, 255, 255
headertag = 50, 0, 180
emphasis = 0, 255, 255
dialog = 0, 255, 0
altdialog = 0, 140, 255
note = 150, 150, 150
hidden = 77, 77, 100
shortcode = 255, 255, 0
keyword = 255, 100, 255
tag = 255, 150, 10
value = 255, 150, 10
spellcheckline = 242, 72, 23
errorline = 186, 218, 4
replacetag = 0, 0, 180
modifier = 144, 142, 176
optional = 180, 180, 180
texthighlight = 255, 255, 132, 96
@@ -1,42 +0,0 @@
[Main]
name = Default Dark
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/
##
# Colours:
# Background = 2a2a2a : 42, 42, 42
# Foreground = cccccc : 204, 204, 204
# Comment = 969696 : 150, 150, 150
# Red = e64250 : 230, 66, 80
# Orange = f99157 : 249, 145, 87
# Yellow = ffff84 : 255, 255, 132
# Green = 99cc99 : 153, 204, 153
# Blue = 288ed7 : 102, 153, 204
# Purple = 8c56d7 : 140, 86, 215
##
[Syntax]
background = 42, 42, 42
text = 204, 204, 204
link = 102, 153, 204
headertext = 153, 204, 153
headertag = 153, 204, 153, 160
emphasis = 249, 145, 87
dialog = 102, 153, 204
altdialog = 102, 153, 204
note = 255, 255, 216
hidden = 150, 150, 150
shortcode = 153, 204, 153
keyword = 230, 66, 80
tag = 153, 204, 153
value = 102, 153, 204
optional = 153, 204, 153
spellcheckline = 230, 66, 80
errorline = 153, 204, 153
replacetag = 153, 204, 153
modifier = 153, 204, 153
texthighlight = 255, 255, 132, 96
@@ -1,42 +0,0 @@
[Main]
name = Default Light
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/
##
# Colours:
# Background = ffffff : 255, 255, 255
# Foreground = 000000 : 0, 0, 0
# Comment = 646464 : 100, 100, 100
# Red = c83232 : 200, 50, 50
# Orange = ae6e1e : 174, 110, 30
# Yellow = 969600 : 150, 150, 0
# Green = 006400 : 0, 100, 0
# Blue = 0000c8 : 0, 0, 200
# Purple = 6400ae : 100, 0, 174
##
[Syntax]
background = 255, 255, 255
text = 0, 0, 0
link = 0, 0, 200
headertext = 0, 100, 0
headertag = 0, 100, 0, 160
emphasis = 174, 110, 30
dialog = 0, 0, 200
altdialog = 0, 0, 200
note = 100, 100, 0
hidden = 100, 100, 100
shortcode = 0, 100, 0
keyword = 200, 50, 50
tag = 0, 100, 0
value = 0, 0, 200
optional = 0, 100, 0
spellcheckline = 200, 50, 50
errorline = 0, 100, 0
replacetag = 0, 100, 0
modifier = 0, 100, 0
texthighlight = 150, 150, 0, 96
-44
View File
@@ -1,44 +0,0 @@
[Main]
name = Dracula
author = Veronica Berglyd Olsen (adaptation)
credit = Zeno Rocha
url = https://draculatheme.com
license = MIT
licenseurl = https://github.com/dracula/dracula-theme/blob/main/LICENSE
##
# Colours:
# Background = 282a36 : 40, 42, 54
# Foreground = f8f8f2 : 248, 248, 242
# Comment = 6272a4 : 98, 114, 164
# Cyan = 8be9fd : 139, 233, 253
# Green = 50fa7b : 80, 250, 123
# Orange = ffb86c : 255, 184, 108
# Pink = ff79c6 : 255, 121, 198
# Purple = bd93f9 : 189, 147, 249
# Red = ff5555 : 255, 85, 85
# Yellow = f1fa8c : 241, 250, 140
##
[Syntax]
# See: https://spec.draculatheme.com/
background = 40, 42, 54
text = 248, 248, 242
link = 255, 121, 198
headertext = 189, 147, 249
headertag = 189, 147, 249, 160
emphasis = 255, 184, 108
dialog = 80, 250, 123
altdialog = 241, 250, 140
note = 255, 204, 233
hidden = 98, 114, 164
shortcode = 139, 233, 253
keyword = 255, 121, 198
tag = 255, 184, 108
value = 255, 184, 108
optional = 80, 250, 123
spellcheckline = 255, 85, 85
errorline = 80, 250, 123
replacetag = 241, 250, 140
modifier = 139, 233, 253
texthighlight = 241, 250, 140, 96
-29
View File
@@ -1,29 +0,0 @@
[Main]
name = Grey Dark
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/
[Syntax]
background = 54, 54, 54
text = 200, 200, 200
link = 200, 200, 200
headertext = 225, 225, 225
headertag = 225, 225, 225, 160
emphasis = 200, 200, 200
dialog = 200, 200, 200
altdialog = 200, 200, 200
note = 200, 200, 200
hidden = 150, 150, 150
shortcode = 225, 225, 225
keyword = 225, 225, 225
tag = 200, 200, 200
value = 200, 200, 200
optional = 225, 225, 225
spellcheckline = 200, 46, 0
errorline = 46, 200, 0
replacetag = 225, 225, 225
modifier = 225, 225, 225
texthighlight = 255, 255, 255, 64
-29
View File
@@ -1,29 +0,0 @@
[Main]
name = Grey Light
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/
[Syntax]
background = 255, 255, 255
text = 20, 20, 20
link = 20, 20, 20
headertext = 0, 0, 0
headertag = 0, 0, 0, 160
emphasis = 20, 20, 20
dialog = 20, 20, 20
altdialog = 20, 20, 20
note = 20, 20, 20
hidden = 100, 100, 100
shortcode = 0, 0, 0
keyword = 0, 0, 0
tag = 20, 20, 20
value = 20, 20, 20
optional = 0, 0, 0
spellcheckline = 200, 0, 0
errorline = 0, 150, 0
replacetag = 0, 0, 0
modifier = 0, 0, 0
texthighlight = 0, 0, 0, 64
-49
View File
@@ -1,49 +0,0 @@
##
# Theme: Light Owl
# Source: https://github.com/sdras/night-owl-vscode-theme
# Credit: Sarah Drasner
##
# Colours:
# Background = fbfbfb : 251, 251, 251
# Current Line = e0e0e0 : 224, 224, 224
# Selection = e0e0e0 : 224, 224, 224
# Foreground = 403f52 : 64, 63, 82
# Comment = 989fb1 : 152, 159, 177
# Red = de3d3a : 222, 61, 58
# Orange = e0af05 : 224, 175, 5
# Yellow = daaa01 : 218, 170, 1
# Green = 08916a : 8, 145, 106
# Aqua = 2aa298 : 42, 162, 152
# Blue = 288ed7 : 40, 142, 215
# Purple = 964ac1 : 150, 74, 193
##
[Main]
name = Light Owl
author = Veronica Berglyd Olsen (adaptation)
credit = Sarah Drasner (color theme)
url = https://github.com/sdras/night-owl-vscode-theme
license = MIT License
licenseurl = https://github.com/sdras/night-owl-vscode-theme/blob/master/LICENSE
[Syntax]
background = 251, 251, 251
text = 64, 63, 82
link = 40, 142, 215
headertext = 40, 142, 215
headertag = 40, 142, 215, 160
emphasis = 224, 175, 5
dialog = 8, 145, 106
altdialog = 218, 170, 1
note = 128, 118, 75
hidden = 152, 159, 177
shortcode = 40, 142, 215
keyword = 222, 61, 58
tag = 150, 74, 193
value = 224, 175, 5
optional = 40, 142, 215
spellcheckline = 222, 61, 58
errorline = 8, 145, 106
replacetag = 42, 162, 152
modifier = 8, 145, 106
texthighlight = 218, 170, 1, 96
-49
View File
@@ -1,49 +0,0 @@
##
# Theme: Night Owl
# Source: https://github.com/sdras/night-owl-vscode-theme
# Credit: Sarah Drasner
##
# Colours:
# Background = 011627 : 1, 22, 39
# Current Line = 32374d : 50, 55, 77
# Selection = 32374d : 50, 55, 77
# Foreground = d6deeb : 214, 222, 235
# Comment = 637777 : 99, 119, 119
# Red = f78c6c : 247, 140, 108
# Orange = ecc48d : 236, 196, 141
# Yellow = ffeb95 : 255, 235, 149
# Green = addb67 : 173, 219, 103
# Aqua = 7fdbca : 127, 219, 202
# Blue = 82aaff : 130, 170, 255
# Purple = c792ea : 199, 146, 234
##
[Main]
name = Night Owl
author = Veronica Berglyd Olsen (adaptation)
credit = Sarah Drasner (color theme)
url = https://github.com/sdras/night-owl-vscode-theme
license = MIT License
licenseurl = https://github.com/sdras/night-owl-vscode-theme/blob/master/LICENSE
[Syntax]
background = 1, 22, 39
text = 214, 222, 235
link = 130, 170, 255
headertext = 130, 170, 255
headertag = 130, 170, 255, 160
emphasis = 236, 196, 141
dialog = 173, 219, 103
altdialog = 255, 235, 149
note = 255, 249, 202
hidden = 99, 119, 119
shortcode = 130, 170, 255
keyword = 247, 140, 108
tag = 199, 146, 234
value = 255, 235, 149
optional = 130, 170, 255
spellcheckline = 247, 140, 108
errorline = 173, 219, 103
replacetag = 127, 219, 202
modifier = 173, 219, 103
texthighlight = 255, 235, 149, 96
-42
View File
@@ -1,42 +0,0 @@
[Main]
name = Snazzy Light
author = Veronica Berglyd Olsen (adaptation)
credit = Florian Reuschel (color theme)
url = https://github.com/loilo/vscode-snazzy-light
license = MIT License
licenseurl = https://github.com/loilo/vscode-snazzy-light/blob/master/LICENSE
##
# Colours:
# Background = fafbfc : 250, 251, 252
# Foreground = 565869 : 86, 88, 105
# Comment = 9194a2 : 145, 148, 162
# Yellow = cf9c00 : 207, 156, 0
# Red = ff5c57 : 255, 92, 87
# Pink = f767bb : 247, 103, 187
# Blue = 09a1ed : 9, 161, 237
# Cyan = 13bbb7 : 19, 187, 183
# Green = 2dae58 : 45, 174, 88
##
[Syntax]
background = 250, 251, 252
text = 86, 88, 105
link = 9, 161, 237
headertext = 45, 174, 88
headertag = 45, 174, 88, 160
emphasis = 247, 103, 187
dialog = 9, 161, 237
altdialog = 207, 156, 0
note = 120, 187, 185
hidden = 145, 148, 162
shortcode = 247, 103, 187
keyword = 9, 161, 237
tag = 45, 174, 88
value = 207, 156, 0
optional = 207, 156, 0
spellcheckline = 255, 92, 87
errorline = 45, 174, 88
replacetag = 19, 187, 183
modifier = 247, 103, 187
texthighlight = 207, 156, 0, 96
@@ -1,29 +0,0 @@
[Main]
name = Solarized Dark
author = nullbasis
credit = Ethan Schoonover
url = https://ethanschoonover.com/solarized/
license = MIT
licenseurl = https://github.com/altercation/solarized/blob/master/LICENSE
[Syntax]
background = 7, 54, 66
text = 253, 246, 227
link = 38, 139, 210
headertext = 147, 161, 161
headertag = 42, 161, 152
emphasis = 38, 139, 210
dialog = 42, 161, 152
altdialog = 42, 161, 152
note = 101, 161, 156
hidden = 147, 161, 161
shortcode = 147, 161, 161
keyword = 133, 153, 0
tag = 203, 75, 22
value = 203, 75, 22
optional = 147, 161, 161
spellcheckline = 203, 75, 22
errorline = 220, 50, 47
replacetag = 133, 153, 0
modifier = 181, 137, 0
texthighlight = 181, 137, 0, 96
@@ -1,29 +0,0 @@
[Main]
name = Solarized Light
author = nullbasis
credit = Ethan Schoonover
url = https://ethanschoonover.com/solarized/
license = MIT
licenseurl = https://github.com/altercation/solarized/blob/master/LICENSE
[Syntax]
background = 253, 246, 227
text = 0, 43, 54
link = 38, 139, 210
headertext = 88, 110, 117
headertag = 42, 161, 152
emphasis = 38, 139, 210
dialog = 42, 161, 152
altdialog = 42, 161, 152
note = 27, 102, 96
hidden = 88, 110, 117
shortcode = 88, 110, 117
keyword = 133, 153, 0
tag = 203, 75, 22
value = 203, 75, 22
optional = 88, 110, 117
spellcheckline = 203, 75, 22
errorline = 220, 50, 47
replacetag = 133, 153, 0
modifier = 181, 137, 0
texthighlight = 181, 137, 0, 96
-39
View File
@@ -1,39 +0,0 @@
[Main]
name = Tango
author = Veronica Berglyd Olsen (adaptation)
##
# Colours:
# Background = 30302f : 48, 48, 47
# Foreground = eeeeec : 238, 238, 236
# Comment = 8f8f8d : 143, 143, 141
# Red = ef2929 : 239, 41, 41
# Orange = c4a000 : 196, 160, 0
# Yellow = fce94f : 252, 233, 79
# Green = 8ae234 : 138, 226, 52
# Cyan = 34e2e2 : 52, 226, 226
# Blue = 3465a4 : 114, 159, 207
# Purple = ad7fa8 : 173, 127, 168
##
[Syntax]
background = 48, 48, 47
text = 238, 238, 236
link = 114, 159, 207
headertext = 114, 159, 207
headertag = 114, 159, 207, 160
emphasis = 196, 160, 0
dialog = 138, 226, 52
altdialog = 252, 233, 79
note = 252, 242, 164
hidden = 143, 143, 141
shortcode = 114, 159, 207
keyword = 239, 41, 41
tag = 173, 127, 168
value = 239, 41, 41
optional = 114, 159, 207
spellcheckline = 239, 41, 41
errorline = 138, 226, 52
replacetag = 52, 226, 226
modifier = 114, 159, 207
texthighlight = 252, 233, 79, 96
-49
View File
@@ -1,49 +0,0 @@
##
# Theme: Tomorrow
# Source: https://github.com/chriskempson/tomorrow-theme
# Credit: Chris Kempson
##
# Colours:
# Background = ffffff : 255, 255, 255
# Current Line = efefef : 239, 239, 239
# Selection = d6d6d6 : 214, 214, 214
# Foreground = 4d4d4c : 77, 77, 76
# Comment = 8e908c : 142, 144, 140
# Red = c82829 : 240, 40, 41
# Orange = f5871f : 245, 135, 31
# Yellow = eab700 : 234, 183, 0
# Green = 718c00 : 113, 140, 0
# Aqua = 3e999f : 62, 153, 159
# Blue = 4271ae : 66, 113, 174
# Purple = 8959a8 : 137, 89, 168
##
[Main]
name = Tomorrow
author = Veronica Berglyd Olsen (adaptation)
credit = Chris Kempson (color theme)
url = https://github.com/chriskempson/tomorrow-theme
license = MIT License
licenseurl = https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.md
[Syntax]
background = 255, 255, 255
text = 77, 77, 76
link = 66, 113, 174
headertext = 66, 113, 174
headertag = 66, 113, 174, 160
emphasis = 245, 135, 31
dialog = 113, 140, 0
altdialog = 234, 183, 0
note = 115, 90, 0
hidden = 142, 144, 140
shortcode = 66, 113, 174
keyword = 240, 40, 41
tag = 137, 89, 168
value = 234, 183, 0
optional = 66, 113, 174
spellcheckline = 240, 40, 41
errorline = 113, 140, 0
replacetag = 62, 153, 159
modifier = 245, 135, 31
texthighlight = 234, 183, 0, 96
@@ -1,49 +0,0 @@
##
# Theme: Tomorrow Night
# Source: https://github.com/chriskempson/tomorrow-theme
# Credit: Chris Kempson
##
# Colours:
# Background = 1d1f21 : 29, 31, 33
# Current Line = 282a2e : 40, 42, 46
# Selection = 373b41 : 55, 59. 65
# Foreground = c5c8c6 : 197, 200, 198
# Comment = 969896 : 150, 152, 150
# Red = cc6666 : 204, 102, 102
# Orange = de935f : 222, 147, 95
# Yellow = f0c674 : 240, 198, 116
# Green = b5bd68 : 181, 189, 104
# Aqua = 8abeb7 : 138, 190, 183
# Blue = 81a2be : 129, 162, 190
# Purple = b294bb : 178, 148, 187
##
[Main]
name = Tomorrow Night
author = Veronica Berglyd Olsen (adaptation)
credit = Chris Kempson (color theme)
url = https://github.com/chriskempson/tomorrow-theme
license = MIT License
licenseurl = https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.md
[Syntax]
background = 29, 31, 33
text = 197, 200, 198
link = 197, 200, 198, 160
headertext = 129, 162, 190
headertag = 94, 119, 139
emphasis = 222, 147, 95
dialog = 181, 189, 104
altdialog = 240, 198, 116
note = 240, 219, 178
hidden = 150, 152, 150
shortcode = 129, 162, 190
keyword = 204, 102, 102
tag = 178, 148, 187
value = 240, 198, 116
optional = 129, 162, 190
spellcheckline = 204, 102, 102
errorline = 181, 189, 104
replacetag = 138, 190, 183
modifier = 222, 147, 95
texthighlight = 240, 198, 116, 96
@@ -1,49 +0,0 @@
##
# Theme: Tomorrow Night Blue
# Source: https://github.com/chriskempson/tomorrow-theme
# Credit: Chris Kempson
##
# Colours:
# Background = 002451 : 0, 36, 81
# Current Line = 00346e : 0, 52, 110
# Selection = 003f8e : 0, 63, 142
# Foreground = ffffff : 255, 255, 255
# Comment = 7285b7 : 114, 133, 183
# Red = ff9da4 : 255, 157, 164
# Orange = ffc58f : 255, 197, 143
# Yellow = ffeead : 255, 238, 173
# Green = d1f1a9 : 209, 241, 169
# Aqua = 99ffff : 153, 255, 255
# Blue = bbdaff : 187, 218, 255
# Purple = ebbbff : 235, 187, 255
##
[Main]
name = Tomorrow Night Blue
author = Veronica Berglyd Olsen (adaptation)
credit = Chris Kempson (color theme)
url = https://github.com/chriskempson/tomorrow-theme
license = MIT License
licenseurl = https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.md
[Syntax]
background = 0, 36, 81
text = 255, 255, 255
link = 187, 218, 255
headertext = 187, 218, 255
headertag = 187, 218, 255, 160
emphasis = 255, 197, 143
dialog = 209, 241, 169
altdialog = 255, 238, 173
note = 255, 247, 214
hidden = 114, 133, 183
shortcode = 187, 218, 255
keyword = 255, 157, 164
tag = 235, 187, 255
value = 255, 238, 173
optional = 187, 218, 255
spellcheckline = 255, 157, 164
errorline = 209, 241, 169
replacetag = 153, 255, 255
modifier = 255, 197, 143
texthighlight = 255, 238, 173, 96
@@ -1,49 +0,0 @@
##
# Theme: Tomorrow Night Bright
# Source: https://github.com/chriskempson/tomorrow-theme
# Credit: Chris Kempson
##
# Colours:
# Background = 000000 : 0, 0, 0
# Current Line = 2a2a2a : 42, 42, 42
# Selection = 424242 : 66, 66, 66
# Foreground = eaeaea : 234, 234, 234
# Comment = 969896 : 150, 152, 150
# Red = d54e53 : 213, 78, 83
# Orange = e78c45 : 231, 140, 69
# Yellow = e7c547 : 231, 197, 71
# Green = b9ca4a : 185, 202, 74
# Aqua = 70c0b1 : 112, 192, 177
# Blue = 7aa6da : 122, 166, 218
# Purple = c397d8 : 195, 151, 216
##
[Main]
name = Tomorrow Night Bright
author = Veronica Berglyd Olsen (adaptation)
credit = Chris Kempson (color theme)
url = https://github.com/chriskempson/tomorrow-theme
license = MIT License
licenseurl = https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.md
[Syntax]
background = 0, 0, 0
text = 234, 234, 234
link = 122, 166, 218
headertext = 122, 166, 218
headertag = 122, 166, 218, 160
emphasis = 231, 140, 69
dialog = 185, 202, 74
altdialog = 231, 197, 71
note = 231, 214, 150
hidden = 150, 152, 150
shortcode = 122, 166, 218
keyword = 213, 78, 83
tag = 195, 151, 216
value = 231, 197, 71
optional = 122, 166, 218
spellcheckline = 213, 78, 83
errorline = 185, 202, 74
replacetag = 112, 192, 177
modifier = 231, 140, 69
texthighlight = 231, 197, 71, 128
@@ -1,49 +0,0 @@
##
# Theme: Tomorrow Night Eighties
# Source: https://github.com/chriskempson/tomorrow-theme
# Credit: Chris Kempson
##
# Colours:
# Background = 2d2d2d : 45, 45, 45
# Current Line = 393939 : 57, 57, 57
# Selection = 515151 : 81, 81, 81
# Foreground = cccccc : 204, 204, 204
# Comment = 999999 : 153, 153, 153
# Red = f2777a : 242, 119, 122
# Orange = f99157 : 249, 145, 57
# Yellow = ffcc66 : 255, 204, 102
# Green = 99cc99 : 153, 204, 153
# Aqua = 66cccc : 102, 204, 204
# Blue = 6699cc : 102, 153, 204
# Purple = cc99cc : 204, 153, 204
##
[Main]
name = Tomorrow Night Eighties
author = Veronica Berglyd Olsen (adaptation)
credit = Chris Kempson (color theme)
url = https://github.com/chriskempson/tomorrow-theme
license = MIT License
licenseurl = https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.md
[Syntax]
background = 45, 45, 45
text = 204, 204, 204
link = 102, 153, 204
headertext = 102, 153, 204
headertag = 102, 153, 204, 160
emphasis = 249, 145, 57
dialog = 153, 204, 153
altdialog = 255, 204, 102
note = 255, 230, 179
hidden = 153, 153, 153
shortcode = 102, 153, 204
keyword = 242, 119, 122
tag = 204, 153, 204
value = 255, 204, 102
optional = 102, 153, 204
spellcheckline = 242, 119, 122
errorline = 153, 204, 153
replacetag = 102, 204, 204
modifier = 249, 145, 57
texthighlight = 255, 204, 102, 96
+26 -8
View File
@@ -67,20 +67,38 @@ more contributions are listed on the project's Members page.</p>
<li><b>PyEnchant</b> by Dimitri Merejkowsky</li>
</ul>
<h3>Assets</h3>
<h3>Icon Themes</h3>
<p>Some of the assets bundled with novelWriter were adapted from the following sources:</p>
<p>The following icon themes are bundles with novelWriter:</p>
<ul>
<li><b>Material Symbols</b> icons by Google Inc (Apache 2.0)</li>
<li><b>Remix</b> icons by RemixIcon (Apache 2.0)</li>
<li><b>Font Awesome</b> icons by Fonticons Inc (CC BY 4.0)</li>
<li><b>Tomorrow</b> syntax themes by Chris Kempson (MIT License)</li>
<li><b>Owl</b> syntax themes by Sarah Drasner (MIT License)</li>
<li><b>Solarized</b> themes by Ethan Schoonover (MIT License)</li>
<li><b>Cyberpunk Night</b> theme by Anders Lemvigh (CC BY-SA 4.0)</li>
<li><b>Dracula</b> theme by Zeno Rocha (MIT License)</li>
<li><b>Snazzy Light</b> theme by Florian Reuschel (MIT License)</li>
</ul>
<h3>Colour Themes</h3>
<p>Some colour themes included with novelWriter were adapted from the following sources:</p>
<ul>
<li><b>Dracula</b> theme by Zeno Rocha</li>
<li><b>Espresso</b> theme by Sainnhe Park and Antoine Cotten</li>
<li><b>Noctis</b> themes by Liviu Schera, and contributors</li>
<li><b>Owl</b> themes by Sarah Drasner</li>
<li><b>Primer</b> themes by GitHub Inc and Myian</li>
<li><b>Snazzy Light</b> theme by Florian Reuschel</li>
<li><b>Solarized</b> themes by Ethan Schoonover</li>
<li><b>Tango</b> themes by the Tango Desktop Project</li>
<li><b>Tomorrow</b> themes by Chris Kempson</li>
</ul>
<p>Other themes were created for novelWriter:</p>
<ul>
<li><b>Blue Streak</b> themes by Veronica Berglyd Olsen</li>
<li><b>Cyberpunk Night</b> theme by Anders Lemvigh</li>
<li><b>LCARS</b> theme by Veronica Berglyd Olsen</li>
</ul>
<h3>Fonts</h3>
@@ -0,0 +1,76 @@
[Main]
name = Blue Streak Dark
mode = dark
description = A theme mainly using shades of blue
author = Veronica Berglyd Olsen
credit = Veronica Berglyd Olsen
url = https://github.com/vkbo/novelWriter
[Base]
base = #2d2d2d
default = #cccccc
faded = #999999
red = #ff6666
orange = #ff9966
yellow = #ffcc66
green = #99cc99
cyan = #66cccc
blue = #6699cc
purple = #cc99cc
[Project]
root = blue
folder = default
file = default
title = blue:D150
chapter = blue
scene = blue:L150
note = yellow
active = blue:L150
inactive = blue:D150
disabled = faded
[Palette]
window = base:L125
windowtext = default
base = base
alternatebase = base:L150
text = default
tooltipbase = yellow:L140
tooltiptext = base:D150
button = base
buttontext = default
brighttext = base
highlight = blue
highlightedtext = default:L120
link = blue
linkvisited = blue
accent = blue
[GUI]
helptext = blue:L125
fadedtext = faded
errortext = red
[Syntax]
background = base
text = default
line = blue:48
link = blue
headertext = blue
headertag = blue:D150
emphasis = blue:L150
dialog = blue
altdialog = blue:L150
note = blue:L150
hidden = faded
shortcode = blue:D125
keyword = blue:D125
tag = blue
value = blue
optional = blue
spellcheckline = red
errorline = green
replacetag = blue:L150
modifier = blue:D125
texthighlight = blue:96
@@ -0,0 +1,76 @@
[Main]
name = Blue Streak Light
mode = light
description = A theme mainly using shades of blue
author = Veronica Berglyd Olsen
credit = Veronica Berglyd Olsen
url = https://github.com/vkbo/novelWriter
[Base]
base = #fcfcfc
default = #303030
faded = #6c6c6c
red = #cc3939
orange = #cc7239
yellow = #cca339
green = #72a372
cyan = #39a3a3
blue = #3972a3
purple = #a372a3
[Project]
root = blue
folder = faded
file = faded
title = blue:D150
chapter = blue
scene = blue:L125
note = yellow
active = blue:L115
inactive = blue:D150
disabled = faded
[Palette]
window = base:D105
windowtext = default
base = base
alternatebase = base:D110
text = default
tooltipbase = yellow:L150
tooltiptext = default:D150
button = base
buttontext = default
brighttext = base
highlight = blue:L110
highlightedtext = base
link = blue
linkvisited = blue
accent = blue
[GUI]
helptext = blue
fadedtext = faded
errortext = red
[Syntax]
background = base
text = default
line = blue:32
link = blue
headertext = blue
headertag = blue:D150
emphasis = blue:L125
dialog = blue
altdialog = blue:L125
note = blue:L125
hidden = faded
shortcode = blue:D125
keyword = blue:D125
tag = blue
value = blue
optional = blue
spellcheckline = red
errorline = green
replacetag = blue:L125
modifier = blue:D125
texthighlight = blue:96
+63 -36
View File
@@ -1,48 +1,75 @@
[Main]
name = Cyberpunk Night
mode = dark
description = A taste of the future 80s
author = Anders Lemvigh
url = https://github.com/alemvigh
license = CC BY-SA 4.0
licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
[Icons]
default = 136, 136, 136
faded = 97, 97, 97
red = 242, 72, 23
orange = 255, 150, 10
yellow = 255, 255, 0
green = 0, 255, 0
aqua = 0, 255, 255
blue = 77, 77, 255
purple = 50, 0, 180
[Base]
base = #000000
default = #888888
faded = #616161
red = #f24817
orange = #ff960a
yellow = #ffff00
green = #00ff00
cyan = #00ffff
blue = #4d4dff
purple = #320064
[Project]
root = 77, 77, 255
folder = 255, 255, 0
file = 136, 136, 136
title = 0, 255, 0
chapter = 242, 72, 23
scene = 77, 77, 255
note = 255, 255, 0
root = blue
folder = yellow
file = default
title = green
chapter = red
scene = blue
note = yellow
active = green
inactive = red
disabled = faded
[Palette]
window = 0, 0, 0
windowtext = 150, 150, 150
base = 0, 0, 0
alternatebase = 40, 40, 40
text = 150, 150, 150
tooltipbase = 40, 20, 70
tooltiptext = 255, 255, 255
button = 5, 0, 10
buttontext = 150, 150, 150
brighttext = 255, 255, 255
highlight = 50, 30, 80
highlightedtext = 255, 255, 255
link = 77, 77, 255
linkvisited = 50, 0, 80
window = base
windowtext = #969696
base = base
alternatebase = #282828
text = #969696
tooltipbase = #281446
tooltiptext = #ffffff
button = #05000a
buttontext = #969696
brighttext = #ffffff
highlight = #321e50
highlightedtext = #ffffff
link = blue
linkvisited = #320050
accent = #321e50
[GUI]
helptext = 97, 97, 97
fadedtext = 97, 97, 97
errortext = 242, 72, 23
helptext = faded
fadedtext = faded
errortext = red
[Syntax]
background = base
text = #969696
line = #282828
link = blue
headertext = #ffffff
headertag = purple
emphasis = cyan
dialog = green
altdialog = #008cff
note = #969696
hidden = #4d4d64
shortcode = yellow
keyword = #ff64ff
tag = orange
value = orange
spellcheckline = red
errorline = #bada04
replacetag = #0000b4
modifier = #908eb0
optional = #b4b4b4
texthighlight = #ffff8460
-3
View File
@@ -1,3 +0,0 @@
[Main]
name = Qt Default Theme
description = Qt standard colours
+63 -36
View File
@@ -1,49 +1,76 @@
[Main]
name = Default Dark Theme
mode = dark
description = The novelWriter standard dark 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/
[Icons]
default = 184, 184, 184
faded = 148, 148, 148
red = 242, 119, 122
orange = 249, 145, 57
yellow = 255, 204, 102
green = 153, 204, 153
aqua = 102, 204, 204
blue = 102, 153, 204
purple = 204, 153, 204
[Base]
base = #373737
default = #cccccc
faded = #949494
red = #ff6666
orange = #ff9966
yellow = #ffcc66
green = #99cc99
cyan = #66cccc
blue = #6699cc
purple = #cc99cc
[Project]
root = 102, 153, 204
folder = 255, 204, 102
file = 184, 184, 184
title = 153, 204, 153
chapter = 242, 119, 122
scene = 102, 153, 204
note = 255, 204, 102
root = blue
folder = yellow
file = default
title = green
chapter = red
scene = blue
note = yellow
active = green
inactive = red
disabled = faded
[Palette]
window = 54, 54, 54
windowtext = 204, 204, 204
base = 62, 62, 62
alternatebase = 78, 78, 78
text = 204, 204, 204
tooltipbase = 255, 255, 192
tooltiptext = 21, 21, 13
button = 62, 62, 62
buttontext = 204, 204, 204
brighttext = 62, 62, 62
highlight = 44, 152, 247
highlightedtext = 255, 255, 255
link = 102, 153, 204
linkvisited = 102, 153, 204
window = base:L125
windowtext = default
base = base
alternatebase = #4e4e4e
text = default
tooltipbase = #ffffc0
tooltiptext = #15150d
button = base
buttontext = default
brighttext = base
highlight = #2c98f7
highlightedtext = #ffffff
link = blue
linkvisited = blue
accent = #2c98f7
[GUI]
helptext = 164, 164, 164
fadedtext = 148, 148, 148
errortext = 255, 164, 164
helptext = #a4a4a4
fadedtext = faded
errortext = red
[Syntax]
background = base
text = default
line = default:32
link = blue
headertext = green
headertag = green:D150
emphasis = orange
dialog = blue
altdialog = red
note = yellow
hidden = faded
shortcode = green
keyword = red
tag = green
value = green
optional = green
spellcheckline = red
errorline = green
replacetag = green
modifier = blue
texthighlight = yellow:72
+63 -36
View File
@@ -1,49 +1,76 @@
[Main]
name = Default Light Theme
mode = light
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/
[Icons]
default = 72, 72, 72
faded = 108, 108, 108
red = 240, 40, 41
orange = 245, 135, 31
yellow = 234, 183, 0
green = 113, 140, 0
aqua = 62, 153, 159
blue = 66, 113, 174
purple = 137, 89, 168
[Base]
base = #fcfcfc
default = #303030
faded = #6c6c6c
red = #a62a2d
orange = #b36829
yellow = #a39c34
green = #296629
cyan = #269999
blue = #3a70a6
purple = #b35ab3
[Project]
root = 66, 113, 174
folder = 234, 183, 0
file = 72, 72, 72
title = 113, 140, 0
chapter = 240, 40, 41
scene = 66, 113, 174
note = 234, 183, 0
root = blue
folder = yellow
file = default
title = green
chapter = red
scene = blue
note = yellow
active = green
inactive = red
disabled = faded
[Palette]
window = 239, 239, 239
windowtext = 0, 0, 0
base = 255, 255, 255
alternatebase = 224, 224, 224
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 = 66, 113, 174
linkvisited = 66, 113, 174
window = base:D105
windowtext = default
base = base
alternatebase = #e0e0e0
text = default
tooltipbase = #ffffc0
tooltiptext = #15150d
button = #efefef
buttontext = default
brighttext = base
highlight = #3087c6
highlightedtext = base
link = blue
linkvisited = blue
accent = #3087c6
[GUI]
helptext = 92, 92, 92
fadedtext = 108, 108, 108
errortext = 255, 92, 92
helptext = #5c5c5c
fadedtext = #6c6c6c
errortext = red
[Syntax]
background = base
text = default
line = default:32
link = blue
headertext = green
headertag = green:L135
emphasis = orange
dialog = blue
altdialog = red
note = yellow:D125
hidden = faded
shortcode = green
keyword = red
tag = green
value = green
optional = green
spellcheckline = red
errorline = green
replacetag = green
modifier = blue
texthighlight = yellow:72
+65 -37
View File
@@ -1,17 +1,17 @@
[Main]
name = Dracula
mode = dark
description = A dark theme with bright colours
author = Veronica Berglyd Olsen (adaptation)
credit = Zeno Rocha
url = https://draculatheme.com
license = MIT
licenseurl = https://github.com/dracula/dracula-theme/blob/main/LICENSE
##
# Colours:
# See: https://spec.draculatheme.com/
# Background = 282a36 : 40, 42, 54
# Foreground = f8f8f2 : 248, 248, 242
# Current Line = 44475A : 68, 71, 90
# Current Line = 44475a : 68, 71, 90
# Comment = 6272a4 : 98, 114, 164
# Cyan = 8be9fd : 139, 233, 253
# Green = 50fa7b : 80, 250, 123
@@ -22,43 +22,71 @@ licenseurl = https://github.com/dracula/dracula-theme/blob/main/LICENSE
# Yellow = f1fa8c : 241, 250, 140
##
[Icons]
default = 230, 230, 224
faded = 98, 114, 164
red = 255, 85, 85
orange = 255, 184, 108
yellow = 241, 250, 140
green = 80, 250, 123
aqua = 139, 233, 253
blue = 147, 207, 249
purple = 189, 147, 249
[Base]
base = #282a36
default = #e6e6e0
faded = #6272a4
red = #ff5555
orange = #ffb86c
yellow = #f1fa8c
green = #50fa7b
cyan = #8be9fd
blue = #93cff9
purple = #bd93f9
[Project]
root = 189, 147, 249
folder = 241, 250, 140
file = 230, 230, 224
title = 80, 250, 123
chapter = 255, 85, 85
scene = 139, 233, 253
note = 241, 250, 140
root = purple
folder = yellow
file = default
title = green
chapter = red
scene = cyan
note = yellow
active = green
inactive = red
disabled = faded
[Palette]
window = 68, 71, 90
windowtext = 248, 248, 242
base = 40, 42, 54
alternatebase = 51, 54, 69
text = 248, 248, 242
tooltipbase = 241, 250, 140
tooltiptext = 40, 42, 54
button = 80, 83, 105
buttontext = 248, 248, 242
brighttext = 68, 71, 90
highlight = 166, 129, 218
highlightedtext = 248, 248, 242
link = 139, 233, 253
linkvisited = 139, 233, 253
window = #44475a
windowtext = #f8f8f2
base = base
alternatebase = #333645
text = #f8f8f2
tooltipbase = yellow
tooltiptext = base
button = #505369
buttontext = #f8f8f2
brighttext = base
highlight = #a681da
highlightedtext = #f8f8f2
link = cyan
linkvisited = cyan
accent = #a681da
[GUI]
helptext = 204, 172, 249
fadedtext = 98, 114, 164
errortext = 255, 85, 85
helptext = #ccacf9
fadedtext = faded
errortext = red
[Syntax]
background = base
text = #f8f8f2
line = base:L150
link = #ff79c6
headertext = purple
headertag = purple:D150
emphasis = orange
dialog = green
altdialog = yellow
note = #ffcce9
hidden = faded
shortcode = cyan
keyword = #ff79c6
tag = orange
value = orange
optional = green
spellcheckline = red
errorline = green
replacetag = yellow
modifier = cyan
texthighlight = yellow:96
+76
View File
@@ -0,0 +1,76 @@
[Main]
name = Espresso
mode = dark
description = High contrast & vivid color scheme based on Monokai Pro
author = Veronica Berglyd Olsen
credit = Sainnhe Park and Antoine Cotten
url = https://github.com/sainnhe/sonokai
[Base]
base = #2f2726
default = #e5e4e2
faded = #96817b
red = #f86882
orange = #f08d71
yellow = #f0c66f
green = #a6cd77
cyan = #81d0c9
blue = #89a8e0
purple = #9fa0e1
[Project]
root = blue
folder = yellow
file = default
title = green
chapter = red
scene = blue
note = yellow
active = green
inactive = red
disabled = faded
[Palette]
window = base:L125
windowtext = default
base = base
alternatebase = #51403c
text = default
tooltipbase = yellow:L125
tooltiptext = base
button = base
buttontext = default
brighttext = base
highlight = #4e5f80
highlightedtext = default
link = blue
linkvisited = blue
accent = blue
[GUI]
helptext = blue
fadedtext = faded
errortext = red
[Syntax]
background = base
text = default
line = #392e2c
link = blue
headertext = yellow:L125
headertag = faded
emphasis = orange
dialog = yellow
altdialog = orange
note = yellow:L125
hidden = faded
shortcode = green
keyword = red
tag = green
value = orange
optional = yellow
spellcheckline = red
errorline = green
replacetag = cyan
modifier = blue
texthighlight = yellow:108
+76
View File
@@ -0,0 +1,76 @@
[Main]
name = Grey Dark
mode = dark
description = A theme with minimal colours
author = Veronica Berglyd Olsen
credit = Veronica Berglyd Olsen
url = https://github.com/vkbo/novelWriter
[Base]
base = #3e3e3e
default = #c8c8c8
faded = #949494
red = #f2777a
orange = #f99139
yellow = #ffcc66
green = #99cc99
cyan = #66cccc
blue = #6699cc
purple = #cc99cc
[Project]
root = blue
folder = yellow
file = default
title = green
chapter = red
scene = blue
note = yellow
active = green
inactive = red
disabled = faded
[Palette]
window = #363636
windowtext = default
base = base
alternatebase = #4e4e4e
text = default
tooltipbase = #ffffc0
tooltiptext = #15150d
button = base
buttontext = default
brighttext = base
highlight = #2c98f7
highlightedtext = #ffffff
link = blue
linkvisited = blue
accent = #2c98f7
[GUI]
helptext = default:D120
fadedtext = faded
errortext = red
[Syntax]
background = #363636
text = default
line = default:32
link = default
headertext = default:L115
headertag = default:D125
emphasis = default
dialog = default
altdialog = default
note = default
hidden = faded
shortcode = default:L115
keyword = default:L115
tag = default
value = default
optional = default:L115
spellcheckline = red
errorline = green
replacetag = default:L115
modifier = default:L115
texthighlight = #ffffff40
+76
View File
@@ -0,0 +1,76 @@
[Main]
name = Grey Light
mode = light
description = A theme with minimal colours
author = Veronica Berglyd Olsen
credit = Veronica Berglyd Olsen
url = https://github.com/vkbo/novelWriter
[Base]
base = #ffffff
default = #282828
faded = #6c6c6c
red = #a62a2d
orange = #b36829
yellow = #a68542
green = #296629
cyan = #269999
blue = #3a70a6
purple = #b35ab3
[Project]
root = blue
folder = yellow
file = default
title = green
chapter = red
scene = blue
note = yellow
active = green
inactive = red
disabled = faded
[Palette]
window = #efefef
windowtext = default
base = base
alternatebase = #e0e0e0
text = default
tooltipbase = #ffffc0
tooltiptext = #15150d
button = #efefef
buttontext = default
brighttext = base
highlight = #3087c6
highlightedtext = base
link = blue
linkvisited = blue
accent = #3087c6
[GUI]
helptext = default:L225
fadedtext = faded
errortext = red
[Syntax]
background = base
text = default
line = default:32
link = default
headertext = default:D200
headertag = default:L400
emphasis = default
dialog = default
altdialog = default
note = default
hidden = faded
shortcode = default:D200
keyword = default:D200
tag = default
value = default
optional = default:D200
spellcheckline = red
errorline = green
replacetag = default:D200
modifier = default:D200
texthighlight = #00000040
+76
View File
@@ -0,0 +1,76 @@
[Main]
name = LCARS
mode = dark
description = The Next Generation of Star Trek colours
author = Veronica Berglyd Olsen
credit = Star Trek TNG
url = https://github.com/vkbo/novelWriter
[Base]
base = #101010
default = #c7c7c7
faded = #757575
red = #e24d5f
orange = #ff9902
yellow = #ffdc59
green = #54e985
cyan = #7ffff2
blue = #009cff
purple = #8aa5ff
[Project]
root = orange
folder = yellow
file = default
title = green
chapter = red
scene = blue
note = yellow
active = blue
inactive = red
disabled = faded
[Palette]
window = base:L180
windowtext = default
base = base
alternatebase = base:L300
text = default
tooltipbase = yellow:L140
tooltiptext = base
button = base:L140
buttontext = default
brighttext = base
highlight = purple
highlightedtext = base
link = purple
linkvisited = purple
accent = orange
[GUI]
helptext = blue
fadedtext = faded
errortext = red
[Syntax]
background = base
text = default
line = yellow:48
link = purple
headertext = orange
headertag = red
emphasis = orange
dialog = yellow
altdialog = green
note = purple
hidden = faded
shortcode = purple
keyword = red
tag = orange
value = orange
optional = purple
spellcheckline = red
errorline = red
replacetag = purple
modifier = red
texthighlight = orange:96
+96
View File
@@ -0,0 +1,96 @@
[Main]
name = Light Owl
mode = light
description = A theme for the night owls out there, but for daylight
author = Veronica Berglyd Olsen
credit = Sarah Drasner
url = https://github.com/sdras/night-owl-vscode-theme
##
# Theme: Light Owl
# Source: https://github.com/sdras/night-owl-vscode-theme
# Credit: Sarah Drasner
##
# Colours:
# Background = #fbfbfb
# Current Line = #eaeaea
# Selection = #e0e0e0
# Foreground = #403f52
# Comment = #989fb1
# Red = #de3d3a
# Orange = #e0af05
# Yellow = #daaa01
# Green = #08916a
# Aqua = #2aa298
# Blue = #288ed7
# Purple = #964ac1
##
[Base]
base = #fbfbfb
default = #403f52
faded = #989fb1
red = #de3d3a
orange = #e0af05
yellow = #daaa01
green = #08916a
cyan = #2aa298
blue = #288ed7
purple = #964ac1
[Project]
root = blue
folder = yellow
file = default
title = green
chapter = red
scene = blue
note = yellow
active = green
inactive = red
disabled = faded
[Palette]
window = #eaeaea
windowtext = default
base = base
alternatebase = #e0e0e0
text = default
tooltipbase = #ffffc0
tooltiptext = #15150d
button = base
buttontext = default
brighttext = base
highlight = #3087c6
highlightedtext = #ffffff
link = blue
linkvisited = blue
accent = #3087c6
[GUI]
helptext = blue
fadedtext = faded
errortext = red
[Syntax]
background = base
text = default
line = default:32
link = blue
headertext = blue
headertag = blue:160
emphasis = orange
dialog = green
altdialog = yellow
note = yellow:D175
hidden = faded
shortcode = blue
keyword = red
tag = purple
value = orange
optional = blue
spellcheckline = red
errorline = green
replacetag = cyan
modifier = green
texthighlight = yellow:96
+96
View File
@@ -0,0 +1,96 @@
[Main]
name = Night Owl
mode = dark
description = A theme for the night owls out there
author = Veronica Berglyd Olsen
credit = Sarah Drasner
url = https://github.com/sdras/night-owl-vscode-theme
##
# Theme: Night Owl
# Source: https://github.com/sdras/night-owl-vscode-theme
# Credit: Sarah Drasner
##
# Colours:
# Background = #011627
# Current Line = #434652
# Selection = #32374d
# Foreground = #d6deeb
# Comment = #637777
# Red = #f78c6c
# Orange = #ecc48d
# Yellow = #ffeb95
# Green = #addb67
# Aqua = #7fdbca
# Blue = #82aaff
# Purple = #c792ea
##
[Base]
base = #011627
default = #d6deeb
faded = #637777
red = #f78c6c
orange = #ecc48d
yellow = #ffeb95
green = #addb67
cyan = #7fdbca
blue = #82aaff
purple = #c792ea
[Project]
root = blue
folder = yellow
file = default
title = green
chapter = red
scene = blue
note = yellow
active = green
inactive = red
disabled = faded
[Palette]
window = base:L140
windowtext = default
base = base
alternatebase = #32374d
text = default
tooltipbase = #ffffc0
tooltiptext = #15150d
button = base
buttontext = default
brighttext = base
highlight = #2c98f7
highlightedtext = #ffffff
link = blue
linkvisited = blue
accent = #2c98f7
[GUI]
helptext = blue
fadedtext = faded
errortext = red
[Syntax]
background = base
text = default
line = default:48
link = blue
headertext = blue
headertag = blue:160
emphasis = orange
dialog = green
altdialog = yellow
note = yellow:L115
hidden = faded
shortcode = blue
keyword = red
tag = purple
value = orange
optional = blue
spellcheckline = red
errorline = green
replacetag = cyan
modifier = green
texthighlight = yellow:96
+108
View File
@@ -0,0 +1,108 @@
[Main]
name = Noctis
mode = dark
description = A dark theme with a well balanced blend of warm and cold medium contrast colors
author = Veronica Berglyd Olsen
credit = Liviu Schera, and contributors
url = https://github.com/liviuschera/noctis
##
# Noctis Lux : themes/noctis.json
##
# colors
# selection.background : #107684 -> Palette.highlight
# sideBar.background : #041d20 -> Palette.window
# editor.background : #052529 -> Base.base
# editor.foreground : #b2cacd -> Base.default
# editor.selectionBackground : #169fb144 -> Syntax.line, Syntax.texthighlight
# textLink.foreground : #40d4e7 -> Palette.link, Syntax.link
# input.background : #052529 -> Palette.button
# input.foreground : #cdd3de -> Palette.buttontext
# notifications.foreground : #cdd3de -> Palette.tooltiptext
# notifications.background : #073940 -> Palette.tooltipbase
##
# tokenColors
# COMMENT : #5b858b -> Base.faded
# TEXT : #b2cacd -> Syntax.text, Syntax.optional
# KEYWORD : #df769b -> Base.red
# VARIABLE : #e4b781 -> Palette.accent
# ANNOTATION : #d67e5c -> Syntax.note, Syntax.emphasis
# CONSTANT : #d5971a -> Base.yellow
# TAG : #e66533 -> Base.orange
# STRING : #49e9a6 -> Syntax.headertext, Palette.accent
# STRINGINTERPOLATED : #16b673 -> Base.green
# NUMBER : #7060eb -> Base.purple
# FUNCTION : #16a3b6 -> Base.cyan
# SUPPORT : #49d6e9 -> Syntax.replacetag
# MISC : #49ace9 -> Base.blue
# INVALID : #e3541c -> GUI.errortext
##
[Base]
base = #052529
default = #b2cacd
faded = #5b858b
red = #df769b
orange = #e66533
yellow = #d5971a
green = #16b673
cyan = #16a3b6
blue = #49ace9
purple = #7060eb
[Project]
root = blue
folder = yellow
file = default
title = green
chapter = red
scene = blue
note = yellow
active = green
inactive = red
disabled = faded
[Palette]
window = #041d20
windowtext = default
base = base
alternatebase = base:L175
text = default
tooltipbase = #073940
tooltiptext = #cdd3de
button = base
buttontext = #cdd3de
brighttext = base
highlight = #107684
highlightedtext = default
link = #40d4e7
linkvisited = blue
accent = #49e9a6
[GUI]
helptext = #e4b781
fadedtext = faded
errortext = #e3541c
[Syntax]
background = base
text = #b2cacd
line = #169fb132
link = #40d4e7
headertext = #49e9a6
headertag = green:D125
emphasis = #d67e5c
dialog = green
altdialog = blue
note = #d67e5c
hidden = faded
shortcode = red
keyword = yellow
tag = blue
value = red:D125
optional = #b2cacd
spellcheckline = red
errorline = green
replacetag = #49d6e9
modifier = red
texthighlight = #169fb144
+108
View File
@@ -0,0 +1,108 @@
[Main]
name = Noctis Lux
mode = light
description = A light theme with a well balanced blend of warm and cold medium contrast colors
author = Veronica Berglyd Olsen
credit = Liviu Schera, and contributors
url = https://github.com/liviuschera/noctis
##
# Noctis Lux : themes/lux.json
##
# colors
# selection.background : #169fb155 -> Palette.highlight
# sideBar.background : #f9f1e1 -> Palette.window
# editor.background : #fef8ec -> Base.base
# editor.foreground : #005661 -> Base.default
# editor.selectionBackground : #ade2eb77 -> Syntax.line, Syntax.texthighlight
# textLink.foreground : #00c6e0 -> Palette.link, Syntax.link
# input.background : #fef8ec -> Palette.button
# input.foreground : #6a7a7c -> Palette.buttontext
# notifications.foreground : #005661 -> Palette.tooltiptext
# notifications.background : #f2edde -> Palette.tooltipbase
##
# tokenColors
# COMMENT : #8ca6a6 -> Base.faded
# TEXT : #004d57 -> Syntax.text, Syntax.optional
# KEYWORD : #ff5792 -> Base.red
# VARIABLE : #fa8900 -> Palette.accent
# ANNOTATION : #b3694d -> Syntax.note, Syntax.emphasis
# CONSTANT : #a88c00 -> Base.yellow
# TAG : #e64100 -> Base.orange
# STRING : #00b368 -> Syntax.headertext, Palette.accent
# STRINGINTERPOLATED : #009456 -> Base.green
# NUMBER : #5842ff -> Base.purple
# FUNCTION : #0095a8 -> Base.cyan
# SUPPORT : #00bdd6 -> Syntax.replacetag
# MISC : #0094f0 -> Base.blue
# INVALID : #ff530f -> GUI.errortext
##
[Base]
base = #fef8ec
default = #005661
faded = #8ca6a6
red = #ff5792
orange = #e64100
yellow = #a88c00
green = #009456
cyan = #0095a8
blue = #0094f0
purple = #5842ff
[Project]
root = blue
folder = yellow
file = default
title = green
chapter = red
scene = blue
note = yellow
active = green
inactive = red
disabled = faded
[Palette]
window = #f9f1e1
windowtext = default
base = base
alternatebase = base:D115
text = default
tooltipbase = #f2edde
tooltiptext = #005661
button = base
buttontext = #6a7a7c
brighttext = base
highlight = #169fb155
highlightedtext = default
link = #00c6e0
linkvisited = blue
accent = #00b368
[GUI]
helptext = #fa8900
fadedtext = faded
errortext = #ff530f
[Syntax]
background = base
text = #004d57
line = #ade2eb56
link = #00c6e0
headertext = #00b368
headertag = green:D125
emphasis = #b3694d
dialog = green
altdialog = blue
note = #b3694d
hidden = faded
shortcode = red
keyword = yellow
tag = blue
value = red:D125
optional = #004d57
spellcheckline = red
errorline = green
replacetag = #00bdd6
modifier = red
texthighlight = #ade2eb77
@@ -0,0 +1,76 @@
[Main]
name = Primer Light
mode = light
description = Primer
author = Myian
credit = GitHub Inc.
url = https://github.com/primer
[Base]
base = #ffffff
default = #1f2328
faded = #8c959f
red = #cf222e
orange = #e16f24
yellow = #d4a72c
green = #2da44e
cyan = #339d9b
blue = #0969da
purple = #8557db
[Project]
root = #32383f
folder = orange
file = #49bcb7
title = #116329
chapter = #2e934a
scene = #4ac26b
note = yellow
active = #24292f
inactive = faded
disabled = #afb8c1
[Palette]
window = #eaeef2
windowtext = default
base = base
alternatebase = #dde3e8
text = default
tooltipbase = #fff8c5
tooltiptext = default
button = base
buttontext = default
brighttext = base
highlight = green
highlightedtext = base
link = blue
linkvisited = blue
accent = green
[GUI]
helptext = #424a53
fadedtext = #8c959f
errortext = red
[Syntax]
background = base
text = #424a53
line = #dde3e8
link = blue
headertext = default
headertag = #8c959f
emphasis = #2e934a
dialog = #000000
altdialog = cyan
note = #bf8700
hidden = #8c959f
shortcode = #e85aad
keyword = blue
tag = #218bff
value = orange
optional = #54aeff
spellcheckline = #fa4549
errorline = cyan
replacetag = purple
modifier = #bc4c00
texthighlight = #a7fbbe
@@ -0,0 +1,76 @@
[Main]
name = Primer Night
mode = dark
description = Primer
author = Myian
credit = GitHub Inc.
url = https://github.com/primer
[Base]
base = #0d1117
default = #f0f6fc
faded = #6e7681
red = #ff7b72
orange = #ffa657
yellow = #f8e3a1
green = #56d364
cyan = #57CCC5
blue = #79c0ff
purple = #c398ff
[Project]
root = default
folder = orange
file = #8adfd7
title = #238636
chapter = #56d364
scene = #aff5b4
note = yellow
active = default
inactive = faded
disabled = #484f58
[Palette]
window = #161b22
windowtext = default
base = base
alternatebase = #30363d
text = default
tooltipbase = yellow
tooltiptext = base
button = #21262d
buttontext = default
brighttext = base
highlight = #207c59
highlightedtext = default
link = blue
linkvisited = blue
accent = #207c59
[GUI]
helptext = #8b949e
fadedtext = #6e7681
errortext = #f85149
[Syntax]
background = base
text = #b1bac4
line = #30363d
link = blue
headertext = default
headertag = #b1bac4
emphasis = #97ee9e
dialog = #ffffff
altdialog = #8adfd7
note = #f8e3a1
hidden = faded
shortcode = #ff9bce
keyword = #4493f8
tag = blue
value = #ffc680
optional = #a5d6ff
spellcheckline = #f85149
errorline = cyan
replacetag = purple
modifier = orange
texthighlight = #104C35
+81 -40
View File
@@ -1,48 +1,89 @@
[Main]
name = Snazzy Light
author = Veronica Berglyd Olsen (adaptation)
credit = Florian Reuschel (color theme)
url = https://github.com/loilo/vscode-snazzy-light
license = MIT License
licenseurl = https://github.com/loilo/vscode-snazzy-light/blob/master/LICENSE
name = Snazzy Light
mode = light
description = A vivid light color theme
author = Veronica Berglyd Olsen
credit = Florian Reuschel, Sindre Sorhus
url = https://github.com/loilo/vscode-snazzy-light
[Icons]
default = 86, 88, 105
faded = 84, 85, 84
red = 255, 92, 87
orange = 245, 185, 0
yellow = 207, 156, 0
green = 45, 174, 88
aqua = 19, 187, 183
blue = 9, 161, 237
purple = 247, 103, 187
##
# Colours:
# Background = fafbfc : 250, 251, 252
# Foreground = 565869 : 86, 88, 105
# Comment = 9194a2 : 145, 148, 162
# Yellow = cf9c00 : 207, 156, 0
# Red = ff5c57 : 255, 92, 87
# Pink = f767bb : 247, 103, 187
# Blue = 09a1ed : 9, 161, 237
# Cyan = 13bbb7 : 19, 187, 183
# Green = 2dae58 : 45, 174, 88
##
[Base]
base = #fafbfc
default = #565869
faded = #545554
red = #ff5c57
orange = #f5b900
yellow = #cf9c00
green = #2dae58
cyan = #13bbb7
blue = #09a1ed
purple = #f767bb
[Project]
root = 9, 161, 237
folder = 207, 156, 0
file = 84, 85, 84
title = 45, 174, 88
chapter = 255, 92, 87
scene = 9, 161, 237
note = 207, 156, 0
root = blue
folder = yellow
file = faded
title = green
chapter = red
scene = blue
note = yellow
active = green
inactive = red
disabled = faded
[Palette]
window = 243, 244, 245
windowtext = 86, 88, 105
base = 250, 251, 252
alternatebase = 234, 234, 235
text = 86, 88, 105
tooltipbase = 245, 233, 194
tooltiptext = 86, 88, 105
button = 250, 251, 252
buttontext = 86, 88, 105
brighttext = 255, 255, 255
highlight = 9, 161, 237
highlightedtext = 255, 255, 255
link = 9, 161, 237
linkvisited = 9, 161, 237
window = #f3f4f5
windowtext = default
base = base
alternatebase = #eaeaeb
text = default
tooltipbase = #f5e9c2
tooltiptext = default
button = base
buttontext = default
brighttext = base
highlight = blue
highlightedtext = #ffffff
link = blue
linkvisited = blue
accent = blue
[GUI]
helptext = 9, 161, 237
fadedtext = 84, 85, 84
errortext = 255, 92, 87
helptext = blue
fadedtext = faded
errortext = red
[Syntax]
background = base
text = default
line = default:32
link = blue
headertext = green
headertag = green:D125
emphasis = purple
dialog = blue
altdialog = yellow
note = cyan
hidden = purple:D150
shortcode = purple
keyword = blue
tag = green
value = yellow
optional = yellow
spellcheckline = red
errorline = green
replacetag = cyan
modifier = purple
texthighlight = yellow:96
+87 -40
View File
@@ -1,48 +1,95 @@
[Main]
name = Solarized Dark
author = nullbasis
credit = Ethan Schoonover
url = https://ethanschoonover.com/solarized/
license = MIT
licenseurl = https://github.com/altercation/solarized/blob/master/LICENSE
name = Solarized Dark
mode = dark
description = Precision colors for machines and people
author = nullbasis, Veronica Berglyd Olsen
credit = Ethan Schoonover
url = https://ethanschoonover.com/solarized/
[Icons]
default = 230, 223, 206
faded = 166, 161, 149
red = 220, 50, 47
orange = 203, 75, 22
yellow = 181, 137, 0
green = 133, 153, 0
aqua = 42, 161, 152
blue = 38, 139, 210
purple = 211, 54, 130
##
# base03: #002b36
# base02: #073642
# base01: #586e75
# base00: #657b83
# base0: #839496
# base1: #93a1a1
# base2: #eee8d5
# base3: #fdf6e3
# yellow: #b58900
# orange: #cb4b16
# red: #dc322f
# magenta: #d33682
# violet: #6c71c4
# blue: #268bd2
# cyan: #2aa198
# green: #859900
##
[Base]
base = #002b36
default = #fdf6e3
faded = #eee8d5
red = #dc322f
orange = #cb4b16
yellow = #b58900
green = #859900
cyan = #2aa198
blue = #268bd2
purple = #6c71c4
[Project]
root = 42, 161, 152
folder = 42, 161, 152
file = 230, 223, 206
title = 133, 153, 0
chapter = 220, 50, 47
scene = 38, 139, 210
note = 181, 137, 0
root = cyan
folder = cyan
file = default
title = green
chapter = red
scene = blue
note = yellow
active = green
inactive = red
disabled = faded
[Palette]
window = 0, 43, 54
windowtext = 253, 246, 227
base = 7, 54, 66
alternatebase = 88, 110, 117
text = 253, 246, 227
tooltipbase = 133, 153, 0
tooltiptext = 0, 43, 54
button = 7, 54, 66
buttontext = 253, 246, 227
brighttext = 7, 54, 66
highlight = 42, 161, 152
highlightedtext = 0, 43, 54
link = 38, 139, 210
linkvisited = 38, 139, 210
window = #073642
windowtext = default
base = base
alternatebase = #586e75
text = default
tooltipbase = yellow:L150
tooltiptext = #002b36
button = base
buttontext = default
brighttext = base
highlight = cyan
highlightedtext = default
link = blue
linkvisited = blue
accent = cyan
[GUI]
helptext = 101, 123, 131
fadedtext = 101, 123, 131
errortext = 220, 50, 47
helptext = cyan
fadedtext = faded
errortext = red
[Syntax]
background = base
text = default
line = #073642
link = blue
headertext = blue
headertag = #657b83
emphasis = blue
dialog = cyan
altdialog = red
note = cyan:D125
hidden = default:L200
shortcode = blue
keyword = green
tag = yellow
value = orange
optional = cyan:D125
spellcheckline = red
errorline = red
replacetag = green
modifier = yellow
texthighlight = yellow:128
+87 -40
View File
@@ -1,48 +1,95 @@
[Main]
name = Solarized Light
author = nullbasis
credit = Ethan Schoonover
url = https://ethanschoonover.com/solarized/
license = MIT
licenseurl = https://github.com/altercation/solarized/blob/master/LICENSE
name = Solarized Light
mode = light
description = Precision colors for machines and people
author = nullbasis, Veronica Berglyd Olsen
credit = Ethan Schoonover
url = https://ethanschoonover.com/solarized/
[Icons]
default = 59, 68, 71
faded = 78, 91, 95
red = 220, 50, 47
orange = 203, 75, 22
yellow = 181, 137, 0
green = 133, 153, 0
aqua = 42, 161, 152
blue = 38, 139, 210
purple = 211, 54, 130
##
# base03: #002b36
# base02: #073642
# base01: #586e75
# base00: #657b83
# base0: #839496
# base1: #93a1a1
# base2: #eee8d5
# base3: #fdf6e3
# yellow: #b58900
# orange: #cb4b16
# red: #dc322f
# magenta: #d33682
# violet: #6c71c4
# blue: #268bd2
# cyan: #2aa198
# green: #859900
##
[Base]
base = #fdf6e3
default = #002b36
faded = #073642
red = #dc322f
orange = #cb4b16
yellow = #b58900
green = #859900
cyan = #2aa198
blue = #268bd2
purple = #6c71c4
[Project]
root = 42, 161, 152
folder = 42, 161, 152
file = 59, 68, 71
title = 133, 153, 0
chapter = 220, 50, 47
scene = 38, 139, 210
note = 181, 137, 0
root = cyan
folder = cyan
file = default
title = green
chapter = red
scene = blue
note = yellow
active = green
inactive = red
disabled = faded
[Palette]
window = 238, 232, 213
windowtext = 0, 43, 54
base = 253, 246, 227
alternatebase = 147, 161, 161
text = 0, 43, 54
tooltipbase = 133, 153, 0
tooltiptext = 0, 43, 54
button = 238, 232, 213
buttontext = 0, 43, 54
brighttext = 253, 246, 227
highlight = 42, 161, 152
highlightedtext = 253, 246, 227
link = 38, 139, 210
linkvisited = 38, 139, 210
window = #eee8d5
windowtext = default
base = base
alternatebase = #93a1a1
text = default
tooltipbase = yellow:L150
tooltiptext = #002b36
button = base
buttontext = default
brighttext = base
highlight = cyan
highlightedtext = default
link = blue
linkvisited = blue
accent = cyan
[GUI]
helptext = 78, 91, 95
fadedtext = 78, 91, 95
errortext = 220, 50, 47
helptext = cyan
fadedtext = faded
errortext = red
[Syntax]
background = base
text = default
line = #eee8d5
link = blue
headertext = blue
headertag = #657b83
emphasis = blue
dialog = cyan
altdialog = red
note = cyan:D125
hidden = default:L200
shortcode = blue
keyword = green
tag = yellow
value = orange
optional = cyan:D125
spellcheckline = red
errorline = red
replacetag = green
modifier = yellow
texthighlight = yellow:128
+90
View File
@@ -0,0 +1,90 @@
[Main]
name = Tango Dark
mode = dark
description = Colors for everyone
author = Veronica Berglyd Olsen
credit = Tango Desktop Project
url = https://en.wikipedia.org/wiki/Tango_Desktop_Project
##
# Colours:
# Background = #30302f
# Foreground = #eeeeec
# Comment = #8f8f8d
# Red = #ef2929
# Orange = #c4a000
# Yellow = #fce94f
# Green = #8ae234
# Cyan = #34e2e2
# Blue = #3465a4
# Purple = #ad7fa8
##
[Base]
base = #2e3436
default = #eeeeec
faded = #babdb6
red = #ef2929
orange = #fcaf3e
yellow = #fce94f
green = #8ae234
cyan = #34e2e2
blue = #729fcf
purple = #ad7fa8
[Project]
root = blue
folder = yellow
file = default
title = green
chapter = red
scene = blue
note = yellow
active = green
inactive = red
disabled = faded
[Palette]
window = base:L140
windowtext = default
base = base
alternatebase = #454e51
text = default
tooltipbase = #ffffc0
tooltiptext = #15150d
button = #555753
buttontext = default
brighttext = #555753
highlight = blue
highlightedtext = #ffffff
link = blue
linkvisited = blue
accent = blue
[GUI]
helptext = orange
fadedtext = faded
errortext = red
[Syntax]
background = base
text = default
line = default:32
link = blue
headertext = blue
headertag = blue:160
emphasis = orange
dialog = green
altdialog = yellow
note = yellow:L115
hidden = faded
shortcode = blue
keyword = red
tag = purple
value = red
optional = blue
spellcheckline = red
errorline = green
replacetag = cyan
modifier = blue
texthighlight = yellow:96
@@ -0,0 +1,90 @@
[Main]
name = Tango Light
mode = light
description = Colors for everyone
author = Veronica Berglyd Olsen
credit = Tango Desktop Project
url = https://en.wikipedia.org/wiki/Tango_Desktop_Project
##
# Colours:
# Background = #eeeeec
# Foreground = #2e3436
# Comment = #8f8f8d
# Red = #a40000
# Orange = #c4a000
# Yellow = #fce94f
# Green = #8ae234
# Cyan = #34e2e2
# Blue = #3465a4
# Purple = #ad7fa8
##
[Base]
base = #eeeeec
default = #2e3436
faded = #888a85
red = #a40000
orange = #ce5c00
yellow = #c4a000
green = #4e9a06
cyan = #069a9a
blue = #204a87
purple = #5c3566
[Project]
root = blue
folder = yellow
file = default
title = green
chapter = red
scene = blue
note = yellow
active = green
inactive = red
disabled = faded
[Palette]
window = base:D110
windowtext = default
base = base
alternatebase = #e0e0e0
text = default
tooltipbase = #ffffc0
tooltiptext = #15150d
button = #d3d7cf
buttontext = default
brighttext = #d3d7cf
highlight = blue:L200
highlightedtext = #ffffff
link = blue
linkvisited = blue
accent = blue:L150
[GUI]
helptext = orange
fadedtext = faded
errortext = red
[Syntax]
background = base
text = default
line = default:32
link = blue
headertext = blue
headertag = blue:160
emphasis = orange
dialog = green
altdialog = yellow
note = yellow:D125
hidden = faded
shortcode = blue
keyword = red
tag = purple
value = red
optional = blue
spellcheckline = red
errorline = green
replacetag = cyan
modifier = blue
texthighlight = yellow:96
+96
View File
@@ -0,0 +1,96 @@
[Main]
name = Tomorrow
mode = light
description = A colourful theme with muted colours and sensible highlighting
author = Veronica Berglyd Olsen
credit = Chris Kempson
url = https://github.com/chriskempson/tomorrow-theme
##
# Theme: Tomorrow
# Source: https://github.com/chriskempson/tomorrow-theme
# Credit: Chris Kempson
##
# Colours:
# Background = #ffffff
# Current Line = #efefef
# Selection = #d6d6d6
# Foreground = #4d4d4c
# Comment = #8e908c
# Red = #c82829
# Orange = #f5871f
# Yellow = #eab700
# Green = #718c00
# Aqua = #3e999f
# Blue = #4271ae
# Purple = #8959a8
##
[Base]
base = #ffffff
default = #484848
faded = #6c6c6c
red = #c82829
orange = #f5871f
yellow = #eab700
green = #718c00
cyan = #3e999f
blue = #4271ae
purple = #8959a8
[Project]
root = blue
folder = yellow
file = default
title = green
chapter = red
scene = blue
note = yellow
active = green
inactive = red
disabled = faded
[Palette]
window = #efefef
windowtext = #000000
base = base
alternatebase = #d6d6d6
text = #000000
tooltipbase = #ffffc0
tooltiptext = #15150d
button = #efefef
buttontext = #000000
brighttext = #efefef
highlight = blue
highlightedtext = base
link = blue
linkvisited = blue
accent = blue
[GUI]
helptext = #5c5c5c
fadedtext = faded
errortext = red
[Syntax]
background = base
text = #4d4d4c
line = default:32
link = blue
headertext = blue
headertag = blue:L135
emphasis = orange
dialog = green
altdialog = yellow
note = #b38c00
hidden = faded
shortcode = blue
keyword = red
tag = purple
value = yellow
optional = blue
spellcheckline = red
errorline = green
replacetag = cyan
modifier = orange
texthighlight = yellow:96
@@ -0,0 +1,96 @@
[Main]
name = Tomorrow Night
mode = dark
description = A colourful theme with muted colours and sensible highlighting
author = Veronica Berglyd Olsen
credit = Chris Kempson
url = https://github.com/chriskempson/tomorrow-theme
##
# Theme: Tomorrow Night
# Source: https://github.com/chriskempson/tomorrow-theme
# Credit: Chris Kempson
##
# Colours:
# Background = #1d1f21
# Current Line = #282a2e
# Selection = #373b41
# Foreground = #c5c8c6
# Comment = #969896
# Red = #cc6666
# Orange = #de935f
# Yellow = #f0c674
# Green = #b5bd68
# Aqua = #8abeb7
# Blue = #81a2be
# Purple = #b294bb
##
[Base]
base = #1d1f21
default = #c5c8c6
faded = #969896
red = #cc6666
orange = #de935f
yellow = #f0c674
green = #b5bd68
cyan = #8abeb7
blue = #81a2be
purple = #b294bb
[Project]
root = blue
folder = yellow
file = default
title = green
chapter = red
scene = blue
note = yellow
active = green
inactive = red
disabled = faded
[Palette]
window = #282a2e
windowtext = default
base = base
alternatebase = #373b41
text = default
tooltipbase = #ffffc0
tooltiptext = #15150d
button = base
buttontext = default
brighttext = base
highlight = #2c98f7
highlightedtext = #ffffff
link = blue
linkvisited = blue
accent = #2c98f7
[GUI]
helptext = #a4a4a4
fadedtext = #949494
errortext = red
[Syntax]
background = base
text = default
line = default:48
link = blue
headertext = blue
headertag = blue:D150
emphasis = orange
dialog = green
altdialog = yellow
note = #f0dbb2
hidden = faded
shortcode = blue
keyword = red
tag = purple
value = yellow
optional = blue
spellcheckline = red
errorline = green
replacetag = cyan
modifier = orange
texthighlight = yellow:96
@@ -0,0 +1,96 @@
[Main]
name = Tomorrow Night Blue
mode = dark
description = A colourful theme with muted colours and sensible highlighting
author = Veronica Berglyd Olsen
credit = Chris Kempson
url = https://github.com/chriskempson/tomorrow-theme
##
# Theme: Tomorrow Night Blue
# Source: https://github.com/chriskempson/tomorrow-theme
# Credit: Chris Kempson
##
# Colours:
# Background = #002451
# Current Line = #00346e
# Selection = #003f8e
# Foreground = #ffffff
# Comment = #7285b7
# Red = #ff9da4
# Orange = #ffc58f
# Yellow = #ffeead
# Green = #d1f1a9
# Aqua = #99ffff
# Blue = #bbdaff
# Purple = #ebbbff
##
[Base]
base = #002451
default = #ffffff
faded = #7285b7
red = #ff9da4
orange = #ffc58f
yellow = #ffeead
green = #d1f1a9
cyan = #99ffff
blue = #bbdaff
purple = #ebbbff
[Project]
root = blue
folder = yellow
file = default
title = green
chapter = red
scene = blue
note = yellow
active = green
inactive = red
disabled = faded
[Palette]
window = base:L125
windowtext = default
base = base
alternatebase = #003f8e
text = default
tooltipbase = #ffffc0
tooltiptext = #15150d
button = base
buttontext = default
brighttext = base
highlight = #2c98f7
highlightedtext = #ffffff
link = blue
linkvisited = blue
accent = #2c98f7
[GUI]
helptext = #a4a4a4
fadedtext = #949494
errortext = red
[Syntax]
background = base
text = default
line = default:32
link = blue
headertext = blue
headertag = blue:D150
emphasis = orange
dialog = green
altdialog = yellow
note = #fff4cc
hidden = faded
shortcode = blue
keyword = red
tag = purple
value = yellow
optional = blue
spellcheckline = red
errorline = green
replacetag = cyan
modifier = orange
texthighlight = yellow:96
@@ -0,0 +1,96 @@
[Main]
name = Tomorrow Night Bright
mode = dark
description = A colourful theme with muted colours and sensible highlighting
author = Veronica Berglyd Olsen
credit = Chris Kempson
url = https://github.com/chriskempson/tomorrow-theme
##
# Theme: Tomorrow Night Bright
# Source: https://github.com/chriskempson/tomorrow-theme
# Credit: Chris Kempson
##
# Colours:
# Background = #000000
# Current Line = #2a2a2a
# Selection = #424242
# Foreground = #eaeaea
# Comment = #969896
# Red = #d54e53
# Orange = #e78c45
# Yellow = #e7c547
# Green = #b9ca4a
# Aqua = #70c0b1
# Blue = #7aa6da
# Purple = #c397d8
##
[Base]
base = #000000
default = #eaeaea
faded = #969896
red = #d54e53
orange = #e78c45
yellow = #e7c547
green = #b9ca4a
cyan = #70c0b1
blue = #7aa6da
purple = #c397d8
[Project]
root = blue
folder = yellow
file = default
title = green
chapter = red
scene = blue
note = yellow
active = green
inactive = red
disabled = faded
[Palette]
window = #181818
windowtext = default
base = base
alternatebase = #424242
text = default
tooltipbase = #ffffc0
tooltiptext = #15150d
button = base
buttontext = default
brighttext = base
highlight = #2c98f7
highlightedtext = #ffffff
link = blue
linkvisited = blue
accent = #2c98f7
[GUI]
helptext = #a4a4a4
fadedtext = #949494
errortext = red
[Syntax]
background = base
text = default
line = default:48
link = blue
headertext = blue
headertag = blue:D150
emphasis = orange
dialog = green
altdialog = yellow
note = #e7d696
hidden = faded
shortcode = blue
keyword = red
tag = purple
value = yellow
optional = blue
spellcheckline = red
errorline = green
replacetag = cyan
modifier = orange
texthighlight = yellow:96
@@ -0,0 +1,96 @@
[Main]
name = Tomorrow Night Eighties
mode = dark
description = A colourful theme with muted colours and sensible highlighting
author = Veronica Berglyd Olsen
credit = Chris Kempson
url = https://github.com/chriskempson/tomorrow-theme
##
# Theme: Tomorrow Night Eighties
# Source: https://github.com/chriskempson/tomorrow-theme
# Credit: Chris Kempson
##
# Colours:
# Background = #2d2d2d
# Current Line = #393939
# Selection = #515151
# Foreground = #cccccc
# Comment = #999999
# Red = #f2777a
# Orange = #f99157
# Yellow = #ffcc66
# Green = #99cc99
# Aqua = #66cccc
# Blue = #6699cc
# Purple = #cc99cc
##
[Base]
base = #2d2d2d
default = #cccccc
faded = #999999
red = #f2777a
orange = #f99157
yellow = #ffcc66
green = #99cc99
cyan = #66cccc
blue = #6699cc
purple = #cc99cc
[Project]
root = blue
folder = yellow
file = default
title = green
chapter = red
scene = blue
note = yellow
active = green
inactive = red
disabled = faded
[Palette]
window = #393939
windowtext = default
base = base
alternatebase = #515151
text = default
tooltipbase = #ffffc0
tooltiptext = #15150d
button = base
buttontext = default
brighttext = base
highlight = #2c98f7
highlightedtext = #ffffff
link = blue
linkvisited = blue
accent = #2c98f7
[GUI]
helptext = #a4a4a4
fadedtext = #949494
errortext = red
[Syntax]
background = base
text = default
line = default:32
link = blue
headertext = blue
headertag = blue:D150
emphasis = orange
dialog = green
altdialog = yellow
note = #ffe6b3
hidden = faded
shortcode = blue
keyword = red
tag = purple
value = yellow
optional = blue
spellcheckline = red
errorline = green
replacetag = cyan
modifier = orange
texthighlight = yellow:96
+11
View File
@@ -31,6 +31,7 @@ import xml.etree.ElementTree as ET
from configparser import ConfigParser
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import TYPE_CHECKING, Any, Literal, TypeGuard, TypeVar
from urllib.parse import urljoin
@@ -691,6 +692,9 @@ def openExternalPath(path: Path) -> bool:
# Classes
##
_T_Enum = TypeVar("_T_Enum", bound=Enum)
class NWConfigParser(ConfigParser):
"""Common: Adapted Config Parser
@@ -751,3 +755,10 @@ class NWConfigParser(ConfigParser):
for i in range(min(len(data), len(result))):
result[i] = checkInt(data[i].strip(), result[i])
return result
def rdEnum(self, section: str, option: str, default: _T_Enum) -> _T_Enum:
"""Read enum value."""
if self.has_option(section, option):
data = self.get(section, option, fallback="")
return type(default).__members__.get(data.upper(), default)
return default
+45 -35
View File
@@ -45,6 +45,7 @@ from novelwriter.common import (
formatTimeStamp, processDialogSymbols, simplified
)
from novelwriter.constants import nwFiles, nwQuotes, nwUnicode
from novelwriter.enum import nwTheme
from novelwriter.error import formatException, logException
if TYPE_CHECKING:
@@ -55,8 +56,8 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
DEF_GUI = "default"
DEF_SYNTAX = "default_light"
DEF_GUI_DARK = "default_dark"
DEF_GUI_LIGHT = "default_light"
DEF_ICONS = "material_rounded_normal"
DEF_TREECOL = "theme"
@@ -69,22 +70,23 @@ class Config:
"_manuals", "_nwLangPath", "_qLocale", "_qtLangPath", "_qtTrans", "_recentPaths",
"_recentProjects", "_splash", "allowOpenDial", "altDialogClose", "altDialogOpen",
"appHandle", "appName", "askBeforeBackup", "askBeforeExit", "autoSaveDoc", "autoSaveProj",
"autoScroll", "autoScrollPos", "autoSelect", "backupOnClose", "cursorWidth", "dialogLine",
"dialogStyle", "doJustify", "doReplace", "doReplaceDQuote", "doReplaceDash",
"autoScroll", "autoScrollPos", "autoSelect", "backupOnClose", "cursorWidth", "darkTheme",
"dialogLine", "dialogStyle", "doJustify", "doReplace", "doReplaceDQuote", "doReplaceDash",
"doReplaceDots", "doReplaceSQuote", "emphLabels", "fmtApostrophe", "fmtDQuoteClose",
"fmtDQuoteOpen", "fmtPadAfter", "fmtPadBefore", "fmtPadThin", "fmtSQuoteClose",
"fmtSQuoteOpen", "focusWidth", "guiFont", "guiLocale", "guiSyntax", "guiTheme",
"hasEnchant", "hideFocusFooter", "hideHScroll", "hideVScroll", "highlightEmph", "hostName",
"iconColDocs", "iconColTree", "iconTheme", "incNotesWCount", "isDebug", "kernelVer",
"lastNotes", "mainPanePos", "mainWinSize", "memInfo", "narratorBreak", "narratorDialog",
"nativeFont", "osDarwin", "osLinux", "osType", "osUnknown", "osWindows", "outlinePanePos",
"prefsWinSize", "scrollPastEnd", "searchCase", "searchLoop", "searchMatchCap",
"searchNextFile", "searchProjCase", "searchProjRegEx", "searchProjWord", "searchRegEx",
"searchWord", "showEditToolBar", "showFullPath", "showLineEndings", "showMultiSpaces",
"showSessionTime", "showTabsNSpaces", "showViewerPanel", "spellLanguage", "stopWhenIdle",
"tabWidth", "textFont", "textMargin", "textWidth", "useCharCount", "userIdleTime",
"verPyQtString", "verPyQtValue", "verPyString", "verQtString", "verQtValue",
"viewComments", "viewPanePos", "viewSynopsis", "welcomeWinSize",
"fmtSQuoteOpen", "focusWidth", "guiFont", "guiLocale", "hasEnchant", "hideFocusFooter",
"hideHScroll", "hideVScroll", "highlightEmph", "hostName", "iconColDocs", "iconColTree",
"iconTheme", "incNotesWCount", "isDebug", "kernelVer", "lastNotes", "lightTheme",
"lineHighlight", "mainPanePos", "mainWinSize", "memInfo", "narratorBreak",
"narratorDialog", "nativeFont", "osDarwin", "osLinux", "osType", "osUnknown", "osWindows",
"outlinePanePos", "prefsWinSize", "scrollPastEnd", "searchCase", "searchLoop",
"searchMatchCap", "searchNextFile", "searchProjCase", "searchProjRegEx", "searchProjWord",
"searchRegEx", "searchWord", "showEditToolBar", "showFullPath", "showLineEndings",
"showMultiSpaces", "showSessionTime", "showTabsNSpaces", "showViewerPanel",
"spellLanguage", "stopWhenIdle", "tabWidth", "textFont", "textMargin", "textWidth",
"themeMode", "useCharCount", "userIdleTime", "verPyQtString", "verPyQtValue",
"verPyString", "verQtString", "verQtValue", "viewComments", "viewNotes", "viewPanePos",
"viewSynopsis", "welcomeWinSize",
)
LANG_NW = 1
@@ -153,14 +155,15 @@ class Config:
# General GUI Settings
self.guiLocale = self._qLocale.name()
self.guiTheme = DEF_GUI # GUI theme
self.guiSyntax = DEF_SYNTAX # Syntax theme
self.guiFont = QFont() # Main GUI font
self.hideVScroll = False # Hide vertical scroll bars on main widgets
self.hideHScroll = False # Hide horizontal scroll bars on main widgets
self.lastNotes = "0x0" # The latest release notes that have been shown
self.nativeFont = True # Use native font dialog
self.useCharCount = False # Use character count as primary count
self.lightTheme = DEF_GUI_LIGHT # Light GUI theme
self.darkTheme = DEF_GUI_DARK # Dark GUI theme
self.themeMode = nwTheme.AUTO # Colour theme mode
self.guiFont = QFont() # Main GUI font
self.hideVScroll = False # Hide vertical scroll bars on main widgets
self.hideHScroll = False # Hide horizontal scroll bars on main widgets
self.lastNotes = "0x0" # The latest release notes that have been shown
self.nativeFont = True # Use native font dialog
self.useCharCount = False # Use character count as primary count
# Icons
self.iconTheme = DEF_ICONS # Icons theme
@@ -178,8 +181,8 @@ class Config:
# Project Settings
self.autoSaveProj = 60 # Interval for auto-saving project, in seconds
self.autoSaveDoc = 30 # Interval for auto-saving document, in seconds
self.emphLabels = True # Add emphasis to H1 and H2 item labels
self.backupOnClose = False # Flag for running automatic backups
self.emphLabels = False # Add emphasis to H1 and H2 item labels
self.backupOnClose = True # Flag for running automatic backups
self.askBeforeBackup = True # Flag for asking before running automatic backup
self.askBeforeExit = True # Flag for asking before exiting the app
@@ -189,6 +192,7 @@ class Config:
self.textMargin = 40 # Editor/viewer text margin
self.tabWidth = 40 # Editor tabulator width
self.cursorWidth = 1 # Editor cursor width
self.lineHighlight = False # Highlight current line in editor
self.focusWidth = 800 # Focus Mode text width
self.hideFocusFooter = False # Hide document footer in Focus Mode
@@ -206,7 +210,7 @@ class Config:
self.doReplaceDash = True # Replace multiple hyphens with dashes
self.doReplaceDots = True # Replace three dots with ellipsis
self.autoScroll = False # Typewriter-like scrolling
self.autoScroll = True # Typewriter-like scrolling
self.autoScrollPos = 30 # Start point for typewriter-like scrolling
self.scrollPastEnd = True # Scroll past end of document, and centre cursor
@@ -245,6 +249,7 @@ class Config:
self.showSessionTime = True # Show the session time in the status bar
self.viewComments = True # Comments are shown in the viewer
self.viewSynopsis = True # Synopsis is shown in the viewer
self.viewNotes = True # Notes are shown in the viewer
# Search Box States
self.searchCase = False
@@ -403,7 +408,7 @@ class Config:
else:
font = QFontDatabase.systemFont(QFontDatabase.SystemFont.GeneralFont)
self.guiFont = fontMatcher(font)
logger.debug("GUI font set to: %s", describeFont(font))
logger.debug("Main font set to: %s", describeFont(font))
QApplication.setFont(self.guiFont)
return
@@ -551,11 +556,10 @@ class Config:
self._confPath.mkdir(exist_ok=True)
self._dataPath.mkdir(exist_ok=True)
# Also create the syntax, themes and icons folders if possible
# Also create the themes and icons folders if possible
if self._dataPath.is_dir():
(self._dataPath / "cache").mkdir(exist_ok=True)
(self._dataPath / "icons").mkdir(exist_ok=True)
(self._dataPath / "syntax").mkdir(exist_ok=True)
(self._dataPath / "themes").mkdir(exist_ok=True)
self._recentPaths.loadCache()
@@ -626,8 +630,9 @@ class Config:
# Main
sec = "Main"
self.setGuiFont(conf.rdStr(sec, "font", ""))
self.guiTheme = conf.rdStr(sec, "theme", self.guiTheme)
self.guiSyntax = conf.rdStr(sec, "syntax", self.guiSyntax)
self.lightTheme = conf.rdStr(sec, "lighttheme", self.lightTheme)
self.darkTheme = conf.rdStr(sec, "darktheme", self.darkTheme)
self.themeMode = conf.rdEnum(sec, "thememode", self.themeMode)
self.iconTheme = conf.rdStr(sec, "icons", self.iconTheme)
self.iconColTree = conf.rdStr(sec, "iconcoltree", self.iconColTree)
self.iconColDocs = conf.rdBool(sec, "iconcoldocs", self.iconColDocs)
@@ -665,6 +670,7 @@ class Config:
self.textMargin = conf.rdInt(sec, "margin", self.textMargin)
self.tabWidth = conf.rdInt(sec, "tabwidth", self.tabWidth)
self.cursorWidth = conf.rdInt(sec, "cursorwidth", self.cursorWidth)
self.lineHighlight = conf.rdBool(sec, "linehighlight", self.lineHighlight)
self.focusWidth = conf.rdInt(sec, "focuswidth", self.focusWidth)
self.hideFocusFooter = conf.rdBool(sec, "hidefocusfooter", self.hideFocusFooter)
self.doJustify = conf.rdBool(sec, "justify", self.doJustify)
@@ -708,6 +714,7 @@ class Config:
self.showSessionTime = conf.rdBool(sec, "showsessiontime", self.showSessionTime)
self.viewComments = conf.rdBool(sec, "viewcomments", self.viewComments)
self.viewSynopsis = conf.rdBool(sec, "viewsynopsis", self.viewSynopsis)
self.viewNotes = conf.rdBool(sec, "viewnotes", self.viewNotes)
self.searchCase = conf.rdBool(sec, "searchcase", self.searchCase)
self.searchWord = conf.rdBool(sec, "searchword", self.searchWord)
self.searchRegEx = conf.rdBool(sec, "searchregex", self.searchRegEx)
@@ -721,7 +728,7 @@ class Config:
# Check Values
# ============
self._prepareFont(self.guiFont, "GUI")
self._prepareFont(self.guiFont, "main")
self._prepareFont(self.textFont, "document")
# If we're using straight quotes, disable auto-replace
@@ -751,8 +758,9 @@ class Config:
conf["Main"] = {
"font": self.guiFont.toString(),
"theme": str(self.guiTheme),
"syntax": str(self.guiSyntax),
"lighttheme": str(self.lightTheme),
"darktheme": str(self.darkTheme),
"thememode": self.themeMode.name,
"icons": str(self.iconTheme),
"iconcoltree": str(self.iconColTree),
"iconcoldocs": str(self.iconColDocs),
@@ -790,6 +798,7 @@ class Config:
"margin": str(self.textMargin),
"tabwidth": str(self.tabWidth),
"cursorwidth": str(self.cursorWidth),
"lineHighlight": str(self.lineHighlight),
"focuswidth": str(self.focusWidth),
"hidefocusfooter": str(self.hideFocusFooter),
"justify": str(self.doJustify),
@@ -833,6 +842,7 @@ class Config:
"showsessiontime": str(self.showSessionTime),
"viewcomments": str(self.viewComments),
"viewsynopsis": str(self.viewSynopsis),
"viewnotes": str(self.viewNotes),
"searchcase": str(self.searchCase),
"searchword": str(self.searchWord),
"searchregex": str(self.searchRegEx),
+15 -3
View File
@@ -28,7 +28,8 @@ from typing import Final
from PyQt6.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
from novelwriter.enum import (
nwBuildFmt, nwComment, nwItemClass, nwItemLayout, nwOutline, nwStatusShape
nwBuildFmt, nwComment, nwItemClass, nwItemLayout, nwOutline, nwStatusShape,
nwTheme
)
@@ -76,6 +77,7 @@ class nwRegEx:
FMT_EI = r"(?<![\w\\])(_)(?![\s_])(.+?)(?<![\s\\])(\1)(?!\w)"
FMT_EB = r"(?<![\w\\])(\*{2})(?![\s\*])(.+?)(?<![\s\\])(\1)(?!\w)"
FMT_ST = r"(?<![\w\\])(~{2})(?![\s~])(.+?)(?<![\s\\])(\1)(?!\w)"
FMT_HL = r"(?<![\w\\])(={2})(?![\s=])(.+?)(?<![\s\\])(\1)(?!\w)"
FMT_SC = r"(?i)(?<!\\)(\[(?:b|/b|i|/i|s|/s|u|/u|m|/m|sup|/sup|sub|/sub|br)\])"
FMT_SV = r"(?i)(?<!\\)(\[(?:footnote|field):)(.+?)(?<!\\)(\])"
@@ -446,17 +448,27 @@ class nwLabels:
"Custom": (-1.0, -1.0),
}
THEME_COLORS: Final[dict[str, str]] = {
"theme": QT_TRANSLATE_NOOP("Constant", "Theme Colours"),
"default": QT_TRANSLATE_NOOP("Constant", "Foreground Colour"),
"base": QT_TRANSLATE_NOOP("Constant", "Background Colour"),
"faded": QT_TRANSLATE_NOOP("Constant", "Faded Colour"),
"red": QT_TRANSLATE_NOOP("Constant", "Red"),
"orange": QT_TRANSLATE_NOOP("Constant", "Orange"),
"yellow": QT_TRANSLATE_NOOP("Constant", "Yellow"),
"green": QT_TRANSLATE_NOOP("Constant", "Green"),
"aqua": QT_TRANSLATE_NOOP("Constant", "Aqua"),
"cyan": QT_TRANSLATE_NOOP("Constant", "Cyan"),
"blue": QT_TRANSLATE_NOOP("Constant", "Blue"),
"purple": QT_TRANSLATE_NOOP("Constant", "Purple"),
}
THEME_MODE_ICON: Final[dict[nwTheme, str]] = {
nwTheme.AUTO: "theme_auto",
nwTheme.LIGHT: "theme_light",
nwTheme.DARK: "theme_dark",
}
THEME_MODE_LABEL: Final[dict[nwTheme, str]] = {
nwTheme.AUTO: QT_TRANSLATE_NOOP("Constant", "System Theme"),
nwTheme.LIGHT: QT_TRANSLATE_NOOP("Constant", "Light Theme"),
nwTheme.DARK: QT_TRANSLATE_NOOP("Constant", "Dark Theme"),
}
class nwHeadFmt:
+1
View File
@@ -607,6 +607,7 @@ class ProjectBuilder:
project.data.setSaveCount(0)
project.data.setAutoCount(0)
project.data.setEditTime(0)
project.index.rebuild()
project.saveProject()
project.closeProject()
return
+4 -4
View File
@@ -363,13 +363,13 @@ class NWItem:
"""
if self.isFileType():
key = "checked" if self._active else "unchecked"
color = "green" if self._active else "red"
color = "active" if self._active else "inactive"
text = trConst(nwLabels.ACTIVE_NAME[key])
icon = SHARED.theme.getIcon(key, color)
else:
key = "noncheckable"
color = "disabled"
text = ""
icon = SHARED.theme.getIcon("noncheckable", "faded")
return text, icon
return text, SHARED.theme.getIcon(key, color)
##
# Checker Methods
+14 -8
View File
@@ -483,14 +483,14 @@ class NWProject:
def setDefaultStatusImport(self) -> None:
"""Set the default status and importance values."""
self._data.itemStatus.add(None, self.tr("New"), (120, 120, 120), "STAR", 0)
self._data.itemStatus.add(None, self.tr("Note"), (205, 171, 143), "TRIANGLE", 0)
self._data.itemStatus.add(None, self.tr("Draft"), (143, 240, 164), "CIRCLE_T", 0)
self._data.itemStatus.add(None, self.tr("Finished"), (249, 240, 107), "STAR", 0)
self._data.itemImport.add(None, self.tr("New"), (120, 120, 120), "SQUARE", 0)
self._data.itemImport.add(None, self.tr("Minor"), (220, 138, 221), "BLOCK_2", 0)
self._data.itemImport.add(None, self.tr("Major"), (220, 138, 221), "BLOCK_3", 0)
self._data.itemImport.add(None, self.tr("Main"), (220, 138, 221), "BLOCK_4", 0)
self._data.itemStatus.add(None, self.tr("New"), "faded", "STAR", 0)
self._data.itemStatus.add(None, self.tr("Note"), "red", "TRIANGLE", 0)
self._data.itemStatus.add(None, self.tr("Draft"), "yellow", "CIRCLE_T", 0)
self._data.itemStatus.add(None, self.tr("Finished"), "green", "STAR", 0)
self._data.itemImport.add(None, self.tr("New"), "purple", "SQUARE", 0)
self._data.itemImport.add(None, self.tr("Minor"), "purple", "BLOCK_2", 0)
self._data.itemImport.add(None, self.tr("Major"), "purple", "BLOCK_3", 0)
self._data.itemImport.add(None, self.tr("Main"), "purple", "BLOCK_4", 0)
return
def setProjectLang(self, language: str | None) -> None:
@@ -547,6 +547,12 @@ class NWProject:
self._tree.refreshAllItems()
return
def updateTheme(self) -> None:
"""Update theme elements."""
self._data.itemStatus.refreshIcons()
self._data.itemImport.refreshIcons()
return
def localLookup(self, word: str | int) -> str:
"""Look up a word or number in the translation map for the
project and return it. The variable is cast to a string before
+10 -5
View File
@@ -46,7 +46,7 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
FILE_VERSION = "1.5" # The current project file format version
FILE_REVISION = "5" # The current project file format revision
FILE_REVISION = "6" # The current project file format revision
HEX_VERSION = 0x0105
NUM_VERSION = {
@@ -111,6 +111,8 @@ class ProjectXMLReader:
nodes. 2.5.
Rev 5: Added novelChars and notesChars attributes to content
node. 2.7 RC 1.
Rev 6: Replaced red, green and blue attributes with a single
color attribute. 2.8 Beta 1.
"""
def __init__(self, path: str | Path) -> None:
@@ -439,12 +441,15 @@ class ProjectXMLReader:
for xEntry in xItem:
if xEntry.tag == "entry":
key = xEntry.attrib.get("key", None)
red = checkInt(xEntry.attrib.get("red", 0), 0)
green = checkInt(xEntry.attrib.get("green", 0), 0)
blue = checkInt(xEntry.attrib.get("blue", 0), 0)
red = checkInt(xEntry.attrib.get("red", 0), 0) # Deprecated in 1.5 R6
green = checkInt(xEntry.attrib.get("green", 0), 0) # Deprecated in 1.5 R6
blue = checkInt(xEntry.attrib.get("blue", 0), 0) # Deprecated in 1.5 R6
color = xEntry.attrib.get("color") # Added in 1.5 R6
count = checkInt(xEntry.attrib.get("count", 0), 0)
shape = xEntry.attrib.get("shape", "")
sObject.add(key, xEntry.text or "", (red, green, blue), shape, count)
if color is None:
color = f"{red}, {green}, {blue}"
sObject.add(key, xEntry.text or "", color, shape, count)
return
def _parseDictKeyText(self, xItem: ET.Element) -> dict:
+23 -12
View File
@@ -35,6 +35,7 @@ from PyQt6.QtGui import QColor, QIcon, QPainter, QPainterPath, QPixmap, QPolygon
from novelwriter import SHARED
from novelwriter.common import simplified
from novelwriter.constants import nwLabels
from novelwriter.enum import nwStatusShape
from novelwriter.types import QtPaintAntiAlias, QtTransparent
@@ -43,12 +44,15 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
CUSTOM_COL = "custom"
@dataclasses.dataclass
class StatusEntry:
name: str
color: QColor
theme: str
shape: nwStatusShape
icon: QIcon
count: int = 0
@@ -62,7 +66,7 @@ class StatusEntry:
return status
NO_ENTRY = StatusEntry("", QColor(0, 0, 0), nwStatusShape.SQUARE, QIcon(), 0)
NO_ENTRY = StatusEntry("", QColor(0, 0, 0), CUSTOM_COL, nwStatusShape.SQUARE, QIcon(), 0)
T_UpdateEntry = list[tuple[str | None, StatusEntry]]
T_StatusKind = Literal["s", "i"]
@@ -97,15 +101,12 @@ class NWStatus:
# Methods
##
def add(self, key: str | None, name: str, color: tuple[int, int, int],
shape: str, count: int) -> str:
def add(self, key: str | None, name: str, color: str, shape: str, count: int) -> str:
"""Add or update a status entry. If the key is invalid, a new
key is generated.
"""
if isinstance(color, tuple) and len(color) == 3:
qColor = QColor(*color)
else:
qColor = QColor(100, 100, 100)
qColor = SHARED.theme.parseColor(color)
theme = color if color in nwLabels.THEME_COLORS else CUSTOM_COL
try:
iShape = nwStatusShape[shape]
@@ -115,7 +116,7 @@ class NWStatus:
key = self._checkKey(key)
name = simplified(name)
icon = self.createIcon(self._height, qColor, iShape)
self._store[key] = StatusEntry(name, qColor, iShape, icon, count)
self._store[key] = StatusEntry(name, qColor, theme, iShape, icon, count)
if self._default is None:
self._default = key
@@ -157,12 +158,14 @@ class NWStatus:
def pack(self) -> Iterable[tuple[str, dict]]:
"""Pack the status entries into a dictionary."""
for key, entry in self._store.items():
if entry.theme == CUSTOM_COL:
color = entry.color.name(QColor.NameFormat.HexRgb)
else:
color = entry.theme
yield (entry.name, {
"key": key,
"count": str(entry.count),
"red": str(entry.color.red()),
"green": str(entry.color.green()),
"blue": str(entry.color.blue()),
"color": color,
"shape": entry.shape.name,
})
return
@@ -179,12 +182,20 @@ class NWStatus:
try:
shape = nwStatusShape[str(data[0])]
color = QColor(str(data[1]))
theme = CUSTOM_COL if data[1].startswith("#") else data[1]
icon = NWStatus.createIcon(self._height, color, shape)
return StatusEntry(simplified(data[2]), color, shape, icon)
return StatusEntry(simplified(data[2]), color, theme, shape, icon)
except Exception:
logger.error("Could not parse entry %s", str(data))
return None
def refreshIcons(self) -> None:
"""Refresh all icons."""
for entry in self._store.values():
entry.color = SHARED.theme.parseColor(entry.theme)
entry.icon = NWStatus.createIcon(self._height, entry.color, entry.shape)
return
@staticmethod
def createIcon(height: int, color: QColor, shape: nwStatusShape) -> QIcon:
"""Generate an icon for a status label."""
+4 -4
View File
@@ -28,13 +28,13 @@ import logging
from PyQt6.QtCore import pyqtSlot
from PyQt6.QtWidgets import (
QAbstractItemView, QComboBox, QDialogButtonBox, QGridLayout, QLabel,
QListWidget, QListWidgetItem, QVBoxLayout, QWidget
QAbstractItemView, QDialogButtonBox, QGridLayout, QLabel, QListWidget,
QListWidgetItem, QVBoxLayout, QWidget
)
from novelwriter import SHARED
from novelwriter.extensions.configlayout import NColorLabel
from novelwriter.extensions.modified import NDialog
from novelwriter.extensions.modified import NComboBox, NDialog
from novelwriter.extensions.switch import NSwitch
from novelwriter.types import QtAccepted, QtDialogCancel, QtDialogOk, QtUserRole
@@ -79,7 +79,7 @@ class GuiDocSplit(NDialog):
self.listBox.setMinimumWidth(400)
self.listBox.setMinimumHeight(180)
self.splitLevel = QComboBox(self)
self.splitLevel = NComboBox(self)
self.splitLevel.addItem(self.tr("Split on Heading Level 1 (Partition)"), 1)
self.splitLevel.addItem(self.tr("Split up to Heading Level 2 (Chapter)"), 2)
self.splitLevel.addItem(self.tr("Split up to Heading Level 3 (Scene)"), 3)
+43 -30
View File
@@ -35,7 +35,7 @@ from PyQt6.QtWidgets import (
from novelwriter import CONFIG, SHARED
from novelwriter.common import compact, describeFont, processDialogSymbols, uniqueCompact
from novelwriter.config import DEF_GUI, DEF_ICONS, DEF_SYNTAX, DEF_TREECOL
from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT, DEF_ICONS, DEF_TREECOL
from novelwriter.constants import nwLabels, nwQuotes, nwUnicode, trConst
from novelwriter.dialogs.quotes import GuiQuoteSelect
from novelwriter.extensions.configlayout import NColorLabel, NScrollableForm
@@ -165,22 +165,34 @@ class GuiPreferences(NDialog):
)
# Colour Theme
self.guiTheme = NComboBox(self)
self.guiTheme.setMinimumWidth(200)
for theme, name in SHARED.theme.listThemes():
self.guiTheme.addItem(name, theme)
self.guiTheme.setCurrentData(CONFIG.guiTheme, DEF_GUI)
self.lightTheme = NComboBox(self)
self.lightTheme.setMinimumWidth(200)
self.darkTheme = NComboBox(self)
self.darkTheme.setMinimumWidth(200)
for key, theme in SHARED.theme.colourThemes.items():
if theme.dark:
self.darkTheme.addItem(theme.name, key)
else:
self.lightTheme.addItem(theme.name, key)
self.lightTheme.setCurrentData(CONFIG.lightTheme, DEF_GUI_LIGHT)
self.darkTheme.setCurrentData(CONFIG.darkTheme, DEF_GUI_DARK)
self.mainForm.addRow(
self.tr("Colour theme"), self.guiTheme,
self.tr("User interface colour theme."), stretch=(3, 2)
self.tr("Light colour theme"), self.lightTheme,
self.tr("You can change theme mode from the sidebar."), stretch=(3, 2)
)
self.mainForm.addRow(
self.tr("Dark colour theme"), self.darkTheme,
self.tr("You can change theme mode from the sidebar."), stretch=(3, 2)
)
# Icon Theme
self.iconTheme = NComboBox(self)
self.iconTheme.setMinimumWidth(200)
for theme, name in SHARED.theme.iconCache.listThemes():
self.iconTheme.addItem(name, theme)
for key, theme in SHARED.theme.iconCache.iconThemes.items():
self.iconTheme.addItem(theme.name, key)
self.iconTheme.setCurrentData(CONFIG.iconTheme, DEF_ICONS)
self.mainForm.addRow(
@@ -242,18 +254,6 @@ class GuiPreferences(NDialog):
self.sidebar.addButton(title, section)
self.mainForm.addGroupLabel(title, section)
# Document Colour Theme
self.guiSyntax = NComboBox(self)
self.guiSyntax.setMinimumWidth(200)
for syntax, name in SHARED.theme.listSyntax():
self.guiSyntax.addItem(name, syntax)
self.guiSyntax.setCurrentData(CONFIG.guiSyntax, DEF_SYNTAX)
self.mainForm.addRow(
self.tr("Document colour theme"), self.guiSyntax,
self.tr("Colour theme for the editor and viewer."), stretch=(3, 2)
)
# Document Font Family
self.textFont = QLineEdit(self)
self.textFont.setReadOnly(True)
@@ -294,6 +294,7 @@ class GuiPreferences(NDialog):
# Tree Icon Colours
self.iconColTree = NComboBox(self)
self.iconColTree.setMinimumWidth(200)
self.iconColTree.addItem(self.tr("Theme Colours"), DEF_TREECOL)
for key, label in nwLabels.THEME_COLORS.items():
self.iconColTree.addItem(trConst(label), key)
self.iconColTree.setCurrentData(CONFIG.iconColTree, DEF_TREECOL)
@@ -543,6 +544,13 @@ class GuiPreferences(NDialog):
unit=self.tr("px")
)
# Highlight Current Line
self.lineHighlight = NSwitch(self)
self.lineHighlight.setChecked(CONFIG.lineHighlight)
self.mainForm.addRow(
self.tr("Highlight current line"), self.lineHighlight
)
# Show Tabs and Spaces
self.showTabsNSpaces = NSwitch(self)
self.showTabsNSpaces.setChecked(CONFIG.showTabsNSpaces)
@@ -966,18 +974,23 @@ class GuiPreferences(NDialog):
# Appearance
guiLocale = self.guiLocale.currentData()
guiTheme = self.guiTheme.currentData()
lightTheme = self.lightTheme.currentData()
darkTheme = self.darkTheme.currentData()
iconTheme = self.iconTheme.currentData()
useCharCount = self.useCharCount.isChecked()
updateTheme |= CONFIG.guiTheme != guiTheme
updateTheme |= CONFIG.lightTheme != lightTheme
updateTheme |= CONFIG.darkTheme != darkTheme
updateTheme |= CONFIG.iconTheme != iconTheme
needsRestart |= CONFIG.guiLocale != guiLocale
needsRestart |= CONFIG.guiFont != self._guiFont
refreshTree |= CONFIG.useCharCount != useCharCount
updateSyntax |= CONFIG.lightTheme != lightTheme
updateSyntax |= CONFIG.darkTheme != darkTheme
CONFIG.guiLocale = guiLocale
CONFIG.guiTheme = guiTheme
CONFIG.lightTheme = lightTheme
CONFIG.darkTheme = darkTheme
CONFIG.iconTheme = iconTheme
CONFIG.hideVScroll = self.hideVScroll.isChecked()
CONFIG.hideHScroll = self.hideHScroll.isChecked()
@@ -986,11 +999,6 @@ class GuiPreferences(NDialog):
CONFIG.setGuiFont(self._guiFont)
# Document Style
guiSyntax = self.guiSyntax.currentData()
updateSyntax |= CONFIG.guiSyntax != guiSyntax
CONFIG.guiSyntax = guiSyntax
CONFIG.showFullPath = self.showFullPath.isChecked()
CONFIG.incNotesWCount = self.incNotesWCount.isChecked()
CONFIG.setTextFont(self._textFont)
@@ -1031,9 +1039,14 @@ class GuiPreferences(NDialog):
CONFIG.tabWidth = self.tabWidth.value()
# Text Editing
lineHighlight = self.lineHighlight.isChecked()
updateSyntax |= CONFIG.lineHighlight != lineHighlight
CONFIG.spellLanguage = self.spellLanguage.currentData()
CONFIG.autoSelect = self.autoSelect.isChecked()
CONFIG.cursorWidth = self.cursorWidth.value()
CONFIG.lineHighlight = lineHighlight
CONFIG.showTabsNSpaces = self.showTabsNSpaces.isChecked()
CONFIG.showLineEndings = self.showLineEndings.isChecked()
+67 -21
View File
@@ -33,14 +33,14 @@ from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt6.QtGui import QCloseEvent, QColor
from PyQt6.QtWidgets import (
QAbstractItemView, QApplication, QColorDialog, QDialogButtonBox,
QFileDialog, QGridLayout, QHBoxLayout, QLineEdit, QMenu, QStackedWidget,
QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
QFileDialog, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu,
QStackedWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, SHARED
from novelwriter.common import formatFileFilter, qtAddAction, qtLambda, simplified
from novelwriter.constants import nwLabels, trConst
from novelwriter.core.status import NWStatus, StatusEntry
from novelwriter.core.status import CUSTOM_COL, NWStatus, StatusEntry
from novelwriter.enum import nwStatusShape
from novelwriter.extensions.configlayout import NColorLabel, NFixedPage, NScrollableForm
from novelwriter.extensions.modified import NComboBox, NDialog, NIconToolButton
@@ -326,6 +326,7 @@ class _StatusPage(NFixedPage):
self._changed = False
self._color = QColor(100, 100, 100)
self._shape = nwStatusShape.SQUARE
self._theme = CUSTOM_COL
self._icons = {}
self._iPx = SHARED.theme.baseIconHeight
@@ -384,11 +385,26 @@ class _StatusPage(NFixedPage):
self.exportButton.clicked.connect(self._exportLabels)
# Edit Form
self.labelText = QLineEdit(self)
self.labelText.setMaxLength(40)
self.labelText.setPlaceholderText(self.tr("Select item to edit"))
self.labelText.setEnabled(False)
self.labelText.textEdited.connect(self._onNameEdit)
self.editName = QLineEdit(self)
self.editName.setMaxLength(40)
self.editName.setPlaceholderText(self.tr("Select item to edit"))
self.editName.setEnabled(False)
self.editName.textEdited.connect(self._onNameEdit)
self.labelName = QLabel(self.tr("Label"), self)
self.labelName.setBuddy(self.editName)
# Icon Colours
self.iconColor = NComboBox(self)
self.iconColor.setMinimumWidth(200)
self.iconColor.setEnabled(False)
self.iconColor.addItem(self.tr("Custom"), CUSTOM_COL)
for key, label in nwLabels.THEME_COLORS.items():
self.iconColor.addItem(trConst(label), key)
self.iconColor.currentIndexChanged.connect(self._onThemeSelect)
self.labelColor = QLabel(self.tr("Colour"), self)
self.labelColor.setBuddy(self.iconColor)
buttonStyle = (
"QToolButton {padding: 0 4px;} "
@@ -425,6 +441,9 @@ class _StatusPage(NFixedPage):
self.shapeButton.setStyleSheet(buttonStyle)
self.shapeButton.setEnabled(False)
self.labelShape = QLabel(self.tr("Shape"), self)
self.labelShape.setBuddy(self.iconColor)
# Assemble
self.listControls = QVBoxLayout()
self.listControls.addWidget(self.addButton)
@@ -435,10 +454,15 @@ class _StatusPage(NFixedPage):
self.listControls.addWidget(self.importButton)
self.listControls.addWidget(self.exportButton)
self.editBox = QHBoxLayout()
self.editBox.addWidget(self.labelText, 1)
self.editBox.addWidget(self.colorButton, 0)
self.editBox.addWidget(self.shapeButton, 0)
self.editBox = QGridLayout()
self.editBox.addWidget(self.labelName, 0, 0)
self.editBox.addWidget(self.editName, 0, 1, 1, 5)
self.editBox.addWidget(self.labelColor, 1, 0)
self.editBox.addWidget(self.iconColor, 1, 1)
self.editBox.addWidget(self.colorButton, 1, 2)
self.editBox.addWidget(self.labelShape, 1, 3)
self.editBox.addWidget(self.shapeButton, 1, 4)
self.editBox.setColumnStretch(5, 1)
self.innerBox = QGridLayout()
self.innerBox.addWidget(self.listBox, 0, 0)
@@ -496,11 +520,20 @@ class _StatusPage(NFixedPage):
self._changed = True
return
@pyqtSlot(int)
def _onThemeSelect(self, index: int) -> None:
"""Update the colour handling on theme selection change."""
self._theme = str(self.iconColor.currentData())
self._setButtonIcons()
self._updateIcon()
return
@pyqtSlot()
def _onColorSelect(self) -> None:
"""Open a dialog to select the status icon colour."""
if (color := QColorDialog.getColor(self._color, self, self.trSelColor)).isValid():
self._color = color
self._theme = CUSTOM_COL
self._setButtonIcons()
self._updateIcon()
return
@@ -511,7 +544,8 @@ class _StatusPage(NFixedPage):
color = QColor(100, 100, 100)
shape = nwStatusShape.SQUARE
icon = NWStatus.createIcon(self._iPx, color, shape)
self._addItem(None, StatusEntry(self.tr("New Item"), color, shape, icon, 0))
theme = str(self.iconColor.currentData())
self._addItem(None, StatusEntry(self.tr("New Item"), color, theme, shape, icon, 0))
self._changed = True
return
@@ -537,22 +571,26 @@ class _StatusPage(NFixedPage):
entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
self._color = entry.color
self._shape = entry.shape
self._theme = entry.theme
self._setButtonIcons()
self.labelText.setText(entry.name)
self.labelText.selectAll()
self.labelText.setFocus()
self.editName.setText(entry.name)
self.editName.selectAll()
self.editName.setFocus()
self.labelText.setEnabled(True)
self.editName.setEnabled(True)
self.iconColor.setEnabled(True)
self.colorButton.setEnabled(True)
self.shapeButton.setEnabled(True)
else:
self._color = QColor(100, 100, 100)
self._shape = nwStatusShape.SQUARE
self._theme = CUSTOM_COL
self._setButtonIcons()
self.labelText.setText("")
self.editName.setText("")
self.labelText.setEnabled(False)
self.editName.setEnabled(False)
self.iconColor.setEnabled(False)
self.colorButton.setEnabled(False)
self.shapeButton.setEnabled(False)
return
@@ -608,10 +646,11 @@ class _StatusPage(NFixedPage):
def _updateIcon(self) -> None:
"""Apply changes made to a status icon."""
if item := self._getSelectedItem():
icon = NWStatus.createIcon(self._iPx, self._color, self._shape)
icon = NWStatus.createIcon(self._iPx, self._pickColor(), self._shape)
entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
entry.color = self._color
entry.shape = self._shape
entry.theme = self._theme
entry.icon = icon
item.setIcon(self.C_LABEL, icon)
self._changed = True
@@ -658,11 +697,18 @@ class _StatusPage(NFixedPage):
def _setButtonIcons(self) -> None:
"""Set the colour of the colour button."""
icon = NWStatus.createIcon(self._iPx, self._color, nwStatusShape.SQUARE)
icon = NWStatus.createIcon(self._iPx, self._pickColor(), nwStatusShape.SQUARE)
self.iconColor.setCurrentData(self._theme, CUSTOM_COL)
self.colorButton.setIcon(icon)
self.shapeButton.setIcon(self._icons[self._shape])
return
def _pickColor(self) -> QColor:
"""Get the correct colour value based on selections."""
if self._theme == CUSTOM_COL:
return self._color
return SHARED.theme.getBaseColor(self._theme)
class _ReplacePage(NFixedPage):
+37 -29
View File
@@ -92,35 +92,36 @@ class nwDocAction(Enum):
MD_ITALIC = 6
MD_BOLD = 7
MD_STRIKE = 8
S_QUOTE = 9
D_QUOTE = 10
SEL_ALL = 11
SEL_PARA = 12
BLOCK_H1 = 13
BLOCK_H2 = 14
BLOCK_H3 = 15
BLOCK_H4 = 16
BLOCK_COM = 17
BLOCK_IGN = 18
BLOCK_TXT = 19
BLOCK_TTL = 20
BLOCK_UNN = 21
BLOCK_HSC = 22
REPL_SNG = 23
REPL_DBL = 24
RM_BREAKS = 25
ALIGN_L = 26
ALIGN_C = 27
ALIGN_R = 28
INDENT_L = 29
INDENT_R = 30
SC_ITALIC = 31
SC_BOLD = 32
SC_STRIKE = 33
SC_ULINE = 34
SC_MARK = 35
SC_SUP = 36
SC_SUB = 37
MD_MARK = 9
S_QUOTE = 10
D_QUOTE = 11
SEL_ALL = 12
SEL_PARA = 13
BLOCK_H1 = 14
BLOCK_H2 = 15
BLOCK_H3 = 16
BLOCK_H4 = 17
BLOCK_COM = 18
BLOCK_IGN = 19
BLOCK_TXT = 20
BLOCK_TTL = 21
BLOCK_UNN = 22
BLOCK_HSC = 23
REPL_SNG = 24
REPL_DBL = 25
RM_BREAKS = 26
ALIGN_L = 27
ALIGN_C = 28
ALIGN_R = 29
INDENT_L = 30
INDENT_R = 31
SC_ITALIC = 32
SC_BOLD = 33
SC_STRIKE = 34
SC_ULINE = 35
SC_MARK = 36
SC_SUP = 37
SC_SUB = 38
class nwDocInsert(Enum):
@@ -156,6 +157,13 @@ class nwFocus(Enum):
OUTLINE = 3
class nwTheme(Enum):
AUTO = 0
LIGHT = 1
DARK = 2
class nwOutline(Enum):
TITLE = 0
+8 -1
View File
@@ -117,12 +117,19 @@ class NTreeView(QTreeView):
class NComboBox(QComboBox):
def __init__(self, parent: QWidget | None = None) -> None:
def __init__(self, parent: QWidget | None = None, maxItems: int = 15) -> None:
super().__init__(parent=parent)
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self.setMaxVisibleItems(maxItems)
# The style sheet disables Fusion style pop-up mode on some platforms
# and allows for scrolling of long lists of items
self.setStyleSheet("QComboBox {combobox-popup: 0;}")
return
def wheelEvent(self, event: QWheelEvent) -> None:
"""Only capture the mouse wheel if the widget has focus."""
if self.hasFocus():
super().wheelEvent(event)
else:
+6 -1
View File
@@ -25,14 +25,19 @@ from __future__ import annotations
import logging
from typing import TYPE_CHECKING
from PyQt6.QtCore import pyqtSignal, pyqtSlot
from PyQt6.QtGui import QPalette
from PyQt6.QtWidgets import QComboBox, QWidget
from PyQt6.QtWidgets import QComboBox
from novelwriter import SHARED
from novelwriter.constants import nwLabels
from novelwriter.enum import nwItemClass
if TYPE_CHECKING:
from PyQt6.QtWidgets import QWidget
logger = logging.getLogger(__name__)
+1 -1
View File
@@ -156,7 +156,7 @@ class _PagedToolButton(QToolButton):
if self.isChecked():
painter.setBrush(palette.highlight())
painter.setOpacity(0.35)
painter.setOpacity(0.8)
painter.drawRoundedRect(0, 0, width, height, 4, 4)
textCol = palette.highlightedText().color()
else:
+5 -2
View File
@@ -33,7 +33,7 @@ from novelwriter.types import QtNoPen, QtPaintAntiAlias, QtSizeFixed
class NSwitch(QAbstractButton):
__slots__ = ("_offset", "_rH", "_rR", "_xH", "_xR", "_xW")
__slots__ = ("_cOff", "_cOn", "_offset", "_rH", "_rR", "_xH", "_xR", "_xW")
def __init__(self, parent: QWidget, height: int = 0) -> None:
super().__init__(parent=parent)
@@ -44,6 +44,9 @@ class NSwitch(QAbstractButton):
self._rH = self._xH - 4
self._rR = self._xR - 2
self._cOn = SHARED.theme.accentCol
self._cOff = self.palette().alternateBase()
self.setCheckable(True)
self.setSizePolicy(QtSizeFixed, QtSizeFixed)
self.setFixedWidth(self._xW)
@@ -97,7 +100,7 @@ class NSwitch(QAbstractButton):
painter.setOpacity(1.0 if self.isEnabled() else 0.5)
painter.setPen(palette.highlight().color() if self.hasFocus() else palette.mid().color())
painter.setBrush(palette.highlight() if self.isChecked() else palette.alternateBase())
painter.setBrush(self._cOn if self.isChecked() else self._cOff)
painter.drawRoundedRect(0, 0, self._xW, self._xH, self._xR, self._xR)
painter.setPen(QtNoPen)
+9 -1
View File
@@ -29,7 +29,15 @@ from enum import Flag, IntEnum
from PyQt6.QtGui import QColor
ESCAPES = {r"\*": "*", r"\~": "~", r"\_": "_", r"\[": "[", r"\]": "]", r"\ ": ""}
ESCAPES = {
r"\*": "*",
r"\~": "~",
r"\=": "=",
r"\_": "_",
r"\[": "[",
r"\]": "]",
r"\ ": "",
}
RX_ESC = re.compile("|".join([re.escape(k) for k in ESCAPES.keys()]), flags=re.DOTALL)
+2 -2
View File
@@ -38,7 +38,7 @@ from novelwriter import __version__
from novelwriter.common import firstFloat, xmlElement, xmlSubElem
from novelwriter.constants import nwHeadFmt, nwStyles
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt, stripEscape
from novelwriter.formats.tokenizer import Tokenizer
from novelwriter.formats.tokenizer import COMMENT_BLOCKS, Tokenizer
from novelwriter.types import QtHexRgb
if TYPE_CHECKING:
@@ -296,7 +296,7 @@ class ToDocX(Tokenizer):
elif tType == BlockTyp.SKIP:
self._processFragments(par, S_NORM, "")
elif tType == BlockTyp.COMMENT:
elif tType in COMMENT_BLOCKS:
self._processFragments(par, S_META, tText, tFormat)
elif tType == BlockTyp.KEYWORD:
+47 -45
View File
@@ -32,7 +32,7 @@ from typing import TYPE_CHECKING
from novelwriter.common import formatTimeStamp
from novelwriter.constants import nwHtmlUnicode, nwStyles
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt, stripEscape
from novelwriter.formats.tokenizer import Tokenizer
from novelwriter.formats.tokenizer import COMMENT_BLOCKS, Tokenizer
from novelwriter.types import FONT_STYLE, FONT_WEIGHTS, QtHexRgb
if TYPE_CHECKING:
@@ -159,34 +159,33 @@ class ToHtml(Tokenizer):
# If we don't have formatting, we can do a plain replace
tText = tText.replace("<", "&lt;").replace(">", "&gt;")
# Styles
# Inline Styles
aStyle = []
if self._cssStyles:
if tStyle & BlockFmt.LEFT:
aStyle.append("text-align: left;")
elif tStyle & BlockFmt.RIGHT:
aStyle.append("text-align: right;")
elif tStyle & BlockFmt.CENTRE:
aStyle.append("text-align: center;")
elif tStyle & BlockFmt.JUSTIFY:
aStyle.append("text-align: justify;")
if tStyle & BlockFmt.LEFT:
aStyle.append("text-align: left;")
elif tStyle & BlockFmt.RIGHT:
aStyle.append("text-align: right;")
elif tStyle & BlockFmt.CENTRE:
aStyle.append("text-align: center;")
elif tStyle & BlockFmt.JUSTIFY:
aStyle.append("text-align: justify;")
if tStyle & BlockFmt.PBB:
aStyle.append("page-break-before: always;")
if tStyle & BlockFmt.PBA:
aStyle.append("page-break-after: always;")
if tStyle & BlockFmt.PBB:
aStyle.append("page-break-before: always;")
if tStyle & BlockFmt.PBA:
aStyle.append("page-break-after: always;")
if tStyle & BlockFmt.Z_BTM:
aStyle.append("margin-bottom: 0;")
if tStyle & BlockFmt.Z_TOP:
aStyle.append("margin-top: 0;")
if tStyle & BlockFmt.Z_BTM:
aStyle.append("margin-bottom: 0;")
if tStyle & BlockFmt.Z_TOP:
aStyle.append("margin-top: 0;")
if tStyle & BlockFmt.IND_L:
aStyle.append(f"margin-left: {self._blockIndent:.2f}em;")
if tStyle & BlockFmt.IND_R:
aStyle.append(f"margin-right: {self._blockIndent:.2f}em;")
if tStyle & BlockFmt.IND_T:
aStyle.append(f"text-indent: {self._firstWidth:.2f}em;")
if tStyle & BlockFmt.IND_L:
aStyle.append(f"margin-left: {self._blockIndent:.2f}em;")
if tStyle & BlockFmt.IND_R:
aStyle.append(f"margin-right: {self._blockIndent:.2f}em;")
if tStyle & BlockFmt.IND_T:
aStyle.append(f"text-indent: {self._firstWidth:.2f}em;")
if aStyle:
stVals = " ".join(aStyle)
@@ -229,7 +228,7 @@ class ToHtml(Tokenizer):
elif tType == BlockTyp.SKIP:
lines.append(f"<p{hStyle}>&nbsp;</p>\n")
elif tType == BlockTyp.COMMENT:
elif tType in COMMENT_BLOCKS:
lines.append(f"<p class='comment'{hStyle}>{self._formatText(tText, tFmt)}</p>\n")
elif tType == BlockTyp.KEYWORD:
@@ -288,26 +287,25 @@ class ToHtml(Tokenizer):
json.dump(data, fObj, indent=2)
else:
html = []
html.append("<!DOCTYPE html>")
html.append("<html>")
html.append("<head>")
html.append(f"<title>{self._project.data.name:s}</title>")
html.append("<meta charset='utf-8'>")
if self._cssStyles:
html.append("<meta name='viewport' content='width=device-width, initial-scale=1'>")
html.append("<style>")
html.extend(self.getStyleSheet())
html.append("</style>")
html.append("</head>")
html.append("<body>")
html.append(("".join(self._pages)).replace("\t", "&#09;").rstrip())
html.append("</body>")
html.append("</html>\n")
with open(path, mode="w", encoding="utf-8") as fObj:
fObj.write((
"<!DOCTYPE html>\n"
"<html>\n"
"<head>\n"
"<meta charset='utf-8'>\n"
"<title>{title:s}</title>\n"
"<style>\n"
"{style:s}\n"
"</style>\n"
"</head>\n"
"<body>\n"
"{body:s}\n"
"</body>\n"
"</html>\n"
).format(
title=self._project.data.name,
style="\n".join(self.getStyleSheet()),
body=("".join(self._pages)).replace("\t", "&#09;").rstrip(),
))
fObj.write("\n".join(html))
logger.info("Wrote file: %s", path)
@@ -410,7 +408,11 @@ class ToHtml(Tokenizer):
# isn't already open, and only closed if it has previously been opened.
tags: list[tuple[int, str]] = []
state = dict.fromkeys(HTML_OPENER, False)
plain = not self._cssStyles
for pos, fmt, data in tFmt:
if plain and fmt in (TextFmt.COL_B, TextFmt.COL_E):
# We ignore colour tags if CSS is off
continue
if m := HTML_OPENER.get(fmt):
if not state.get(fmt, True):
if fmt == TextFmt.COL_B and (color := self._classes.get(data)):
+22 -9
View File
@@ -71,11 +71,23 @@ COMMENT_STYLE = {
nwComment.COMMENT: ComStyle(),
nwComment.STORY: ComStyle("Story Structure", "modifier", "note"),
}
HEADINGS = [
COMMENT_TYPE = {
nwComment.PLAIN: BlockTyp.COMMENT,
nwComment.IGNORE: BlockTyp.COMMENT,
nwComment.SYNOPSIS: BlockTyp.SUMMARY,
nwComment.SHORT: BlockTyp.SUMMARY,
nwComment.NOTE: BlockTyp.NOTE,
nwComment.FOOTNOTE: BlockTyp.COMMENT,
nwComment.COMMENT: BlockTyp.COMMENT,
nwComment.STORY: BlockTyp.NOTE,
}
HEADING_BLOCKS = [
BlockTyp.TITLE, BlockTyp.PART, BlockTyp.HEAD1,
BlockTyp.HEAD2, BlockTyp.HEAD3, BlockTyp.HEAD4,
]
SKIP_INDENT = [*HEADINGS, BlockTyp.SEP, BlockTyp.SKIP]
COMMENT_BLOCKS = (BlockTyp.COMMENT, BlockTyp.SUMMARY, BlockTyp.NOTE)
META_BLOCKS = (BlockTyp.COMMENT, BlockTyp.SUMMARY, BlockTyp.NOTE, BlockTyp.KEYWORD)
SKIP_INDENT = [*HEADING_BLOCKS, BlockTyp.SEP, BlockTyp.SKIP]
B_EMPTY: T_Block = (BlockTyp.EMPTY, "", "", [], BlockFmt.NONE)
@@ -189,6 +201,7 @@ class Tokenizer(ABC):
(REGEX_PATTERNS.markdownItalic, [0, TextFmt.I_B, 0, TextFmt.I_E]),
(REGEX_PATTERNS.markdownBold, [0, TextFmt.B_B, 0, TextFmt.B_E]),
(REGEX_PATTERNS.markdownStrike, [0, TextFmt.D_B, 0, TextFmt.D_E]),
(REGEX_PATTERNS.markdownMark, [0, TextFmt.M_B, 0, TextFmt.M_E]),
]
self._shortCodeFmt = {
@@ -618,7 +631,7 @@ class Tokenizer(ABC):
bStyle = COMMENT_STYLE[cStyle]
tLine, tFmt = self._formatComment(bStyle, cKey, cText)
tBlocks.append((
BlockTyp.COMMENT, "", tLine, tFmt, tStyle
COMMENT_TYPE[cStyle], "", tLine, tFmt, tStyle
))
elif cStyle == nwComment.FOOTNOTE:
@@ -862,12 +875,12 @@ class Tokenizer(ABC):
# We don't need to keep the empty lines after this pass
pass
elif cBlock[0] == BlockTyp.KEYWORD:
# Adjust margins for lines in a list of keyword lines
elif cBlock[0] in (BlockTyp.KEYWORD, BlockTyp.NOTE):
# Adjust margins for lines in repeated meta blocks
aStyle = cBlock[4]
if pBlock[0] == BlockTyp.KEYWORD:
if pBlock[0] == cBlock[0]:
aStyle |= BlockFmt.Z_TOP
if nBlock[0] == BlockTyp.KEYWORD:
if nBlock[0] == cBlock[0]:
aStyle |= BlockFmt.Z_BTM
sBlocks.append((
cBlock[0], cBlock[1], cBlock[2], cBlock[3], aStyle
@@ -991,7 +1004,7 @@ class Tokenizer(ABC):
allWordChars += nPWChars
textWordChars += nPWChars
elif tType in HEADINGS:
elif tType in HEADING_BLOCKS:
titleCount += 1
allWords += nWords
titleWords += nWords
@@ -1005,7 +1018,7 @@ class Tokenizer(ABC):
allChars += nChars
allWordChars += nWChars
elif tType in (BlockTyp.COMMENT, BlockTyp.KEYWORD):
elif tType in META_BLOCKS:
words = tText.split()
allWords += len(words)
allChars += len(tText)
+2 -2
View File
@@ -29,7 +29,7 @@ from typing import TYPE_CHECKING
from novelwriter.constants import nwUnicode
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt
from novelwriter.formats.tokenizer import Tokenizer
from novelwriter.formats.tokenizer import COMMENT_BLOCKS, Tokenizer
if TYPE_CHECKING:
from pathlib import Path
@@ -144,7 +144,7 @@ class ToMarkdown(Tokenizer):
elif tType == BlockTyp.SKIP:
lines.append(f"{cSkip}\n\n")
elif tType == BlockTyp.COMMENT:
elif tType in COMMENT_BLOCKS:
lines.append(f"{self._formatText(tText, tFormat, mTags)}\n\n")
elif tType == BlockTyp.KEYWORD:
+2 -2
View File
@@ -40,7 +40,7 @@ from novelwriter import __version__
from novelwriter.common import xmlElement, xmlIndent, xmlSubElem
from novelwriter.constants import nwHeadFmt, nwStyles
from novelwriter.formats.shared import BlockFmt, BlockTyp, TextFmt, stripEscape
from novelwriter.formats.tokenizer import Tokenizer
from novelwriter.formats.tokenizer import COMMENT_BLOCKS, Tokenizer
from novelwriter.types import FONT_STYLE, QtHexRgb
if TYPE_CHECKING:
@@ -389,7 +389,7 @@ class ToOdt(Tokenizer):
elif tType == BlockTyp.SKIP:
self._addTextPar(xText, S_TEXT, oStyle, "")
elif tType == BlockTyp.COMMENT:
elif tType in COMMENT_BLOCKS:
self._addTextPar(xText, S_META, oStyle, tText, tFmt=tFormat)
elif tType == BlockTyp.KEYWORD:
+8 -4
View File
@@ -37,7 +37,7 @@ from PyQt6.QtPrintSupport import QPrinter
from novelwriter import __version__
from novelwriter.constants import nwStyles, nwUnicode
from novelwriter.formats.shared import BlockFmt, BlockTyp, T_Formats, TextFmt, stripEscape
from novelwriter.formats.tokenizer import HEADINGS, Tokenizer
from novelwriter.formats.tokenizer import HEADING_BLOCKS, META_BLOCKS, Tokenizer
from novelwriter.types import (
QtAlignAbsolute, QtAlignCenter, QtAlignJustify, QtAlignLeft, QtAlignRight,
QtKeepAnchor, QtMoveAnchor, QtPageBreakAfter, QtPageBreakAuto,
@@ -214,7 +214,7 @@ class ToQTextDocument(Tokenizer):
for tType, tMeta, tText, tFormat, tStyle in self._blocks:
bFmt = QTextBlockFormat(self._blockFmt)
if tType in (BlockTyp.COMMENT, BlockTyp.KEYWORD):
if tType in META_BLOCKS:
bFmt.setTopMargin(self._mMeta[0])
bFmt.setBottomMargin(self._mMeta[1])
elif tType == BlockTyp.SEP:
@@ -248,17 +248,21 @@ class ToQTextDocument(Tokenizer):
if tStyle & BlockFmt.IND_T:
bFmt.setTextIndent(self._tIndent)
if tType in (BlockTyp.TEXT, BlockTyp.COMMENT, BlockTyp.KEYWORD):
if tType == BlockTyp.TEXT:
newBlock(cursor, bFmt)
self._insertFragments(tText, tFormat, cursor, self._charFmt)
elif tType in HEADINGS:
elif tType in HEADING_BLOCKS:
bFmt, cFmt = self._genHeadStyle(tType, tMeta, bFmt)
for tPart in tText.split("\n"):
newBlock(cursor, bFmt)
cursor.insertText(tPart, cFmt)
bFmt.setPageBreakPolicy(QtPageBreakAuto)
elif tType in META_BLOCKS:
newBlock(cursor, bFmt)
self._insertFragments(tText, tFormat, cursor, self._charFmt)
elif tType == BlockTyp.SEP:
newBlock(cursor, bFmt)
cursor.insertText(tText, self._charFmt)
+27 -3
View File
@@ -45,11 +45,11 @@ from PyQt6.QtCore import (
from PyQt6.QtGui import (
QAction, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent, QKeyEvent,
QKeySequence, QMouseEvent, QPalette, QPixmap, QResizeEvent, QShortcut,
QTextBlock, QTextCursor, QTextDocument, QTextOption
QTextBlock, QTextCursor, QTextDocument, QTextFormat, QTextOption
)
from PyQt6.QtWidgets import (
QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu,
QPlainTextEdit, QToolBar, QVBoxLayout, QWidget
QPlainTextEdit, QTextEdit, QToolBar, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, SHARED
@@ -75,7 +75,8 @@ from novelwriter.tools.lipsum import GuiLipsum
from novelwriter.types import (
QtAlignCenterTop, QtAlignJustify, QtAlignLeft, QtAlignLeftTop,
QtAlignRight, QtKeepAnchor, QtModCtrl, QtModNone, QtModShift, QtMouseLeft,
QtMoveAnchor, QtMoveLeft, QtMoveRight, QtScrollAlwaysOff, QtScrollAsNeeded
QtMoveAnchor, QtMoveLeft, QtMoveRight, QtScrollAlwaysOff, QtScrollAsNeeded,
QtTransparent
)
logger = logging.getLogger(__name__)
@@ -145,6 +146,8 @@ class GuiDocEditor(QPlainTextEdit):
self._lastActive = 0.0 # Timestamp of last activity
self._lastFind = None # Position of the last found search word
self._doReplace = False # Switch to temporarily disable auto-replace
self._lineColor = QtTransparent
self._selection = QTextEdit.ExtraSelection()
# Auto-Replace
self._autoReplace = TextAutoReplace()
@@ -283,6 +286,7 @@ class GuiDocEditor(QPlainTextEdit):
self.docHeader.clearHeader()
self.docFooter.setHandle(self._docHandle)
self.docToolBar.setVisible(False)
self.setExtraSelections([])
self.itemHandleChanged.emit("")
@@ -315,6 +319,10 @@ class GuiDocEditor(QPlainTextEdit):
self.docHeader.matchColors()
self.docFooter.matchColors()
self._lineColor = syntax.line
self._selection.format.setBackground(self._lineColor)
self._selection.format.setProperty(QTextFormat.Property.FullWidthSelection, True)
return
def initEditor(self) -> None:
@@ -372,6 +380,8 @@ class GuiDocEditor(QPlainTextEdit):
# Refresh sizes
self.setTabStopDistance(CONFIG.tabWidth)
self.setCursorWidth(CONFIG.cursorWidth)
self.setExtraSelections([])
self._cursorMoved()
# If we have a document open, we should refresh it in case the
# font changed, otherwise we just clear the editor entirely,
@@ -733,6 +743,8 @@ class GuiDocEditor(QPlainTextEdit):
self._toggleFormat(2, "*")
elif action == nwDocAction.MD_STRIKE:
self._toggleFormat(2, "~")
elif action == nwDocAction.MD_MARK:
self._toggleFormat(2, "=")
elif action == nwDocAction.S_QUOTE:
self._wrapSelection(CONFIG.fmtSQuoteOpen, CONFIG.fmtSQuoteClose)
elif action == nwDocAction.D_QUOTE:
@@ -1110,6 +1122,10 @@ class GuiDocEditor(QPlainTextEdit):
def _cursorMoved(self) -> None:
"""Triggered when the cursor moved in the editor."""
self.docFooter.updateLineCount(self.textCursor())
if CONFIG.lineHighlight:
self._selection.cursor = self.textCursor()
self._selection.cursor.clearSelection()
self.setExtraSelections([self._selection])
return
@pyqtSlot(int, int, str)
@@ -2410,6 +2426,12 @@ class GuiDocToolBar(QWidget):
qtLambda(self.requestDocAction.emit, nwDocAction.MD_STRIKE)
)
self.tbMarkMD = NIconToolButton(self, iSz)
self.tbMarkMD.setToolTip(self.tr("Markdown Highlight"))
self.tbMarkMD.clicked.connect(
qtLambda(self.requestDocAction.emit, nwDocAction.MD_MARK)
)
self.tbBold = NIconToolButton(self, iSz)
self.tbBold.setToolTip(self.tr("Shortcode Bold"))
self.tbBold.clicked.connect(
@@ -2459,6 +2481,7 @@ class GuiDocToolBar(QWidget):
self.outerBox.addWidget(self.tbBoldMD)
self.outerBox.addWidget(self.tbItalicMD)
self.outerBox.addWidget(self.tbStrikeMD)
self.outerBox.addWidget(self.tbMarkMD)
self.outerBox.addSpacing(4)
self.outerBox.addWidget(self.tbBold)
self.outerBox.addWidget(self.tbItalic)
@@ -2493,6 +2516,7 @@ class GuiDocToolBar(QWidget):
self.tbBoldMD.setThemeIcon("fmt_bold", "orange")
self.tbItalicMD.setThemeIcon("fmt_italic", "orange")
self.tbStrikeMD.setThemeIcon("fmt_strike", "orange")
self.tbMarkMD.setThemeIcon("fmt_mark", "orange")
self.tbBold.setThemeIcon("fmt_bold")
self.tbItalic.setThemeIcon("fmt_italic")
self.tbStrike.setThemeIcon("fmt_strike")
+16 -3
View File
@@ -113,6 +113,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self._addCharFormat("bold", colEmph, "b")
self._addCharFormat("italic", colEmph, "i")
self._addCharFormat("strike", syntax.hidden, "s")
self._addCharFormat("mark", syntax.mark, "bg")
self._addCharFormat("mspaces", syntax.error, "err")
self._addCharFormat("nobreak", colBreak, "bg")
self._addCharFormat("altdialog", syntax.dialA)
@@ -200,6 +201,17 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self._txtRules.append((rxRule, hlRule))
self._cmnRules.append((rxRule, hlRule))
# Markdown Highlight
rxRule = REGEX_PATTERNS.markdownMark
hlRule = {
1: self._hStyles["markup"],
2: self._hStyles["mark"],
3: self._hStyles["markup"],
}
self._minRules.append((rxRule, hlRule))
self._txtRules.append((rxRule, hlRule))
self._cmnRules.append((rxRule, hlRule))
# Shortcodes
rxRule = QRegularExpression(REGEX_PATTERNS.shortcodePlain.pattern, RX_UNICODE)
hlRule = {
@@ -460,9 +472,6 @@ class GuiDocHighlighter(QSyntaxHighlighter):
blockMerge = name == "markup"
charFormat.setProperty(QtTextUserProperty, blockMerge)
if color:
charFormat.setForeground(color)
if style:
styles = style.split(",")
if "b" in styles:
@@ -478,6 +487,10 @@ class GuiDocHighlighter(QSyntaxHighlighter):
charFormat.setUnderlineStyle(QTextCharFormat.UnderlineStyle.SpellCheckUnderline)
if "bg" in styles and color is not None:
charFormat.setBackground(QBrush(color, Qt.BrushStyle.SolidPattern))
color = None
if color:
charFormat.setForeground(color)
if size:
charFormat.setFontPointSize(round(size*CONFIG.textFont.pointSize()))
+23 -2
View File
@@ -228,11 +228,11 @@ class GuiDocViewer(QTextBrowser):
qDoc.setTheme(self._docTheme)
qDoc.initDocument()
qDoc.setKeywords(True)
qDoc.setCommentType(nwComment.NOTE, CONFIG.viewComments)
qDoc.setCommentType(nwComment.STORY, CONFIG.viewComments)
qDoc.setCommentType(nwComment.PLAIN, CONFIG.viewComments)
qDoc.setCommentType(nwComment.SYNOPSIS, CONFIG.viewSynopsis)
qDoc.setCommentType(nwComment.SHORT, CONFIG.viewSynopsis)
qDoc.setCommentType(nwComment.STORY, CONFIG.viewNotes)
qDoc.setCommentType(nwComment.NOTE, CONFIG.viewNotes)
# Be extra careful here to prevent crashes when first opening a
# project as a crash here leaves no way of recovering.
@@ -913,12 +913,23 @@ class GuiDocViewFooter(QWidget):
self.showSynopsis.toggled.connect(self._doToggleSynopsis)
self.showSynopsis.setToolTip(self.tr("Show Synopsis Comments"))
# Show Notes
self.showNotes = QToolButton(self)
self.showNotes.setText(self.tr("Notes"))
self.showNotes.setCheckable(True)
self.showNotes.setChecked(CONFIG.viewNotes)
self.showNotes.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
self.showNotes.setIconSize(iSz)
self.showNotes.toggled.connect(self._doToggleNotes)
self.showNotes.setToolTip(self.tr("Show Notes"))
# Assemble Layout
self.outerBox = QHBoxLayout()
self.outerBox.addWidget(self.showHide, 0)
self.outerBox.addStretch(1)
self.outerBox.addWidget(self.showComments, 0)
self.outerBox.addWidget(self.showSynopsis, 0)
self.outerBox.addWidget(self.showNotes, 0)
self.outerBox.setSpacing(4)
self.setLayout(self.outerBox)
@@ -944,6 +955,7 @@ class GuiDocViewFooter(QWidget):
self.setFont(SHARED.theme.guiFont)
self.showComments.setFont(SHARED.theme.guiFontSmall)
self.showSynopsis.setFont(SHARED.theme.guiFontSmall)
self.showNotes.setFont(SHARED.theme.guiFontSmall)
return
def updateTheme(self) -> None:
@@ -955,11 +967,13 @@ class GuiDocViewFooter(QWidget):
self.showHide.setThemeIcon("panel")
self.showComments.setIcon(bulletIcon)
self.showSynopsis.setIcon(bulletIcon)
self.showNotes.setIcon(bulletIcon)
buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
self.showHide.setStyleSheet(buttonStyle)
self.showComments.setStyleSheet(buttonStyle)
self.showSynopsis.setStyleSheet(buttonStyle)
self.showNotes.setStyleSheet(buttonStyle)
self.matchColors()
@@ -994,3 +1008,10 @@ class GuiDocViewFooter(QWidget):
CONFIG.viewSynopsis = state
self.docViewer.reloadText()
return
@pyqtSlot(bool)
def _doToggleNotes(self, state: bool) -> None:
"""Toggle the view notes button and reload the document."""
CONFIG.viewNotes = state
self.docViewer.reloadText()
return
+2 -8
View File
@@ -233,14 +233,8 @@ class GuiItemDetails(QWidget):
# Label
# =====
if nwItem.isFileType():
if nwItem.isActive:
self.labelIcon.setPixmap(SHARED.theme.getPixmap("checked", (iPx, iPx), "green"))
else:
self.labelIcon.setPixmap(SHARED.theme.getPixmap("unchecked", (iPx, iPx), "red"))
else:
self.labelIcon.setPixmap(SHARED.theme.getPixmap("noncheckable", (iPx, iPx), "faded"))
_, icon = nwItem.getActiveStatus()
self.labelIcon.setPixmap(icon.pixmap(iPx, iPx))
self.labelData.setText(elide(nwItem.itemName, 100))
# Status
+8
View File
@@ -675,6 +675,14 @@ class GuiMainMenu(QMenuBar):
)
self.mainGui.addAction(self.aFmtStrike)
# Format > Highlight
self.aFmtMark = qtAddAction(self.fmtMenu, self.tr("Highlight"))
self.aFmtMark.setShortcut("Ctrl+M")
self.aFmtMark.triggered.connect(
lambda: self.requestDocAction.emit(nwDocAction.MD_MARK)
)
self.mainGui.addAction(self.aFmtStrike)
# Edit > Separator
self.fmtMenu.addSeparator()
+43 -6
View File
@@ -27,12 +27,13 @@ import logging
from typing import TYPE_CHECKING
from PyQt6.QtCore import QEvent, QPoint, QSize, pyqtSignal
from PyQt6.QtCore import QEvent, QPoint, QSize, pyqtSignal, pyqtSlot
from PyQt6.QtWidgets import QMenu, QVBoxLayout, QWidget
from novelwriter import SHARED
from novelwriter import CONFIG, SHARED
from novelwriter.common import qtLambda
from novelwriter.enum import nwView
from novelwriter.constants import nwLabels, trConst
from novelwriter.enum import nwTheme, nwView
from novelwriter.extensions.eventfilters import StatusTipFilter
from novelwriter.extensions.modified import NIconToolButton
from novelwriter.gui.theme import STYLES_BIG_TOOLBUTTON
@@ -77,9 +78,9 @@ class GuiSideBar(QWidget):
self.tbOutline.setToolTip("{0} [Ctrl+Shift+T]".format(self.tr("Novel Outline View")))
self.tbOutline.clicked.connect(qtLambda(self.requestViewChange.emit, nwView.OUTLINE))
self.tbBuild = NIconToolButton(self, iSz)
self.tbBuild.setToolTip("{0} [F5]".format(self.tr("Build Manuscript")))
self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog)
self.tbTheme = NIconToolButton(self, iSz)
self.tbTheme.setToolTip(self.tr("Switch Colour Theme"))
self.tbTheme.clicked.connect(self._cycleColurTheme)
self.tbDetails = NIconToolButton(self, iSz)
self.tbDetails.setToolTip("{0} [Shift+F6]".format(self.tr("Novel Details")))
@@ -89,6 +90,10 @@ class GuiSideBar(QWidget):
self.tbStats.setToolTip("{0} [F6]".format(self.tr("Writing Statistics")))
self.tbStats.clicked.connect(self.mainGui.showWritingStatsDialog)
self.tbBuild = NIconToolButton(self, iSz)
self.tbBuild.setToolTip("{0} [F5]".format(self.tr("Build Manuscript")))
self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog)
# Settings Menu
self.tbSettings = NIconToolButton(self, iSz)
self.tbSettings.setToolTip(self.tr("Settings"))
@@ -111,6 +116,7 @@ class GuiSideBar(QWidget):
self.outerBox.addStretch(1)
self.outerBox.addWidget(self.tbDetails)
self.outerBox.addWidget(self.tbStats)
self.outerBox.addWidget(self.tbTheme)
self.outerBox.addWidget(self.tbSettings)
self.outerBox.setContentsMargins(0, 0, 0, 0)
self.outerBox.setSpacing(6)
@@ -133,6 +139,7 @@ class GuiSideBar(QWidget):
self.tbBuild.setStyleSheet(buttonStyle)
self.tbDetails.setStyleSheet(buttonStyle)
self.tbStats.setStyleSheet(buttonStyle)
self.tbTheme.setStyleSheet(buttonStyle)
self.tbSettings.setStyleSheet(buttonStyle)
self.tbProject.setThemeIcon("sb_project")
@@ -144,6 +151,36 @@ class GuiSideBar(QWidget):
self.tbStats.setThemeIcon("sb_stats")
self.tbSettings.setThemeIcon("settings")
self._setThemeModeIcon()
return
##
# Private Slots
##
@pyqtSlot()
def _cycleColurTheme(self) -> None:
"""Go to nex colour theme."""
match CONFIG.themeMode:
case nwTheme.AUTO:
CONFIG.themeMode = nwTheme.LIGHT
case nwTheme.LIGHT:
CONFIG.themeMode = nwTheme.DARK
case nwTheme.DARK:
CONFIG.themeMode = nwTheme.AUTO
self.mainGui.checkThemeUpdate()
self._setThemeModeIcon()
return
##
# Internal Functions
##
def _setThemeModeIcon(self) -> None:
"""Set the theme button icon."""
self.tbTheme.setThemeIcon(nwLabels.THEME_MODE_ICON[CONFIG.themeMode])
self.tbTheme.setToolTip(trConst(nwLabels.THEME_MODE_LABEL[CONFIG.themeMode]))
return
+3 -3
View File
@@ -143,9 +143,9 @@ class GuiMainStatus(QStatusBar):
self.idlePixmap = SHARED.theme.getPixmap("timer_off", (iPx, iPx))
self.timeIcon.setPixmap(self.timePixmap)
colNone = SHARED.theme.getIconColor("default").darker(150)
colSaved = SHARED.theme.getIconColor("green").darker(150)
colUnsaved = SHARED.theme.getIconColor("red").darker(150)
colNone = SHARED.theme.getBaseColor("default").darker(150)
colSaved = SHARED.theme.getBaseColor("green").darker(150)
colUnsaved = SHARED.theme.getBaseColor("red").darker(150)
self.docIcon.setColors(colNone, colSaved, colUnsaved)
self.projIcon.setColors(colNone, colSaved, colUnsaved)
+362 -326
View File
@@ -26,21 +26,23 @@ from __future__ import annotations
import logging
from configparser import ConfigParser
from dataclasses import dataclass
from math import ceil
from typing import TYPE_CHECKING, Final
from PyQt6.QtCore import QSize, Qt
from PyQt6.QtGui import (
QColor, QFont, QFontDatabase, QFontMetrics, QIcon, QPainter, QPainterPath,
QPalette, QPixmap
QColor, QFont, QFontDatabase, QFontMetrics, QGuiApplication, QIcon,
QPainter, QPainterPath, QPalette, QPixmap
)
from PyQt6.QtWidgets import QApplication
from novelwriter import CONFIG
from novelwriter.common import NWConfigParser, minmax
from novelwriter.config import DEF_GUI, DEF_ICONS, DEF_SYNTAX
from novelwriter.common import checkInt, minmax
from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT, DEF_ICONS, DEF_TREECOL
from novelwriter.constants import nwLabels
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType, nwTheme
from novelwriter.error import logException
from novelwriter.types import QtBlack, QtHexArgb, QtPaintAntiAlias, QtTransparent
@@ -54,21 +56,36 @@ STYLES_MIN_TOOLBUTTON = "minimalToolButton"
STYLES_BIG_TOOLBUTTON = "bigToolButton"
@dataclass
class ThemeEntry:
name: str
dark: bool
path: Path
class ThemeMeta:
name: str = ""
mode: str = ""
description: str = ""
author: str = ""
credit: str = ""
url: str = ""
license: str = ""
licenseUrl: str = ""
class IconsMeta:
name: str = ""
author: str = ""
license: str = ""
class SyntaxColors:
back: QColor = QColor(255, 255, 255)
text: QColor = QColor(0, 0, 0)
line: QColor = QColor(0, 0, 0)
link: QColor = QColor(0, 0, 0)
head: QColor = QColor(0, 0, 0)
headH: QColor = QColor(0, 0, 0)
@@ -96,45 +113,41 @@ class GuiTheme:
"""
__slots__ = (
"_availSyntax", "_availThemes", "_guiPalette", "_styleSheets", "_syntaxList", "_themeList",
"baseButtonHeight", "baseIconHeight", "baseIconSize", "buttonIconSize", "errorText",
"fadedText", "fontPixelSize", "fontPointSize", "getDecoration", "getHeaderDecoration",
"getHeaderDecorationNarrow", "getIcon", "getIconColor", "getItemIcon", "getPixmap",
"getToggleIcon", "guiFont", "guiFontB", "guiFontBU", "guiFontFixed", "guiFontSmall",
"helpText", "iconCache", "isDarkTheme", "syntaxMeta", "syntaxTheme", "textNHeight",
"textNWidth", "themeMeta",
"_allThemes", "_currentTheme", "_darkThemes", "_guiPalette", "_lightThemes", "_meta",
"_qColors", "_styleSheets", "_svgColors", "_syntaxList", "accentCol", "baseButtonHeight",
"baseIconHeight", "baseIconSize", "buttonIconSize", "errorText", "fadedText",
"fontPixelSize", "fontPointSize", "getDecoration", "getHeaderDecoration",
"getHeaderDecorationNarrow", "getIcon", "getItemIcon", "getPixmap", "getToggleIcon",
"guiFont", "guiFontB", "guiFontBU", "guiFontFixed", "guiFontSmall", "helpText",
"iconCache", "isDarkTheme", "syntaxTheme", "textNHeight", "textNWidth",
)
def __init__(self) -> None:
self.iconCache = GuiIcons(self)
# GUI Theme
self.themeMeta = ThemeMeta()
# Theme Objects
self.iconCache = GuiIcons(self)
self.syntaxTheme = SyntaxColors()
self.isDarkTheme = False
# Special Text Colours
# Special Colours
self.helpText = QColor(0, 0, 0)
self.fadedText = QColor(0, 0, 0)
self.errorText = QColor(255, 0, 0)
self.accentCol = QColor(255, 0, 255) # Needed until we move to Qt 6.6
# Syntax Theme
self.syntaxMeta = ThemeMeta()
self.syntaxTheme = SyntaxColors()
# Load Themes
# Theme Data
self._meta = ThemeMeta()
self._currentTheme = ""
self._guiPalette = QPalette()
self._themeList: list[tuple[str, str]] = []
self._syntaxList: list[tuple[str, str]] = []
self._availThemes: dict[str, Path] = {}
self._availSyntax: dict[str, Path] = {}
self._allThemes: dict[str, ThemeEntry] = {}
self._styleSheets: dict[str, str] = {}
self._svgColors: dict[str, bytes] = {}
self._qColors: dict[str, QColor] = {}
# Icon Functions
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.getDecoration = self.iconCache.getDecoration
self.getHeaderDecoration = self.iconCache.getHeaderDecoration
@@ -178,19 +191,19 @@ class GuiTheme:
logger.debug("Text 'N' Height: %d", self.textNHeight)
logger.debug("Text 'N' Width: %d", self.textNWidth)
# Process Themes
_listConf(self._availSyntax, CONFIG.assetPath("syntax"), ".conf")
_listConf(self._availThemes, CONFIG.assetPath("themes"), ".conf")
_listConf(self._availSyntax, CONFIG.dataPath("syntax"), ".conf")
_listConf(self._availThemes, CONFIG.dataPath("themes"), ".conf")
self.loadTheme()
self.loadSyntax()
return
##
# Methods
# Properties
##
@property
def colourThemes(self) -> dict[str, ThemeEntry]:
"""Return a dictionary of all themes."""
return self._allThemes
##
# Getters
##
def getTextWidth(self, text: str, font: QFont | None = None) -> int:
@@ -203,30 +216,79 @@ class GuiTheme:
qMetrics = QFontMetrics(self.guiFont)
return ceil(qMetrics.boundingRect(text).width())
def getBaseColor(self, name: str) -> QColor:
"""Return a base color."""
return QColor(self._qColors.get(name) or QtBlack)
def getRawBaseColor(self, name: str) -> bytes:
"""Return a base color."""
return self._svgColors.get(name, self._svgColors.get("default", b"#000000"))
##
# Theme Methods
##
def loadTheme(self) -> bool:
"""Load the currently specified GUI theme."""
theme = CONFIG.guiTheme
if theme not in self._availThemes:
logger.error("Could not find GUI theme '%s'", theme)
theme = DEF_GUI
CONFIG.guiTheme = theme
def initThemes(self) -> None:
"""Initialise themes."""
CONFIG.splashMessage("Scanning for colour themes ...")
themes: list[Path] = []
_listContent(themes, CONFIG.assetPath("themes"), ".conf")
_listContent(themes, CONFIG.dataPath("themes"), ".conf")
self._scanThemes(themes)
if not (file := self._availThemes.get(theme)):
self.iconCache.initIcons()
self.loadTheme()
return
def isDesktopDarkMode(self) -> bool:
"""Check if the desktop is in dark mode."""
if CONFIG.verQtValue >= 0x060500 and (hint := QGuiApplication.styleHints()):
return hint.colorScheme() == Qt.ColorScheme.Dark
palette = QPalette()
text = palette.windowText().color()
window = palette.window().color()
return text.lightnessF() > window.lightnessF()
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.
"""
match CONFIG.themeMode:
case nwTheme.LIGHT:
darkMode = False
case nwTheme.DARK:
darkMode = True
case _:
darkMode = self.isDesktopDarkMode()
theme = CONFIG.darkTheme if darkMode else CONFIG.lightTheme
if theme not in self._allThemes:
logger.error("Could not find theme '%s'", theme)
if darkMode:
theme = DEF_GUI_DARK
CONFIG.darkTheme = DEF_GUI_DARK
else:
theme = DEF_GUI_LIGHT
CONFIG.lightTheme = DEF_GUI_LIGHT
if theme == self._currentTheme and not force:
logger.info("Theme '%s' is already loaded", theme)
return False
entry = self._allThemes.get(theme)
if not entry:
logger.error("Could not load GUI theme")
return False
CONFIG.splashMessage("Loading GUI theme ...")
CONFIG.splashMessage(f"Loading colour theme: {entry.name}")
logger.info("Loading GUI theme '%s'", theme)
parser = NWConfigParser()
parser = ConfigParser()
try:
with open(file, mode="r", encoding="utf-8") as fo:
parser.read_file(fo)
parser.read(entry.path, encoding="utf-8")
except Exception:
logger.error("Could not read file: %s", file)
logger.error("Could not read file: %s", entry.path)
logException()
return False
@@ -237,39 +299,42 @@ class GuiTheme:
sec = "Main"
meta = ThemeMeta()
if parser.has_section(sec):
meta.name = parser.rdStr(sec, "name", "")
meta.description = parser.rdStr(sec, "description", "N/A")
meta.author = parser.rdStr(sec, "author", "N/A")
meta.credit = parser.rdStr(sec, "credit", "N/A")
meta.url = parser.rdStr(sec, "url", "")
meta.license = parser.rdStr(sec, "license", "N/A")
meta.licenseUrl = parser.rdStr(sec, "licenseurl", "")
meta.name = parser.get(sec, "name", fallback="")
meta.mode = parser.get(sec, "mode", fallback="light")
meta.description = parser.get(sec, "description", fallback="")
meta.author = parser.get(sec, "author", fallback="")
meta.credit = parser.get(sec, "credit", fallback="")
meta.url = parser.get(sec, "url", fallback="")
self.themeMeta = meta
self._meta = meta
# Icons
sec = "Icons"
# Base
sec = "Base"
if parser.has_section(sec):
self.iconCache.setIconColor("default", self._parseColor(parser, sec, "default"))
self.iconCache.setIconColor("faded", self._parseColor(parser, sec, "faded"))
self.iconCache.setIconColor("red", self._parseColor(parser, sec, "red"))
self.iconCache.setIconColor("orange", self._parseColor(parser, sec, "orange"))
self.iconCache.setIconColor("yellow", self._parseColor(parser, sec, "yellow"))
self.iconCache.setIconColor("green", self._parseColor(parser, sec, "green"))
self.iconCache.setIconColor("aqua", self._parseColor(parser, sec, "aqua"))
self.iconCache.setIconColor("blue", self._parseColor(parser, sec, "blue"))
self.iconCache.setIconColor("purple", self._parseColor(parser, sec, "purple"))
self._setBaseColor("base", self._readColor(parser, sec, "base"))
self._setBaseColor("default", self._readColor(parser, sec, "default"))
self._setBaseColor("faded", self._readColor(parser, sec, "faded"))
self._setBaseColor("red", self._readColor(parser, sec, "red"))
self._setBaseColor("orange", self._readColor(parser, sec, "orange"))
self._setBaseColor("yellow", self._readColor(parser, sec, "yellow"))
self._setBaseColor("green", self._readColor(parser, sec, "green"))
self._setBaseColor("cyan", self._readColor(parser, sec, "cyan"))
self._setBaseColor("blue", self._readColor(parser, sec, "blue"))
self._setBaseColor("purple", self._readColor(parser, sec, "purple"))
# Project
sec = "Project"
if parser.has_section(sec):
self.iconCache.setIconColor("root", self._parseColor(parser, sec, "root"))
self.iconCache.setIconColor("folder", self._parseColor(parser, sec, "folder"))
self.iconCache.setIconColor("file", self._parseColor(parser, sec, "file"))
self.iconCache.setIconColor("title", self._parseColor(parser, sec, "title"))
self.iconCache.setIconColor("chapter", self._parseColor(parser, sec, "chapter"))
self.iconCache.setIconColor("scene", self._parseColor(parser, sec, "scene"))
self.iconCache.setIconColor("note", self._parseColor(parser, sec, "note"))
self._setBaseColor("root", self._readColor(parser, sec, "root"))
self._setBaseColor("folder", self._readColor(parser, sec, "folder"))
self._setBaseColor("file", self._readColor(parser, sec, "file"))
self._setBaseColor("title", self._readColor(parser, sec, "title"))
self._setBaseColor("chapter", self._readColor(parser, sec, "chapter"))
self._setBaseColor("scene", self._readColor(parser, sec, "scene"))
self._setBaseColor("note", self._readColor(parser, sec, "note"))
self._setBaseColor("active", self._readColor(parser, sec, "active"))
self._setBaseColor("inactive", self._readColor(parser, sec, "inactive"))
self._setBaseColor("disabled", self._readColor(parser, sec, "disabled"))
# Palette
sec = "Palette"
@@ -288,20 +353,46 @@ class GuiTheme:
self._setPalette(parser, sec, "highlightedtext", QPalette.ColorRole.HighlightedText)
self._setPalette(parser, sec, "link", QPalette.ColorRole.Link)
self._setPalette(parser, sec, "linkvisited", QPalette.ColorRole.LinkVisited)
self.accentCol = self._readColor(parser, sec, "accent") # Special handling 'til Qt 6.6
# GUI
sec = "GUI"
if parser.has_section(sec):
self.helpText = self._parseColor(parser, sec, "helptext")
self.fadedText = self._parseColor(parser, sec, "fadedtext")
self.errorText = self._parseColor(parser, sec, "errortext")
self.helpText = self._readColor(parser, sec, "helptext")
self.fadedText = self._readColor(parser, sec, "fadedtext")
self.errorText = self._readColor(parser, sec, "errortext")
# Syntax
sec = "Syntax"
self.syntaxTheme = SyntaxColors()
if parser.has_section(sec):
self.syntaxTheme.back = self._readColor(parser, sec, "background")
self.syntaxTheme.text = self._readColor(parser, sec, "text")
self.syntaxTheme.line = self._readColor(parser, sec, "line")
self.syntaxTheme.link = self._readColor(parser, sec, "link")
self.syntaxTheme.head = self._readColor(parser, sec, "headertext")
self.syntaxTheme.headH = self._readColor(parser, sec, "headertag")
self.syntaxTheme.emph = self._readColor(parser, sec, "emphasis")
self.syntaxTheme.dialN = self._readColor(parser, sec, "dialog")
self.syntaxTheme.dialA = self._readColor(parser, sec, "altdialog")
self.syntaxTheme.hidden = self._readColor(parser, sec, "hidden")
self.syntaxTheme.note = self._readColor(parser, sec, "note")
self.syntaxTheme.code = self._readColor(parser, sec, "shortcode")
self.syntaxTheme.key = self._readColor(parser, sec, "keyword")
self.syntaxTheme.tag = self._readColor(parser, sec, "tag")
self.syntaxTheme.val = self._readColor(parser, sec, "value")
self.syntaxTheme.opt = self._readColor(parser, sec, "optional")
self.syntaxTheme.spell = self._readColor(parser, sec, "spellcheckline")
self.syntaxTheme.error = self._readColor(parser, sec, "errorline")
self.syntaxTheme.repTag = self._readColor(parser, sec, "replacetag")
self.syntaxTheme.mod = self._readColor(parser, sec, "modifier")
self.syntaxTheme.mark = self._readColor(parser, sec, "texthighlight")
# Update Dependant Colours
# Based on: https://github.com/qt/qtbase/blob/dev/src/gui/kernel/qplatformtheme.cpp
text = self._guiPalette.text().color()
window = self._guiPalette.window().color()
highlight = self._guiPalette.highlight().color()
isDark = text.lightnessF() > window.lightnessF()
QtColActive = QPalette.ColorGroup.Active
QtColInactive = QPalette.ColorGroup.Inactive
@@ -321,8 +412,8 @@ class GuiTheme:
darkOff = dark.darker(150)
shadowOff = ref.darker(150)
grey = QColor(120, 120, 120) if isDark else QColor(140, 140, 140)
dimmed = QColor(130, 130, 130) if isDark else QColor(190, 190, 190)
grey = QColor(120, 120, 120) if darkMode else QColor(140, 140, 140)
dimmed = QColor(130, 130, 130) if darkMode else QColor(190, 190, 190)
placeholder = QColor(text)
placeholder.setAlpha(128)
@@ -347,145 +438,94 @@ class GuiTheme:
self._guiPalette.setBrush(QtColDisabled, QPalette.ColorRole.Highlight, grey)
if CONFIG.verQtValue >= 0x060600:
self._guiPalette.setBrush(QtColActive, QPalette.ColorRole.Accent, highlight)
self._guiPalette.setBrush(QtColInactive, QPalette.ColorRole.Accent, highlight)
self._guiPalette.setBrush(QtColActive, QPalette.ColorRole.Accent, self.accentCol)
self._guiPalette.setBrush(QtColInactive, QPalette.ColorRole.Accent, self.accentCol)
self._guiPalette.setBrush(QtColDisabled, QPalette.ColorRole.Accent, grey)
# Set project override colours
if (override := CONFIG.iconColTree) != DEF_TREECOL:
color = self._qColors.get(override, QtBlack)
self._setBaseColor("root", color)
self._setBaseColor("folder", color)
if not CONFIG.iconColDocs:
self._setBaseColor("file", color)
self._setBaseColor("title", color)
self._setBaseColor("chapter", color)
self._setBaseColor("scene", color)
self._setBaseColor("note", color)
self.isDarkTheme = darkMode
self._currentTheme = theme
# Load icons after the theme is parsed
self.iconCache.loadTheme(CONFIG.iconTheme)
# Finalise
self.isDarkTheme = isDark
QApplication.setPalette(self._guiPalette)
self._buildStyleSheets(self._guiPalette)
CONFIG.splashMessage(f"Loaded GUI theme: {meta.name}")
return True
def loadSyntax(self) -> bool:
"""Load the currently specified syntax highlighter theme."""
theme = CONFIG.guiSyntax
if theme not in self._availSyntax:
logger.error("Could not find syntax theme '%s'", theme)
theme = DEF_SYNTAX
CONFIG.guiSyntax = theme
if not (file := self._availSyntax.get(theme)):
logger.error("Could not load syntax theme")
return False
CONFIG.splashMessage("Loading syntax theme ...")
logger.info("Loading syntax theme '%s'", theme)
parser = NWConfigParser()
try:
with open(file, mode="r", encoding="utf-8") as fo:
parser.read_file(fo)
except Exception:
logger.error("Could not read file: %s", file)
logException()
return False
# Main
sec = "Main"
meta = ThemeMeta()
if parser.has_section(sec):
meta.name = parser.rdStr(sec, "name", "")
meta.description = parser.rdStr(sec, "description", "N/A")
meta.author = parser.rdStr(sec, "author", "N/A")
meta.credit = parser.rdStr(sec, "credit", "N/A")
meta.url = parser.rdStr(sec, "url", "")
meta.license = parser.rdStr(sec, "license", "N/A")
meta.licenseUrl = parser.rdStr(sec, "licenseurl", "")
# Syntax
sec = "Syntax"
syntax = SyntaxColors()
if parser.has_section(sec):
syntax.back = self._parseColor(parser, sec, "background")
syntax.text = self._parseColor(parser, sec, "text")
syntax.link = self._parseColor(parser, sec, "link")
syntax.head = self._parseColor(parser, sec, "headertext")
syntax.headH = self._parseColor(parser, sec, "headertag")
syntax.emph = self._parseColor(parser, sec, "emphasis")
syntax.dialN = self._parseColor(parser, sec, "dialog")
syntax.dialA = self._parseColor(parser, sec, "altdialog")
syntax.hidden = self._parseColor(parser, sec, "hidden")
syntax.note = self._parseColor(parser, sec, "note")
syntax.code = self._parseColor(parser, sec, "shortcode")
syntax.key = self._parseColor(parser, sec, "keyword")
syntax.tag = self._parseColor(parser, sec, "tag")
syntax.val = self._parseColor(parser, sec, "value")
syntax.opt = self._parseColor(parser, sec, "optional")
syntax.spell = self._parseColor(parser, sec, "spellcheckline")
syntax.error = self._parseColor(parser, sec, "errorline")
syntax.repTag = self._parseColor(parser, sec, "replacetag")
syntax.mod = self._parseColor(parser, sec, "modifier")
syntax.mark = self._parseColor(parser, sec, "texthighlight")
CONFIG.splashMessage(f"Loaded syntax theme: {meta.name}")
self.syntaxMeta = meta
self.syntaxTheme = syntax
return True
def listThemes(self) -> list[tuple[str, str]]:
"""Scan the GUI themes folder and list all themes."""
if self._themeList:
return self._themeList
themes = []
parser = NWConfigParser()
for key, path in self._availThemes.items():
logger.debug("Checking theme config '%s'", key)
if name := _loadInternalName(parser, path):
themes.append((key, name))
self._themeList = sorted(themes, key=_sortTheme)
return self._themeList
def listSyntax(self) -> list[tuple[str, str]]:
"""Scan the syntax themes folder and list all themes."""
if self._syntaxList:
return self._syntaxList
themes = []
parser = NWConfigParser()
for key, path in self._availSyntax.items():
logger.debug("Checking theme syntax '%s'", key)
if name := _loadInternalName(parser, path):
themes.append((key, name))
self._syntaxList = sorted(themes, key=_sortTheme)
return self._syntaxList
def getStyleSheet(self, name: str) -> str:
"""Load a standard style sheet."""
return self._styleSheets.get(name, "")
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 lighter, darker or 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
##
# Internal Functions
##
def _setBaseColor(self, key: str, color: QColor) -> None:
"""Set the colour for a named colour."""
self._qColors[key] = QColor(color)
self._svgColors[key] = color.name(QColor.NameFormat.HexRgb).encode("utf-8")
return
def _resetTheme(self) -> None:
"""Reset GUI colours to default values."""
palette = QPalette()
text = palette.color(QPalette.ColorRole.Text)
window = palette.color(QPalette.ColorRole.Window)
isDark = text.lightnessF() > window.lightnessF()
isDark = self.isDesktopDarkMode()
# Reset GUI Palette
base = palette.color(QPalette.ColorRole.Base)
default = palette.color(QPalette.ColorRole.Text)
faded = QColor(128, 128, 128)
dimmed = QColor(130, 130, 130) if isDark else QColor(190, 190, 190)
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)
green = QColor(153, 204, 153) if isDark else QColor(113, 140, 0)
aqua = QColor(102, 204, 204) if isDark else QColor(62, 153, 159)
cyan = QColor(102, 204, 204) if isDark else QColor(62, 153, 159)
blue = QColor(102, 153, 204) if isDark else QColor(66, 113, 174)
purple = QColor(204, 153, 204) if isDark else QColor(137, 89, 168)
@@ -496,37 +536,42 @@ class GuiTheme:
self._guiPalette = palette
# Reset Icons
icons = self.iconCache
icons.clear()
icons.setIconColor("default", text)
icons.setIconColor("faded", faded)
icons.setIconColor("red", red)
icons.setIconColor("orange", orange)
icons.setIconColor("yellow", yellow)
icons.setIconColor("green", green)
icons.setIconColor("aqua", aqua)
icons.setIconColor("blue", blue)
icons.setIconColor("purple", purple)
icons.setIconColor("root", blue)
icons.setIconColor("folder", yellow)
icons.setIconColor("file", text)
icons.setIconColor("title", green)
icons.setIconColor("chapter", red)
icons.setIconColor("scene", blue)
icons.setIconColor("note", yellow)
# Reset Base Colours and Icons
self.iconCache.clear()
self._svgColors = {}
self._qColors = {}
self._setBaseColor("base", base)
self._setBaseColor("default", default)
self._setBaseColor("faded", faded)
self._setBaseColor("red", red)
self._setBaseColor("orange", orange)
self._setBaseColor("yellow", yellow)
self._setBaseColor("green", green)
self._setBaseColor("cyan", cyan)
self._setBaseColor("blue", blue)
self._setBaseColor("purple", purple)
self._setBaseColor("root", blue)
self._setBaseColor("folder", yellow)
self._setBaseColor("file", default)
self._setBaseColor("title", green)
self._setBaseColor("chapter", red)
self._setBaseColor("scene", blue)
self._setBaseColor("note", yellow)
self._setBaseColor("active", green)
self._setBaseColor("inactive", red)
self._setBaseColor("disabled", faded)
return
def _parseColor(self, parser: NWConfigParser, section: str, name: str) -> QColor:
def _readColor(self, parser: ConfigParser, section: str, name: str) -> QColor:
"""Parse a colour value from a config string."""
return QColor(*parser.rdIntList(section, name, [0, 0, 0, 255]))
return self.parseColor(parser.get(section, name, fallback="default"))
def _setPalette(
self, parser: NWConfigParser, section: str, name: str, value: QPalette.ColorRole
self, parser: ConfigParser, section: str, name: str, value: QPalette.ColorRole
) -> None:
"""Set a palette colour value from a config string."""
self._guiPalette.setBrush(value, self._parseColor(parser, section, name))
self._guiPalette.setBrush(value, self._readColor(parser, section, name))
return
def _buildStyleSheets(self, palette: QPalette) -> None:
@@ -561,6 +606,35 @@ class GuiTheme:
return
def _scanThemes(self, files: list[Path]) -> None:
"""Scan the GUI themes folder and list all themes."""
parser = ConfigParser()
data: dict[str, tuple[str, str, bool, Path]] = {}
keys = []
for file in files:
try:
parser.clear()
parser.read(file, encoding="utf-8")
name = parser.get("Main", "name", fallback="")
mode = parser.get("Main", "mode", fallback="").lower()
if name and mode in ("light", "dark"):
key = file.stem
prefix = "*" if key.startswith("default") else ""
lookup = f"{prefix}{name} {key}"
keys.append(lookup)
data[lookup] = (file.stem, name, mode == "dark", file)
except Exception: # noqa: PERF203
logger.error("Could not read file: %s", file)
logException()
self._allThemes = {}
for lookup in sorted(keys):
key, name, dark, item = data[lookup]
logger.debug("Checking theme config '%s'", key)
self._allThemes[key] = ThemeEntry(name, dark, item)
return
class GuiIcons:
"""The icon class manages the content of the assets/icons folder,
@@ -572,9 +646,8 @@ class GuiIcons:
"""
__slots__ = (
"_availThemes", "_headerDec", "_headerDecNarrow", "_noIcon",
"_qColors", "_qIcons", "_svgColors", "_svgData", "_themeList",
"mainTheme", "themeMeta",
"_allThemes", "_headerDec", "_headerDecNarrow", "_meta",
"_noIcon", "_qIcons", "_svgData", "_theme",
)
TOGGLE_ICON_KEYS: Final[dict[str, tuple[str, str]]] = {
@@ -582,69 +655,78 @@ 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"),
}
def __init__(self, mainTheme: GuiTheme) -> None:
self.mainTheme = mainTheme
self.themeMeta = ThemeMeta()
self._theme = mainTheme
self._meta = IconsMeta()
# Storage
self._allThemes: dict[str, ThemeEntry] = {}
self._svgData: 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] = []
# Icon Theme Path
self._availThemes: dict[str, Path] = {}
self._themeList: list[tuple[str, str]] = []
# None Icon
self._noIcon = QIcon(str(CONFIG.assetPath("icons") / "none.svg"))
_listConf(self._availThemes, CONFIG.assetPath("icons"), ".icons")
_listConf(self._availThemes, CONFIG.dataPath("icons"), ".icons")
return
def clear(self) -> None:
"""Clear the icon cache."""
self._svgData = {}
self._svgColors = {}
self._qColors = {}
self._qIcons = {}
self._headerDec = []
self._headerDecNarrow = []
self.themeMeta = ThemeMeta()
self._meta = ThemeMeta()
return
##
# Properties
##
@property
def iconThemes(self) -> dict[str, ThemeEntry]:
"""Return a dictionary of all icon themes."""
return self._allThemes
##
# Actions
##
def loadTheme(self, theme: str) -> bool:
def initIcons(self) -> None:
"""Initialise icons."""
CONFIG.splashMessage("Scanning for icon themes ...")
icons: list[Path] = []
_listContent(icons, CONFIG.assetPath("icons"), ".icons")
_listContent(icons, CONFIG.dataPath("icons"), ".icons")
self._scanThemes(icons)
return
def loadTheme(self, theme: str) -> None:
"""Update the theme map. This is more of an init, since many of
the GUI icons cannot really be replaced without writing specific
update functions for the classes where they're used.
"""
if theme not in self._availThemes:
if theme not in self._allThemes:
logger.error("Could not find icon theme '%s'", theme)
theme = DEF_ICONS
CONFIG.iconTheme = theme
if not (file := self._availThemes.get(theme)):
entry = self._allThemes.get(theme)
if not entry:
logger.error("Could not load icon theme")
return False
return
CONFIG.splashMessage("Loading icon theme ...")
CONFIG.splashMessage(f"Loading icon theme: {entry.name}")
logger.info("Loading icon theme '%s'", theme)
try:
meta = ThemeMeta()
with open(file, mode="r", encoding="utf-8") as icons:
meta = IconsMeta()
with open(entry.path, mode="r", encoding="utf-8") as icons:
for icon in icons:
bits = icon.partition("=")
key = bits[0].strip()
@@ -658,47 +740,23 @@ class GuiIcons:
meta.author = value
elif key == "meta:license":
meta.license = value
self.themeMeta = meta
self._meta = meta
except Exception:
logger.error("Could not read file: %s", file)
logger.error("Could not read file: %s", entry.path)
logException()
return False
CONFIG.splashMessage(f"Loaded icon theme: {meta.name}")
CONFIG.splashMessage("Generating additional icons ...")
# Set colour overrides for project item icons
if (override := CONFIG.iconColTree) != "theme":
color = self._svgColors.get(override, b"#000000")
self._svgColors["root"] = color
self._svgColors["folder"] = color
if not CONFIG.iconColDocs:
self._svgColors["file"] = color
self._svgColors["title"] = color
self._svgColors["chapter"] = color
self._svgColors["scene"] = color
self._svgColors["note"] = color
return
# Populate generated icons cache
CONFIG.splashMessage("Generating additional icons ...")
self.getHeaderDecoration(0)
self.getHeaderDecorationNarrow(0)
return True
def setIconColor(self, key: str, color: QColor) -> None:
"""Set an icon colour for a named colour."""
self._qColors[key] = QColor(color)
self._svgColors[key] = color.name(QColor.NameFormat.HexRgb).encode("utf-8")
return
##
# Access Functions
##
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."""
variant = f"{name}-{color}" if color else name
@@ -769,7 +827,7 @@ class GuiIcons:
map or the icon map. This function always returns a QPixmap.
"""
if name in self.IMAGE_MAP:
idx = int(self.mainTheme.isDarkTheme)
idx = int(self._theme.isDarkTheme)
imgPath = CONFIG.assetPath("images") / self.IMAGE_MAP[name][idx]
else:
logger.error("Decoration with name '%s' does not exist", name)
@@ -793,7 +851,7 @@ class GuiIcons:
def getHeaderDecoration(self, hLevel: int) -> QPixmap:
"""Get the decoration for a specific heading level."""
if not self._headerDec:
iPx = self.mainTheme.baseIconHeight
iPx = self._theme.baseIconHeight
self._headerDec = [
self._generateDecoration("file", iPx, 0),
self._generateDecoration("title", iPx, 0),
@@ -806,7 +864,7 @@ class GuiIcons:
def getHeaderDecorationNarrow(self, hLevel: int) -> QPixmap:
"""Get the narrow decoration for a specific heading level."""
if not self._headerDecNarrow:
iPx = self.mainTheme.baseIconHeight
iPx = self._theme.baseIconHeight
self._headerDecNarrow = [
self._generateDecoration("file", iPx, 0),
self._generateDecoration("title", iPx, 0),
@@ -817,21 +875,6 @@ class GuiIcons:
]
return self._headerDecNarrow[minmax(hLevel, 0, 5)]
def listThemes(self) -> list[tuple[str, str]]:
"""Scan the GUI icons folder and list all themes."""
if self._themeList:
return self._themeList
themes = []
for key, path in self._availThemes.items():
logger.debug("Checking icon theme '%s'", key)
if name := _loadIconName(path):
themes.append((key, name))
self._themeList = sorted(themes, key=_sortTheme)
return self._themeList
##
# Internal Functions
##
@@ -847,7 +890,7 @@ class GuiIcons:
return QIcon(str(CONFIG.assetPath("icons") / "x-novelwriter-project.svg"))
if svg := self._svgData.get(name, b""):
if fill := self._svgColors.get(color or "default"):
if fill := self._theme.getRawBaseColor(color or "default"):
svg = svg.replace(b"#000000", fill)
pixmap = QPixmap(w, h)
pixmap.fill(QtTransparent)
@@ -869,53 +912,46 @@ class GuiIcons:
painter = QPainter(pixmap)
painter.setRenderHint(QtPaintAntiAlias)
if fill := self._svgColors.get(color or "default"):
if fill := self._theme.getRawBaseColor(color or "default"):
painter.fillPath(path, QColor(fill.decode(encoding="utf-8")))
painter.end()
tMode = Qt.TransformationMode.SmoothTransformation
return pixmap.scaledToHeight(height, tMode)
def _scanThemes(self, entries: list[Path]) -> None:
"""Scan the GUI themes folder and list all themes."""
data: dict[str, tuple[str, str, Path]] = {}
keys = []
for entry in entries:
try:
with open(entry, mode="r", encoding="utf-8") as fo:
for line in fo:
key, _, value = line.partition("=")
if key.strip() == "meta:name":
if name := value.strip():
lookup = entry.stem
keys.append(lookup)
data[lookup] = (lookup, name, entry)
break
except Exception:
logger.error("Could not read file: %s", entry)
logException()
self._allThemes = {}
for lookup in sorted(keys):
key, name, item = data[lookup]
logger.debug("Checking icon theme '%s'", key)
self._allThemes[key] = ThemeEntry(name, False, item)
return
# Module Functions
# ================
def _listConf(target: dict, path: Path, extension: str) -> None:
"""Scan for theme files and populate the dictionary."""
def _listContent(data: list[Path], path: Path, extension: str) -> None:
"""List files of a specific type and extend the list."""
if path.is_dir():
for item in path.iterdir():
if item.is_file() and item.name.endswith(extension):
target[item.stem] = item
data.extend(n for n in path.iterdir() if n.is_file() and n.suffix == extension)
return
def _sortTheme(data: tuple[str, str]) -> str:
"""Key function for theme sorting."""
key, name = data
return f"*{name}" if key.startswith("default_") else name
def _loadInternalName(parser: NWConfigParser, path: str | Path) -> str:
"""Open a conf file and read the 'name' setting."""
try:
with open(path, mode="r", encoding="utf-8") as inFile:
parser.read_file(inFile)
return parser.rdStr("Main", "name", "")
except Exception:
logger.error("Could not read file: %s", path)
logException()
return ""
def _loadIconName(path: Path) -> str:
"""Open an icons file and read the name setting."""
try:
with open(path, mode="r", encoding="utf-8") as icons:
for icon in icons:
key, _, value = icon.partition("=")
if key.strip() == "meta:name":
return value.strip()
except Exception:
logger.error("Could not read file: %s", path)
logException()
return ""
+49 -20
View File
@@ -30,7 +30,7 @@ from datetime import datetime
from pathlib import Path
from time import time
from PyQt6.QtCore import Qt, QTimer, pyqtSlot
from PyQt6.QtCore import QEvent, Qt, QTimer, pyqtSlot
from PyQt6.QtGui import QCloseEvent, QCursor, QIcon, QShortcut
from PyQt6.QtWidgets import (
QApplication, QFileDialog, QHBoxLayout, QMainWindow, QMessageBox,
@@ -38,7 +38,7 @@ from PyQt6.QtWidgets import (
)
from novelwriter import CONFIG, SHARED, __hexversion__, __version__
from novelwriter.common import formatFileFilter, formatVersion, hexToInt
from novelwriter.common import formatFileFilter, formatVersion, hexToInt, minmax
from novelwriter.constants import nwConst
from novelwriter.dialogs.about import GuiAbout
from novelwriter.dialogs.preferences import GuiPreferences
@@ -103,7 +103,7 @@ class GuiMain(QMainWindow):
SHARED.initSharedData(self)
# Prepare Main Window
self.resize(*CONFIG.mainWinSize)
self._setWindowSize(CONFIG.mainWinSize)
self._updateWindowTitle()
nwIcon = CONFIG.assetPath("icons") / "novelwriter.svg"
@@ -902,10 +902,46 @@ class GuiMain(QMainWindow):
return not self.splitView.isVisible()
def checkThemeUpdate(self) -> None:
"""Load theme if mode changed."""
if SHARED.theme.loadTheme():
self.refreshThemeColors(syntax=True)
self.docEditor.initEditor()
self.docViewer.initViewer()
return
def refreshThemeColors(self, syntax: bool = False, force: bool = False) -> None:
"""Refresh the GUI theme."""
SHARED.theme.loadTheme(force=force)
SHARED.project.updateTheme()
self.setPalette(QApplication.palette())
self.docEditor.updateTheme()
self.docViewer.updateTheme()
self.docViewerPanel.updateTheme()
self.sideBar.updateTheme()
self.projView.updateTheme()
self.novelView.updateTheme()
self.projSearch.updateTheme()
self.outlineView.updateTheme()
self.itemDetails.updateTheme()
self.mainStatus.updateTheme()
SHARED.project.tree.refreshAllItems()
if syntax:
self.docEditor.updateSyntaxColors()
return
##
# Events
##
def changeEvent(self, event: QEvent) -> None:
"""Capture application change events."""
if int(event.type()) == 210: # ThemeChange
self.checkThemeUpdate()
return
def closeEvent(self, event: QCloseEvent) -> None:
"""Capture the closing event of the GUI and call the close
function to handle all the close process steps.
@@ -1054,23 +1090,7 @@ class GuiMain(QMainWindow):
self.novelView.refreshCurrentTree()
if theme:
SHARED.theme.loadTheme()
self.setPalette(QApplication.palette())
self.docEditor.updateTheme()
self.docViewer.updateTheme()
self.docViewerPanel.updateTheme()
self.sideBar.updateTheme()
self.projView.updateTheme()
self.novelView.updateTheme()
self.projSearch.updateTheme()
self.outlineView.updateTheme()
self.itemDetails.updateTheme()
self.mainStatus.updateTheme()
SHARED.project.tree.refreshAllItems()
if syntax:
SHARED.theme.loadSyntax()
self.docEditor.updateSyntaxColors()
self.refreshThemeColors(syntax=syntax, force=True)
self.docEditor.initEditor()
self.docViewer.initViewer()
@@ -1326,6 +1346,15 @@ class GuiMain(QMainWindow):
# Internal Functions
##
def _setWindowSize(self, size: list[int]) -> None:
"""Set the main window size."""
if len(size) == 2 and (screen := SHARED.mainScreen):
availSize = screen.availableSize()
width = minmax(size[0], 900, availSize.width())
height = minmax(size[1], 500, availSize.height())
self.resize(width, height)
return
def _updateWindowTitle(self, projName: str | None = None) -> None:
"""Set the window title and add the project's name."""
self.setWindowTitle(" - ".join(filter(None, [projName, CONFIG.appName])))
+22 -5
View File
@@ -25,6 +25,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations
import logging
import re
from enum import Enum
from pathlib import Path
@@ -32,8 +33,8 @@ from time import time
from typing import TYPE_CHECKING, TypeVar
from PyQt6.QtCore import QObject, QRunnable, QThreadPool, QTimer, QUrl, pyqtSignal, pyqtSlot
from PyQt6.QtGui import QDesktopServices, QFont
from PyQt6.QtWidgets import QFileDialog, QFontDialog, QMessageBox, QWidget
from PyQt6.QtGui import QDesktopServices, QFont, QScreen
from PyQt6.QtWidgets import QApplication, QFileDialog, QFontDialog, QMessageBox, QWidget
from novelwriter.common import formatFileFilter
from novelwriter.constants import nwFiles
@@ -41,6 +42,8 @@ from novelwriter.core.spellcheck import NWSpellEnchant
from novelwriter.enum import nwChange, nwItemClass
if TYPE_CHECKING:
from collections.abc import Callable
from novelwriter.core.project import NWProject
from novelwriter.core.status import T_StatusKind
from novelwriter.gui.theme import GuiTheme
@@ -50,6 +53,8 @@ logger = logging.getLogger(__name__)
NWWidget = TypeVar("NWWidget", bound=QWidget)
RX_HTML = re.compile(r"<.*?>")
class SharedData(QObject):
@@ -150,6 +155,11 @@ class SharedData(QObject):
"""Return the last alert message."""
return self._lastAlert
@property
def mainScreen(self) -> QScreen | None:
"""Return the screen of the main window."""
return QApplication.screenAt(self.mainGui.rect().center())
##
# Setters
##
@@ -170,6 +180,7 @@ class SharedData(QObject):
is created.
"""
self._theme = theme
self._theme.initThemes()
return
def initSharedData(self, gui: GuiMain) -> None:
@@ -376,7 +387,7 @@ class SharedData(QObject):
alert.setAlertType(_GuiAlert.INFO, False)
self._lastAlert = alert.logMessage
if log:
logger.info(self._lastAlert, stacklevel=2)
self._logMessage(self._lastAlert, logger.info)
alert.exec()
return
@@ -387,7 +398,7 @@ class SharedData(QObject):
alert.setAlertType(_GuiAlert.WARN, False)
self._lastAlert = alert.logMessage
if log:
logger.warning(self._lastAlert, stacklevel=2)
self._logMessage(self._lastAlert, logger.warning)
alert.exec()
return
@@ -401,7 +412,7 @@ class SharedData(QObject):
alert.setException(exc)
self._lastAlert = alert.logMessage
if log:
logger.error(self._lastAlert, stacklevel=2)
self._logMessage(self._lastAlert, logger.error)
alert.exec()
return
@@ -419,6 +430,12 @@ class SharedData(QObject):
# Internal Functions
##
def _logMessage(self, message: str, log: Callable) -> None:
"""Print message to log."""
for text in message.split("<br>"):
log(RX_HTML.sub("", text), stacklevel=3)
return
def _resetProject(self) -> None:
"""Create a new project and spell checking instance."""
from novelwriter.core.project import NWProject
+6
View File
@@ -42,6 +42,7 @@ class RegExPatterns:
_rxItalic = re.compile(nwRegEx.FMT_EI)
_rxBold = re.compile(nwRegEx.FMT_EB)
_rxStrike = re.compile(nwRegEx.FMT_ST)
_rxMark = re.compile(nwRegEx.FMT_HL)
_rxSCPlain = re.compile(nwRegEx.FMT_SC)
_rxSCValue = re.compile(nwRegEx.FMT_SV)
@@ -75,6 +76,11 @@ class RegExPatterns:
"""Markdown strikethrough style."""
return self._rxStrike
@property
def markdownMark(self) -> re.Pattern:
"""Markdown highlight style."""
return self._rxMark
@property
def shortcodePlain(self) -> re.Pattern:
"""Plain shortcode style."""
+3 -4
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
@@ -420,7 +419,7 @@ class _ProjectListItem(QStyledItemDelegate):
painter.save()
if opt.state & QtSelected == QtSelected:
painter.setOpacity(0.25)
painter.fillRect(rect, QApplication.palette().highlight())
painter.fillRect(rect, QApplication.palette().text())
painter.setOpacity(1.0)
painter.drawPixmap(2, rect.top() + 6, self._icon)
+1
View File
@@ -128,6 +128,7 @@ QtHeaderFixed = QHeaderView.ResizeMode.Fixed
# Scroll Bar Policy
QtScrollAlwaysOn = Qt.ScrollBarPolicy.ScrollBarAlwaysOn
QtScrollAlwaysOff = Qt.ScrollBarPolicy.ScrollBarAlwaysOff
QtScrollAsNeeded = Qt.ScrollBarPolicy.ScrollBarAsNeeded

Some files were not shown because too many files have changed in this diff Show More