Add support for the extensions syntax in configurations.
parent
458fb583dc
commit
846254032c
15
README.md
15
README.md
|
@ -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
|
||||||
|
|
||||||
|
|
424
configure.py
424
configure.py
|
@ -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)
|
|
||||||
|
|
|
@ -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.
|
||||||
|
* ---------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
|
@ -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...
|
|
@ -0,0 +1,4 @@
|
||||||
|
// NOTE: This is a custom JSON file, where lines leading
|
||||||
|
// with `//` are removed. No other comments are valid.
|
||||||
|
{
|
||||||
|
}
|
|
@ -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.
|
||||||
|
* ---------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
|
@ -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.
|
||||||
|
* ---------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
recipes
|
|
||||||
=======
|
|
||||||
|
|
||||||
Sample recipes and template files for adding third-party Qt widgets and stylesheets into BreezeStyleSheets.
|
|
|
@ -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 |
|
@ -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"]
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue