Browse Source

Icons: Replace svgSalamander with JSVG

pull/295/head
weisj 3 years ago committed by Jannis Weis
parent
commit
d24e3ba741
  1. 4
      core/build.gradle.kts
  2. 2
      core/src/main/java/com/github/weisj/darklaf/components/ColoredRadioButton.java
  3. 77
      core/src/test/java/com/github/weisj/darklaf/core/documentation/CreateUITable.java
  4. 2
      core/src/test/java/com/github/weisj/darklaf/icon/AllIcons.java
  5. 2
      gradle.properties
  6. 2
      property-loader/build.gradle.kts
  7. 37
      property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/CustomThemedIcon.java
  8. 167
      property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/DarkSVGIcon.java
  9. 48
      property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/DarkSVGIconDomProcessor.java
  10. 278
      property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/IconColorMapper.java
  11. 41
      property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/IconLoader.java
  12. 40
      property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/ThemedSVGIcon.java
  13. 158
      property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/ThemedSVGIconParserProvider.java
  14. 2
      property-loader/src/main/module/module-info.java
  15. 20
      property-loader/src/test/java/com/github/weisj/darklaf/properties/icons/SVGImageTest.java
  16. 2
      settings.gradle.kts

4
core/build.gradle.kts

@ -19,7 +19,7 @@ dependencies {
implementation(projects.darklafWindows)
implementation(projects.darklafMacos)
implementation(libs.swingDslLafSupport)
implementation(libs.svgSalamander)
implementation(libs.jsvg)
compileOnly(libs.nullabilityAnnotations)
compileOnly(libs.swingx)
@ -27,7 +27,7 @@ dependencies {
compileOnly(toolLibs.autoservice.annotations)
annotationProcessor(toolLibs.autoservice.processor)
testImplementation(libs.svgSalamander)
testImplementation(libs.jsvg)
testImplementation(libs.swingx)
testImplementation(testLibs.bundles.miglayout)
testImplementation(testLibs.swingDslInspector)

2
core/src/main/java/com/github/weisj/darklaf/components/ColoredRadioButton.java

@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2020-2021 Jannis Weis
* Copyright (c) 2020-2022 Jannis Weis
*
* 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,

77
core/src/test/java/com/github/weisj/darklaf/core/documentation/CreateUITable.java

@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2020-2021 Jannis Weis
* Copyright (c) 2020-2022 Jannis Weis
*
* 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,
@ -21,12 +21,12 @@
package com.github.weisj.darklaf.core.documentation;
import java.awt.*;
import java.awt.Font;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@ -40,23 +40,12 @@ import com.github.weisj.darklaf.DarkLaf;
import com.github.weisj.darklaf.LafManager;
import com.github.weisj.darklaf.components.border.DropShadowBorder;
import com.github.weisj.darklaf.defaults.SampleRenderer;
import com.github.weisj.darklaf.properties.icons.DarkSVGIcon;
import com.github.weisj.darklaf.properties.icons.EmptyIcon;
import com.github.weisj.darklaf.properties.icons.IconColorMapper;
import com.github.weisj.darklaf.properties.icons.StateIcon;
import com.github.weisj.darklaf.properties.icons.*;
import com.github.weisj.darklaf.properties.parser.ParseResult;
import com.github.weisj.darklaf.properties.parser.Parser;
import com.github.weisj.darklaf.properties.parser.PrimitiveParser;
import com.github.weisj.darklaf.theme.Theme;
import com.github.weisj.darklaf.util.ColorUtil;
import com.github.weisj.darklaf.util.ImageUtil;
import com.github.weisj.darklaf.util.StringUtil;
import com.github.weisj.darklaf.util.SystemInfo;
import com.github.weisj.darklaf.util.Types;
import com.kitfox.svg.LinearGradient;
import com.kitfox.svg.SVGDiagram;
import com.kitfox.svg.SVGElement;
import com.kitfox.svg.app.beans.SVGIcon;
import com.github.weisj.darklaf.util.*;
public class CreateUITable {
@ -131,8 +120,8 @@ public class CreateUITable {
}
private UIDefaults setupThemeDefaults(final Theme theme) {
LafManager.installTheme(theme);
Parser.setDebugMode(true);
LafManager.setTheme(theme);
UIDefaults defaults = new DarkLaf() {
@Override
public Theme getTheme() {
@ -140,7 +129,6 @@ public class CreateUITable {
}
}.getDefaults();
Parser.setDebugMode(false);
LafManager.installTheme(theme);
currentDefaults = UIManager.getLookAndFeelDefaults();
return defaults;
}
@ -286,43 +274,36 @@ public class CreateUITable {
}
private String parseSVGIcon(final DarkSVGIcon value, final int ident) {
SVGIcon icon = value.getSVGIcon();
StringBuilder sb = new StringBuilder(StringUtil.repeat(IDENT, ident)).append("<td align=\"center\">\n");
try {
readFile(icon.getSvgURI().toURL(), sb, ident + 1);
} catch (final IOException e) {
e.printStackTrace();
}
sb.append(StringUtil.repeat(IDENT, ident)).append("</td>\n");
String svg = sb.toString();
SVGDiagram svgDiagram = icon.getSvgUniverse().getDiagram(icon.getSvgURI());
SVGElement defs = svgDiagram.getElement("colors");
if (defs != null) {
List<?> children = defs.getChildren(null);
for (Object child : children) {
if (child instanceof LinearGradient) {
float opacity = IconColorMapper.getOpacity((LinearGradient) child, currentDefaults, null);
if (opacity < 0) opacity = 1;
Color color = IconColorMapper.getColor((LinearGradient) child, currentDefaults, null);
String id = ((LinearGradient) child).getId();
String match = "=\"url\\(#" + id + "\\)\"";
String fillReplacement = "fill=\"#" + ColorUtil.toHex(color) + "\"";
if (opacity != 1) {
fillReplacement += " fill-opacity=\"" + opacity + "\"";
svg = svg.replaceAll("fill-opacity=\"[^\"]*\"", "");
}
svg = svg.replaceAll("fill" + match, fillReplacement);
String strokeReplacement = "stroke=\"#" + ColorUtil.toHex(color) + "\"";
if (opacity != 1) strokeReplacement += " stroke-opacity=\"" + opacity + "\"";
svg = svg.replaceAll("stroke" + match, strokeReplacement);
readFile(value.getURI().toURL(), sb, ident + 1);
sb.append(StringUtil.repeat(IDENT, ident)).append("</td>\n");
String svg = sb.toString();
for (Map.Entry<String, Color> entry : ThemedSVGIconParserProvider.getNamedColors(
new CustomThemedIcon(value, currentDefaults, CustomThemedIcon.MergeMode.REMOVE_REFERENCES))
.entrySet()) {
String id = entry.getKey();
String match = "=\"url\\(#" + id + "\\)\"";
String colorHex = ColorUtil.toHex(ColorUtil.removeAlpha(entry.getValue()));
String fillReplacement = "fill=\"#" + colorHex + "\"";
float opacity = entry.getValue().getAlpha() / 255f;
if (opacity != 1) {
fillReplacement += " fill-opacity=\"" + opacity + "\"";
}
svg = svg.replaceAll("fill" + match, fillReplacement);
String strokeReplacement = "stroke=\"#" + colorHex + "\"";
if (opacity != 1) strokeReplacement += " stroke-opacity=\"" + opacity + "\"";
svg = svg.replaceAll("stroke" + match, strokeReplacement);
}
svg = svg.replaceAll("<defs id=\"colors\">(\\n.*)* </defs>\\s+", "");
return svg;
} catch (final Exception e) {
throw new RuntimeException(e);
}
return svg;
}
private void readFile(final URL url, final StringBuilder builder, final int ident) throws IOException {

2
core/src/test/java/com/github/weisj/darklaf/icon/AllIcons.java

@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2020-2021 Jannis Weis
* Copyright (c) 2020-2022 Jannis Weis
*
* 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,

2
gradle.properties

@ -29,7 +29,7 @@ net.ltgt.errorprone.version = 2.0.2
# Dependencies
# Libraries
svgSalamander.version = 1.1.2.4
jsvg.version = 0.0.1
swingDsl.version = 0.1.3
swingx.version = 1.6.1
nullabilityAnnotations.version = 23.0.0

2
property-loader/build.gradle.kts

@ -5,7 +5,7 @@ plugins {
dependencies {
api(projects.darklafUtils)
implementation(libs.svgSalamander)
implementation(libs.jsvg)
implementation(libs.visualPaddings)
compileOnly(libs.nullabilityAnnotations)
compileOnly(toolLibs.errorprone.annotations)

37
property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/CustomThemedIcon.java

@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2019-2021 Jannis Weis
* Copyright (c) 2019-2022 Jannis Weis
*
* 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,
@ -22,12 +22,10 @@ package com.github.weisj.darklaf.properties.icons;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import com.github.weisj.darklaf.properties.PropertyLoader;
import com.kitfox.svg.SVGUniverse;
import com.kitfox.svg.app.beans.SVGIcon;
public class CustomThemedIcon extends ThemedSVGIcon implements MutableThemedIcon {
@ -36,16 +34,6 @@ public class CustomThemedIcon extends ThemedSVGIcon implements MutableThemedIcon
private Map<Object, Object> contextDefaults;
private boolean derived;
public CustomThemedIcon(final Supplier<URI> uriSupplier, final int displayWidth, final int displayHeight) {
this(uriSupplier, displayWidth, displayHeight, null);
}
public CustomThemedIcon(final Supplier<URI> uriSupplier, final int displayWidth, final int displayHeight,
final Map<Object, Object> colors) {
super(uriSupplier, displayWidth, displayHeight);
defaults = colors;
}
public CustomThemedIcon(final URI uri, final int displayWidth, final int displayHeight,
final Map<Object, Object> colors) {
super(uri, displayWidth, displayHeight);
@ -59,9 +47,16 @@ public class CustomThemedIcon extends ThemedSVGIcon implements MutableThemedIcon
public CustomThemedIcon(final DarkSVGIcon icon, final Map<Object, Object> contextDefaults,
final MergeMode mergeMode) {
super(icon.getUri(), icon.getIconWidth(), icon.getIconHeight());
List<ThemedSVGIconParserProvider.ThemedSolidColorPaint> customPaints;
if (icon instanceof ThemedSVGIcon) {
icon.ensureLoaded(false);
customPaints = ((ThemedSVGIcon) icon).paints();
} else {
ensureLoaded(false);
customPaints = paints();
}
defaults = ThemedSVGIconParserProvider.getProperties(customPaints);
setContextProperties(contextDefaults);
ensureLoaded(false);
defaults = IconColorMapper.getProperties(getSVGIcon());
mergeProperties(mergeMode, icon);
}
@ -123,13 +118,6 @@ public class CustomThemedIcon extends ThemedSVGIcon implements MutableThemedIcon
return new CustomThemedIcon(width, height, this);
}
@Override
protected SVGIcon createSVGIcon() {
SVGIcon icon = new SVGIcon();
icon.setSvgUniverse(new SVGUniverse());
return icon;
}
@Override
public Map<Object, Object> getContextProperties() {
return contextDefaults != LAF_CONTEXT ? contextDefaults : getContextDefaults();
@ -143,7 +131,8 @@ public class CustomThemedIcon extends ThemedSVGIcon implements MutableThemedIcon
@Override
protected void patchColors() {
IconColorMapper.patchColors(getSVGIcon(), getProperties(), getContextProperties());
super.patchColors();
ThemedSVGIconParserProvider.patchColors(paints(), getProperties(), getContextProperties());
}
public enum MergeMode {

167
property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/DarkSVGIcon.java

@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2019-2021 Jannis Weis
* Copyright (c) 2019-2022 Jannis Weis
*
* 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,
@ -24,22 +24,24 @@ import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.UIManager;
import javax.swing.*;
import org.jetbrains.annotations.NotNull;
import com.github.weisj.darklaf.util.LogUtil;
import com.github.weisj.darklaf.util.Scale;
import com.github.weisj.jsvg.SVGDocument;
import com.github.weisj.jsvg.attributes.ViewBox;
import com.github.weisj.jsvg.geometry.size.FloatSize;
import com.github.weisj.jsvg.parser.DefaultParserProvider;
import com.github.weisj.jsvg.parser.ParserProvider;
import com.github.weisj.swingdsl.visualpadding.VisualPaddingProvider;
import com.kitfox.svg.SVGException;
import com.kitfox.svg.SVGRoot;
import com.kitfox.svg.app.beans.SVGIcon;
import com.kitfox.svg.xml.StyleAttribute;
/**
* Icon from SVG image.
@ -58,16 +60,18 @@ public class DarkSVGIcon
* accounts for diagonal lines (i.e. when the rotation is pi/4). Ideally this value would only need
* to be sqrt(2) but 2.0 behaves a lot better w.r.t. floating point calculations.
*
* The scale factor is only used if the icon is painted with a non trivial rotation.
* The scale factor is only used if the icon is painted with a non-trivial rotation.
*/
private static final double extraScale = 2.0;
private final AtomicBoolean loaded;
private final Dimension iconSize;
private final SVGIcon icon;
private final @NotNull AtomicBoolean loaded;
private final @NotNull Dimension iconSize;
private Supplier<URI> uriSupplier;
private URI uri;
private SVGDocument svgDocument;
private Insets visualPadding;
private final @NotNull URI uri;
private IconLoader.IconKey iconKey;
@ -78,44 +82,22 @@ public class DarkSVGIcon
private Image image;
/**
* Method to fetch the SVG icon from a url.
*
* @param uriSupplier supplier for the uri from which to fetch the SVG icon.
* @param displayWidth display width of icon.
* @param displayHeight display height of icon.
*/
public DarkSVGIcon(final Supplier<URI> uriSupplier, final int displayWidth, final int displayHeight) {
this.uri = null;
this.uriSupplier = uriSupplier;
iconSize = new Dimension(displayWidth, displayHeight);
icon = createSVGIcon();
icon.setAutosize(SVGIcon.AUTOSIZE_STRETCH);
icon.setAntiAlias(true);
loaded = new AtomicBoolean(false);
}
/**
* Method to fetch the SVG icon from a url.
* Method to fetch the SVG icon from an url.
*
* @param uri the uri from which to fetch the SVG icon.
* @param uri the svg uri.
* @param displayWidth display width of icon.
* @param displayHeight display height of icon.
*/
public DarkSVGIcon(final URI uri, final int displayWidth, final int displayHeight) {
public DarkSVGIcon(final @NotNull URI uri, final int displayWidth, final int displayHeight) {
this.uri = uri;
uriSupplier = null;
iconSize = new Dimension(displayWidth, displayHeight);
icon = createSVGIcon();
icon.setAutosize(SVGIcon.AUTOSIZE_STRETCH);
icon.setAntiAlias(true);
loaded = new AtomicBoolean(false);
}
protected DarkSVGIcon(final int width, final int height, final DarkSVGIcon parent) {
this.iconSize = new Dimension(width, height);
this.icon = parent.icon;
this.svgDocument = parent.svgDocument;
this.uri = parent.uri;
this.uriSupplier = parent.uriSupplier;
this.loaded = parent.loaded;
}
@ -156,34 +138,31 @@ public class DarkSVGIcon
return ensureSVGLoaded();
}
protected URI getUri() {
ensureURILoaded();
return uri;
}
private boolean ensureSVGLoaded() {
if (!isSVGLoaded()) {
URI iconUri = getUri();
LOGGER.finer(() -> "Loading icon '" + iconUri.toASCIIString() + "'.");
icon.setSvgURI(iconUri);
try {
svgDocument = IconLoader.svgLoader().load(uri.toURL(), createParserProvider());
} catch (MalformedURLException e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
}
loaded.set(true);
return true;
}
return false;
}
private boolean isSVGLoaded() {
return loaded.get();
protected @NotNull ParserProvider createParserProvider() {
return new DefaultParserProvider();
}
private void ensureURILoaded() {
if (uri == null && uriSupplier != null) {
uri = uriSupplier.get();
uriSupplier = null;
}
if (uri == null) {
throw new IllegalStateException("Uri is null.");
}
protected @NotNull URI getUri() {
return uri;
}
private boolean isSVGLoaded() {
return loaded.get();
}
protected void updateCache(final boolean update, final Component c) {
@ -203,7 +182,6 @@ public class DarkSVGIcon
@Override
public Image createImage(final Dimension size) {
ensureLoaded(false);
icon.setPreferredSize(size);
try {
BufferedImage bi = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = (Graphics2D) bi.getGraphics();
@ -213,20 +191,10 @@ public class DarkSVGIcon
RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
Object aaHint = UIManager.get(RenderingHints.KEY_TEXT_ANTIALIASING);
if (aaHint != null) g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, aaHint);
icon.paintIcon(null, g, 0, 0);
svgDocument.render(null, g, new ViewBox(0, 0, size.width, size.height));
g.dispose();
return bi;
} catch (final RuntimeException e) {
if (!(this instanceof ThemedSVGIcon)) {
IconColorMapper.patchColors(icon);
Image img = icon.getImage();
/*
* If we get to here the issue was that the icon hasn't been patched because it isn't loaded as a
* themed svg icon.
*/
LOGGER.severe("Icon '" + getName(uri) + "' that defines custom colors isn't loaded as themed icon.");
return img;
}
throw new RuntimeException("Exception while painting '" + uri.toASCIIString() + "'.", e);
}
}
@ -252,16 +220,11 @@ public class DarkSVGIcon
|| Scale.equalWithError(r, 3 * Math.PI / 2);
}
protected SVGIcon createSVGIcon() {
return new SVGIcon();
}
@Override
public void paintIcon(final Component c, final Graphics g, final int x, final int y, final double rotation) {
boolean dr = isDirectRenderingMode();
if (dr) {
ensureLoaded(true);
icon.setPreferredSize(getSize());
} else {
ensureImageLoaded(c, rotation);
}
@ -275,17 +238,19 @@ public class DarkSVGIcon
double imageHeight = dr ? size.height : image.getHeight(null);
double sx = size.width / imageWidth;
double sy = size.height / imageHeight;
g2.scale(sx, sy);
if (!dr) g2.scale(sx, sy);
if (rotation != 0) {
g2.rotate(rotation, imageWidth / 2.0, imageHeight / 2.0);
}
if (dr) {
getSVGIcon().paintIcon(c, g, 0, 0);
SVGDocument svg = getSVGDocument();
svg.render((JComponent) c, (Graphics2D) g, new ViewBox(0, 0, size.width, size.height));
} else {
g2.drawImage(image, 0, 0, c);
g2.scale(1 / sx, 1 / sy);
}
g2.scale(1 / sx, 1 / sy);
g2.translate(-x, -y);
g2.setTransform(transform);
}
@ -316,12 +281,10 @@ public class DarkSVGIcon
private void ensureSizeLoaded() {
if (iconSize.width < 0 || iconSize.height < 0) {
SVGIcon svg = getSVGIcon();
int autoSizeMode = svg.getAutosize();
svg.setAutosize(SVGIcon.AUTOSIZE_NONE);
int width = svg.getIconWidthIgnoreAutosize();
int height = svg.getIconHeightIgnoreAutosize();
svg.setAutosize(autoSizeMode);
SVGDocument svg = getSVGDocument();
FloatSize svgSize = svg.size();
int width = (int) (svgSize.width + 0.5);
int height = (int) (svgSize.height + 0.5);
if (iconSize.height < 0 && iconSize.width >= 0) {
height = (int) ((iconSize.width * height) / (double) width);
@ -330,7 +293,7 @@ public class DarkSVGIcon
width = (int) ((iconSize.height * width) / (double) height);
height = iconSize.height;
} else if (iconSize.width == iconSize.height && iconSize.height < -1) {
// Scale to make largest side fit the given size.
// Scale to make the largest side fit the given size.
int size = Math.abs(iconSize.width);
if (width == height) {
width = size;
@ -348,9 +311,23 @@ public class DarkSVGIcon
}
}
public SVGIcon getSVGIcon() {
public SVGDocument getSVGDocument() {
ensureSVGLoaded();
return icon;
return svgDocument;
}
@Override
public @NotNull Insets getVisualPaddings(@NotNull Component component) {
ensureSVGLoaded();
return visualPadding != null ? visualPadding : new Insets(0, 0, 0, 0);
}
void setVisualPadding(final Insets visualPadding) {
this.visualPadding = visualPadding;
}
public URI getURI() {
return uri;
}
@Override
@ -358,8 +335,8 @@ public class DarkSVGIcon
return "DarkSVGIcon{" +
"loaded=" + loaded +
", iconSize=" + iconSize +
", icon=" + icon +
", uriSupplier=" + uriSupplier +
", svgDocument=" + svgDocument +
", visualPadding=" + visualPadding +
", uri=" + uri +
", iconKey=" + iconKey +
", directRendering=" + directRendering +
@ -369,22 +346,4 @@ public class DarkSVGIcon
", image=" + image +
'}';
}
@Override
@SuppressWarnings("EmptyCatch")
public @NotNull Insets getVisualPaddings(@NotNull Component component) {
SVGIcon icon = getSVGIcon();
SVGRoot root = icon.getSvgUniverse().getDiagram(icon.getSvgURI()).getRoot();
StyleAttribute attr = new StyleAttribute("visualPadding");
try {
if (root.getStyle(attr, false)) {
int[] paddings = attr.getIntList();
if (paddings.length == 4) {
return new Insets(paddings[0], paddings[1], paddings[2], paddings[3]);
}
}
} catch (SVGException ignore) {
}
return new Insets(0, 0, 0, 0);
}
}

48
property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/DarkSVGIconDomProcessor.java

@ -0,0 +1,48 @@
/*
* MIT License
*
* Copyright (c) 2021-2022 Jannis Weis
*
* 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.
*/
package com.github.weisj.darklaf.properties.icons;
import java.awt.*;
import org.jetbrains.annotations.NotNull;
import com.github.weisj.jsvg.parser.DomProcessor;
import com.github.weisj.jsvg.parser.ParsedElement;
public class DarkSVGIconDomProcessor<T extends DarkSVGIcon> implements DomProcessor {
protected final @NotNull T icon;
public DarkSVGIconDomProcessor(@NotNull T icon) {
this.icon = icon;
}
@Override
public void process(@NotNull ParsedElement root) {
float[] visualPaddings = root.attributeNode().getFloatList("visualPadding");
if (visualPaddings.length == 4) {
icon.setVisualPadding(new Insets(
(int) visualPaddings[0],
(int) visualPaddings[1],
(int) visualPaddings[2],
(int) visualPaddings[3]));
}
}
}

278
property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/IconColorMapper.java

@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2019-2021 Jannis Weis
* Copyright (c) 2019-2022 Jannis Weis
*
* 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,
@ -22,24 +22,15 @@ package com.github.weisj.darklaf.properties.icons;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
import com.github.weisj.darklaf.properties.PropertyLoader;
import com.github.weisj.darklaf.properties.parser.ParseResult;
import com.github.weisj.darklaf.properties.parser.Parser;
import com.github.weisj.darklaf.properties.parser.ParserContext;
import com.github.weisj.darklaf.util.ColorUtil;
import com.github.weisj.darklaf.util.LogUtil;
import com.github.weisj.darklaf.util.Pair;
import com.github.weisj.darklaf.util.Types;
import com.kitfox.svg.*;
import com.kitfox.svg.animation.AnimationElement;
import com.kitfox.svg.app.beans.SVGIcon;
import com.kitfox.svg.xml.StyleAttribute;
/**
* Utility class responsible for patching color definitions in svg icons.
@ -47,156 +38,23 @@ import com.kitfox.svg.xml.StyleAttribute;
* @author Jannis Weis
*/
public final class IconColorMapper {
private static final String INLINE_VALUE_PREFIX = "%";
private static final Logger LOGGER = LogUtil.getLogger(IconLoader.class);
private static final String INLINE_VALUE_PREFIX = "%";
private static final Color FALLBACK_COLOR = Color.RED;
public static void patchColors(final SVGIcon svgIcon) {
patchColors(svgIcon, UIManager.getDefaults());
}
public static void patchColors(final SVGIcon svgIcon, final Map<Object, Object> contextDefaults) {
patchColors(svgIcon, contextDefaults, null);
}
public static void patchColors(final SVGIcon svgIcon, final Map<Object, Object> defaults,
final Map<Object, Object> contextDefaults) {
SVGUniverse universe = svgIcon.getSvgUniverse();
SVGDiagram diagram = universe.getDiagram(svgIcon.getSvgURI());
LOGGER.finer(() -> "Patching colors of icon " + svgIcon.getSvgURI());
try {
loadColors(diagram, defaults, contextDefaults);
} catch (final SVGElementException e) {
LOGGER.log(Level.SEVERE, "Failed patching colors. " + e.getMessage(), e);
}
}
private static void loadColors(final SVGDiagram diagram, final Map<Object, Object> defaults,
final Map<Object, Object> contextDefaults)
throws SVGElementException {
SVGRoot root = diagram.getRoot();
SVGElement defs = diagram.getElement("colors");
if (defs == null) {
LOGGER.info(() -> {
String uri = diagram.getXMLBase().toASCIIString();
String name = uri.substring(Math.min(uri.lastIndexOf('/') + 1, uri.length() - 1));
return "Themed icon '" + name
+ "' has no color definitions. Consider loading it as a standard icon or add missing definitions";
});
return;
}
List<SVGElement> children = defs.getChildren(null);
root.removeChild(defs);
Defs themedDefs = new Defs();
themedDefs.addAttribute("id", AnimationElement.AT_XML, "colors");
root.loaderAddChild(null, themedDefs);
for (SVGElement child : children) {
if (child instanceof LinearGradient) {
LinearGradient grad = (LinearGradient) child;
String id = grad.getId();
StyleAttribute colorFallbacks = getAttribute("fallback", grad);
StyleAttribute opacityFallbacks = getAttribute("opacity-fallback", grad);
String opacityKey = getOpacityKey(grad);
float opacity = getOpacity(opacityKey, getFallbacks(opacityFallbacks), defaults, contextDefaults);
float opacity1 = opacity;
float opacity2 = opacity;
if (opacity < 0) {
opacity = 1;
int childCount = grad.getNumChildren();
if (childCount > 0) {
SVGElement elem = grad.getChild(0);
if (elem instanceof Stop) {
opacity1 = getStopOpacity((Stop) elem);
}
}
if (childCount > 1) {
SVGElement elem = grad.getChild(1);
if (elem instanceof Stop) {
opacity2 = getStopOpacity((Stop) elem);
}
}
if (opacity1 < 0) opacity1 = opacity;
if (opacity2 < 0) opacity2 = opacity;
}
Color c = resolveColor(id, getFallbacks(colorFallbacks), FALLBACK_COLOR, defaults, contextDefaults);
float finalOpacity1 = opacity1;
float finalOpacity2 = opacity2;
LOGGER.finest(() -> "Color: " + c + " opacity1: " + finalOpacity1 + " opacity2: " + finalOpacity2);
ColorResult result =
createColor(c, id, opacityKey, new StyleAttribute[] {colorFallbacks, opacityFallbacks},
finalOpacity2, opacity2);
themedDefs.loaderAddChild(null, result.gradient);
result.finalizer.run();
int resultRGB = ColorUtil.rgbNoAlpha(result.gradient.getStopColors()[0]);
int expectedRGB = ColorUtil.rgbNoAlpha(result.color);
if (expectedRGB != resultRGB) {
throw new IllegalStateException("Color not applied. Expected " + result.color + " but received "
+ result.gradient.getStopColors()[0] + " (rgb " + expectedRGB + " != " + resultRGB + ")");
}
LOGGER.finest(() -> Arrays.toString(result.gradient.getStopColors()));
}
}
LOGGER.fine("Patching done");
}
public static float getOpacity(final LinearGradient gradient, final Map<Object, Object> propertyMap,
final Map<Object, Object> contextDefaults) {
String opacityKey = getOpacityKey(gradient);
return getOpacity(opacityKey, null, propertyMap, contextDefaults);
}
public static Color getColor(final LinearGradient gradient, final Map<Object, Object> propertyMap,
final Map<Object, Object> contextDefaults) {
String id = gradient.getId();
StyleAttribute fallbacks = getAttribute("fallback", gradient);
return resolveColor(id, getFallbacks(fallbacks), FALLBACK_COLOR, propertyMap, contextDefaults);
}
private static Color resolveColor(final String key, final String[] fallbacks, final Color fallbackColor,
public static Color resolveColor(final String key, final String[] fallbacks,
final Map<Object, Object> propertyMap, final Map<Object, Object> contextDefaults) {
Color color = get(propertyMap, contextDefaults, key, fallbacks, Color.class);
if (color == null) {
color = fallbackColor;
color = FALLBACK_COLOR;
LOGGER.warning("Could not load color with id '" + key + "' fallbacks" + Arrays.toString(fallbacks)
+ ". Using color '" + fallbackColor + "' instead.");
+ ". Using color '" + color + "' instead.");
}
return color;
}
private static StyleAttribute getAttribute(final String key, final SVGElement child) {
StyleAttribute attribute = new StyleAttribute();
attribute.setName(key);
try {
child.getStyle(attribute);
} catch (final SVGException e) {
return null;
}
return attribute;
}
private static float getStopOpacity(final Stop stop) {
StyleAttribute attribute = new StyleAttribute();
attribute.setName("stop-opacity");
try {
stop.getStyle(attribute);
} catch (final SVGException e) {
return -1;
}
return !attribute.getStringValue().isEmpty() ? attribute.getFloatValue() : -1;
}
private static String[] getFallbacks(final StyleAttribute fallbacks) {
if (fallbacks == null) return new String[0];
return fallbacks.getStringList();
}
private static float getOpacity(final String key, final String[] fallbacks, final Map<Object, Object> propertyMap,
public static float getOpacity(final String key, final String[] fallbacks, final Map<Object, Object> propertyMap,
final Map<Object, Object> contextDefaults) {
if ((key == null || key.isEmpty()) && (fallbacks == null || fallbacks.length == 0)) return -1;
// UIManager defaults to 0, if the value isn't an integer (or null).
@ -215,124 +73,6 @@ public final class IconColorMapper {
return -1;
}
private static String getOpacityKey(final LinearGradient child) {
StyleAttribute attribute = new StyleAttribute();
attribute.setName("opacity");
try {
child.getStyle(attribute);
} catch (final SVGException e) {
e.printStackTrace();
return null;
}
return attribute.getStringValue();
}
private static class ColorResult {
private final LinearGradient gradient;
private final Runnable finalizer;
private final Color color;
private ColorResult(LinearGradient gradient, Runnable finalizer, Color color) {
this.gradient = gradient;
this.finalizer = finalizer;
this.color = color;
}
}
private static ColorResult createColor(final Color c, final String name, final String opacityKey,
final StyleAttribute[] extraAttributes, final float opacity1, final float opacity2)
throws SVGElementException {
LinearGradient grad = new LinearGradient();
grad.addAttribute("id", AnimationElement.AT_XML, name);
if (opacityKey != null && !opacityKey.isEmpty()) {
grad.addAttribute("opacity", AnimationElement.AT_XML, opacityKey);
}
if (extraAttributes != null) {
for (StyleAttribute attribute : extraAttributes) {
if (attribute != null && !attribute.getStringValue().isEmpty()) {
grad.addAttribute(attribute.getName(), AnimationElement.AT_XML, attribute.getStringValue());
}
}
}
return new ColorResult(grad, () -> {
String color = toHexString(c);
BuildableStop stop1 = new BuildableStop(color);
BuildableStop stop2 = new BuildableStop(color);
try {
stop1.addAttribute("stop-color", AnimationElement.AT_XML, color);
stop1.addAttribute("offset", AnimationElement.AT_XML, "0");
stop2.addAttribute("stop-color", AnimationElement.AT_XML, color);
stop2.addAttribute("offset", AnimationElement.AT_XML, "1");
if (opacity1 != 1) {
stop1.addAttribute("stop-opacity", AnimationElement.AT_XML, String.valueOf(opacity1));
}
if (opacity2 != 1) {
stop2.addAttribute("stop-opacity", AnimationElement.AT_XML, String.valueOf(opacity2));
}
grad.loaderAddChild(null, stop1);
grad.loaderAddChild(null, stop2);
stop1.build();
stop2.build();
} catch (final SVGException e) {
throw new RuntimeException(e);
}
}, ColorUtil.toAlpha(c, opacity1));
}
private static class BuildableStop extends Stop {
private final String color;
private BuildableStop(final String color) {
this.color = color;
}
@Override
public boolean getStyle(StyleAttribute attrib, boolean recursive, boolean evalAnimation) throws SVGException {
if ("stop-color".equals(attrib.getName())) {
attrib.setStringValue(color);
return true;
}
return super.getStyle(attrib, recursive, evalAnimation);
}
@Override
protected void build() throws SVGException {
super.build();
}
}
public static Map<Object, Object> getProperties(final SVGIcon svgIcon) {
SVGUniverse universe = svgIcon.getSvgUniverse();
SVGDiagram diagram = universe.getDiagram(svgIcon.getSvgURI());
SVGElement defs = diagram.getElement("colors");
Map<Object, Object> values = new HashMap<>();
if (defs != null) {
List<SVGElement> children = defs.getChildren(null);
for (SVGElement child : children) {
if (child instanceof LinearGradient) {
LinearGradient grad = (LinearGradient) child;
String colorKey = grad.getId();
String opacityKey = getOpacityKey(grad);
SVGElement c = grad.getChild(0);
if (c instanceof Stop) {
Stop stop = (Stop) c;
StyleAttribute colorAttr = getAttribute("stop-color", stop);
Color color = colorAttr != null ? colorAttr.getColorValue() : null;
values.put(colorKey, color != null ? color : Color.BLACK);
if (opacityKey != null && !opacityKey.isEmpty()) {
StyleAttribute opacityAttr = getAttribute("stop-opacity", stop);
int opacity = opacityAttr != null ? (int) (100 * opacityAttr.getFloatValue()) : 100;
values.put(opacityKey, opacity);
}
}
}
}
}
return values;
}
public static <T> Pair<Object, T> getEntry(final Map<Object, Object> map, final Map<Object, Object> contextDefaults,
final Object key, final Object[] fallbacks, final Class<T> type) {
Object obj = null;
@ -366,7 +106,7 @@ public final class IconColorMapper {
// We found the value
break outer;
} else {
// Further search won't find anything.
// Any further searches won't find anything.
// The value doesn't explicitly reference other keys.
continue outer;
}
@ -381,8 +121,4 @@ public final class IconColorMapper {
final Object[] fallbacks, final Class<T> type) {
return getEntry(map, contextDefaults, key, fallbacks, type).getSecond();
}
private static String toHexString(final Color color) {
return "#" + ColorUtil.toHex(color);
}
}

41
property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/IconLoader.java

@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2019-2021 Jannis Weis
* Copyright (c) 2019-2022 Jannis Weis
*
* 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,
@ -28,8 +28,6 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
@ -41,6 +39,7 @@ import org.jetbrains.annotations.Nullable;
import com.github.weisj.darklaf.util.LazyValue;
import com.github.weisj.darklaf.util.LogUtil;
import com.github.weisj.darklaf.util.cache.SoftCache;
import com.github.weisj.jsvg.parser.SVGLoader;
/**
* Default implementation of {@link IconResolver}, which provides some additional convenience
@ -56,6 +55,8 @@ public final class IconLoader implements IconResolver {
private static final AtomicReference<Object> currentThemeKey = new AtomicReference<>(null);
private static final AtomicReference<AwareIconStyle> currentAwareStyle = new AtomicReference<>(null);
private static final SVGLoader loader;
// Infer size by default.
private static final int DEFAULT_WIDTH_SVG = -1;
private static final int DEFAULT_HEIGHT_SVG = -1;
@ -73,6 +74,11 @@ public final class IconLoader implements IconResolver {
updateThemeStatus(new Object());
}
});
loader = new SVGLoader();
}
static SVGLoader svgLoader() {
return loader;
}
/**
@ -415,35 +421,26 @@ public final class IconLoader implements IconResolver {
private CacheableIcon loadSVGIconInternal(final String path, final int w, final int h, final boolean themed,
final Map<Object, Object> propertyMap) {
Supplier<URI> uriSupplier = createURISupplier(path);
URI uri = createURI(path);
DarkSVGIcon svgIcon;
if (themed) {
if (propertyMap != null) {
svgIcon = new CustomThemedIcon(uriSupplier, w, h, propertyMap);
svgIcon = new CustomThemedIcon(uri, w, h, propertyMap);
} else {
svgIcon = new ThemedSVGIcon(uriSupplier, w, h);
svgIcon = new ThemedSVGIcon(uri, w, h);
}
} else {
svgIcon = new DarkSVGIcon(createURISupplier(path), w, h);
svgIcon = new DarkSVGIcon(uri, w, h);
}
return svgIcon;
}
private Supplier<URI> createURISupplier(final String path) {
return () -> {
try {
URL url = getResource(path);
if (url.getPath().startsWith("file:///")) {
return new URI(url.toString().replace("file:///", "file:/"));
} else {
return Objects.requireNonNull(url.toURI());
}
} catch (NullPointerException | URISyntaxException e) {
LOGGER.log(Level.SEVERE,
"Exception while loading '" + path + "'" + ". Resolving from " + parentClass, e);
}
return null;
};
private @NotNull URI createURI(final String path) {
try {
return Objects.requireNonNull(getResource(path)).toURI();
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
/**

40
property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/ThemedSVGIcon.java

@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2019-2021 Jannis Weis
* Copyright (c) 2019-2022 Jannis Weis
*
* 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,
@ -21,10 +21,15 @@
package com.github.weisj.darklaf.properties.icons;
import java.net.URI;
import java.util.function.Supplier;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import org.jetbrains.annotations.NotNull;
import com.github.weisj.jsvg.parser.*;
/**
* An {@link DarkSVGIcon}, which will patch dynamic color values defined in the svg file.
@ -33,21 +38,18 @@ import javax.swing.*;
*/
public class ThemedSVGIcon extends DarkSVGIcon implements ThemedIcon {
private final List<ThemedSVGIconParserProvider.ThemedSolidColorPaint> paints;
private Object currentTheme;
private boolean updatedNotDuringPaint;
public ThemedSVGIcon(final Supplier<URI> uriSupplier, final int displayWidth, final int displayHeight) {
super(uriSupplier, displayWidth, displayHeight);
currentTheme = new Object();
}
public ThemedSVGIcon(final URI uri, final int displayWidth, final int displayHeight) {
super(uri, displayWidth, displayHeight);
currentTheme = new Object();
this.paints = new ArrayList<>();
}
protected ThemedSVGIcon(final int width, final int height, final ThemedSVGIcon icon) {
super(width, height, icon);
this.paints = icon.paints;
this.currentTheme = icon.currentTheme;
this.updatedNotDuringPaint = icon.updatedNotDuringPaint;
}
@ -83,22 +85,36 @@ public class ThemedSVGIcon extends DarkSVGIcon implements ThemedIcon {
if (updatedNotDuringPaint) {
updatedNotDuringPaint = false;
// Update didn't happen during painting call.
// Image might not be up to date.
// Image might not be up-to-date.
return true;
}
return false;
}
@Override
protected @NotNull ParserProvider createParserProvider() {
return new ThemedSVGIconParserProvider(this);
}
@Override
public String toString() {
return "ThemedSVGIcon{" +
"parentState= " + super.toString() +
"currentTheme=" + currentTheme +
"paints=" + paints +
", currentTheme=" + currentTheme +
", updatedNotDuringPaint=" + updatedNotDuringPaint +
'}';
}
protected void invalidate() {
void registerPaint(final ThemedSVGIconParserProvider.ThemedSolidColorPaint paint) {
paints.add(paint);
}
List<ThemedSVGIconParserProvider.ThemedSolidColorPaint> paints() {
return paints;
}
public void invalidate() {
currentTheme = new Object();
}
@ -107,6 +123,6 @@ public class ThemedSVGIcon extends DarkSVGIcon implements ThemedIcon {
}
protected void patchColors() {
IconColorMapper.patchColors(getSVGIcon(), getContextDefaults());
ThemedSVGIconParserProvider.patchColors(paints, getContextDefaults(), null);
}
}

158
property-loader/src/main/java/com/github/weisj/darklaf/properties/icons/ThemedSVGIconParserProvider.java

@ -0,0 +1,158 @@
/*
* MIT License
*
* Copyright (c) 2021-2022 Jannis Weis
*
* 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.
*/
package com.github.weisj.darklaf.properties.icons;
import java.awt.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.github.weisj.darklaf.util.ColorUtil;
import com.github.weisj.jsvg.attributes.paint.DefaultPaintParser;
import com.github.weisj.jsvg.attributes.paint.SimplePaintSVGPaint;
import com.github.weisj.jsvg.nodes.Defs;
import com.github.weisj.jsvg.nodes.LinearGradient;
import com.github.weisj.jsvg.parser.AttributeNode;
import com.github.weisj.jsvg.parser.DefaultParserProvider;
import com.github.weisj.jsvg.parser.DomProcessor;
import com.github.weisj.jsvg.parser.ParsedElement;
public class ThemedSVGIconParserProvider extends DefaultParserProvider {
private final DomProcessor preProcessor;
public ThemedSVGIconParserProvider(final @NotNull ThemedSVGIcon icon) {
preProcessor = new ThemedSVGIconDomProcessor(icon);
}
@Override
public @Nullable DomProcessor createPreProcessor() {
return preProcessor;
}
private static class ThemedSVGIconDomProcessor extends DarkSVGIconDomProcessor<ThemedSVGIcon> {
public ThemedSVGIconDomProcessor(@NotNull ThemedSVGIcon icon) {
super(icon);
}
@Override
public void process(final @NotNull ParsedElement root) {
super.process(root);
for (ParsedElement child : root.children()) {
if ("colors".equals(child.id()) && child.node() instanceof Defs) {
replaceGradients(child);
return;
}
}
}
private void replaceGradients(final ParsedElement element) {
for (ParsedElement child : element.children()) {
if (child.node() instanceof LinearGradient) {
replaceSingleGradient(child);
}
}
}
private void replaceSingleGradient(final ParsedElement gradient) {
String id = gradient.id();
if (id == null) return;
AttributeNode attributeNode = gradient.attributeNode();
String[] fallbacks = attributeNode.getStringList("fallback");
String opacityKey = attributeNode.getValue("opacity");
String[] opacityFallback = attributeNode.getStringList("opacity-fallback");
List<ParsedElement> stops = gradient.children();
float originalOpacity = 1;
if (!stops.isEmpty()) {
originalOpacity = stops.get(0).attributeNode().getPercentage("stop-opacity", originalOpacity);
}
ThemedSVGIconParserProvider.ThemedSolidColorPaint themedPaint =
new ThemedSVGIconParserProvider.ThemedSolidColorPaint(
id, fallbacks, opacityKey, opacityFallback, originalOpacity);
gradient.registerNamedElement(id, themedPaint);
icon.registerPaint(themedPaint);
}
}
public static void patchColors(final List<ThemedSolidColorPaint> paints, final Map<Object, Object> propertyMap,
final Map<Object, Object> contextDefaults) {
for (ThemedSolidColorPaint paint : paints) {
paint.color = IconColorMapper.resolveColor(
paint.colorKey, paint.colorFallbacks, propertyMap, contextDefaults);
float opacity = IconColorMapper.getOpacity(
paint.opacityKey, paint.opacityFallbacks, propertyMap, contextDefaults);
if (opacity < 0) opacity = paint.originalOpacity;
paint.color = ColorUtil.toAlpha(paint.color, opacity);
}
}
public static Map<Object, Object> getProperties(List<ThemedSVGIconParserProvider.ThemedSolidColorPaint> paints) {
Map<Object, Object> values = new HashMap<>(paints.size() * 2, 0.75f);
for (ThemedSVGIconParserProvider.ThemedSolidColorPaint paint : paints) {
values.put(paint.colorKey, ColorUtil.removeAlpha(paint.color));
if (paint.opacityKey != null && !paint.opacityKey.isEmpty()) {
values.put(paint.opacityKey, (int) (paint.color.getAlpha() / 255f));
}
}
return values;
}
public static Map<String, Color> getNamedColors(final ThemedSVGIcon icon) {
icon.ensureLoaded(false);
return icon.paints().stream().collect(Collectors.toMap(
p -> p.colorKey,
p -> p.color));
}
static class ThemedSolidColorPaint implements SimplePaintSVGPaint {
private final String colorKey;
private final String[] colorFallbacks;
private final String opacityKey;
private final String[] opacityFallbacks;
private final float originalOpacity;
private Color color = DefaultPaintParser.DEFAULT_COLOR;
ThemedSolidColorPaint(final String colorKey, final String[] colorFallbacks,
final String opacityKey, final String[] opacityFallbacks,
float originalOpacity) {
this.colorKey = colorKey;
this.colorFallbacks = colorFallbacks;
this.opacityKey = opacityKey;
this.opacityFallbacks = opacityFallbacks;
this.originalOpacity = originalOpacity;
}
@Override
public @NotNull Paint paint() {
return color;
}
}
}

2
property-loader/src/main/module/module-info.java

@ -26,7 +26,7 @@ module darklaf.properties {
requires transitive java.desktop;
requires transitive darklaf.utils;
requires swingdsl.visualPadding;
requires com.kitfox.svg;
requires com.github.weisj.jsvg;
requires static org.jetbrains.annotations;

20
property-loader/src/test/java/com/github/weisj/darklaf/properties/icons/SVGImageTest.java

@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2019-2021 Jannis Weis
* Copyright (c) 2019-2022 Jannis Weis
*
* 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,
@ -28,7 +28,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.ResourceLock;
import com.kitfox.svg.app.beans.SVGIcon;
import com.github.weisj.jsvg.SVGDocument;
@ResourceLock(value = "IconLoader")
class SVGImageTest {
@ -45,8 +45,8 @@ class SVGImageTest {
DarkSVGIcon icon = (DarkSVGIcon) loader.getIcon("svg_icon.svg", 16, 16);
Assertions.assertSame(icon, icon.derive(16, 16));
// Force load the icon.
icon.getSVGIcon();
// Forcefully load the icon.
icon.getSVGDocument();
icon.setDisplaySize(-1, -1);
Assertions.assertSame(icon, loader.getIcon("svg_icon.svg", 16, 16));
@ -62,7 +62,7 @@ class SVGImageTest {
Assertions.assertSame(icon, loader.getIcon("svg_icon.svg", 16, 16));
// Force load
icon.getSVGIcon();
icon.getSVGDocument();
Assertions.assertSame(icon, loader.getIcon("svg_icon.svg", 16, 16));
icon.setDisplaySize(50, 50);
@ -70,8 +70,8 @@ class SVGImageTest {
icon.setDisplaySize(-1, -1);
Assertions.assertNotSame(icon, loader.getIcon("svg_icon.svg", 50, 50));
Assertions.assertSame(icon.getSVGIcon(),
((DarkSVGIcon) loader.getIcon("svg_icon.svg", 50, 50)).getSVGIcon());
Assertions.assertSame(icon.getSVGDocument(),
((DarkSVGIcon) loader.getIcon("svg_icon.svg", 50, 50)).getSVGDocument());
Assertions.assertEquals(16, icon.getIconWidth());
@ -86,17 +86,17 @@ class SVGImageTest {
Assertions.assertEquals(16, icon.getIconWidth());
Assertions.assertEquals(16, icon.getIconHeight());
Set<SVGIcon> svgSet = new HashSet<>();
Set<SVGDocument> svgSet = new HashSet<>();
for (int i = 0; i < 100; i++) {
DarkSVGIcon svgIcon = (DarkSVGIcon) loader.getIcon("svg_icon.svg");
svgSet.add(svgIcon.getSVGIcon());
svgSet.add(svgIcon.getSVGDocument());
}
Assertions.assertEquals(1, svgSet.size());
svgSet.clear();
for (int i = 0; i < 100; i++) {
DarkSVGIcon icon2 = icon.derive(i, i);
svgSet.add(icon2.getSVGIcon());
svgSet.add(icon2.getSVGDocument());
}
Assertions.assertEquals(1, svgSet.size());
}

2
settings.gradle.kts

@ -32,7 +32,7 @@ dependencyResolutionManagement {
}
create("libs") {
idv("svgSalamander", "com.formdev:svgSalamander")
idv("jsvg", "com.github.weisj:jsvg")
idv("swingDslLafSupport", "com.github.weisj:swing-extensions-laf-support", "swingDsl")
idv("visualPaddings", "com.github.weisj:swing-extensions-visual-padding", "swingDsl")
idv("swingx", "org.swinglabs:swingx")

Loading…
Cancel
Save