From 9896165b68b3d03fe355e032c618bcaff9046c9c Mon Sep 17 00:00:00 2001 From: yushijinhun Date: Wed, 18 Jul 2018 21:36:44 +0800 Subject: [PATCH 1/4] Remove theme in Settings --- .../org/jackhuang/hmcl/setting/Config.java | 9 ++++--- .../org/jackhuang/hmcl/setting/Settings.java | 25 ------------------- .../org/jackhuang/hmcl/setting/Theme.java | 19 +++++++++++++- .../org/jackhuang/hmcl/ui/Controllers.java | 4 ++- .../java/org/jackhuang/hmcl/ui/LogWindow.java | 3 ++- .../org/jackhuang/hmcl/ui/SettingsPage.java | 4 +-- .../java/org/jackhuang/hmcl/ui/WebStage.java | 5 ++-- 7 files changed, 33 insertions(+), 36 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java index 6c0b60e8d..b1ab82646 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java @@ -66,6 +66,7 @@ public final class Config implements Cloneable, Observable { .registerTypeAdapter(ObservableSet.class, new ObservableSetCreator()) .registerTypeAdapter(ObservableMap.class, new ObservableMapCreator()) .registerTypeAdapterFactory(new JavaFxPropertyTypeAdapterFactory(true, true)) + .registerTypeAdapter(Theme.class, new Theme.TypeAdapter()) .registerTypeAdapter(EnumBackgroundImage.class, new EnumOrdinalDeserializer<>(EnumBackgroundImage.class)) // backward compatibility for backgroundType .registerTypeAdapter(Proxy.Type.class, new EnumOrdinalDeserializer<>(Proxy.Type.class)) // backward compatibility for hasProxy .setPrettyPrinting() @@ -113,7 +114,7 @@ public final class Config implements Cloneable, Observable { private StringProperty proxyPass = new SimpleStringProperty(); @SerializedName("theme") - private StringProperty theme = new SimpleStringProperty(); + private ObjectProperty theme = new SimpleObjectProperty<>(Theme.BLUE); @SerializedName("localization") private StringProperty localization = new SimpleStringProperty(); @@ -320,15 +321,15 @@ public final class Config implements Cloneable, Observable { return proxyPass; } - public String getTheme() { + public Theme getTheme() { return theme.get(); } - public void setTheme(String theme) { + public void setTheme(Theme theme) { this.theme.set(theme); } - public StringProperty themeProperty() { + public ObjectProperty themeProperty() { return theme; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java index c5d73164c..a1b667c2b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java @@ -257,31 +257,6 @@ public class Settings { selectedAccount.get(); } - /**************************************** - * THEME * - ****************************************/ - - private final ImmediateObjectProperty theme = new ImmediateObjectProperty(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 themeProperty() { - return theme; - } - /**************************************** * PROFILES * ****************************************/ 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 0ec6d8b0d..d3ee6ba6d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java @@ -24,6 +24,11 @@ import org.jackhuang.hmcl.util.FileUtils; import org.jackhuang.hmcl.util.IOUtils; 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.IOException; import java.util.Optional; @@ -115,7 +120,7 @@ public class Theme { } public static ObjectBinding foregroundFillBinding() { - return Bindings.createObjectBinding(() -> Settings.INSTANCE.getTheme().getForegroundColor(), Settings.INSTANCE.themeProperty()); + return Bindings.createObjectBinding(() -> CONFIG.getTheme().getForegroundColor(), CONFIG.themeProperty()); } public static ObjectBinding blackFillBinding() { @@ -125,4 +130,16 @@ public class Theme { public static ObjectBinding whiteFillBinding() { return Bindings.createObjectBinding(() -> Color.WHITE); } + + public static class TypeAdapter extends com.google.gson.TypeAdapter { + @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); + } + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java index bc4f297f5..e2abc47d5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -34,6 +34,8 @@ import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane; import org.jackhuang.hmcl.util.FutureCallback; import org.jackhuang.hmcl.util.JavaVersion; +import static org.jackhuang.hmcl.setting.ConfigHolder.CONFIG; + import java.util.function.Consumer; public final class Controllers { @@ -106,7 +108,7 @@ public final class Controllers { decorator.setCustomMaximize(false); scene = new Scene(decorator, 804, 521); - scene.getStylesheets().setAll(Settings.INSTANCE.getTheme().getStylesheets()); + scene.getStylesheets().setAll(CONFIG.getTheme().getStylesheets()); stage.setMinWidth(804); stage.setMaxWidth(804); stage.setMinHeight(521); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java index 4a2de4f0f..4cc24c1b2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java @@ -43,6 +43,7 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; +import static org.jackhuang.hmcl.setting.ConfigHolder.CONFIG; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import java.util.concurrent.CountDownLatch; @@ -64,7 +65,7 @@ public final class LogWindow extends Stage { public LogWindow() { setScene(new Scene(impl, 800, 480)); - getScene().getStylesheets().addAll(Settings.INSTANCE.getTheme().getStylesheets()); + getScene().getStylesheets().addAll(CONFIG.getTheme().getStylesheets()); setTitle(i18n("logwindow.title")); getIcons().add(new Image("/assets/img/icon.png")); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java index 25a4aa39e..be56e0d43 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java @@ -202,13 +202,13 @@ public final class SettingsPage extends StackPane implements DecoratorPage { CONFIG.setBackgroundImageType((EnumBackgroundImage) newValue.getUserData())); // 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.setRecentColorsText(i18n("color.recent")); picker.getCustomColors().setAll(Theme.SUGGESTED_COLORS); picker.setOnAction(e -> { Theme theme = Theme.custom(Theme.getColorDisplayName(picker.getValue())); - Settings.INSTANCE.setTheme(theme); + CONFIG.setTheme(theme); Controllers.getScene().getStylesheets().setAll(theme.getStylesheets()); }); themeColorPickerContainer.getChildren().setAll(picker); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java index 34089f027..3d9217fb3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java @@ -21,14 +21,15 @@ import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.scene.web.WebView; import javafx.stage.Stage; -import org.jackhuang.hmcl.setting.Settings; + +import static org.jackhuang.hmcl.setting.ConfigHolder.CONFIG; public class WebStage extends Stage { private final WebView webView = new WebView(); public WebStage() { 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")); } From a47f0f6ecffbd1387d2bce6890c1aea66b9abc05 Mon Sep 17 00:00:00 2001 From: yushijinhun Date: Wed, 18 Jul 2018 21:44:22 +0800 Subject: [PATCH 2/4] Use databind for theme subtitle --- .../org/jackhuang/hmcl/ui/SettingsPage.java | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java index be56e0d43..83df018f3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java @@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui; import com.jfoenix.controls.*; import com.jfoenix.effects.JFXDepthManager; import javafx.application.Platform; +import javafx.beans.binding.When; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; @@ -46,7 +47,6 @@ import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.i18n.Locales; 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 java.net.Proxy; @@ -194,9 +194,10 @@ public final class SettingsPage extends StackPane implements DecoratorPage { backgroundItem.setCustomUserData(EnumBackgroundImage.CUSTOM); backgroundItem.getGroup().getToggles().stream().filter(it -> it.getUserData() == CONFIG.getBackgroundImageType()).findFirst().ifPresent(it -> it.setSelected(true)); - CONFIG.backgroundImageProperty().addListener(onInvalidating(this::initBackgroundItemSubtitle)); - CONFIG.backgroundImageTypeProperty().addListener(onInvalidating(this::initBackgroundItemSubtitle)); - initBackgroundItemSubtitle(); + backgroundItem.subtitleProperty().bind( + new When(CONFIG.backgroundImageTypeProperty().isEqualTo(EnumBackgroundImage.DEFAULT)) + .then(i18n("launcher.background.default")) + .otherwise(CONFIG.backgroundImageProperty())); backgroundItem.setToggleSelectedListener(newValue -> CONFIG.setBackgroundImageType((EnumBackgroundImage) newValue.getUserData())); @@ -215,17 +216,6 @@ public final class SettingsPage extends StackPane implements DecoratorPage { 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() { return title.get(); } From e5f5fe878e597511aaf2eab9b9c76d2fb09d50c6 Mon Sep 17 00:00:00 2001 From: yushijinhun Date: Wed, 18 Jul 2018 21:59:12 +0800 Subject: [PATCH 3/4] Use bindBidirectional for background type --- .../org/jackhuang/hmcl/ui/SettingsPage.java | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java index 83df018f3..3b88e0a99 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java @@ -188,20 +188,30 @@ public final class SettingsPage extends StackPane implements DecoratorPage { backgroundItem.loadChildren(Collections.singletonList( backgroundItem.createChildren(i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT) )); - - FXUtils.bindString(backgroundItem.getTxtCustom(), CONFIG.backgroundImageProperty()); - 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()); + + ObjectProperty backgroundType = new SimpleObjectProperty(EnumBackgroundImage.DEFAULT) { + { + invalidated(); + } + + @Override + 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(CONFIG.backgroundImageTypeProperty().isEqualTo(EnumBackgroundImage.DEFAULT)) + new When(backgroundType.isEqualTo(EnumBackgroundImage.DEFAULT)) .then(i18n("launcher.background.default")) .otherwise(CONFIG.backgroundImageProperty())); - backgroundItem.setToggleSelectedListener(newValue -> - CONFIG.setBackgroundImageType((EnumBackgroundImage) newValue.getUserData())); - // theme JFXColorPicker picker = new JFXColorPicker(Color.web(CONFIG.getTheme().getColor()), null); picker.setCustomColorText(i18n("color.custom")); From eff3186ab5d3ff6fe433a4da37da5d6cb1fb2f93 Mon Sep 17 00:00:00 2001 From: yushijinhun Date: Wed, 18 Jul 2018 22:51:02 +0800 Subject: [PATCH 4/4] Background hot replacement --- .../java/org/jackhuang/hmcl/ui/Decorator.java | 145 ++++++++++-------- 1 file changed, 78 insertions(+), 67 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Decorator.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Decorator.java index 91bb9ae33..3c1274c44 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Decorator.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Decorator.java @@ -66,17 +66,22 @@ import org.jackhuang.hmcl.ui.construct.DialogCloseEvent; import org.jackhuang.hmcl.ui.construct.StackContainerPane; import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogWizardDisplayer; import org.jackhuang.hmcl.ui.wizard.*; -import org.jackhuang.hmcl.util.FileUtils; import org.jackhuang.hmcl.util.Lang; -import org.jackhuang.hmcl.util.StringUtils; -import java.io.File; +import java.io.IOException; +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.Optional; import java.util.Queue; import java.util.Random; 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.util.Logging.LOG; 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), @@ -212,81 +217,87 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza animationHandler = new TransitionHandler(contentPlaceHolder); - loadBackground(); - + setupBackground(); 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 = 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 randomImageIn(Path imageDir) { + if (!Files.isDirectory(imageDir)) { + return Optional.empty(); + } + + List candidates; try { - Image background; - - if (CONFIG.getBackgroundImageType() == EnumBackgroundImage.DEFAULT) - background = searchBackgroundImage(new Image("/assets/img/background.jpg"), ""); - else - background = searchBackgroundImage(new Image("/assets/img/background.jpg"), CONFIG.getBackgroundImage()); - - drawerWrapper.setBackground(new Background(new BackgroundImage(background, BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, new BackgroundSize(800, 480, false, false, true, true)))); - } catch (IllegalArgumentException ignore) { + candidates = Files.list(imageDir) + .filter(Files::isRegularFile) + .filter(it -> { + String filename = it.getFileName().toString(); + return filename.endsWith(".png") || filename.endsWith(".jpg"); + }) + .collect(toList()); + } catch (IOException e) { + 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 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) { - Random random = new Random(); - boolean loaded = false; - Image background = def; - - // custom path - if (StringUtils.isNotBlank(customPath)) { + private Optional tryLoadImage(Path path) { + if (Files.isRegularFile(path)) { try { - background = new Image("file:" + customPath); - loaded = true; - } catch (IllegalArgumentException ignore) { + return Optional.of(new Image(path.toAbsolutePath().toUri().toString())); + } catch (IllegalArgumentException ignored) { } } - - // 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; + return Optional.empty(); } + // ==== @FXML private void onMouseMoved(MouseEvent mouseEvent) {