From 846254032c09ff54b274faf9bdd48daee811d3f6 Mon Sep 17 00:00:00 2001 From: Alex Huszagh Date: Mon, 19 Jul 2021 21:51:24 -0500 Subject: [PATCH] Add support for the extensions syntax in configurations. --- README.md | 15 + configure.py | 424 +++++++++--------- dark/stylesheet.qss | 35 +- extension/README.md | 11 + extension/advanced-docking-system/icons.json | 4 + .../advanced-docking-system/stylesheet.qss.in | 32 ++ light/stylesheet.qss | 35 +- recipes/README.md | 4 - template/dialog-saveall.svg.in | 11 - template/icons.json | 163 +++++++ template/stylesheet.qss.in | 2 +- {tests => test}/ui.py | 0 12 files changed, 500 insertions(+), 236 deletions(-) create mode 100644 extension/README.md create mode 100644 extension/advanced-docking-system/icons.json create mode 100644 extension/advanced-docking-system/stylesheet.qss.in delete mode 100644 recipes/README.md delete mode 100644 template/dialog-saveall.svg.in create mode 100644 template/icons.json rename {tests => test}/ui.py (100%) diff --git a/README.md b/README.md index 2bebd57..8a4097c 100644 --- a/README.md +++ b/README.md @@ -243,10 +243,24 @@ python configure.py --styles=dark,light, --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. +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** 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 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`. - Customizable, beautiful light and dark themes. - 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 diff --git a/configure.py b/configure.py index 76a19b3..78758ad 100644 --- a/configure.py +++ b/configure.py @@ -8,173 +8,112 @@ import argparse import glob import json -import re import os +import re +import sys home = os.path.dirname(os.path.realpath(__file__)) -# Create our arguments. -parser = argparse.ArgumentParser(description='Styles to configure for a Qt application.') -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' -) +def parse_args(argv=None): + '''Parse the command-line options.''' -# List of all icons to configure. -icons = { - # 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'], - }, -} + parser = argparse.ArgumentParser(description='Styles to configure for a Qt application.') + parser.add_argument( + '--styles', + help='''comma-separate list of styles to configure. pass `all` to build all themes''', + default='light,dark', + ) + parser.add_argument( + '--extensions', + help='''comma-separate list of styles to configure. pass `all` to build all themes''', + default='', + ) + 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' + ) + args = parser.parse_args(argv) + parse_styles(args) + parse_extensions(args) + set_style_home(args) + + return args + +def load_json(path): + '''Read a JSON file with limited comments support.''' + + # Note: we need comments for maintainability, so we + # can annotate what works and the rationale, but + # we don't want to prevent code from working without + # a complex parser, so we do something very simple: + # only remove lines starting with '//'. + with open(path) as file: + lines = file.read().splitlines() + lines = [i for i in lines if not i.strip().startswith('//')] + return json.loads('\n'.join(lines)) + +def read_template_dir(directory): + '''Read the template data from a directory''' + + data = { + 'stylesheet': open(f'{directory}/stylesheet.qss.in').read(), + 'icons': [], + } + icon_data = load_json(f'{directory}/icons.json') + for file in glob.glob(f'{directory}/*.svg.in'): + svg = open(file).read() + name = os.path.splitext(os.path.splitext(os.path.basename(file))[0])[0] + if name in icon_data: + replacements = icon_data[name] + else: + # Need to find all the values inside the image. + keys = re.findall(r'\^[0-9a-zA-Z_-]+\^', svg) + replacements = [i[1:-1] for i in keys] + data['icons'].append({ + 'name': name, + 'svg': svg, + 'replacements': replacements, + }) + + return data + +def split_csv(string): + '''Split a list of values provided as comma-separated values.''' + + values = string.split(',') + return [i for i in values if i] + +def parse_styles(args): + '''Parse a list of valid styles.''' + + values = split_csv(args.styles) + if 'all' in values: + files = glob.glob(f'{home}/theme/*json') + values = [os.path.splitext(os.path.basename(i))[0] for i in files] + args.styles = values + +def parse_extensions(args): + '''Parse a list of valid extensions.''' + + values = split_csv(args.extensions) + if 'all' in values: + files = glob.glob(f'{home}/extension/*/*stylesheet.qss.in') + values = [os.path.basename(os.path.dirname(i)) for i in files] + args.extensions = values + +def set_style_home(args): + '''Get the home directory to write the configured styles to.''' + + if args.pyqt6: + args.style_home = f'{home}/pyqt6' + else: + args.style_home = f'{home}' def parse_hexcolor(color): '''Parse a hexadecimal color.''' @@ -219,70 +158,119 @@ def parse_color(color): return parse_rgba(color) raise NotImplementedError -def replace(contents, colors, color_map): - '''Replace all template values.''' +def icon_basename(icon, extension): + '''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): 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'): - color = color_map[key[:-len(':hex')]] + color = theme[key[:-len(':hex')]] rgb = [f"{i:02x}" for i in parse_color(color)[:3]] value = f'#{"".join(rgb)}' elif key.endswith(':opacity'): - color = color_map[key[:-len(':opacity')]] + color = theme[key[:-len(':opacity')]] value = str(parse_color(color)[3]) else: - value = color_map[key] + value = theme[key] contents = contents.replace(sub, value) return contents -def configure_icons(style, color_map): +def configure_icons(config, style): '''Configure icons for a given style.''' - for icon, extensions in icons.items(): - template = f'{home}/template/{icon}.svg.in' - template_contents = open(template).read() - for extension, colors in extensions.items(): - contents = replace(template_contents, colors, color_map) - if extension == 'default': - filename = f'{style_home}/{style}/{icon}.svg' + theme = config['themes'][style] + style_home = config['style_home'] + for template in config['templates']: + for icon in template['icons']: + replacements = icon['replacements'] + name = icon['name'] + 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: - filename = f'{style_home}/{style}/{icon}_{extension}.svg' - with open(filename, 'w') as file: - file.write(contents) + # Then we just have a list of replacements for the + # icon, using standard colors. For example, + # 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.''' - contents = open(f'{home}/template/stylesheet.qss.in').read() - for key, color in color_map.items(): - contents = contents.replace(f'^{key}^', color) - if args.pyqt6: + contents = '\n'.join([i['stylesheet'] for i in config['templates']]) + contents = replace_by_name(contents, config['themes'][style]) + # Need to replace the URL paths for loading icons/ + # 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}:') else: 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) -def configure_style(style, color_map): +def configure_style(config, style): '''Configure the icons and stylesheet for a given style.''' - os.makedirs(f'{style_home}/{style}', exist_ok=True) - configure_icons(style, color_map) - configure_stylesheet(style, color_map) + os.makedirs(f'{config["style_home"]}/{style}', exist_ok=True) + configure_icons(config, style) + configure_stylesheet(config, style) -def write_xml(styles, path): +def write_xml(config): '''Simple QRC writer.''' - # Can't be used with PyQt6. - assert not args.pyqt6 + # rcc doesn't exist for PyQt6 + assert not config['pyqt6'] resources = [] - for style in styles: - files = os.listdir(f'{style_home}/{style}') + for style in config['themes'].keys(): + files = os.listdir(f'{config["style_home"]}/{style}') resources += [f'{style}/{i}' for i in files] - with open(path, 'w') as file: + with open(config['path'], 'w') as file: print('', file=file) print(' ', file=file) for resource in sorted(resources): @@ -290,34 +278,34 @@ def write_xml(styles, path): print(' ', file=file) print('', file=file) -def configure(styles, path): +def configure(args): '''Configure all styles and write the files to a QRC file.''' - for style in styles: - # Note: we need comments for maintainability, so we - # can annotate what works and the rationale, but - # we don't want to prevent code from working without - # a complex parser, so we do something very simple: - # only remove lines starting with '//'. - with open(f'{home}/theme/{style}.json') as file: - lines = file.read().splitlines() - lines = [i for i in lines if not i.strip().startswith('//')] - color_map = json.loads('\n'.join(lines)) - configure_style(style, color_map) + # Need to convert our styles accordingly. + config = { + 'themes': {}, + 'templates': [], + 'pyqt6': args.pyqt6, + 'style_home': args.style_home, + 'path': args.resource + } + config['templates'].append(read_template_dir(f'{home}/template')) + for style in args.styles: + 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: # No point generating a resource file for PyQt6, # 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__': - args = parser.parse_args() - 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) + sys.exit(main()) diff --git a/dark/stylesheet.qss b/dark/stylesheet.qss index 9e8c25d..013c909 100644 --- a/dark/stylesheet.qss +++ b/dark/stylesheet.qss @@ -12,7 +12,7 @@ * The MIT License (MIT) * * Copyright (c) <2013-2014> - * Copyright (c) <2015-2016> + * Copyright (c) <2015-2021> * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -1951,3 +1951,36 @@ QMessageBox QPushButton min-height: 1.1em; 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> + * + * 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. + * --------------------------------------------------------------------- + */ + diff --git a/extension/README.md b/extension/README.md new file mode 100644 index 0000000..34143a6 --- /dev/null +++ b/extension/README.md @@ -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... diff --git a/extension/advanced-docking-system/icons.json b/extension/advanced-docking-system/icons.json new file mode 100644 index 0000000..859c964 --- /dev/null +++ b/extension/advanced-docking-system/icons.json @@ -0,0 +1,4 @@ +// NOTE: This is a custom JSON file, where lines leading +// with `//` are removed. No other comments are valid. +{ +} diff --git a/extension/advanced-docking-system/stylesheet.qss.in b/extension/advanced-docking-system/stylesheet.qss.in new file mode 100644 index 0000000..5b5356f --- /dev/null +++ b/extension/advanced-docking-system/stylesheet.qss.in @@ -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> + * + * 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. + * --------------------------------------------------------------------- + */ + diff --git a/light/stylesheet.qss b/light/stylesheet.qss index 29db65b..b5c3f88 100644 --- a/light/stylesheet.qss +++ b/light/stylesheet.qss @@ -12,7 +12,7 @@ * The MIT License (MIT) * * Copyright (c) <2013-2014> - * Copyright (c) <2015-2016> + * Copyright (c) <2015-2021> * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -1951,3 +1951,36 @@ QMessageBox QPushButton min-height: 1.1em; 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> + * + * 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. + * --------------------------------------------------------------------- + */ + diff --git a/recipes/README.md b/recipes/README.md deleted file mode 100644 index 66ba4f5..0000000 --- a/recipes/README.md +++ /dev/null @@ -1,4 +0,0 @@ -recipes -======= - -Sample recipes and template files for adding third-party Qt widgets and stylesheets into BreezeStyleSheets. diff --git a/template/dialog-saveall.svg.in b/template/dialog-saveall.svg.in deleted file mode 100644 index eec55c9..0000000 --- a/template/dialog-saveall.svg.in +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/template/icons.json b/template/icons.json new file mode 100644 index 0000000..d950030 --- /dev/null +++ b/template/icons.json @@ -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"] + } +} diff --git a/template/stylesheet.qss.in b/template/stylesheet.qss.in index f93fa8b..af70b05 100644 --- a/template/stylesheet.qss.in +++ b/template/stylesheet.qss.in @@ -12,7 +12,7 @@ * The MIT License (MIT) * * Copyright (c) <2013-2014> - * Copyright (c) <2015-2016> + * Copyright (c) <2015-2021> * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the diff --git a/tests/ui.py b/test/ui.py similarity index 100% rename from tests/ui.py rename to test/ui.py