diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java index 076c28461..e5b116ad8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java @@ -24,9 +24,8 @@ import javafx.beans.binding.Bindings; import javafx.beans.binding.ObjectBinding; import javafx.scene.paint.Color; import javafx.scene.text.Font; -import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.util.io.FileUtils; -import org.jackhuang.hmcl.util.io.IOUtils; import java.io.File; import java.io.IOException; @@ -36,11 +35,10 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Base64; import java.util.Locale; import java.util.Objects; import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import static org.jackhuang.hmcl.setting.ConfigHolder.config; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -58,34 +56,6 @@ public class Theme { Color.web("#B71C1C") // red }; - private static Charset cssCharset; - - private static Charset getCssCharset() { - if (cssCharset != null) - return cssCharset; - - Charset defaultCharset = Charset.defaultCharset(); - if (defaultCharset != StandardCharsets.UTF_8) { - // https://bugs.openjdk.org/browse/JDK-8279328 - // For JavaFX 17 or earlier, native encoding should be used - String jfxVersion = System.getProperty("javafx.version"); - if (jfxVersion != null) { - Matcher matcher = Pattern.compile("^(?[0-9]+)").matcher(jfxVersion); - if (matcher.find()) { - int v = Lang.parseInt(matcher.group(), -1); - if (v >= 18) { - cssCharset = StandardCharsets.UTF_8; - } - } - } - } - - if (cssCharset == null) - cssCharset = defaultCharset; - - return cssCharset; - } - @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private static Optional font; @@ -145,6 +115,14 @@ public class Theme { return isLight() ? Color.BLACK : Color.WHITE; } + private static String rgba(Color color, double opacity) { + return String.format("rgba(%d, %d, %d, %.1f)", + (int) Math.ceil(color.getRed() * 256), + (int) Math.ceil(color.getGreen() * 256), + (int) Math.ceil(color.getBlue() * 256), + opacity); + } + public String[] getStylesheets(String overrideFontFamily) { String css = "/assets/css/blue.css"; @@ -163,30 +141,41 @@ public class Theme { if (fontFamily != null || !this.color.equalsIgnoreCase(BLUE.color)) { Color textFill = getForegroundColor(); - String fontCss = ""; + + StringBuilder themeBuilder = new StringBuilder(512); + themeBuilder.append(".root {") + .append("-fx-base-color:").append(color).append(';') + .append("-fx-base-darker-color: derive(-fx-base-color, -10%);") + .append("-fx-base-check-color: derive(-fx-base-color, 30%);") + .append("-fx-rippler-color:").append(rgba(paint, 0.3)).append(';') + .append("-fx-base-rippler-color: derive(").append(rgba(paint, 0.3)).append(", 100%);") + .append("-fx-base-disabled-text-fill:").append(rgba(textFill, 0.7)).append(";") + .append("-fx-base-text-fill:").append(getColorDisplayName(getForegroundColor())).append(";") + .append("-theme-thumb:").append(rgba(paint, 0.7)).append(";"); + if (fontFamily != null) { - fontCss = "-fx-font-family: \"" + fontFamily + "\";"; + themeBuilder.append("-fx-font-family:\"").append(fontFamily).append("\";"); if (fontStyle != null && !fontStyle.isEmpty()) - fontCss += " -fx-font-style: \"" + fontStyle + "\";"; + themeBuilder.append("-fx-font-style:\"").append(fontStyle).append("\";"); } - try { - File temp = File.createTempFile("hmcl", ".css"); - String themeText = IOUtils.readFullyAsString(Theme.class.getResourceAsStream("/assets/css/custom.css")) - .replace("%base-color%", color) - .replace("%base-red%", Integer.toString((int) Math.ceil(paint.getRed() * 256))) - .replace("%base-green%", Integer.toString((int) Math.ceil(paint.getGreen() * 256))) - .replace("%base-blue%", Integer.toString((int) Math.ceil(paint.getBlue() * 256))) - .replace("%base-rippler-color%", String.format("rgba(%d, %d, %d, 0.3)", (int) Math.ceil(paint.getRed() * 256), (int) Math.ceil(paint.getGreen() * 256), (int) Math.ceil(paint.getBlue() * 256))) - .replace("%disabled-font-color%", String.format("rgba(%d, %d, %d, 0.7)", (int) Math.ceil(textFill.getRed() * 256), (int) Math.ceil(textFill.getGreen() * 256), (int) Math.ceil(textFill.getBlue() * 256))) - .replace("%font-color%", getColorDisplayName(getForegroundColor())) - .replace("%font%", fontCss); - FileUtils.writeText(temp, themeText, getCssCharset()); - temp.deleteOnExit(); - css = temp.toURI().toString(); - } catch (IOException | NullPointerException e) { - LOG.error("Unable to create theme stylesheet. Fallback to blue theme.", e); - } + themeBuilder.append('}'); + + if (FXUtils.JAVAFX_MAJOR_VERSION >= 17) + // JavaFX 17+ support loading stylesheets from data URIs + // https://bugs.openjdk.org/browse/JDK-8267554 + css = "data:text/css;charset=UTF-8;base64," + Base64.getEncoder().encodeToString(themeBuilder.toString().getBytes(StandardCharsets.UTF_8)); + else + try { + File temp = File.createTempFile("hmcl", ".css"); + // For JavaFX 17 or earlier, CssParser uses the default charset + // https://bugs.openjdk.org/browse/JDK-8279328 + FileUtils.writeText(temp, themeBuilder.toString(), Charset.defaultCharset()); + temp.deleteOnExit(); + css = temp.toURI().toString(); + } catch (IOException | NullPointerException e) { + LOG.error("Unable to create theme stylesheet. Fallback to blue theme.", e); + } } return new String[]{css, "/assets/css/root.css"}; @@ -235,7 +224,7 @@ public class Theme { } public static String getColorDisplayName(Color c) { - return c != null ? String.format("#%02x%02x%02x", Math.round(c.getRed() * 255.0D), Math.round(c.getGreen() * 255.0D), Math.round(c.getBlue() * 255.0D)).toUpperCase(Locale.ROOT) : null; + return c != null ? String.format("#%02X%02X%02X", Math.round(c.getRed() * 255.0D), Math.round(c.getGreen() * 255.0D), Math.round(c.getBlue() * 255.0D)) : null; } private static ObjectBinding FOREGROUND_FILL; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index 2e2985725..5d0d68473 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -54,6 +54,7 @@ import org.glavo.png.javafx.PNGJavaFXUtils; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.animation.AnimationUtils; import org.jackhuang.hmcl.util.Holder; +import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.ResourceNotFoundError; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.FileUtils; @@ -83,6 +84,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import static org.jackhuang.hmcl.util.Lang.thread; @@ -94,6 +97,20 @@ public final class FXUtils { private FXUtils() { } + public static final int JAVAFX_MAJOR_VERSION; + + static { + String jfxVersion = System.getProperty("javafx.version"); + int majorVersion = -1; + if (jfxVersion != null) { + Matcher matcher = Pattern.compile("^(?[0-9]+)").matcher(jfxVersion); + if (matcher.find()) { + majorVersion = Lang.parseInt(matcher.group(), -1); + } + } + JAVAFX_MAJOR_VERSION = majorVersion; + } + public static final String DEFAULT_MONOSPACE_FONT = OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "Consolas" : "Monospace"; private static final Map builtinImageCache = new ConcurrentHashMap<>(); diff --git a/HMCL/src/main/resources/assets/css/custom.css b/HMCL/src/main/resources/assets/css/custom.css deleted file mode 100644 index 72c29e9ac..000000000 --- a/HMCL/src/main/resources/assets/css/custom.css +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -.root { - -fx-base-color: %base-color%; - -fx-base-darker-color: derive(-fx-base-color, -10%); - -fx-base-check-color: derive(-fx-base-color, 30%); - -fx-rippler-color: rgba(%base-red%, %base-green%, %base-blue%, 0.3); - -fx-base-rippler-color: derive(%base-rippler-color%, 100%); - -fx-base-disabled-text-fill: %disabled-font-color%; - -fx-base-text-fill: %font-color%; - - -theme-thumb: rgba(%base-red%, %base-green%, %base-blue%, 0.7); - - %font% -} \ No newline at end of file