修复主题相关问题 (#1915)

* Update blue.css

* fix

* fix

* Cleanup Code

* Use blue.bss

* fix read theme
This commit is contained in:
Glavo 2022-12-17 18:34:10 +08:00 committed by GitHub
parent 7f599638d7
commit d67dd700e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 104 additions and 59 deletions

View File

@ -40,7 +40,6 @@ import org.jackhuang.hmcl.util.i18n.Locales;
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jetbrains.annotations.Nullable;
import java.io.File;
@ -120,7 +119,7 @@ public final class Config implements Cloneable, Observable {
private DoubleProperty height = new SimpleDoubleProperty();
@SerializedName("theme")
private ObjectProperty<Theme> theme = new SimpleObjectProperty<>(Theme.BLUE);
private ObjectProperty<Theme> theme = new SimpleObjectProperty<>();
@SerializedName("localization")
private ObjectProperty<SupportedLocale> localization = new SimpleObjectProperty<>(Locales.DEFAULT);
@ -147,7 +146,7 @@ public final class Config implements Cloneable, Observable {
private ObservableList<Map<Object, Object>> accountStorages = FXCollections.observableArrayList();
@SerializedName("fontFamily")
private StringProperty fontFamily = new SimpleStringProperty(OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "Consolas" : "Monospace");
private StringProperty fontFamily = new SimpleStringProperty();
@SerializedName("fontSize")
private DoubleProperty fontSize = new SimpleDoubleProperty(12);

View File

@ -24,13 +24,12 @@ import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.scene.paint.Color;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.ResourceNotFoundError;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.IOUtils;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import java.io.File;
import java.io.IOException;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.logging.Level;
@ -50,6 +49,11 @@ public class Theme {
Color.web("#B71C1C") // red
};
public static Theme getTheme() {
Theme theme = config().getTheme();
return theme == null ? BLUE : theme;
}
private final Color paint;
private final String color;
private final String name;
@ -73,7 +77,7 @@ public class Theme {
}
public boolean isLight() {
return Color.web(color).grayscale().getRed() >= 0.5;
return paint.grayscale().getRed() >= 0.5;
}
public Color getForegroundColor() {
@ -81,33 +85,31 @@ public class Theme {
}
public String[] getStylesheets(String overrideFontFamily) {
Color textFill = getForegroundColor();
String css = "/assets/css/blue.css";
String fontFamily = System.getProperty("hmcl.font.override", overrideFontFamily);
String css;
try {
File temp = File.createTempFile("hmcl", ".css");
FileUtils.writeText(temp, IOUtils.readFullyAsString(ResourceNotFoundError.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%", Optional.ofNullable(fontFamily).map(f -> "-fx-font-family: \"" + f + "\";").orElse("")));
temp.deleteOnExit();
css = temp.toURI().toString();
} catch (IOException | NullPointerException e) {
Logging.LOG.log(Level.SEVERE, "Unable to create theme stylesheet. Fallback to blue theme.", e);
css = "/assets/css/blue.css";
if (fontFamily != null || !this.color.equalsIgnoreCase(BLUE.color)) {
Color textFill = getForegroundColor();
try {
File temp = File.createTempFile("hmcl", ".css");
FileUtils.writeText(temp, 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%", Optional.ofNullable(fontFamily).map(f -> "-fx-font-family: \"" + f + "\";").orElse("")));
temp.deleteOnExit();
css = temp.toURI().toString();
} catch (IOException | NullPointerException e) {
Logging.LOG.log(Level.SEVERE, "Unable to create theme stylesheet. Fallback to blue theme.", e);
}
}
return new String[]{
css,
"/assets/css/root.css"
};
return new String[]{css, "/assets/css/root.css"};
}
public static Theme custom(String color) {
@ -119,25 +121,35 @@ public class Theme {
public static Optional<Theme> getTheme(String name) {
if (name == null)
return Optional.empty();
else if (name.equalsIgnoreCase("blue"))
return Optional.of(custom("#5C6BC0"));
else if (name.equalsIgnoreCase("darker_blue"))
return Optional.of(custom("#283593"));
else if (name.equalsIgnoreCase("green"))
return Optional.of(custom("#43A047"));
else if (name.equalsIgnoreCase("orange"))
return Optional.of(custom("#E67E22"));
else if (name.equalsIgnoreCase("purple"))
return Optional.of(custom("#9C27B0"));
else if (name.equalsIgnoreCase("red"))
return Optional.of(custom("#F44336"));
if (name.startsWith("#"))
else if (name.startsWith("#"))
try {
Color.web(name);
return Optional.of(custom(name));
} catch (IllegalArgumentException ignore) {
}
else {
String color = null;
switch (name.toLowerCase(Locale.ROOT)) {
case "blue":
return Optional.of(BLUE);
case "darker_blue":
color = "#283593";
break;
case "green":
color = "#43A047";
break;
case "orange":
color = "#E67E22";
break;
case "purple":
color = "#9C27B0";
break;
case "red":
color = "#F44336";
}
if (color != null)
return Optional.of(new Theme(name, color));
}
return Optional.empty();
}
@ -146,17 +158,26 @@ public class Theme {
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() : null;
}
private static final ObjectBinding<Color> BLACK_FILL = Bindings.createObjectBinding(() -> BLACK);
private static final ObjectBinding<Color> WHITE_FILL = Bindings.createObjectBinding(() -> Color.WHITE);
private static ObjectBinding<Color> FOREGROUND_FILL;
public static ObjectBinding<Color> foregroundFillBinding() {
return BindingMapping.of(config().themeProperty())
.map(Theme::getForegroundColor);
if (FOREGROUND_FILL == null)
FOREGROUND_FILL = Bindings.createObjectBinding(
() -> Theme.getTheme().getForegroundColor(),
config().themeProperty()
);
return FOREGROUND_FILL;
}
public static ObjectBinding<Color> blackFillBinding() {
return Bindings.createObjectBinding(() -> BLACK);
return BLACK_FILL;
}
public static ObjectBinding<Color> whiteFillBinding() {
return Bindings.createObjectBinding(() -> Color.WHITE);
return WHITE_FILL;
}
public static class TypeAdapter extends com.google.gson.TypeAdapter<Theme> {

View File

@ -35,6 +35,7 @@ import org.jackhuang.hmcl.game.ModpackHelper;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
import org.jackhuang.hmcl.setting.Profiles;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.account.AccountListPage;
@ -181,7 +182,7 @@ public final class Controllers {
stage.setMinWidth(800 + 2 + 16); // bg width + border width*2 + shadow width*2
decorator.getDecorator().prefWidthProperty().bind(scene.widthProperty());
decorator.getDecorator().prefHeightProperty().bind(scene.heightProperty());
scene.getStylesheets().setAll(config().getTheme().getStylesheets(config().getLauncherFontFamily()));
scene.getStylesheets().setAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily()));
stage.getIcons().add(newImage("/assets/img/icon.png"));
stage.setTitle(Metadata.FULL_TITLE);

View File

@ -94,6 +94,8 @@ public final class FXUtils {
private FXUtils() {
}
public static String DEFAULT_MONOSPACE_FONT = OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "Consolas" : "Monospace";
public static void runInFX(Runnable runnable) {
if (Platform.isFxApplicationThread()) {
runnable.run();

View File

@ -38,6 +38,7 @@ import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.game.*;
import org.jackhuang.hmcl.launch.ProcessListener;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import org.jackhuang.hmcl.util.Lang;
@ -107,7 +108,7 @@ public class GameCrashWindow extends Stage {
this.feedbackTextFlow.getChildren().addAll(FXUtils.parseSegment(i18n("game.crash.feedback"), Controllers::onHyperlinkAction));
setScene(new Scene(view, 800, 480));
getScene().getStylesheets().addAll(config().getTheme().getStylesheets(config().getLauncherFontFamily()));
getScene().getStylesheets().addAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily()));
setTitle(i18n("game.crash.title"));
getIcons().add(newImage("/assets/img/icon.png"));

View File

@ -36,6 +36,8 @@ import javafx.scene.layout.*;
import javafx.stage.Stage;
import org.apache.commons.lang3.mutable.MutableObject;
import org.jackhuang.hmcl.game.LauncherHelper;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Log4jLevel;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
@ -87,7 +89,7 @@ public final class LogWindow extends Stage {
public LogWindow() {
setScene(new Scene(impl, 800, 480));
getScene().getStylesheets().addAll(config().getTheme().getStylesheets(config().getLauncherFontFamily()));
getScene().getStylesheets().addAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily()));
setTitle(i18n("logwindow.title"));
getIcons().add(newImage("/assets/img/icon.png"));
@ -286,7 +288,9 @@ public final class LogWindow extends Stage {
if (!listView.getItems().isEmpty() && control.autoScroll.get())
listView.scrollTo(listView.getItems().size() - 1);
});
listView.setStyle("-fx-font-family: " + config().getFontFamily() + "; -fx-font-size: " + config().getFontSize() + "px;");
listView.setStyle("-fx-font-family: " + Lang.requireNonNullElse(config().getFontFamily(), FXUtils.DEFAULT_MONOSPACE_FONT)
+ "; -fx-font-size: " + config().getFontSize() + "px;");
MutableObject<Object> lastCell = new MutableObject<>();
listView.setCellFactory(x -> new ListCell<Log>() {
{

View File

@ -26,6 +26,7 @@ import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.setting.Theme;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
@ -42,7 +43,7 @@ public class WebStage extends Stage {
public WebStage(int width, int height) {
setScene(new Scene(pane, width, height));
getScene().getStylesheets().addAll(config().getTheme().getStylesheets(config().getLauncherFontFamily()));
getScene().getStylesheets().addAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily()));
getIcons().add(newImage("/assets/img/icon.png"));
webView.getEngine().setUserDataDirectory(Metadata.HMCL_DIRECTORY.toFile());
webView.setContextMenuEnabled(false);

View File

@ -26,7 +26,6 @@ import org.jackhuang.hmcl.util.javafx.BindingMapping;
import com.jfoenix.controls.JFXComboBox;
import com.jfoenix.controls.JFXListCell;
import javafx.beans.NamedArg;
import javafx.beans.binding.Bindings;
import javafx.scene.text.Font;
@ -34,7 +33,7 @@ public class FontComboBox extends JFXComboBox<String> {
private boolean loaded = false;
public FontComboBox(@NamedArg(value = "fontSize", defaultValue = "12.0") double fontSize) {
public FontComboBox() {
styleProperty().bind(Bindings.concat("-fx-font-family: \"", valueProperty(), "\""));
setCellFactory(listView -> new JFXListCell<String>() {

View File

@ -41,6 +41,7 @@ import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
import java.util.Arrays;
@ -72,7 +73,7 @@ public class PersonalizationPage extends StackPane {
themeColorPickerContainer.setMinHeight(30);
themePane.setRight(themeColorPickerContainer);
ColorPicker picker = new ColorPicker(Color.web(config().getTheme().getColor()));
ColorPicker picker = new ColorPicker(Color.web(Theme.getTheme().getColor()));
picker.getCustomColors().setAll(Theme.SUGGESTED_COLORS);
picker.setOnAction(e -> {
Theme theme = Theme.custom(Theme.getColorDisplayName(picker.getValue()));
@ -141,7 +142,7 @@ public class PersonalizationPage extends StackPane {
HBox hBox = new HBox();
hBox.setSpacing(3);
FontComboBox cboLogFont = new FontComboBox(12);
FontComboBox cboLogFont = new FontComboBox();
cboLogFont.valueProperty().bindBidirectional(config().fontFamilyProperty());
JFXTextField txtLogFontSize = new JFXTextField();
@ -159,7 +160,7 @@ public class PersonalizationPage extends StackPane {
Label lblLogFontDisplay = new Label("[23:33:33] [Client Thread/INFO] [WaterPower]: Loaded mod WaterPower.");
lblLogFontDisplay.fontProperty().bind(Bindings.createObjectBinding(
() -> Font.font(config().getFontFamily(), config().getFontSize()),
() -> Font.font(Lang.requireNonNullElse(config().getFontFamily(), FXUtils.DEFAULT_MONOSPACE_FONT), config().getFontSize()),
config().fontFamilyProperty(), config().fontSizeProperty()));
fontPane.getChildren().add(lblLogFontDisplay);
@ -191,7 +192,7 @@ public class PersonalizationPage extends StackPane {
HBox hBox = new HBox();
hBox.setSpacing(8);
FontComboBox cboFont = new FontComboBox(12);
FontComboBox cboFont = new FontComboBox();
cboFont.valueProperty().bindBidirectional(config().launcherFontFamilyProperty());
JFXButton clearButton = new JFXButton();
@ -210,7 +211,7 @@ public class PersonalizationPage extends StackPane {
() -> Font.font(config().getLauncherFontFamily(), 12),
config().launcherFontFamilyProperty()));
config().launcherFontFamilyProperty().addListener((a, b, newValue) -> {
Controllers.getScene().getStylesheets().setAll(config().getTheme().getStylesheets(newValue));
Controllers.getScene().getStylesheets().setAll(Theme.getTheme().getStylesheets(newValue));
});
vbox.getChildren().add(lblFontDisplay);

View File

@ -16,9 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.root {
-fx-base-color: #5c6bc0;
-fx-base-color: #5C6BC0;
-fx-base-darker-color: derive(-fx-base-color, -10%);
-fx-base-check-color: derive(-fx-base-color, 30%);
-fx-rippler-color: rgba(92, 107, 192, 0.3);
-fx-base-rippler-color: rgba(92, 107, 192, 0.3);
-fx-base-disabled-text-fill: rgba(256, 256, 256, 0.7);
-fx-base-text-fill: white;
-theme-thumb: rgba(92, 107, 192, 0.3);
}

View File

@ -34,6 +34,18 @@ public final class Lang {
private Lang() {
}
public static <T> T requireNonNullElse(T value, T defaultValue) {
return value != null ? value : defaultValue;
}
public static <T> T requireNonNullElseGet(T value, Supplier<? extends T> defaultValue) {
return value != null ? value : defaultValue.get();
}
public static <T, U> U requireNonNullElseGet(T value, Function<? super T, ? extends U> mapper, Supplier<? extends U> defaultValue) {
return value != null ? mapper.apply(value) : defaultValue.get();
}
/**
* Construct a mutable map by given key-value pairs.
* @param pairs entries in the new map