mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-11 12:56:53 -04:00
parent
9c0f823705
commit
76ed9353bd
121
HMCL/src/main/java/org/jackhuang/hmcl/setting/FontManager.java
Normal file
121
HMCL/src/main/java/org/jackhuang/hmcl/setting/FontManager.java
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher
|
||||||
|
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.setting;
|
||||||
|
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.scene.text.Font;
|
||||||
|
import org.jackhuang.hmcl.Metadata;
|
||||||
|
import org.jackhuang.hmcl.util.Lazy;
|
||||||
|
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||||
|
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Glavo
|
||||||
|
*/
|
||||||
|
public final class FontManager {
|
||||||
|
|
||||||
|
public static final double DEFAULT_FONT_SIZE = 12.0f;
|
||||||
|
|
||||||
|
private static final Lazy<Font> DEFAULT_FONT = new Lazy<>(() -> {
|
||||||
|
Font font = tryLoadDefaultFont(Metadata.HMCL_CURRENT_DIRECTORY);
|
||||||
|
if (font != null)
|
||||||
|
return font;
|
||||||
|
|
||||||
|
font = tryLoadDefaultFont(Metadata.CURRENT_DIRECTORY);
|
||||||
|
if (font != null)
|
||||||
|
return font;
|
||||||
|
|
||||||
|
font = tryLoadDefaultFont(Metadata.HMCL_GLOBAL_DIRECTORY);
|
||||||
|
if (font != null)
|
||||||
|
return font;
|
||||||
|
|
||||||
|
Path thisJar = JarUtils.thisJarPath();
|
||||||
|
if (thisJar != null && thisJar.getParent() != null)
|
||||||
|
return tryLoadDefaultFont(thisJar.getParent());
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
private static final ObjectProperty<Font> fontProperty = new SimpleObjectProperty<>(getDefaultFont());
|
||||||
|
|
||||||
|
static {
|
||||||
|
fontProperty.addListener((obs, oldValue, newValue) -> {
|
||||||
|
if (newValue != null)
|
||||||
|
config().setLauncherFontFamily(newValue.getFamily());
|
||||||
|
else
|
||||||
|
config().setLauncherFontFamily(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Font tryLoadDefaultFont(Path dir) {
|
||||||
|
String[] fileNames = {"font.ttf", "font.otf", "font.woff"};
|
||||||
|
|
||||||
|
for (String fileName : fileNames) {
|
||||||
|
Path path = dir.resolve(fileName);
|
||||||
|
if (Files.isRegularFile(path)) {
|
||||||
|
try {
|
||||||
|
Font font = Font.loadFont(path.toUri().toURL().toExternalForm(), DEFAULT_FONT_SIZE);
|
||||||
|
if (font != null) {
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
} catch (MalformedURLException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.warning("Failed to load font " + path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Font getDefaultFont() {
|
||||||
|
String fontFamily = config().getLauncherFontFamily();
|
||||||
|
if (fontFamily == null)
|
||||||
|
fontFamily = System.getProperty("hmcl.font.override");
|
||||||
|
if (fontFamily == null)
|
||||||
|
fontFamily = System.getenv("HMCL_FONT");
|
||||||
|
|
||||||
|
return fontFamily == null ? DEFAULT_FONT.get() : Font.font(fontFamily, DEFAULT_FONT_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ObjectProperty<Font> fontProperty() {
|
||||||
|
return fontProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Font getFont() {
|
||||||
|
return fontProperty.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setFont(Font font) {
|
||||||
|
fontProperty.set(font);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setFontFamily(String fontFamily) {
|
||||||
|
setFont(fontFamily != null ? Font.font(fontFamily, DEFAULT_FONT_SIZE) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private FontManager() {
|
||||||
|
}
|
||||||
|
}
|
168
HMCL/src/main/java/org/jackhuang/hmcl/setting/StyleSheets.java
Normal file
168
HMCL/src/main/java/org/jackhuang/hmcl/setting/StyleSheets.java
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher
|
||||||
|
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.setting;
|
||||||
|
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.text.Font;
|
||||||
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||||
|
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Glavo
|
||||||
|
*/
|
||||||
|
public final class StyleSheets {
|
||||||
|
private static final int FONT_STYLE_SHEET_INDEX = 0;
|
||||||
|
private static final int THEME_STYLE_SHEET_INDEX = 1;
|
||||||
|
|
||||||
|
private static final ObservableList<String> stylesheets;
|
||||||
|
|
||||||
|
static {
|
||||||
|
String[] array = new String[]{
|
||||||
|
getFontStyleSheet(),
|
||||||
|
getThemeStyleSheet(),
|
||||||
|
"/assets/css/root.css"
|
||||||
|
};
|
||||||
|
stylesheets = FXCollections.observableList(Arrays.asList(array));
|
||||||
|
|
||||||
|
FontManager.fontProperty().addListener(o -> stylesheets.set(FONT_STYLE_SHEET_INDEX, getFontStyleSheet()));
|
||||||
|
config().themeProperty().addListener(o -> stylesheets.set(THEME_STYLE_SHEET_INDEX, getThemeStyleSheet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toStyleSheetUri(String styleSheet, String fallback) {
|
||||||
|
if (FXUtils.JAVAFX_MAJOR_VERSION >= 17)
|
||||||
|
// JavaFX 17+ support loading stylesheets from data URIs
|
||||||
|
// https://bugs.openjdk.org/browse/JDK-8267554
|
||||||
|
return "data:text/css;charset=UTF-8;base64," + Base64.getEncoder().encodeToString(styleSheet.getBytes(StandardCharsets.UTF_8));
|
||||||
|
else
|
||||||
|
try {
|
||||||
|
Path temp = Files.createTempFile("hmcl", ".css");
|
||||||
|
// For JavaFX 17 or earlier, CssParser uses the default charset
|
||||||
|
// https://bugs.openjdk.org/browse/JDK-8279328
|
||||||
|
FileUtils.writeText(temp, styleSheet, Charset.defaultCharset());
|
||||||
|
temp.toFile().deleteOnExit();
|
||||||
|
return temp.toUri().toString();
|
||||||
|
} catch (IOException | NullPointerException e) {
|
||||||
|
LOG.error("Unable to create stylesheet, fallback to " + fallback, e);
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getFontStyleSheet() {
|
||||||
|
final String defaultCss = "/assets/css/font.css";
|
||||||
|
final Font font = FontManager.getFont();
|
||||||
|
|
||||||
|
if (font == null || font == Font.getDefault())
|
||||||
|
return defaultCss;
|
||||||
|
|
||||||
|
String fontFamily = font.getFamily();
|
||||||
|
String style = font.getStyle();
|
||||||
|
String weight = null;
|
||||||
|
String posture = null;
|
||||||
|
|
||||||
|
if (style != null) {
|
||||||
|
style = style.toLowerCase(Locale.ROOT);
|
||||||
|
|
||||||
|
if (style.contains("thin"))
|
||||||
|
weight = "100";
|
||||||
|
else if (style.contains("extralight") || style.contains("extra light") || style.contains("ultralight") | style.contains("ultra light"))
|
||||||
|
weight = "200";
|
||||||
|
else if (style.contains("medium"))
|
||||||
|
weight = "500";
|
||||||
|
else if (style.contains("semibold") || style.contains("semi bold") || style.contains("demibold") || style.contains("demi bold"))
|
||||||
|
weight = "600";
|
||||||
|
else if (style.contains("extrabold") || style.contains("extra bold") || style.contains("ultrabold") || style.contains("ultra bold"))
|
||||||
|
weight = "800";
|
||||||
|
else if (style.contains("black") || style.contains("heavy"))
|
||||||
|
weight = "900";
|
||||||
|
else if (style.contains("light"))
|
||||||
|
weight = "lighter";
|
||||||
|
else if (style.contains("bold"))
|
||||||
|
weight = "bold";
|
||||||
|
|
||||||
|
posture = style.contains("italic") || style.contains("oblique") ? "italic" : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append(".root {");
|
||||||
|
if (fontFamily == null)
|
||||||
|
// https://github.com/HMCL-dev/HMCL/pull/3423
|
||||||
|
builder.append("-fx-font-family: -fx-base-font-family;");
|
||||||
|
else
|
||||||
|
builder.append("-fx-font-family:\"").append(fontFamily).append("\";");
|
||||||
|
|
||||||
|
if (weight != null)
|
||||||
|
builder.append("-fx-font-weight:").append(weight).append(";");
|
||||||
|
|
||||||
|
if (posture != null)
|
||||||
|
builder.append("-fx-font-style:").append(posture).append(";");
|
||||||
|
|
||||||
|
builder.append('}');
|
||||||
|
|
||||||
|
return toStyleSheetUri(builder.toString(), fontFamily);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getThemeStyleSheet() {
|
||||||
|
final String blueCss = "/assets/css/blue.css";
|
||||||
|
|
||||||
|
Theme theme = config().getTheme();
|
||||||
|
if (theme == null || theme.getPaint().equals(Theme.BLUE.getPaint()))
|
||||||
|
return blueCss;
|
||||||
|
|
||||||
|
return toStyleSheetUri(".root {" +
|
||||||
|
"-fx-base-color:" + theme.getColor() + ';' +
|
||||||
|
"-fx-base-darker-color: derive(-fx-base-color, -10%);" +
|
||||||
|
"-fx-base-check-color: derive(-fx-base-color, 30%);" +
|
||||||
|
"-fx-rippler-color:" + rgba(theme.getPaint(), 0.3) + ';' +
|
||||||
|
"-fx-base-rippler-color: derive(" + rgba(theme.getPaint(), 0.3) + ", 100%);" +
|
||||||
|
"-fx-base-disabled-text-fill:" + rgba(theme.getForegroundColor(), 0.7) + ";" +
|
||||||
|
"-fx-base-text-fill:" + Theme.getColorDisplayName(theme.getForegroundColor()) + ";" +
|
||||||
|
"-theme-thumb:" + rgba(theme.getPaint(), 0.7) + ";" +
|
||||||
|
'}', blueCss);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void init(Scene scene) {
|
||||||
|
Bindings.bindContent(scene.getStylesheets(), stylesheets);
|
||||||
|
}
|
||||||
|
|
||||||
|
private StyleSheets() {
|
||||||
|
}
|
||||||
|
}
|
@ -23,31 +23,16 @@ import com.google.gson.stream.JsonWriter;
|
|||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.ObjectBinding;
|
import javafx.beans.binding.ObjectBinding;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.scene.text.Font;
|
|
||||||
import org.jackhuang.hmcl.Metadata;
|
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
|
||||||
import org.jackhuang.hmcl.util.Lazy;
|
|
||||||
import org.jackhuang.hmcl.util.Pair;
|
|
||||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
|
||||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
|
||||||
|
|
||||||
@JsonAdapter(Theme.TypeAdapter.class)
|
@JsonAdapter(Theme.TypeAdapter.class)
|
||||||
public class Theme {
|
public final class Theme {
|
||||||
public static final Theme BLUE = new Theme("blue", "#5C6BC0");
|
public static final Theme BLUE = new Theme("blue", "#5C6BC0");
|
||||||
public static final Color BLACK = Color.web("#292929");
|
public static final Color BLACK = Color.web("#292929");
|
||||||
public static final Color[] SUGGESTED_COLORS = new Color[]{
|
public static final Color[] SUGGESTED_COLORS = new Color[]{
|
||||||
@ -59,47 +44,6 @@ public class Theme {
|
|||||||
Color.web("#B71C1C") // red
|
Color.web("#B71C1C") // red
|
||||||
};
|
};
|
||||||
|
|
||||||
private static Font tryLoadDefaultFont(Path dir) {
|
|
||||||
String[] fileNames = {"font.ttf", "font.otf", "font.woff"};
|
|
||||||
|
|
||||||
for (String fileName : fileNames) {
|
|
||||||
Path path = dir.resolve(fileName);
|
|
||||||
if (Files.isRegularFile(path)) {
|
|
||||||
try {
|
|
||||||
Font font = Font.loadFont(path.toUri().toURL().toExternalForm(), 0);
|
|
||||||
if (font != null) {
|
|
||||||
return font;
|
|
||||||
}
|
|
||||||
} catch (MalformedURLException ignored) {
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG.warning("Failed to load font " + path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Lazy<Font> FONT = new Lazy<>(() -> {
|
|
||||||
Font font = tryLoadDefaultFont(Metadata.HMCL_CURRENT_DIRECTORY);
|
|
||||||
if (font != null)
|
|
||||||
return font;
|
|
||||||
|
|
||||||
font = tryLoadDefaultFont(Metadata.CURRENT_DIRECTORY);
|
|
||||||
if (font != null)
|
|
||||||
return font;
|
|
||||||
|
|
||||||
font = tryLoadDefaultFont(Metadata.HMCL_GLOBAL_DIRECTORY);
|
|
||||||
if (font != null)
|
|
||||||
return font;
|
|
||||||
|
|
||||||
Path thisJar = JarUtils.thisJarPath();
|
|
||||||
if (thisJar != null && thisJar.getParent() != null)
|
|
||||||
return tryLoadDefaultFont(thisJar.getParent());
|
|
||||||
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
public static Theme getTheme() {
|
public static Theme getTheme() {
|
||||||
Theme theme = config().getTheme();
|
Theme theme = config().getTheme();
|
||||||
return theme == null ? BLUE : theme;
|
return theme == null ? BLUE : theme;
|
||||||
@ -123,6 +67,10 @@ public class Theme {
|
|||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Color getPaint() {
|
||||||
|
return paint;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isCustom() {
|
public boolean isCustom() {
|
||||||
return name.startsWith("#");
|
return name.startsWith("#");
|
||||||
}
|
}
|
||||||
@ -135,119 +83,6 @@ public class Theme {
|
|||||||
return isLight() ? Color.BLACK : Color.WHITE;
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Pair<String, String> parseFontStyle(String style) {
|
|
||||||
if (style == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
style = style.toLowerCase(Locale.ROOT);
|
|
||||||
|
|
||||||
String weight;
|
|
||||||
String posture;
|
|
||||||
|
|
||||||
if (style.contains("thin")) {
|
|
||||||
weight = "100";
|
|
||||||
} else if (style.contains("extralight") || style.contains("extra light") || style.contains("ultralight") | style.contains("ultra light")) {
|
|
||||||
weight = "200";
|
|
||||||
} else if (style.contains("medium")) {
|
|
||||||
weight = "500";
|
|
||||||
} else if (style.contains("semibold") || style.contains("semi bold") || style.contains("demibold") || style.contains("demi bold")) {
|
|
||||||
weight = "600";
|
|
||||||
} else if (style.contains("extrabold") || style.contains("extra bold") || style.contains("ultrabold") || style.contains("ultra bold")) {
|
|
||||||
weight = "800";
|
|
||||||
} else if (style.contains("black") || style.contains("heavy")) {
|
|
||||||
weight = "900";
|
|
||||||
} else if (style.contains("light")) {
|
|
||||||
weight = "lighter";
|
|
||||||
} else if (style.contains("bold")) {
|
|
||||||
weight = "bold";
|
|
||||||
} else {
|
|
||||||
weight = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (style.contains("italic") || style.contains("oblique")) {
|
|
||||||
posture = "italic";
|
|
||||||
} else {
|
|
||||||
posture = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Pair.pair(weight, posture);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String[] getStylesheets(String overrideFontFamily) {
|
|
||||||
String css = "/assets/css/blue.css";
|
|
||||||
|
|
||||||
String fontFamily = overrideFontFamily == null
|
|
||||||
? System.getProperty("hmcl.font.override", System.getenv("HMCL_FONT"))
|
|
||||||
: overrideFontFamily;
|
|
||||||
|
|
||||||
Pair<String, String> fontStyle = null;
|
|
||||||
if (fontFamily == null) {
|
|
||||||
Font font = FONT.get();
|
|
||||||
if (font != null) {
|
|
||||||
fontFamily = font.getFamily();
|
|
||||||
fontStyle = parseFontStyle(font.getStyle());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fontFamily != null || !this.color.equalsIgnoreCase(BLUE.color)) {
|
|
||||||
Color textFill = getForegroundColor();
|
|
||||||
|
|
||||||
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)
|
|
||||||
// https://github.com/HMCL-dev/HMCL/pull/3423
|
|
||||||
themeBuilder.append("-fx-font-family: -fx-base-font-family;");
|
|
||||||
else
|
|
||||||
themeBuilder.append("-fx-font-family:\"").append(fontFamily).append("\";");
|
|
||||||
|
|
||||||
if (fontStyle != null) {
|
|
||||||
if (fontStyle.getKey() != null)
|
|
||||||
themeBuilder.append("-fx-font-weight:").append(fontStyle.getKey()).append(";");
|
|
||||||
|
|
||||||
if (fontStyle.getValue() != null)
|
|
||||||
themeBuilder.append("-fx-font-style:").append(fontStyle.getValue()).append(";");
|
|
||||||
}
|
|
||||||
|
|
||||||
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"};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Theme custom(String color) {
|
public static Theme custom(String color) {
|
||||||
if (!color.startsWith("#"))
|
if (!color.startsWith("#"))
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
|
@ -265,7 +265,7 @@ public final class Controllers {
|
|||||||
stage.setMinHeight(MIN_HEIGHT);
|
stage.setMinHeight(MIN_HEIGHT);
|
||||||
decorator.getDecorator().prefWidthProperty().bind(scene.widthProperty());
|
decorator.getDecorator().prefWidthProperty().bind(scene.widthProperty());
|
||||||
decorator.getDecorator().prefHeightProperty().bind(scene.heightProperty());
|
decorator.getDecorator().prefHeightProperty().bind(scene.heightProperty());
|
||||||
scene.getStylesheets().setAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily()));
|
StyleSheets.init(scene);
|
||||||
|
|
||||||
FXUtils.setIcon(stage);
|
FXUtils.setIcon(stage);
|
||||||
stage.setTitle(Metadata.FULL_TITLE);
|
stage.setTitle(Metadata.FULL_TITLE);
|
||||||
|
@ -37,7 +37,7 @@ import org.jackhuang.hmcl.Metadata;
|
|||||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||||
import org.jackhuang.hmcl.game.*;
|
import org.jackhuang.hmcl.game.*;
|
||||||
import org.jackhuang.hmcl.launch.ProcessListener;
|
import org.jackhuang.hmcl.launch.ProcessListener;
|
||||||
import org.jackhuang.hmcl.setting.Theme;
|
import org.jackhuang.hmcl.setting.StyleSheets;
|
||||||
import org.jackhuang.hmcl.task.Schedulers;
|
import org.jackhuang.hmcl.task.Schedulers;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||||
@ -58,7 +58,6 @@ import java.util.regex.Matcher;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
|
||||||
import static org.jackhuang.hmcl.util.DataSizeUnit.MEGABYTES;
|
import static org.jackhuang.hmcl.util.DataSizeUnit.MEGABYTES;
|
||||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||||
@ -104,7 +103,7 @@ public class GameCrashWindow extends Stage {
|
|||||||
this.feedbackTextFlow.getChildren().addAll(FXUtils.parseSegment(i18n("game.crash.feedback"), Controllers::onHyperlinkAction));
|
this.feedbackTextFlow.getChildren().addAll(FXUtils.parseSegment(i18n("game.crash.feedback"), Controllers::onHyperlinkAction));
|
||||||
|
|
||||||
setScene(new Scene(view, 800, 480));
|
setScene(new Scene(view, 800, 480));
|
||||||
getScene().getStylesheets().addAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily()));
|
StyleSheets.init(getScene());
|
||||||
setTitle(i18n("game.crash.title"));
|
setTitle(i18n("game.crash.title"));
|
||||||
FXUtils.setIcon(this);
|
FXUtils.setIcon(this);
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ import javafx.stage.Stage;
|
|||||||
import org.jackhuang.hmcl.game.GameDumpGenerator;
|
import org.jackhuang.hmcl.game.GameDumpGenerator;
|
||||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||||
import org.jackhuang.hmcl.game.Log;
|
import org.jackhuang.hmcl.game.Log;
|
||||||
import org.jackhuang.hmcl.setting.Theme;
|
import org.jackhuang.hmcl.setting.StyleSheets;
|
||||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||||
import org.jackhuang.hmcl.util.Holder;
|
import org.jackhuang.hmcl.util.Holder;
|
||||||
import org.jackhuang.hmcl.util.CircularArrayList;
|
import org.jackhuang.hmcl.util.CircularArrayList;
|
||||||
@ -92,7 +92,7 @@ public final class LogWindow extends Stage {
|
|||||||
this.logs = logs;
|
this.logs = logs;
|
||||||
this.impl = new LogWindowImpl();
|
this.impl = new LogWindowImpl();
|
||||||
setScene(new Scene(impl, 800, 480));
|
setScene(new Scene(impl, 800, 480));
|
||||||
getScene().getStylesheets().addAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily()));
|
StyleSheets.init(getScene());
|
||||||
setTitle(i18n("logwindow.title"));
|
setTitle(i18n("logwindow.title"));
|
||||||
FXUtils.setIcon(this);
|
FXUtils.setIcon(this);
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ import com.jfoenix.controls.JFXListCell;
|
|||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.scene.text.Font;
|
import javafx.scene.text.Font;
|
||||||
|
|
||||||
public class FontComboBox extends JFXComboBox<String> {
|
public final class FontComboBox extends JFXComboBox<String> {
|
||||||
|
|
||||||
private boolean loaded = false;
|
private boolean loaded = false;
|
||||||
|
|
||||||
|
@ -35,8 +35,8 @@ import javafx.scene.layout.VBox;
|
|||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.scene.text.Font;
|
import javafx.scene.text.Font;
|
||||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||||
|
import org.jackhuang.hmcl.setting.FontManager;
|
||||||
import org.jackhuang.hmcl.setting.Theme;
|
import org.jackhuang.hmcl.setting.Theme;
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
import org.jackhuang.hmcl.ui.construct.*;
|
import org.jackhuang.hmcl.ui.construct.*;
|
||||||
@ -74,11 +74,8 @@ public class PersonalizationPage extends StackPane {
|
|||||||
|
|
||||||
ColorPicker picker = new ColorPicker(Color.web(Theme.getTheme().getColor()));
|
ColorPicker picker = new ColorPicker(Color.web(Theme.getTheme().getColor()));
|
||||||
picker.getCustomColors().setAll(Theme.SUGGESTED_COLORS);
|
picker.getCustomColors().setAll(Theme.SUGGESTED_COLORS);
|
||||||
picker.setOnAction(e -> {
|
picker.setOnAction(e ->
|
||||||
Theme theme = Theme.custom(Theme.getColorDisplayName(picker.getValue()));
|
config().setTheme(Theme.custom(Theme.getColorDisplayName(picker.getValue()))));
|
||||||
config().setTheme(theme);
|
|
||||||
Controllers.getScene().getStylesheets().setAll(theme.getStylesheets(config().getLauncherFontFamily()));
|
|
||||||
});
|
|
||||||
themeColorPickerContainer.getChildren().setAll(picker);
|
themeColorPickerContainer.getChildren().setAll(picker);
|
||||||
Platform.runLater(() -> JFXDepthManager.setDepth(picker, 0));
|
Platform.runLater(() -> JFXDepthManager.setDepth(picker, 0));
|
||||||
}
|
}
|
||||||
@ -200,12 +197,13 @@ public class PersonalizationPage extends StackPane {
|
|||||||
hBox.setSpacing(8);
|
hBox.setSpacing(8);
|
||||||
|
|
||||||
FontComboBox cboFont = new FontComboBox();
|
FontComboBox cboFont = new FontComboBox();
|
||||||
cboFont.valueProperty().bindBidirectional(config().launcherFontFamilyProperty());
|
cboFont.setValue(config().getLauncherFontFamily());
|
||||||
|
FXUtils.onChange(cboFont.valueProperty(), FontManager::setFontFamily);
|
||||||
|
|
||||||
JFXButton clearButton = new JFXButton();
|
JFXButton clearButton = new JFXButton();
|
||||||
clearButton.getStyleClass().add("toggle-icon4");
|
clearButton.getStyleClass().add("toggle-icon4");
|
||||||
clearButton.setGraphic(SVG.RESTORE.createIcon(Theme.blackFill(), -1));
|
clearButton.setGraphic(SVG.RESTORE.createIcon(Theme.blackFill(), -1));
|
||||||
clearButton.setOnAction(e -> config().setLauncherFontFamily(null));
|
clearButton.setOnAction(e -> cboFont.setValue(null));
|
||||||
|
|
||||||
hBox.getChildren().setAll(cboFont, clearButton);
|
hBox.getChildren().setAll(cboFont, clearButton);
|
||||||
|
|
||||||
@ -213,15 +211,7 @@ public class PersonalizationPage extends StackPane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label lblFontDisplay = new Label("Hello Minecraft! Launcher");
|
vbox.getChildren().add(new Label("Hello Minecraft! Launcher"));
|
||||||
lblFontDisplay.fontProperty().bind(Bindings.createObjectBinding(
|
|
||||||
() -> Font.font(config().getLauncherFontFamily(), 12),
|
|
||||||
config().launcherFontFamilyProperty()));
|
|
||||||
config().launcherFontFamilyProperty().addListener((a, b, newValue) -> {
|
|
||||||
Controllers.getScene().getStylesheets().setAll(Theme.getTheme().getStylesheets(newValue));
|
|
||||||
});
|
|
||||||
|
|
||||||
vbox.getChildren().add(lblFontDisplay);
|
|
||||||
|
|
||||||
fontPane.getContent().add(vbox);
|
fontPane.getContent().add(vbox);
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,5 @@
|
|||||||
-fx-base-disabled-text-fill: rgba(256, 256, 256, 0.7);
|
-fx-base-disabled-text-fill: rgba(256, 256, 256, 0.7);
|
||||||
-fx-base-text-fill: white;
|
-fx-base-text-fill: white;
|
||||||
|
|
||||||
/* https://github.com/HMCL-dev/HMCL/pull/3423 */
|
|
||||||
-fx-font-family: -fx-base-font-family;
|
|
||||||
|
|
||||||
-theme-thumb: rgba(92, 107, 192, 0.7);
|
-theme-thumb: rgba(92, 107, 192, 0.7);
|
||||||
}
|
}
|
21
HMCL/src/main/resources/assets/css/font.css
Normal file
21
HMCL/src/main/resources/assets/css/font.css
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Hello Minecraft! Launcher
|
||||||
|
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
.root {
|
||||||
|
/* https://github.com/HMCL-dev/HMCL/pull/3423 */
|
||||||
|
-fx-font-family: -fx-base-font-family;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user