Merge pull request #396 from yushijinhun/fix

支持背景热替换
This commit is contained in:
huanghongxun 2018-07-18 23:05:56 +08:00 committed by GitHub
commit 3a9017adb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 132 additions and 124 deletions

View File

@ -66,6 +66,7 @@ public final class Config implements Cloneable, Observable {
.registerTypeAdapter(ObservableSet.class, new ObservableSetCreator()) .registerTypeAdapter(ObservableSet.class, new ObservableSetCreator())
.registerTypeAdapter(ObservableMap.class, new ObservableMapCreator()) .registerTypeAdapter(ObservableMap.class, new ObservableMapCreator())
.registerTypeAdapterFactory(new JavaFxPropertyTypeAdapterFactory(true, true)) .registerTypeAdapterFactory(new JavaFxPropertyTypeAdapterFactory(true, true))
.registerTypeAdapter(Theme.class, new Theme.TypeAdapter())
.registerTypeAdapter(EnumBackgroundImage.class, new EnumOrdinalDeserializer<>(EnumBackgroundImage.class)) // backward compatibility for backgroundType .registerTypeAdapter(EnumBackgroundImage.class, new EnumOrdinalDeserializer<>(EnumBackgroundImage.class)) // backward compatibility for backgroundType
.registerTypeAdapter(Proxy.Type.class, new EnumOrdinalDeserializer<>(Proxy.Type.class)) // backward compatibility for hasProxy .registerTypeAdapter(Proxy.Type.class, new EnumOrdinalDeserializer<>(Proxy.Type.class)) // backward compatibility for hasProxy
.setPrettyPrinting() .setPrettyPrinting()
@ -113,7 +114,7 @@ public final class Config implements Cloneable, Observable {
private StringProperty proxyPass = new SimpleStringProperty(); private StringProperty proxyPass = new SimpleStringProperty();
@SerializedName("theme") @SerializedName("theme")
private StringProperty theme = new SimpleStringProperty(); private ObjectProperty<Theme> theme = new SimpleObjectProperty<>(Theme.BLUE);
@SerializedName("localization") @SerializedName("localization")
private StringProperty localization = new SimpleStringProperty(); private StringProperty localization = new SimpleStringProperty();
@ -320,15 +321,15 @@ public final class Config implements Cloneable, Observable {
return proxyPass; return proxyPass;
} }
public String getTheme() { public Theme getTheme() {
return theme.get(); return theme.get();
} }
public void setTheme(String theme) { public void setTheme(Theme theme) {
this.theme.set(theme); this.theme.set(theme);
} }
public StringProperty themeProperty() { public ObjectProperty<Theme> themeProperty() {
return theme; return theme;
} }

View File

@ -257,31 +257,6 @@ public class Settings {
selectedAccount.get(); selectedAccount.get();
} }
/****************************************
* THEME *
****************************************/
private final ImmediateObjectProperty<Theme> theme = new ImmediateObjectProperty<Theme>(this, "theme", Theme.getTheme(CONFIG.getTheme()).orElse(Theme.BLUE)) {
@Override
public void invalidated() {
super.invalidated();
CONFIG.setTheme(get().getName().toLowerCase());
}
};
public Theme getTheme() {
return theme.get();
}
public void setTheme(Theme theme) {
this.theme.set(theme);
}
public ImmediateObjectProperty<Theme> themeProperty() {
return theme;
}
/**************************************** /****************************************
* PROFILES * * PROFILES *
****************************************/ ****************************************/

View File

@ -24,6 +24,11 @@ import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.IOUtils; import org.jackhuang.hmcl.util.IOUtils;
import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.Logging;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import static org.jackhuang.hmcl.setting.ConfigHolder.CONFIG;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Optional; import java.util.Optional;
@ -115,7 +120,7 @@ public class Theme {
} }
public static ObjectBinding<Color> foregroundFillBinding() { public static ObjectBinding<Color> foregroundFillBinding() {
return Bindings.createObjectBinding(() -> Settings.INSTANCE.getTheme().getForegroundColor(), Settings.INSTANCE.themeProperty()); return Bindings.createObjectBinding(() -> CONFIG.getTheme().getForegroundColor(), CONFIG.themeProperty());
} }
public static ObjectBinding<Color> blackFillBinding() { public static ObjectBinding<Color> blackFillBinding() {
@ -125,4 +130,16 @@ public class Theme {
public static ObjectBinding<Color> whiteFillBinding() { public static ObjectBinding<Color> whiteFillBinding() {
return Bindings.createObjectBinding(() -> Color.WHITE); return Bindings.createObjectBinding(() -> Color.WHITE);
} }
public static class TypeAdapter extends com.google.gson.TypeAdapter<Theme> {
@Override
public void write(JsonWriter out, Theme value) throws IOException {
out.value(value.getName().toLowerCase());
}
@Override
public Theme read(JsonReader in) throws IOException {
return getTheme(in.nextString()).orElse(Theme.BLUE);
}
}
} }

View File

@ -34,6 +34,8 @@ import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
import org.jackhuang.hmcl.util.FutureCallback; import org.jackhuang.hmcl.util.FutureCallback;
import org.jackhuang.hmcl.util.JavaVersion; import org.jackhuang.hmcl.util.JavaVersion;
import static org.jackhuang.hmcl.setting.ConfigHolder.CONFIG;
import java.util.function.Consumer; import java.util.function.Consumer;
public final class Controllers { public final class Controllers {
@ -106,7 +108,7 @@ public final class Controllers {
decorator.setCustomMaximize(false); decorator.setCustomMaximize(false);
scene = new Scene(decorator, 804, 521); scene = new Scene(decorator, 804, 521);
scene.getStylesheets().setAll(Settings.INSTANCE.getTheme().getStylesheets()); scene.getStylesheets().setAll(CONFIG.getTheme().getStylesheets());
stage.setMinWidth(804); stage.setMinWidth(804);
stage.setMaxWidth(804); stage.setMaxWidth(804);
stage.setMinHeight(521); stage.setMinHeight(521);

View File

@ -66,17 +66,22 @@ import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.StackContainerPane; import org.jackhuang.hmcl.ui.construct.StackContainerPane;
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogWizardDisplayer; import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogWizardDisplayer;
import org.jackhuang.hmcl.ui.wizard.*; import org.jackhuang.hmcl.ui.wizard.*;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils; import java.io.IOException;
import java.io.File; import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Optional; import java.util.Optional;
import java.util.Queue; import java.util.Queue;
import java.util.Random; import java.util.Random;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import static java.util.stream.Collectors.toList;
import static org.jackhuang.hmcl.setting.ConfigHolder.CONFIG; import static org.jackhuang.hmcl.setting.ConfigHolder.CONFIG;
import static org.jackhuang.hmcl.util.Logging.LOG;
public final class Decorator extends StackPane implements TaskExecutorDialogWizardDisplayer { public final class Decorator extends StackPane implements TaskExecutorDialogWizardDisplayer {
private static final SVGGlyph minus = Lang.apply(new SVGGlyph(0, "MINUS", "M804.571 420.571v109.714q0 22.857-16 38.857t-38.857 16h-694.857q-22.857 0-38.857-16t-16-38.857v-109.714q0-22.857 16-38.857t38.857-16h694.857q22.857 0 38.857 16t16 38.857z", Color.WHITE), private static final SVGGlyph minus = Lang.apply(new SVGGlyph(0, "MINUS", "M804.571 420.571v109.714q0 22.857-16 38.857t-38.857 16h-694.857q-22.857 0-38.857-16t-16-38.857v-109.714q0-22.857 16-38.857t38.857-16h694.857q22.857 0 38.857 16t16 38.857z", Color.WHITE),
@ -212,81 +217,87 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
animationHandler = new TransitionHandler(contentPlaceHolder); animationHandler = new TransitionHandler(contentPlaceHolder);
loadBackground(); setupBackground();
setupAuthlibInjectorDnD(); setupAuthlibInjectorDnD();
} }
private void loadBackground() { // ==== Background ====
private void setupBackground() {
drawerWrapper.backgroundProperty().bind(
Bindings.createObjectBinding(
() -> {
Image image = null;
if (CONFIG.getBackgroundImageType() == EnumBackgroundImage.CUSTOM) {
image = tryLoadImage(Paths.get(CONFIG.getBackgroundImage()))
.orElse(null);
}
if (image == null) {
image = loadDefaultBackgroundImage();
}
return new Background(new BackgroundImage(image, BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, new BackgroundSize(800, 480, false, false, true, true)));
},
CONFIG.backgroundImageTypeProperty(),
CONFIG.backgroundImageProperty()));
}
private Image defaultBackground = new Image("/assets/img/background.jpg");
/**
* Load background image from bg/, background.png, background.jpg
*/
private Image loadDefaultBackgroundImage() {
Optional<Image> image = randomImageIn(Paths.get("bg"));
if (!image.isPresent()) {
image = tryLoadImage(Paths.get("background.png"));
}
if (!image.isPresent()) {
image = tryLoadImage(Paths.get("background.jpg"));
}
return image.orElse(defaultBackground);
}
private Optional<Image> randomImageIn(Path imageDir) {
if (!Files.isDirectory(imageDir)) {
return Optional.empty();
}
List<Path> candidates;
try { try {
Image background; candidates = Files.list(imageDir)
.filter(Files::isRegularFile)
if (CONFIG.getBackgroundImageType() == EnumBackgroundImage.DEFAULT) .filter(it -> {
background = searchBackgroundImage(new Image("/assets/img/background.jpg"), ""); String filename = it.getFileName().toString();
else return filename.endsWith(".png") || filename.endsWith(".jpg");
background = searchBackgroundImage(new Image("/assets/img/background.jpg"), CONFIG.getBackgroundImage()); })
.collect(toList());
drawerWrapper.setBackground(new Background(new BackgroundImage(background, BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, new BackgroundSize(800, 480, false, false, true, true)))); } catch (IOException e) {
} catch (IllegalArgumentException ignore) { LOG.log(Level.WARNING, "Failed to list files in ./bg", e);
return Optional.empty();
} }
Random rnd = new Random();
while (candidates.size() > 0) {
int selected = rnd.nextInt(candidates.size());
Optional<Image> loaded = tryLoadImage(candidates.get(selected));
if (loaded.isPresent()) {
return Optional.of(loaded.get());
} else {
candidates.remove(selected);
}
}
return Optional.empty();
} }
private static Image searchBackgroundImage(Image def, String customPath) { private Optional<Image> tryLoadImage(Path path) {
Random random = new Random(); if (Files.isRegularFile(path)) {
boolean loaded = false;
Image background = def;
// custom path
if (StringUtils.isNotBlank(customPath)) {
try { try {
background = new Image("file:" + customPath); return Optional.of(new Image(path.toAbsolutePath().toUri().toString()));
loaded = true; } catch (IllegalArgumentException ignored) {
} catch (IllegalArgumentException ignore) {
} }
} }
return Optional.empty();
// images in ./bg
if (!loaded) {
File backgroundImageFile = new File("bg");
if (backgroundImageFile.isDirectory()) {
File[] backgroundPath = backgroundImageFile.listFiles(file -> StringUtils.containsOne(FileUtils.getExtension(file), "png", "jpg"));
if (backgroundPath != null && backgroundPath.length > 0) {
int index = random.nextInt(backgroundPath.length);
try {
background = new Image("file:" + backgroundPath[index].getAbsolutePath());
loaded = true;
} catch (IllegalArgumentException ignore) {
}
}
}
}
// background.png
if (!loaded) {
File backgroundImageFile = new File("background.png");
if (backgroundImageFile.exists()) {
try {
background = new Image("file:" + backgroundImageFile.getAbsolutePath());
loaded = true;
} catch (IllegalArgumentException ignore) {
}
}
}
// background.jpg
if (!loaded) {
File backgroundImageFile = new File("background.jpg");
if (backgroundImageFile.exists()) {
try {
background = new Image("file:" + backgroundImageFile.getAbsolutePath());
loaded = true;
} catch (IllegalArgumentException ignore) {
}
}
}
return background;
} }
// ====
@FXML @FXML
private void onMouseMoved(MouseEvent mouseEvent) { private void onMouseMoved(MouseEvent mouseEvent) {

View File

@ -43,6 +43,7 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import static org.jackhuang.hmcl.setting.ConfigHolder.CONFIG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
@ -64,7 +65,7 @@ public final class LogWindow extends Stage {
public LogWindow() { public LogWindow() {
setScene(new Scene(impl, 800, 480)); setScene(new Scene(impl, 800, 480));
getScene().getStylesheets().addAll(Settings.INSTANCE.getTheme().getStylesheets()); getScene().getStylesheets().addAll(CONFIG.getTheme().getStylesheets());
setTitle(i18n("logwindow.title")); setTitle(i18n("logwindow.title"));
getIcons().add(new Image("/assets/img/icon.png")); getIcons().add(new Image("/assets/img/icon.png"));
} }

View File

@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.*; import com.jfoenix.controls.*;
import com.jfoenix.effects.JFXDepthManager; import com.jfoenix.effects.JFXDepthManager;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.binding.When;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
@ -46,7 +47,6 @@ import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.i18n.Locales; import org.jackhuang.hmcl.util.i18n.Locales;
import static org.jackhuang.hmcl.setting.ConfigHolder.CONFIG; import static org.jackhuang.hmcl.setting.ConfigHolder.CONFIG;
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.net.Proxy; import java.net.Proxy;
@ -188,44 +188,44 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
backgroundItem.loadChildren(Collections.singletonList( backgroundItem.loadChildren(Collections.singletonList(
backgroundItem.createChildren(i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT) backgroundItem.createChildren(i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT)
)); ));
FXUtils.bindString(backgroundItem.getTxtCustom(), CONFIG.backgroundImageProperty());
backgroundItem.setCustomUserData(EnumBackgroundImage.CUSTOM); backgroundItem.setCustomUserData(EnumBackgroundImage.CUSTOM);
backgroundItem.getGroup().getToggles().stream().filter(it -> it.getUserData() == CONFIG.getBackgroundImageType()).findFirst().ifPresent(it -> it.setSelected(true)); backgroundItem.getTxtCustom().textProperty().bindBidirectional(CONFIG.backgroundImageProperty());
CONFIG.backgroundImageProperty().addListener(onInvalidating(this::initBackgroundItemSubtitle)); ObjectProperty<EnumBackgroundImage> backgroundType = new SimpleObjectProperty<EnumBackgroundImage>(EnumBackgroundImage.DEFAULT) {
CONFIG.backgroundImageTypeProperty().addListener(onInvalidating(this::initBackgroundItemSubtitle)); {
initBackgroundItemSubtitle(); invalidated();
}
backgroundItem.setToggleSelectedListener(newValue -> @Override
CONFIG.setBackgroundImageType((EnumBackgroundImage) newValue.getUserData())); protected void invalidated() {
backgroundItem.getGroup().getToggles().stream()
.filter(it -> it.getUserData() == get())
.findFirst()
.ifPresent(it -> it.setSelected(true));
}
};
backgroundItem.getGroup().selectedToggleProperty().addListener((observable, oldValue, newValue) -> backgroundType.set((EnumBackgroundImage) newValue.getUserData()));
backgroundType.bindBidirectional(CONFIG.backgroundImageTypeProperty());
backgroundItem.subtitleProperty().bind(
new When(backgroundType.isEqualTo(EnumBackgroundImage.DEFAULT))
.then(i18n("launcher.background.default"))
.otherwise(CONFIG.backgroundImageProperty()));
// theme // theme
JFXColorPicker picker = new JFXColorPicker(Color.web(Settings.INSTANCE.getTheme().getColor()), null); JFXColorPicker picker = new JFXColorPicker(Color.web(CONFIG.getTheme().getColor()), null);
picker.setCustomColorText(i18n("color.custom")); picker.setCustomColorText(i18n("color.custom"));
picker.setRecentColorsText(i18n("color.recent")); picker.setRecentColorsText(i18n("color.recent"));
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())); Theme theme = Theme.custom(Theme.getColorDisplayName(picker.getValue()));
Settings.INSTANCE.setTheme(theme); CONFIG.setTheme(theme);
Controllers.getScene().getStylesheets().setAll(theme.getStylesheets()); Controllers.getScene().getStylesheets().setAll(theme.getStylesheets());
}); });
themeColorPickerContainer.getChildren().setAll(picker); themeColorPickerContainer.getChildren().setAll(picker);
Platform.runLater(() -> JFXDepthManager.setDepth(picker, 0)); Platform.runLater(() -> JFXDepthManager.setDepth(picker, 0));
} }
private void initBackgroundItemSubtitle() {
switch (CONFIG.getBackgroundImageType()) {
case DEFAULT:
backgroundItem.setSubtitle(i18n("launcher.background.default"));
break;
case CUSTOM:
backgroundItem.setSubtitle(CONFIG.getBackgroundImage());
break;
}
}
public String getTitle() { public String getTitle() {
return title.get(); return title.get();
} }

View File

@ -21,14 +21,15 @@ import javafx.scene.Scene;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.web.WebView; import javafx.scene.web.WebView;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.jackhuang.hmcl.setting.Settings;
import static org.jackhuang.hmcl.setting.ConfigHolder.CONFIG;
public class WebStage extends Stage { public class WebStage extends Stage {
private final WebView webView = new WebView(); private final WebView webView = new WebView();
public WebStage() { public WebStage() {
setScene(new Scene(webView, 800, 480)); setScene(new Scene(webView, 800, 480));
getScene().getStylesheets().addAll(Settings.INSTANCE.getTheme().getStylesheets()); getScene().getStylesheets().addAll(CONFIG.getTheme().getStylesheets());
getIcons().add(new Image("/assets/img/icon.png")); getIcons().add(new Image("/assets/img/icon.png"));
} }