Make mdi icons for Qt themeable (#2078)

* Fix create_vars_from_map not creating vars with default definition

* Add white and black to vars

* Replace some hard-coded SVGs with mdi equivalents

* Implement function to dynamically adjust SVG icon color

* Use new svg function to make Qt stylesheet icons respond to theme changes

* Use svg function for sidebar tool icons

* Create copy for each new color instead of modifying source file

* Fix check fails

* Add custom checkbox style for #2079

* Add example of how to generate svgs during build (dae)

* Create arbitrary color variants for each icon with Bazel

* Remove unused label (dae)
This commit is contained in:
Matthias Metelka 2022-09-21 04:02:30 +02:00 committed by GitHub
parent e109c62aa9
commit bff76727fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 276 additions and 296 deletions

View file

@ -2,7 +2,6 @@ load("@rules_python//python:defs.bzl", "py_library")
load("@py_deps//:requirements.bzl", "requirement") load("@py_deps//:requirements.bzl", "requirement")
load("@rules_python//python:packaging.bzl", "py_package", "py_wheel") load("@rules_python//python:packaging.bzl", "py_package", "py_wheel")
load("//:defs.bzl", "anki_version") load("//:defs.bzl", "anki_version")
load("//ts:copy.bzl", "copy_files_into_group") load("//ts:copy.bzl", "copy_files_into_group")
load("//ts:compile_sass.bzl", "compile_sass") load("//ts:compile_sass.bzl", "compile_sass")
@ -31,13 +30,22 @@ genrule(
srcs = [ srcs = [
"_vars.css", "_vars.css",
], ],
outs = ["colors.py", "props.py"], outs = [
"colors.py",
"props.py",
],
cmd = "$(location //qt:extract_sass_vars) $(SRCS) $(OUTS)", cmd = "$(location //qt:extract_sass_vars) $(SRCS) $(OUTS)",
tools = [ tools = [
"//qt:extract_sass_vars", "//qt:extract_sass_vars",
], ],
) )
py_library(
name = "colors",
srcs = [":colors.py"],
visibility = ["//qt:__subpackages__"],
)
_py_srcs = glob( _py_srcs = glob(
[ [
"**/*.py", "**/*.py",

View file

@ -20,8 +20,16 @@ class SidebarTool(Enum):
class SidebarToolbar(QToolBar): class SidebarToolbar(QToolBar):
_tools: tuple[tuple[SidebarTool, str, Callable[[], str]], ...] = ( _tools: tuple[tuple[SidebarTool, str, Callable[[], str]], ...] = (
(SidebarTool.SEARCH, "icons:magnifying_glass.svg", tr.actions_search), (
(SidebarTool.SELECT, "icons:select.svg", tr.actions_select), SidebarTool.SEARCH,
"mdi:magnify",
tr.actions_search,
),
(
SidebarTool.SELECT,
"mdi:selection-drag",
tr.actions_select,
),
) )
def __init__(self, sidebar: aqt.browser.sidebar.SidebarTreeView) -> None: def __init__(self, sidebar: aqt.browser.sidebar.SidebarTreeView) -> None:

View file

@ -1,4 +1,5 @@
load("//ts:vendor.bzl", "copy_mdi_icons") load("//ts:vendor.bzl", "copy_mdi_icons")
load("color_svg.bzl", "color_svg")
copy_mdi_icons( copy_mdi_icons(
name = "mdi-icons", name = "mdi-icons",
@ -33,6 +34,19 @@ copy_mdi_icons(
# tags # tags
"tag-outline.svg", "tag-outline.svg",
"tag-off-outline.svg", "tag-off-outline.svg",
],
)
copy_mdi_icons(
name = "mdi-themed",
icons = [
# sidebar tools
"magnify.svg",
"selection-drag.svg",
# QComboBox arrows
"chevron-up.svg",
"chevron-down.svg",
# QHeaderView arrows # QHeaderView arrows
"menu-up.svg", "menu-up.svg",
@ -41,12 +55,73 @@ copy_mdi_icons(
# drag handle # drag handle
"drag-vertical.svg", "drag-vertical.svg",
"drag-horizontal.svg", "drag-horizontal.svg",
# checkbox
"check.svg",
"minus-thick.svg",
], ],
) )
py_binary(
name = "color_svg",
srcs = [
"color_svg.py",
"//qt/aqt:colors",
],
imports = ["."],
visibility = [":__subpackages__"],
)
color_svg(
name = "magnify",
)
color_svg(
name = "selection-drag",
)
color_svg(
name = "chevron-up",
extra_colors = ["FG_DISABLED"],
)
color_svg(
name = "chevron-down",
extra_colors = ["FG_DISABLED"],
)
color_svg(
name = "menu-up",
)
color_svg(
name = "menu-down",
)
color_svg(
name = "drag-vertical",
extra_colors = ["FG_SUBTLE"],
)
color_svg(
name = "drag-horizontal",
extra_colors = ["FG_SUBTLE"],
)
color_svg(
name = "check",
)
color_svg(
name = "minus-thick",
)
filegroup( filegroup(
name = "icons", name = "icons",
srcs = ["mdi-icons"] + glob([ srcs = [
"mdi-icons",
"magnify",
"selection-drag",
"chevron-up",
"chevron-down",
"menu-up",
"menu-down",
"drag-vertical",
"drag-horizontal",
"check",
"minus-thick",
] + glob([
"*.svg", "*.svg",
"*.png", "*.png",
]), ]),

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="#f4f4f4" d="M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z" />
</svg>

Before

Width:  |  Height:  |  Size: 157 B

View file

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="#f4f4f4" d="M7.41,15.41L12,10.83L16.59,15.41L18,14L12,8L6,14L7.41,15.41Z" />
</svg>

Before

Width:  |  Height:  |  Size: 159 B

View file

@ -0,0 +1,30 @@
def color_svg(name, extra_colors = [], visibility = ["//qt:__submodules__"]):
native.genrule(
name = name,
srcs = ["mdi-themed"],
outs = [
name + "-light.svg",
] + [
# additional light colors
"{}{}{}".format(
name,
"-{}".format(color),
"-light.svg"
) for color in extra_colors
] + [
name + "-dark.svg",
] + [
# additional dark colors
"{}{}{}".format(
name,
"-{}".format(color),
"-dark.svg"
) for color in extra_colors
],
cmd = "$(location color_svg) {}.svg {} $(OUTS) $(SRCS)".format(
name, ":".join(["FG"] + extra_colors)
),
tools = [
"color_svg",
],
)

View file

@ -0,0 +1,47 @@
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import re
import sys
from pathlib import Path
from qt.aqt import colors
input_filename = sys.argv[1]
input_name = input_filename.replace(".svg", "")
color_names = sys.argv[2].split(":")
# two files created for each additional color
offset = len(color_names) * 2
svg_paths = sys.argv[3 : 3 + offset]
# as we've received a group of files, we need to manually join the path
input_folder = Path(sys.argv[4]).parent
input_svg = input_folder / input_filename
with open(input_svg, "r") as f:
svg_data = f.read()
for color_name in color_names:
color = getattr(colors, color_name)
light_svg = dark_svg = ""
if color_name == "FG":
prefix = input_name
else:
prefix = f"{input_name}-{color_name}"
for path in svg_paths:
if f"{prefix}-light.svg" in path:
light_svg = path
elif f"{prefix}-dark.svg" in path:
dark_svg = path
for (idx, filename) in enumerate((light_svg, dark_svg)):
data = svg_data
if "fill" in data:
data = re.sub(r"fill=\"#.+?\"", f'fill="{color[idx]}"', data)
else:
data = re.sub(r"<svg", f'<svg fill="{color[idx]}"', data, 1)
with open(filename, "w") as f:
f.write(data)

View file

@ -1,84 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="64"
height="64"
viewBox="0 0 16.933333 16.933334"
version="1.1"
id="svg8"
inkscape:version="1.0.2-2 (e86c870879, 2021-01-15)"
sodipodi:docname="magnifying_glass.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="8"
inkscape:cx="10.039334"
inkscape:cy="35.645602"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
gridtolerance="10000"
objecttolerance="51"
guidetolerance="51"
inkscape:snap-global="false">
<inkscape:grid
type="xygrid"
id="grid833" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1">
<circle
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.38115;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
id="path835"
cx="5.5429349"
cy="5.5176048"
r="4.7567849" />
<g
id="path837"
style="opacity:1"
transform="translate(0.280633,0.25724692)">
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.1866;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 9.270412,9.1682417 5.763677,5.8797903"
id="path3348" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.997025;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="M 8.519367,8.5427531 C 7.9111727,9.1535363 7.8640343,9.5551464 8.1618931,9.8774543 l 5.8029559,6.2792797 c 0.603423,0.638261 1.591613,0.648031 2.206659,0.0218 0.614172,-0.625577 0.623586,-1.648878 0.02103,-2.286493 0,0 -6.025394,-5.3742675 -6.3649177,-5.6724746 C 9.4880962,7.9213592 9.1275613,7.9319698 8.519367,8.5427531 Z"
id="path3350"
sodipodi:nodetypes="zzccczz" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.3 KiB

View file

@ -1,168 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="64"
height="64"
viewBox="0 0 16.933333 16.933334"
version="1.1"
id="svg8"
inkscape:version="1.0.2-2 (e86c870879, 2021-01-15)"
sodipodi:docname="select.svg">
<defs
id="defs2">
<inkscape:path-effect
effect="powermask"
id="path-effect4000"
is_visible="true"
lpeversion="1"
uri="#mask-powermask-path-effect4000"
invert="false"
hide_mask="false"
background="true"
background_color="#ffffffff" />
<inkscape:path-effect
effect="powermask"
id="path-effect3981"
is_visible="true"
lpeversion="1"
uri="#mask-powermask-path-effect3981"
invert="false"
hide_mask="false"
background="true"
background_color="#ffffffff" />
<inkscape:path-effect
effect="powermask"
id="path-effect3966"
is_visible="true"
lpeversion="1"
uri="#mask-powermask-path-effect3966"
invert="false"
hide_mask="false"
background="true"
background_color="#ffffffff" />
<inkscape:path-effect
effect="powermask"
id="path-effect2895"
is_visible="true"
lpeversion="1"
uri="#mask-powermask-path-effect2895"
invert="false"
hide_mask="false"
background="true"
background_color="#ffffffff" />
<linearGradient
id="linearGradient866"
osb:paint="solid">
<stop
style="stop-color:#838799;stop-opacity:1;"
offset="0"
id="stop864" />
</linearGradient>
<marker
style="overflow:visible"
id="Arrow1Lstart"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="Arrow1Lstart"
inkscape:isstock="true">
<path
transform="scale(0.8) translate(12.5,0)"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path2747" />
</marker>
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 8.466667 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="16.933333 : 8.466667 : 1"
inkscape:persp3d-origin="8.4666665 : 5.6444447 : 1"
id="perspective2694" />
<filter
id="mask-powermask-path-effect4000_inverse"
inkscape:label="filtermask-powermask-path-effect4000"
style="color-interpolation-filters:sRGB"
height="100"
width="100"
x="-50"
y="-50">
<feColorMatrix
id="mask-powermask-path-effect4000_primitive1"
values="1"
type="saturate"
result="fbSourceGraphic" />
<feColorMatrix
id="mask-powermask-path-effect4000_primitive2"
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0 "
in="fbSourceGraphic" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="9.1371454"
inkscape:cx="33.803843"
inkscape:cy="32.605832"
inkscape:document-units="mm"
inkscape:current-layer="layer2"
inkscape:document-rotation="0"
showgrid="true"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid833" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Back"
style="display:inline;opacity:0.997">
<path
id="rect2692"
transform="translate(0.26458378,0.26458346)"
mask="none"
d="m 7.4083329,10.847917 -6.87916626,0 V 0.52916664 H 14.022917 l 0,5.29166656"
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.165;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:2.33,2.33;stroke-dashoffset:8.621;stroke-opacity:1;paint-order:normal"
sodipodi:nodetypes="ccccc" />
</g>
<g
inkscape:label="Front"
inkscape:groupmode="layer"
id="layer1"
style="display:inline">
<path
style="opacity:1;fill:#000000;fill-opacity:0.997319;stroke:#000000;stroke-width:0.535;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 10.433094,5.9254024 v 8.8238546 l 2.129895,-1.217083 1.217083,3.042708 1.521355,-0.608542 -1.217083,-3.042708 h 2.434166 z"
id="path2710"
sodipodi:nodetypes="cccccccc" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.3 KiB

View file

@ -100,19 +100,19 @@ QComboBox:!editable:pressed {{
def splitter_styles(tm: ThemeManager, buf: str) -> str: def splitter_styles(tm: ThemeManager, buf: str) -> str:
buf += """ buf += f"""
QSplitter::handle, QSplitter::handle,
QMainWindow::separator { QMainWindow::separator {{
height: 16px; height: 16px;
} }}
QSplitter::handle:vertical, QSplitter::handle:vertical,
QMainWindow::separator:horizontal { QMainWindow::separator:horizontal {{
image: url(icons:drag-horizontal.svg); image: url({tm.themed_icon("mdi:drag-horizontal-FG_SUBTLE")});
} }}
QSplitter::handle:horizontal, QSplitter::handle:horizontal,
QMainWindow::separator:vertical { QMainWindow::separator:vertical {{
image: url(icons:drag-vertical.svg); image: url({tm.themed_icon("mdi:drag-vertical-FG_SUBTLE")});
} }}
""" """
return buf return buf
@ -156,21 +156,21 @@ QComboBox::drop-down {{
border-bottom-right-radius: {tm.var(props.BORDER_RADIUS)}; border-bottom-right-radius: {tm.var(props.BORDER_RADIUS)};
}} }}
QComboBox::down-arrow {{ QComboBox::down-arrow {{
image: url(icons:chevron-down.svg); image: url({tm.themed_icon("mdi:chevron-down")});
}} }}
QComboBox::drop-down {{ QComboBox::drop-down {{
background: { background: {
button_gradient( button_gradient(
tm.var(colors.BUTTON_PRIMARY_GRADIENT_START), tm.var(colors.BUTTON_GRADIENT_START),
tm.var(colors.BUTTON_PRIMARY_GRADIENT_END) tm.var(colors.BUTTON_GRADIENT_END)
) )
}; };
}} }}
QComboBox::drop-down:hover {{ QComboBox::drop-down:hover {{
background: { background: {
button_gradient( button_gradient(
tm.var(colors.BUTTON_PRIMARY_HOVER_GRADIENT_START), tm.var(colors.BUTTON_HOVER_GRADIENT_START),
tm.var(colors.BUTTON_PRIMARY_HOVER_GRADIENT_END) tm.var(colors.BUTTON_HOVER_GRADIENT_END)
) )
}; };
}} }}
@ -288,10 +288,10 @@ QHeaderView::down-arrow {{
height: 20px; height: 20px;
}} }}
QHeaderView::up-arrow {{ QHeaderView::up-arrow {{
image: url(icons:menu-up.svg); image: url({tm.themed_icon("mdi:menu-up")});
}} }}
QHeaderView::down-arrow {{ QHeaderView::down-arrow {{
image: url(icons:menu-down.svg); image: url({tm.themed_icon("mdi:menu-down")});
}} }}
""" """
return buf return buf
@ -306,8 +306,8 @@ QSpinBox::down-button {{
border: 1px solid {tm.var(colors.BUTTON_BORDER)}; border: 1px solid {tm.var(colors.BUTTON_BORDER)};
background: { background: {
button_gradient( button_gradient(
tm.var(colors.BUTTON_PRIMARY_GRADIENT_START), tm.var(colors.BUTTON_GRADIENT_START),
tm.var(colors.BUTTON_PRIMARY_GRADIENT_END) tm.var(colors.BUTTON_GRADIENT_END)
) )
}; };
}} }}
@ -316,8 +316,8 @@ QSpinBox::down-button:pressed {{
border: 1px solid {tm.var(colors.BUTTON_PRESSED_BORDER)}; border: 1px solid {tm.var(colors.BUTTON_PRESSED_BORDER)};
background: { background: {
button_pressed_gradient( button_pressed_gradient(
tm.var(colors.BUTTON_PRIMARY_GRADIENT_START), tm.var(colors.BUTTON_GRADIENT_START),
tm.var(colors.BUTTON_PRIMARY_GRADIENT_END), tm.var(colors.BUTTON_GRADIENT_END),
tm.var(colors.BUTTON_PRESSED_SHADOW) tm.var(colors.BUTTON_PRESSED_SHADOW)
) )
} }
@ -326,8 +326,8 @@ QSpinBox::up-button:hover,
QSpinBox::down-button:hover {{ QSpinBox::down-button:hover {{
background: { background: {
button_gradient( button_gradient(
tm.var(colors.BUTTON_PRIMARY_HOVER_GRADIENT_START), tm.var(colors.BUTTON_HOVER_GRADIENT_START),
tm.var(colors.BUTTON_PRIMARY_HOVER_GRADIENT_END) tm.var(colors.BUTTON_HOVER_GRADIENT_END)
) )
}; };
}} }}
@ -342,10 +342,10 @@ QSpinBox::down-button {{
border-bottom-right-radius: {tm.var(props.BORDER_RADIUS)}; border-bottom-right-radius: {tm.var(props.BORDER_RADIUS)};
}} }}
QSpinBox::up-arrow {{ QSpinBox::up-arrow {{
image: url(icons:chevron-up.svg); image: url({tm.themed_icon("mdi:chevron-up")});
}} }}
QSpinBox::down-arrow {{ QSpinBox::down-arrow {{
image: url(icons:chevron-down.svg); image: url({tm.themed_icon("mdi:chevron-down")});
}} }}
QSpinBox::up-arrow, QSpinBox::up-arrow,
QSpinBox::down-arrow, QSpinBox::down-arrow,
@ -363,7 +363,40 @@ QSpinBox::down-arrow:hover {{
}} }}
QSpinBox::up-button:disabled, QSpinBox::up-button:off, QSpinBox::up-button:disabled, QSpinBox::up-button:off,
QSpinBox::down-button:disabled, QSpinBox::down-button:off {{ QSpinBox::down-button:disabled, QSpinBox::down-button:off {{
background: {tm.var(colors.BUTTON_PRIMARY_DISABLED)}; background: {tm.var(colors.BUTTON_DISABLED)};
}}
QSpinBox::up-arrow:off,
QSpinBox::down-arrow:off {{
image: url({tm.themed_icon("mdi:chevron-down-FG_DISABLED")});
}}
"""
return buf
def checkbox_styles(tm: ThemeManager, buf: str) -> str:
buf += f"""
QCheckBox {{
spacing: 8px;
margin: 2px 0;
}}
QCheckBox::indicator {{
border: 1px solid {tm.var(colors.BUTTON_BORDER)};
border-radius: {tm.var(props.BORDER_RADIUS)};
background: {tm.var(colors.CANVAS_INSET)};
width: 16px;
height: 16px;
}}
QCheckBox::indicator:hover,
QCheckBox::indicator:checked:hover {{
border: 2px solid {tm.var(colors.BORDER_STRONG)};
width: 14px;
height: 14px;
}}
QCheckBox::indicator:checked {{
image: url({tm.themed_icon("mdi:check")});
}}
QCheckBox::indicator:indeterminate {{
image: url({tm.themed_icon("mdi:minus-thick")});
}} }}
""" """
return buf return buf

View file

@ -4,7 +4,9 @@
from __future__ import annotations from __future__ import annotations
import enum import enum
import os
import platform import platform
import re
import subprocess import subprocess
from dataclasses import dataclass from dataclasses import dataclass
from typing import Callable, List, Tuple from typing import Callable, List, Tuple
@ -81,8 +83,21 @@ class ThemeManager:
night_mode = property(get_night_mode, set_night_mode) night_mode = property(get_night_mode, set_night_mode)
def themed_icon(self, path: str) -> str:
"Fetch themed version of svg."
from aqt.utils import aqt_data_folder
if m := re.match(r"(?:mdi:)(.+)$", path):
name = m.group(1)
else:
return path
filename = f"{name}-{'dark' if self.night_mode else 'light'}.svg"
return os.path.join(aqt_data_folder(), "qt", "icons", filename)
def icon_from_resources(self, path: str | ColoredIcon) -> QIcon: def icon_from_resources(self, path: str | ColoredIcon) -> QIcon:
"Fetch icon from Qt resources, and invert if in night mode." "Fetch icon from Qt resources."
if self.night_mode: if self.night_mode:
cache = self._icon_cache_light cache = self._icon_cache_light
else: else:
@ -99,6 +114,9 @@ class ThemeManager:
if isinstance(path, str): if isinstance(path, str):
# default black/white # default black/white
if "mdi:" in path:
icon = QIcon(self.themed_icon(path))
else:
icon = QIcon(path) icon = QIcon(path)
if self.night_mode: if self.night_mode:
img = icon.pixmap(self._icon_size, self._icon_size).toImage() img = icon.pixmap(self._icon_size, self._icon_size).toImage()
@ -193,6 +211,7 @@ class ThemeManager:
if not is_mac: if not is_mac:
from aqt.stylesheets import ( from aqt.stylesheets import (
button_styles, button_styles,
checkbox_styles,
combobox_styles, combobox_styles,
general_styles, general_styles,
scrollbar_styles, scrollbar_styles,
@ -210,6 +229,7 @@ class ThemeManager:
tabwidget_styles(self, buf), tabwidget_styles(self, buf),
table_styles(self, buf), table_styles(self, buf),
spinbox_styles(self, buf), spinbox_styles(self, buf),
checkbox_styles(self, buf),
scrollbar_styles(self, buf), scrollbar_styles(self, buf),
] ]
) )

View file

@ -22,6 +22,9 @@
); );
} }
} }
@else if $key == "default" {
@return map.set($output, $name, map.get($map, $key));
}
} }
@return $output; @return $output;
} }

View file

@ -18,6 +18,12 @@ $vars: (
), ),
), ),
colors: ( colors: (
white: (
default: white,
),
black: (
default: black,
),
fg: ( fg: (
default: ( default: (
light: palette(darkgray, 9), light: palette(darkgray, 9),
@ -28,8 +34,8 @@ $vars: (
dark: palette(lightgray, 3), dark: palette(lightgray, 3),
), ),
disabled: ( disabled: (
light: palette(darkgray, 3), light: palette(darkgray, 2),
dark: palette(lightgray, 6), dark: palette(lightgray, 8),
), ),
faint: ( faint: (
light: palette(lightgray, 7), light: palette(lightgray, 7),
@ -67,6 +73,10 @@ $vars: (
light: palette(lightgray, 5), light: palette(lightgray, 5),
dark: palette(darkgray, 4), dark: palette(darkgray, 4),
), ),
strong: (
light: palette(lightgray, 9),
dark: palette(darkgray, 1),
),
focus: ( focus: (
light: palette(blue, 5), light: palette(blue, 5),
dark: palette(blue, 5), dark: palette(blue, 5),
@ -91,6 +101,10 @@ $vars: (
dark: palette(darkgray, 9), dark: palette(darkgray, 9),
), ),
), ),
disabled: (
light: color.scale(palette(lightgray, 5), $alpha: -50%),
dark: color.scale(palette(darkgray, 3), $alpha: -50%),
),
gradient: ( gradient: (
start: ( start: (
light: white, light: white,