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

View File

@ -20,8 +20,16 @@ class SidebarTool(Enum):
class SidebarToolbar(QToolBar):
_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:

View File

@ -1,4 +1,5 @@
load("//ts:vendor.bzl", "copy_mdi_icons")
load("color_svg.bzl", "color_svg")
copy_mdi_icons(
name = "mdi-icons",
@ -33,6 +34,19 @@ copy_mdi_icons(
# tags
"tag-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
"menu-up.svg",
@ -41,12 +55,73 @@ copy_mdi_icons(
# drag handle
"drag-vertical.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(
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",
"*.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:
buf += """
buf += f"""
QSplitter::handle,
QMainWindow::separator {
QMainWindow::separator {{
height: 16px;
}
}}
QSplitter::handle:vertical,
QMainWindow::separator:horizontal {
image: url(icons:drag-horizontal.svg);
}
QMainWindow::separator:horizontal {{
image: url({tm.themed_icon("mdi:drag-horizontal-FG_SUBTLE")});
}}
QSplitter::handle:horizontal,
QMainWindow::separator:vertical {
image: url(icons:drag-vertical.svg);
}
QMainWindow::separator:vertical {{
image: url({tm.themed_icon("mdi:drag-vertical-FG_SUBTLE")});
}}
"""
return buf
@ -156,21 +156,21 @@ QComboBox::drop-down {{
border-bottom-right-radius: {tm.var(props.BORDER_RADIUS)};
}}
QComboBox::down-arrow {{
image: url(icons:chevron-down.svg);
image: url({tm.themed_icon("mdi:chevron-down")});
}}
QComboBox::drop-down {{
background: {
button_gradient(
tm.var(colors.BUTTON_PRIMARY_GRADIENT_START),
tm.var(colors.BUTTON_PRIMARY_GRADIENT_END)
tm.var(colors.BUTTON_GRADIENT_START),
tm.var(colors.BUTTON_GRADIENT_END)
)
};
}}
QComboBox::drop-down:hover {{
background: {
button_gradient(
tm.var(colors.BUTTON_PRIMARY_HOVER_GRADIENT_START),
tm.var(colors.BUTTON_PRIMARY_HOVER_GRADIENT_END)
tm.var(colors.BUTTON_HOVER_GRADIENT_START),
tm.var(colors.BUTTON_HOVER_GRADIENT_END)
)
};
}}
@ -288,10 +288,10 @@ QHeaderView::down-arrow {{
height: 20px;
}}
QHeaderView::up-arrow {{
image: url(icons:menu-up.svg);
image: url({tm.themed_icon("mdi:menu-up")});
}}
QHeaderView::down-arrow {{
image: url(icons:menu-down.svg);
image: url({tm.themed_icon("mdi:menu-down")});
}}
"""
return buf
@ -306,8 +306,8 @@ QSpinBox::down-button {{
border: 1px solid {tm.var(colors.BUTTON_BORDER)};
background: {
button_gradient(
tm.var(colors.BUTTON_PRIMARY_GRADIENT_START),
tm.var(colors.BUTTON_PRIMARY_GRADIENT_END)
tm.var(colors.BUTTON_GRADIENT_START),
tm.var(colors.BUTTON_GRADIENT_END)
)
};
}}
@ -316,8 +316,8 @@ QSpinBox::down-button:pressed {{
border: 1px solid {tm.var(colors.BUTTON_PRESSED_BORDER)};
background: {
button_pressed_gradient(
tm.var(colors.BUTTON_PRIMARY_GRADIENT_START),
tm.var(colors.BUTTON_PRIMARY_GRADIENT_END),
tm.var(colors.BUTTON_GRADIENT_START),
tm.var(colors.BUTTON_GRADIENT_END),
tm.var(colors.BUTTON_PRESSED_SHADOW)
)
}
@ -326,8 +326,8 @@ QSpinBox::up-button:hover,
QSpinBox::down-button:hover {{
background: {
button_gradient(
tm.var(colors.BUTTON_PRIMARY_HOVER_GRADIENT_START),
tm.var(colors.BUTTON_PRIMARY_HOVER_GRADIENT_END)
tm.var(colors.BUTTON_HOVER_GRADIENT_START),
tm.var(colors.BUTTON_HOVER_GRADIENT_END)
)
};
}}
@ -342,10 +342,10 @@ QSpinBox::down-button {{
border-bottom-right-radius: {tm.var(props.BORDER_RADIUS)};
}}
QSpinBox::up-arrow {{
image: url(icons:chevron-up.svg);
image: url({tm.themed_icon("mdi:chevron-up")});
}}
QSpinBox::down-arrow {{
image: url(icons:chevron-down.svg);
image: url({tm.themed_icon("mdi:chevron-down")});
}}
QSpinBox::up-arrow,
QSpinBox::down-arrow,
@ -363,12 +363,45 @@ QSpinBox::down-arrow:hover {{
}}
QSpinBox::up-button:disabled, QSpinBox::up-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
def scrollbar_styles(tm: ThemeManager, buf: str) -> str:
buf += f"""
QAbstractScrollArea::corner {{

View File

@ -4,7 +4,9 @@
from __future__ import annotations
import enum
import os
import platform
import re
import subprocess
from dataclasses import dataclass
from typing import Callable, List, Tuple
@ -81,8 +83,21 @@ class ThemeManager:
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:
"Fetch icon from Qt resources, and invert if in night mode."
"Fetch icon from Qt resources."
if self.night_mode:
cache = self._icon_cache_light
else:
@ -99,11 +114,14 @@ class ThemeManager:
if isinstance(path, str):
# default black/white
icon = QIcon(path)
if self.night_mode:
img = icon.pixmap(self._icon_size, self._icon_size).toImage()
img.invertPixels()
icon = QIcon(QPixmap(img))
if "mdi:" in path:
icon = QIcon(self.themed_icon(path))
else:
icon = QIcon(path)
if self.night_mode:
img = icon.pixmap(self._icon_size, self._icon_size).toImage()
img.invertPixels()
icon = QIcon(QPixmap(img))
else:
# specified colours
icon = QIcon(path.path)
@ -193,6 +211,7 @@ class ThemeManager:
if not is_mac:
from aqt.stylesheets import (
button_styles,
checkbox_styles,
combobox_styles,
general_styles,
scrollbar_styles,
@ -210,6 +229,7 @@ class ThemeManager:
tabwidget_styles(self, buf),
table_styles(self, buf),
spinbox_styles(self, buf),
checkbox_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;
}

View File

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