Add support for the extensions syntax in configurations.

main
Alex Huszagh 2021-07-19 21:51:24 -05:00
parent 458fb583dc
commit 846254032c
12 changed files with 500 additions and 236 deletions

View File

@ -243,10 +243,24 @@ python configure.py --styles=dark,light,<custom> --resource custom.qrc
Then, you can use `custom.qrc`, along with the generated icons and stylesheets in each folder, in place of `breeze.qrc` for any style. Then, you can use `custom.qrc`, along with the generated icons and stylesheets in each folder, in place of `breeze.qrc` for any style.
The `--styles` command flag takes a comma-separated list of values, or `all`, which will configure every theme present in the [themes](/theme) directory.
**Generating Colors** **Generating Colors**
As a reference point, see the pre-generated [themes](/theme). In general, to create a good theme, modify only the highlight colors (blues, greens, purples) to a new color, such that the saturation and lightness stay the same (only the hue changes). For example, the color `rgba(51, 164, 223, 0.5)` becomes `rgba(164, 51, 223, 0.5)`. As a reference point, see the pre-generated [themes](/theme). In general, to create a good theme, modify only the highlight colors (blues, greens, purples) to a new color, such that the saturation and lightness stay the same (only the hue changes). For example, the color `rgba(51, 164, 223, 0.5)` becomes `rgba(164, 51, 223, 0.5)`.
**Extensions**
We also allow customizable extensions to extend the default stylesheets with additional style rules, using the colors defined in your theme. This also enables the integration of third-party Qt plugins/widgets into the generated stylesheets.
For example, to configure with extensions for the [Advanced Docking System](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System), run:
```bash
python configure.py --extensions=advanced-docking-system --resource custom.qrc
```
Like with styles, `--extensions` takes a comma-separated list of values, or `all`, which will add every extension present in the [extensions](/extension) directory. For a detailed introduction to creating your own extensions, see the extensions [tutorial](/extensions/README.md).
# Limitations # Limitations
There are some limitations of using Qt stylesheets in general, which cannot be solved by stylesheets. To get more fine-grained style control, you should subclass `QCommonStyle`: There are some limitations of using Qt stylesheets in general, which cannot be solved by stylesheets. To get more fine-grained style control, you should subclass `QCommonStyle`:
@ -271,6 +285,7 @@ The limitations of stylesheets include:
- Complete stylesheet for all Qt widgets, including esoteric widgets like `QCalendarWidget`. - Complete stylesheet for all Qt widgets, including esoteric widgets like `QCalendarWidget`.
- Customizable, beautiful light and dark themes. - Customizable, beautiful light and dark themes.
- Cross-platform icon packs for standard icons. - Cross-platform icon packs for standard icons.
- Extensible stylesheets: add your own plugins or rules and automatically configure them using the same configuration syntax.
# Debugging # Debugging

View File

@ -8,173 +8,112 @@
import argparse import argparse
import glob import glob
import json import json
import re
import os import os
import re
import sys
home = os.path.dirname(os.path.realpath(__file__)) home = os.path.dirname(os.path.realpath(__file__))
# Create our arguments. def parse_args(argv=None):
parser = argparse.ArgumentParser(description='Styles to configure for a Qt application.') '''Parse the command-line options.'''
parser.add_argument(
'--styles',
help='''comma-separate list of styles to configure. pass `all` to build all themes''',
default='light,dark',
)
parser.add_argument(
'--resource',
help='''output resource file name''',
default='custom.qrc',
)
parser.add_argument(
'--pyqt6',
help='''use PyQt6 rather than PyQt5.''',
action='store_true'
)
# List of all icons to configure. parser = argparse.ArgumentParser(description='Styles to configure for a Qt application.')
icons = { parser.add_argument(
# Arrows '--styles',
'down_arrow': { help='''comma-separate list of styles to configure. pass `all` to build all themes''',
'default': ['foreground:hex', 'foreground:opacity'], default='light,dark',
'hover': ['highlight:hex', 'highlight:opacity'], )
'disabled': ['midtone:light:hex', 'midtone:light:opacity'], parser.add_argument(
}, '--extensions',
'left_arrow': { help='''comma-separate list of styles to configure. pass `all` to build all themes''',
'default': ['foreground'], default='',
'disabled': ['midtone:light'], )
}, parser.add_argument(
'right_arrow': { '--resource',
'default': ['foreground'], help='''output resource file name''',
'disabled': ['midtone:light'], default='custom.qrc',
}, )
'up_arrow': { parser.add_argument(
'default': ['foreground:hex', 'foreground:opacity'], '--pyqt6',
'hover': ['highlight:hex', 'highlight:opacity'], help='''use PyQt6 rather than PyQt5.''',
'disabled': ['midtone:light:hex', 'midtone:light:opacity'], action='store_true'
}, )
# Abstract buttons. args = parser.parse_args(argv)
'checkbox_checked': { parse_styles(args)
'default': ['checkbox:light'], parse_extensions(args)
'disabled': ['checkbox:disabled'], set_style_home(args)
},
'checkbox_indeterminate': { return args
'default': ['checkbox:light'],
'disabled': ['checkbox:disabled'], def load_json(path):
}, '''Read a JSON file with limited comments support.'''
'checkbox_unchecked': {
'default': ['checkbox:light'], # Note: we need comments for maintainability, so we
'disabled': ['checkbox:disabled'], # can annotate what works and the rationale, but
}, # we don't want to prevent code from working without
'radio_checked': { # a complex parser, so we do something very simple:
'default': ['checkbox:light'], # only remove lines starting with '//'.
'disabled': ['checkbox:disabled'], with open(path) as file:
}, lines = file.read().splitlines()
'radio_unchecked': { lines = [i for i in lines if not i.strip().startswith('//')]
'default': ['checkbox:light'], return json.loads('\n'.join(lines))
'disabled': ['checkbox:disabled'],
}, def read_template_dir(directory):
# Dock/Tab widgets '''Read the template data from a directory'''
'close': {
'default': ['midtone:dark:hex', 'midtone:dark:opacity'], data = {
'hover': ['close:hover:hex', 'close:hover:opacity'], 'stylesheet': open(f'{directory}/stylesheet.qss.in').read(),
'pressed': ['close:pressed:hex', 'close:pressed:opacity'], 'icons': [],
}, }
'undock': { icon_data = load_json(f'{directory}/icons.json')
'default': ['dock:float'], for file in glob.glob(f'{directory}/*.svg.in'):
}, svg = open(file).read()
'undock_hover': { name = os.path.splitext(os.path.splitext(os.path.basename(file))[0])[0]
'default': ['dock:float', 'foreground'], if name in icon_data:
}, replacements = icon_data[name]
# Tree views. else:
'branch_open': { # Need to find all the values inside the image.
'default': ['tree:hex', 'tree:opacity'], keys = re.findall(r'\^[0-9a-zA-Z_-]+\^', svg)
'hover': ['highlight:hex', 'highlight:opacity'], replacements = [i[1:-1] for i in keys]
}, data['icons'].append({
'branch_closed': { 'name': name,
'default': ['tree:hex', 'tree:opacity'], 'svg': svg,
'hover': ['highlight:hex', 'highlight:opacity'], 'replacements': replacements,
}, })
'branch_end': {
'default': ['tree'], return data
},
'branch_end_arrow': { def split_csv(string):
'default': ['tree'], '''Split a list of values provided as comma-separated values.'''
},
'branch_more': { values = string.split(',')
'default': ['tree'], return [i for i in values if i]
},
'branch_more_arrow': { def parse_styles(args):
'default': ['tree'], '''Parse a list of valid styles.'''
},
'vline': { values = split_csv(args.styles)
'default': ['tree'], if 'all' in values:
}, files = glob.glob(f'{home}/theme/*json')
'calendar_next': { values = [os.path.splitext(os.path.basename(i))[0] for i in files]
'default': ['foreground'], args.styles = values
},
'calendar_previous': { def parse_extensions(args):
'default': ['foreground'], '''Parse a list of valid extensions.'''
},
'transparent': { values = split_csv(args.extensions)
'default': [], if 'all' in values:
}, files = glob.glob(f'{home}/extension/*/*stylesheet.qss.in')
'hmovetoolbar': { values = [os.path.basename(os.path.dirname(i)) for i in files]
'default': ['midtone:light'], args.extensions = values
},
'vmovetoolbar': { def set_style_home(args):
'default': ['midtone:light'], '''Get the home directory to write the configured styles to.'''
},
'hseptoolbar': { if args.pyqt6:
'default': ['midtone:light'], args.style_home = f'{home}/pyqt6'
}, else:
'vseptoolbar': { args.style_home = f'{home}'
'default': ['midtone:light'],
},
'sizegrip': {
'default': ['midtone:light'],
},
# Dialog icons
'dialog-cancel': {
'default': ['foreground'],
},
'dialog-close': {
'default': ['foreground'],
},
'dialog-ok': {
'default': ['foreground'],
},
'dialog-open': {
'default': ['foreground'],
},
'dialog-save': {
'default': ['foreground'],
},
'dialog-reset': {
'default': ['foreground'],
},
'dialog-help': {
'default': ['foreground'],
},
'dialog-no': {
'default': ['foreground'],
},
'dialog-discard': {
'default': ['foreground'],
},
# Message icons
'message-critical': {
'default': ['critical', 'foreground'],
},
'message-information': {
'default': ['information', 'foreground'],
},
'message-question': {
'default': ['question', 'foreground'],
},
'message-warning': {
'default': ['warning', 'foreground'],
},
}
def parse_hexcolor(color): def parse_hexcolor(color):
'''Parse a hexadecimal color.''' '''Parse a hexadecimal color.'''
@ -219,70 +158,119 @@ def parse_color(color):
return parse_rgba(color) return parse_rgba(color)
raise NotImplementedError raise NotImplementedError
def replace(contents, colors, color_map): def icon_basename(icon, extension):
'''Replace all template values.''' '''Get the basename for an icon.'''
if extension == 'default':
return icon
return f'{icon}_{extension}'
def replace_by_name(contents, theme, colors=None):
'''Replace values by color name.'''
# The placeholders have a syntax like `^foreground^`.
# To simplify the replacement process, you can specify
# a limited subset of colors, rather than use all of them.
if colors is None:
colors = theme.keys()
for key in colors:
color = theme[key]
contents = contents.replace(f'^{key}^', color)
return contents
def replace_by_index(contents, theme, colors):
'''Replace values by color name.'''
# The placeholders have a syntax like `^0^`, where
# the is a list of valid colors and the index of
# the color is the replacement key.
# This is useful since we can want multiple colors
# for the same icon (such as hovered arrows).
for index, key in enumerate(colors): for index, key in enumerate(colors):
sub = f'^{index}^' sub = f'^{index}^'
# Need special handling if we have a hex or non:hex character. # Need special handle values with opacities. Standard
# SVG currently does not support `rgba` syntax, with an
# opacity, but it does provide `fill-opacity` and `stroke-opacity`.
# Therefore, if the replacement specifies `opacity` or `hex`,
# parse the color, get the correct value, and use only that
# for the replacement.
if key.endswith(':hex'): if key.endswith(':hex'):
color = color_map[key[:-len(':hex')]] color = theme[key[:-len(':hex')]]
rgb = [f"{i:02x}" for i in parse_color(color)[:3]] rgb = [f"{i:02x}" for i in parse_color(color)[:3]]
value = f'#{"".join(rgb)}' value = f'#{"".join(rgb)}'
elif key.endswith(':opacity'): elif key.endswith(':opacity'):
color = color_map[key[:-len(':opacity')]] color = theme[key[:-len(':opacity')]]
value = str(parse_color(color)[3]) value = str(parse_color(color)[3])
else: else:
value = color_map[key] value = theme[key]
contents = contents.replace(sub, value) contents = contents.replace(sub, value)
return contents return contents
def configure_icons(style, color_map): def configure_icons(config, style):
'''Configure icons for a given style.''' '''Configure icons for a given style.'''
for icon, extensions in icons.items(): theme = config['themes'][style]
template = f'{home}/template/{icon}.svg.in' style_home = config['style_home']
template_contents = open(template).read() for template in config['templates']:
for extension, colors in extensions.items(): for icon in template['icons']:
contents = replace(template_contents, colors, color_map) replacements = icon['replacements']
if extension == 'default': name = icon['name']
filename = f'{style_home}/{style}/{icon}.svg' if isinstance(replacements, dict):
# Then we have the following format:
# The key is the substate of the icon, such
# as default, hover, pressed, etc, and the value
# is an ordered list of replacements.
for ext, colors in replacements.items():
contents = replace_by_index(icon['svg'], theme, colors)
filename = f'{style_home}/{style}/{icon_basename(name, ext)}.svg'
with open(filename, 'w') as file:
file.write(contents)
else: else:
filename = f'{style_home}/{style}/{icon}_{extension}.svg' # Then we just have a list of replacements for the
with open(filename, 'w') as file: # icon, using standard colors. For example,
file.write(contents) # replacement values might be `^foreground^`.
assert isinstance(replacements, list)
contents = replace_by_name(icon['svg'], theme, replacements)
filename = f'{style_home}/{style}/{name}.svg'
with open(filename, 'w') as file:
file.write(contents)
def configure_stylesheet(style, color_map): def configure_stylesheet(config, style):
'''Configure the stylesheet for a given style.''' '''Configure the stylesheet for a given style.'''
contents = open(f'{home}/template/stylesheet.qss.in').read() contents = '\n'.join([i['stylesheet'] for i in config['templates']])
for key, color in color_map.items(): contents = replace_by_name(contents, config['themes'][style])
contents = contents.replace(f'^{key}^', color) # Need to replace the URL paths for loading icons/
if args.pyqt6: # assets. In C++ Qt and PyQt5, this uses the resource
# system, AKA, `url(:/dark/path/to/resource)`. In PyQt6, the
# resource system has been replaced to use native
# Python packaging, so we define a user-friendly name
# based on the theme name, so `url(dark:path/to/resource)`.
if config['pyqt6']:
contents = contents.replace('^style^', f'{style}:') contents = contents.replace('^style^', f'{style}:')
else: else:
contents = contents.replace('^style^', f':/{style}/') contents = contents.replace('^style^', f':/{style}/')
with open(f'{style_home}/{style}/stylesheet.qss', 'w') as file: with open(f'{config["style_home"]}/{style}/stylesheet.qss', 'w') as file:
file.write(contents) file.write(contents)
def configure_style(style, color_map): def configure_style(config, style):
'''Configure the icons and stylesheet for a given style.''' '''Configure the icons and stylesheet for a given style.'''
os.makedirs(f'{style_home}/{style}', exist_ok=True) os.makedirs(f'{config["style_home"]}/{style}', exist_ok=True)
configure_icons(style, color_map) configure_icons(config, style)
configure_stylesheet(style, color_map) configure_stylesheet(config, style)
def write_xml(styles, path): def write_xml(config):
'''Simple QRC writer.''' '''Simple QRC writer.'''
# Can't be used with PyQt6. # rcc doesn't exist for PyQt6
assert not args.pyqt6 assert not config['pyqt6']
resources = [] resources = []
for style in styles: for style in config['themes'].keys():
files = os.listdir(f'{style_home}/{style}') files = os.listdir(f'{config["style_home"]}/{style}')
resources += [f'{style}/{i}' for i in files] resources += [f'{style}/{i}' for i in files]
with open(path, 'w') as file: with open(config['path'], 'w') as file:
print('<RCC>', file=file) print('<RCC>', file=file)
print(' <qresource>', file=file) print(' <qresource>', file=file)
for resource in sorted(resources): for resource in sorted(resources):
@ -290,34 +278,34 @@ def write_xml(styles, path):
print(' </qresource>', file=file) print(' </qresource>', file=file)
print('</RCC>', file=file) print('</RCC>', file=file)
def configure(styles, path): def configure(args):
'''Configure all styles and write the files to a QRC file.''' '''Configure all styles and write the files to a QRC file.'''
for style in styles: # Need to convert our styles accordingly.
# Note: we need comments for maintainability, so we config = {
# can annotate what works and the rationale, but 'themes': {},
# we don't want to prevent code from working without 'templates': [],
# a complex parser, so we do something very simple: 'pyqt6': args.pyqt6,
# only remove lines starting with '//'. 'style_home': args.style_home,
with open(f'{home}/theme/{style}.json') as file: 'path': args.resource
lines = file.read().splitlines() }
lines = [i for i in lines if not i.strip().startswith('//')] config['templates'].append(read_template_dir(f'{home}/template'))
color_map = json.loads('\n'.join(lines)) for style in args.styles:
configure_style(style, color_map) config['themes'][style] = load_json(f'{home}/theme/{style}.json')
for extension in args.extensions:
config['templates'].append(read_template_dir(f'{home}/extension/{extension}'))
for style in config['themes'].keys():
configure_style(config, style)
if not args.pyqt6: if not args.pyqt6:
# No point generating a resource file for PyQt6, # No point generating a resource file for PyQt6,
# since we can't use rcc6 anyway. # since we can't use rcc6 anyway.
write_xml(styles, path) write_xml(config)
def main(argv=None):
'''Configuration entry point'''
configure(parse_args(argv))
if __name__ == '__main__': if __name__ == '__main__':
args = parser.parse_args() sys.exit(main())
styles = args.styles.split(',')
if args.pyqt6:
style_home = f'{home}/pyqt6'
else:
style_home = f'{home}'
if args.styles == 'all':
files = glob.glob(f'{home}/theme/*json')
styles = [os.path.splitext(os.path.basename(i))[0] for i in files]
configure(styles, args.resource)

View File

@ -12,7 +12,7 @@
* The MIT License (MIT) * The MIT License (MIT)
* *
* Copyright (c) <2013-2014> <Colin Duquesnoy> * Copyright (c) <2013-2014> <Colin Duquesnoy>
* Copyright (c) <2015-2016> <Alex Huszagh> * Copyright (c) <2015-2021> <Alex Huszagh>
* *
* Permission is hereby granted, free of charge, to any person obtaining * Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the * a copy of this software and associated documentation files (the
@ -1951,3 +1951,36 @@ QMessageBox QPushButton
min-height: 1.1em; min-height: 1.1em;
min-width: 5em; min-width: 5em;
} }
/*
* Extension stylesheet for the Advanced Docking System.
*
* :author: Alex Huszagh
* :license: MIT, see LICENSE.md
*
* ---------------------------------------------------------------------
* The MIT License (MIT)
*
* Copyright (c) <2021> <Alex Huszagh>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ---------------------------------------------------------------------
*/

11
extension/README.md Normal file
View File

@ -0,0 +1,11 @@
extensions
==========
Extensions enable the creation of stylesheets using the same, customizable themes of the original stylesheet. This both allows refining the generated stylesheet and supporting third-party Qt plugins/widgets.
**TODO(ahuszagh)**
- Describe stylesheet.qss.in
- Describe icons.json
Document this...

View File

@ -0,0 +1,4 @@
// NOTE: This is a custom JSON file, where lines leading
// with `//` are removed. No other comments are valid.
{
}

View File

@ -0,0 +1,32 @@
/*
* Extension stylesheet for the Advanced Docking System.
*
* :author: Alex Huszagh
* :license: MIT, see LICENSE.md
*
* ---------------------------------------------------------------------
* The MIT License (MIT)
*
* Copyright (c) <2021> <Alex Huszagh>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ---------------------------------------------------------------------
*/

View File

@ -12,7 +12,7 @@
* The MIT License (MIT) * The MIT License (MIT)
* *
* Copyright (c) <2013-2014> <Colin Duquesnoy> * Copyright (c) <2013-2014> <Colin Duquesnoy>
* Copyright (c) <2015-2016> <Alex Huszagh> * Copyright (c) <2015-2021> <Alex Huszagh>
* *
* Permission is hereby granted, free of charge, to any person obtaining * Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the * a copy of this software and associated documentation files (the
@ -1951,3 +1951,36 @@ QMessageBox QPushButton
min-height: 1.1em; min-height: 1.1em;
min-width: 5em; min-width: 5em;
} }
/*
* Extension stylesheet for the Advanced Docking System.
*
* :author: Alex Huszagh
* :license: MIT, see LICENSE.md
*
* ---------------------------------------------------------------------
* The MIT License (MIT)
*
* Copyright (c) <2021> <Alex Huszagh>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* ---------------------------------------------------------------------
*/

View File

@ -1,4 +0,0 @@
recipes
=======
Sample recipes and template files for adding third-party Qt widgets and stylesheets into BreezeStyleSheets.

View File

@ -1,11 +0,0 @@
<svg height="90" width="90">
<g transform="matrix(4.05,0,0,4.05,4.55,12.55)">
<g fill="^0^" fill-rule="evenodd" transform="translate(-255,-381)">
<g transform="translate(255,381)">
<path d="M 14,0 H 2 C 0.9,0 0,0.9 0,2 v 14 c 0,1.1 0.9,2 2,2 h 14 c 1.1,0 2,-0.9 2,-2 V 4 Z M 9,16 c -1.7,0 -3,-1.3 -3,-3 0,-1.7 1.3,-3 3,-3 1.7,0 3,1.3 3,3 0,1.7 -1.3,3 -3,3 z M 12,6 H 2 V 2 h 10 z"/>
</g>
</g>
</g>
<path stroke="^0^" stroke-width="4" fill="none" d="m 18.798165,13.22123 c 0,-3.588874 2.889238,-6.478111 6.478111,-6.478111 h 6.51694 6.516941 6.51694 6.51694 6.51694 6.51694 m 0,0 h 6.516941 m 12.995051,13.36252 v 6.884409 6.884409 6.88441 6.884409 6.884409 6.884409 6.884409 c 0,3.588873 -2.889237,6.47811 -6.478111,6.47811"/>
<path stroke="^0^" stroke-width="4" stroke-linecap="butt" stroke-linejoin="miter" d="M 69.439106,6.1172946 84.439417,21.496069"/>
</svg>

Before

Width:  |  Height:  |  Size: 900 B

163
template/icons.json Normal file
View File

@ -0,0 +1,163 @@
// NOTE: This is a custom JSON file, where lines leading
// with `//` are removed. No other comments are valid.
{
// There's 3 different ways to provide valid replacement colors for
// icons:
// 1. Provide a dict of the icon suffix to the color replacements.
// This will do index-based replacement, so, for example,
// `"down_arrow": { "default": ["foreground"] }` will
// replace the `down_arrow` template and create an icon
// named `down_arrow.svg` with `^0^` replaced by the
// theme's foreground color.
// 2. Provide a list to the color replacements.
// This will do name-based replacement, so, for example,
// `"down_arrow": ["foreground"]` will replace the
// `down_arrow` template and create an icon named
// `down_arrow.svg` with `^foreground^` replaced by
// the theme's foreground color.
// 3. Don't provide an entry for the icon.
// The replacements will be auto-deduced from the file,
// and will use name-based replacement like above.
// Arrows
"down_arrow": {
"default": ["foreground:hex", "foreground:opacity"],
"hover": ["highlight:hex", "highlight:opacity"],
"disabled": ["midtone:light:hex", "midtone:light:opacity"]
},
"left_arrow": {
"default": ["foreground"],
"disabled": ["midtone:light"]
},
"right_arrow": {
"default": ["foreground"],
"disabled": ["midtone:light"]
},
"up_arrow": {
"default": ["foreground:hex", "foreground:opacity"],
"hover": ["highlight:hex", "highlight:opacity"],
"disabled": ["midtone:light:hex", "midtone:light:opacity"]
},
// Abstract buttons.
"checkbox_checked": {
"default": ["checkbox:light"],
"disabled": ["checkbox:disabled"]
},
"checkbox_indeterminate": {
"default": ["checkbox:light"],
"disabled": ["checkbox:disabled"]
},
"checkbox_unchecked": {
"default": ["checkbox:light"],
"disabled": ["checkbox:disabled"]
},
"radio_checked": {
"default": ["checkbox:light"],
"disabled": ["checkbox:disabled"]
},
"radio_unchecked": {
"default": ["checkbox:light"],
"disabled": ["checkbox:disabled"]
},
// Dock/Tab widgets
"close": {
"default": ["midtone:dark:hex", "midtone:dark:opacity"],
"hover": ["close:hover:hex", "close:hover:opacity"],
"pressed": ["close:pressed:hex", "close:pressed:opacity"]
},
"undock": {
"default": ["dock:float"]
},
"undock_hover": {
"default": ["dock:float", "foreground"]
},
// Tree views.
"branch_open": {
"default": ["tree:hex", "tree:opacity"],
"hover": ["highlight:hex", "highlight:opacity"]
},
"branch_closed": {
"default": ["tree:hex", "tree:opacity"],
"hover": ["highlight:hex", "highlight:opacity"]
},
"branch_end": {
"default": ["tree"]
},
"branch_end_arrow": {
"default": ["tree"]
},
"branch_more": {
"default": ["tree"]
},
"branch_more_arrow": {
"default": ["tree"]
},
"vline": {
"default": ["tree"]
},
"calendar_next": {
"default": ["foreground"]
},
"calendar_previous": {
"default": ["foreground"]
},
"transparent": {
"default": []
},
"hmovetoolbar": {
"default": ["midtone:light"]
},
"vmovetoolbar": {
"default": ["midtone:light"]
},
"hseptoolbar": {
"default": ["midtone:light"]
},
"vseptoolbar": {
"default": ["midtone:light"]
},
"sizegrip": {
"default": ["midtone:light"]
},
// Dialog icons
"dialog-cancel": {
"default": ["foreground"]
},
"dialog-close": {
"default": ["foreground"]
},
"dialog-ok": {
"default": ["foreground"]
},
"dialog-open": {
"default": ["foreground"]
},
"dialog-save": {
"default": ["foreground"]
},
"dialog-reset": {
"default": ["foreground"]
},
"dialog-help": {
"default": ["foreground"]
},
"dialog-no": {
"default": ["foreground"]
},
"dialog-discard": {
"default": ["foreground"]
},
// Message icons
"message-critical": {
"default": ["critical", "foreground"]
},
"message-information": {
"default": ["information", "foreground"]
},
"message-question": {
"default": ["question", "foreground"]
},
"message-warning": {
"default": ["warning", "foreground"]
}
}

View File

@ -12,7 +12,7 @@
* The MIT License (MIT) * The MIT License (MIT)
* *
* Copyright (c) <2013-2014> <Colin Duquesnoy> * Copyright (c) <2013-2014> <Colin Duquesnoy>
* Copyright (c) <2015-2016> <Alex Huszagh> * Copyright (c) <2015-2021> <Alex Huszagh>
* *
* Permission is hereby granted, free of charge, to any person obtaining * Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the * a copy of this software and associated documentation files (the