From e37322d147341ea94a6efeebdc88f34f3a43d5db Mon Sep 17 00:00:00 2001 From: Haowei Wen Date: Fri, 27 Aug 2021 20:05:18 +0800 Subject: [PATCH 1/9] fix: NPE caused by uninitialized processPriority --- .../src/main/java/org/jackhuang/hmcl/game/LaunchOptions.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.java index 179a35510..ab39645d6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.java @@ -55,7 +55,7 @@ public class LaunchOptions implements Serializable { private String preLaunchCommand; private NativesDirectoryType nativesDirType; private String nativesDir; - private ProcessPriority processPriority; + private ProcessPriority processPriority = ProcessPriority.NORMAL; /** * The game directory @@ -497,7 +497,7 @@ public class LaunchOptions implements Serializable { return this; } - public Builder setProcessPriority(ProcessPriority processPriority) { + public Builder setProcessPriority(@NotNull ProcessPriority processPriority) { options.processPriority = processPriority; return this; } From 6ac3f68dfcebc39e8e5cea4095d15b6759ced1bb Mon Sep 17 00:00:00 2001 From: Haowei Wen Date: Fri, 27 Aug 2021 20:38:12 +0800 Subject: [PATCH 2/9] feat: add param hmcl.openjfx.repo --- .../java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java | 2 +- README.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java index 2f9784e6b..b1ab83e3a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java @@ -88,7 +88,7 @@ public final class SelfDependencyPatcher { static class DependencyDescriptor { - private static final String REPOSITORY_URL = "https://maven.aliyun.com/repository/central/"; + private static final String REPOSITORY_URL = System.getProperty("hmcl.openjfx.repo", "https://maven.aliyun.com/repository/central/"); private static final Path DEPENDENCIES_DIR_PATH = HMCL_DIRECTORY.resolve("dependencies"); private static String currentArchClassifier() { diff --git a/README.md b/README.md index d93f25f23..cad2e02df 100644 --- a/README.md +++ b/README.md @@ -53,3 +53,4 @@ Make sure you have Java installed with JavaFX 8 at least. Liberica full JDK 8~16 |`-Dhmcl.version.override=`|Override the version number.| |`-Dhmcl.update_source.override=`|Override the update source.| |`-Dhmcl.authlibinjector.location=`|Use specified authlib-injector (instead of downloading one).| +|`-Dhmcl.openjfx.repo=`|Download OpenJFX from specified Maven repository. Default value is `https://maven.aliyun.com/repository/central/`.| From a85d3b504fb365963bdbc2bc43acc8fd92bf1707 Mon Sep 17 00:00:00 2001 From: Haowei Wen Date: Fri, 27 Aug 2021 22:57:49 +0800 Subject: [PATCH 3/9] fix: main page account item not working properly --- .../jackhuang/hmcl/game/TexturesLoader.java | 27 +++--------- .../java/org/jackhuang/hmcl/ui/FXUtils.java | 19 ++++++++ .../ui/account/AccountAdvancedListItem.java | 44 ++++++++++++------- .../org/jackhuang/hmcl/ui/main/RootPage.java | 18 ++++++-- 4 files changed, 67 insertions(+), 41 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java index 3f8969aa4..3927f2be9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java @@ -1,6 +1,6 @@ /* * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors + * Copyright (C) 2021 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 @@ -20,14 +20,13 @@ package org.jackhuang.hmcl.game; import javafx.beans.binding.Bindings; import javafx.beans.binding.ObjectBinding; import javafx.scene.image.Image; -import javafx.scene.image.PixelWriter; -import javafx.scene.image.WritableImage; import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.ServerResponseMalformedException; import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount; import org.jackhuang.hmcl.auth.yggdrasil.*; import org.jackhuang.hmcl.task.FileDownloadTask; +import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.util.ResourceNotFoundError; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.javafx.BindingMapping; @@ -231,34 +230,18 @@ public final class TexturesLoader { public static ObjectBinding fxAvatarBinding(YggdrasilService service, UUID uuid, int size) { return BindingMapping.of(skinBinding(service, uuid)) .map(it -> toAvatar(it.image, size)) - .map(TexturesLoader::toFXImage); + .map(FXUtils::toFXImage); } public static ObjectBinding fxAvatarBinding(Account account, int size) { if (account instanceof YggdrasilAccount || account instanceof MicrosoftAccount) { return BindingMapping.of(skinBinding(account)) .map(it -> toAvatar(it.image, size)) - .map(TexturesLoader::toFXImage); + .map(FXUtils::toFXImage); } else { return Bindings.createObjectBinding( - () -> toFXImage(toAvatar(getDefaultSkin(TextureModel.detectUUID(account.getUUID())).image, size))); + () -> FXUtils.toFXImage(toAvatar(getDefaultSkin(TextureModel.detectUUID(account.getUUID())).image, size))); } } // ==== - - // Based on https://stackoverflow.com/a/57552025 - // Fix #874: Use it instead of SwingFXUtils.toFXImage - private static WritableImage toFXImage(BufferedImage image) { - WritableImage wr = new WritableImage(image.getWidth(), image.getHeight()); - PixelWriter pw = wr.getPixelWriter(); - - final int iw = image.getWidth(); - final int ih = image.getHeight(); - for (int x = 0; x < iw; x++) { - for (int y = 0; y < ih; y++) { - pw.setArgb(x, y, image.getRGB(x, y)); - } - } - return wr; - } } 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 691cd52dc..8ddc24616 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -38,6 +38,8 @@ import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import javafx.scene.image.PixelWriter; +import javafx.scene.image.WritableImage; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.input.TransferMode; @@ -55,6 +57,7 @@ import org.jackhuang.hmcl.util.javafx.ExtendedProperties; import org.jackhuang.hmcl.util.javafx.SafeStringConverter; import org.jackhuang.hmcl.util.platform.OperatingSystem; +import java.awt.image.BufferedImage; import java.io.File; import java.io.FileFilter; import java.io.IOException; @@ -552,4 +555,20 @@ public final class FXUtils { } }); } + + // Based on https://stackoverflow.com/a/57552025 + // Fix #874: Use it instead of SwingFXUtils.toFXImage + public static WritableImage toFXImage(BufferedImage image) { + WritableImage wr = new WritableImage(image.getWidth(), image.getHeight()); + PixelWriter pw = wr.getPixelWriter(); + + final int iw = image.getWidth(); + final int ih = image.getHeight(); + for (int x = 0; x < iw; x++) { + for (int y = 0; y < ih; y++) { + pw.setArgb(x, y, image.getRGB(x, y)); + } + } + return wr; + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountAdvancedListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountAdvancedListItem.java index b295f6d94..0e0252e18 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountAdvancedListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountAdvancedListItem.java @@ -1,6 +1,6 @@ /* * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors + * Copyright (C) 2021 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 @@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui.account; import javafx.beans.binding.Bindings; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; import javafx.scene.Node; import javafx.scene.control.Tooltip; @@ -27,14 +28,19 @@ import javafx.scene.image.ImageView; import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; +import org.jackhuang.hmcl.auth.yggdrasil.TextureModel; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; import org.jackhuang.hmcl.game.TexturesLoader; import org.jackhuang.hmcl.setting.Accounts; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.construct.AdvancedListItem; import org.jackhuang.hmcl.util.Pair; +import org.jackhuang.hmcl.util.javafx.BindingMapping; -import static org.jackhuang.hmcl.ui.FXUtils.newImage; +import static javafx.beans.binding.Bindings.createStringBinding; +import static org.jackhuang.hmcl.setting.Accounts.getAccountFactory; +import static org.jackhuang.hmcl.setting.Accounts.getLocalizedLoginTypeName; +import static org.jackhuang.hmcl.ui.FXUtils.toFXImage; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class AccountAdvancedListItem extends AdvancedListItem { @@ -48,16 +54,18 @@ public class AccountAdvancedListItem extends AdvancedListItem { Account account = get(); if (account == null) { titleProperty().unbind(); + subtitleProperty().unbind(); + imageView.imageProperty().unbind(); + tooltip.textProperty().unbind(); setTitle(i18n("account.missing")); setSubtitle(i18n("account.missing.add")); - imageView.imageProperty().unbind(); - imageView.setImage(newImage("/assets/img/steve.png")); - tooltip.setText(""); + imageView.setImage(toFXImage(TexturesLoader.toAvatar(TexturesLoader.getDefaultSkin(TextureModel.STEVE).getImage(), 32))); + tooltip.setText(i18n("account.create")); } else { - titleProperty().bind(Bindings.createStringBinding(account::getCharacter, account)); - setSubtitle(accountSubtitle(account)); + titleProperty().bind(BindingMapping.of(account, Account::getCharacter)); + subtitleProperty().bind(accountSubtitle(account)); imageView.imageProperty().bind(TexturesLoader.fxAvatarBinding(account, 32)); - tooltip.setText(account.getCharacter() + " " + accountTooltip(account)); + tooltip.textProperty().bind(accountTooltip(account)); } } }; @@ -88,23 +96,27 @@ public class AccountAdvancedListItem extends AdvancedListItem { return account; } - private static String accountSubtitle(Account account) { - String loginTypeName = Accounts.getLocalizedLoginTypeName(Accounts.getAccountFactory(account)); + private static ObservableValue accountSubtitle(Account account) { if (account instanceof AuthlibInjectorAccount) { - return ((AuthlibInjectorAccount) account).getServer().getName(); + return BindingMapping.of(((AuthlibInjectorAccount) account).getServer(), AuthlibInjectorServer::getName); } else { - return loginTypeName; + return createStringBinding(() -> getLocalizedLoginTypeName(getAccountFactory(account))); } } - private static String accountTooltip(Account account) { + private static ObservableValue accountTooltip(Account account) { if (account instanceof AuthlibInjectorAccount) { AuthlibInjectorServer server = ((AuthlibInjectorAccount) account).getServer(); - return account.getUsername() + ", " + i18n("account.injector.server") + ": " + server.getName(); + return Bindings.format("%s (%s) (%s)", + BindingMapping.of(account, Account::getCharacter), + account.getUsername(), + BindingMapping.of(server, AuthlibInjectorServer::getName)); } else if (account instanceof YggdrasilAccount) { - return account.getUsername(); + return Bindings.format("%s (%s)", + BindingMapping.of(account, Account::getCharacter), + account.getUsername()); } else { - return ""; + return BindingMapping.of(account, Account::getCharacter); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java index 8e3e8dc6f..9bf5e77d9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java @@ -1,6 +1,6 @@ /* * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors + * Copyright (C) 2021 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 @@ -157,11 +157,16 @@ public class RootPage extends DecoratorTabPage { return accountTab; } - private void selectPage(Tab tab) { + /** + * @return true if the tab is being opened, or false if the tab is being closed + */ + private boolean selectPage(Tab tab) { if (getSelectionModel().getSelectedItem() == tab) { getSelectionModel().select(getMainTab()); + return false; } else { getSelectionModel().select(tab); + return true; } } @@ -173,7 +178,14 @@ public class RootPage extends DecoratorTabPage { // first item in left sidebar AccountAdvancedListItem accountListItem = new AccountAdvancedListItem(); accountListItem.activeProperty().bind(control.accountTab.selectedProperty()); - accountListItem.setOnAction(e -> control.selectPage(control.accountTab)); + accountListItem.setOnAction(e -> { + if (control.selectPage(control.accountTab)) { + // open create account dialog if no account exists + if (Accounts.getAccounts().isEmpty()) { + Controllers.dialog(new AddAccountPane()); + } + } + }); accountListItem.accountProperty().bind(Accounts.selectedAccountProperty()); // second item in left sidebar From 8230c28084d3c4dff8162a5bce93735170b2e4e6 Mon Sep 17 00:00:00 2001 From: Haowei Wen Date: Fri, 27 Aug 2021 23:24:57 +0800 Subject: [PATCH 4/9] feat: press esc to go back --- .../ui/decorator/DecoratorController.java | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java index 2b463cb4b..d241563e8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java @@ -57,6 +57,7 @@ import static java.util.logging.Level.WARNING; import static java.util.stream.Collectors.toList; import static org.jackhuang.hmcl.setting.ConfigHolder.config; import static org.jackhuang.hmcl.ui.FXUtils.newImage; +import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.io.FileUtils.getExtension; @@ -86,33 +87,39 @@ public class DecoratorController { setupAuthlibInjectorDnD(); - // pass key events to current dialog + // pass key events to current dialog / current page decorator.addEventFilter(KeyEvent.ANY, e -> { - if (dialogPane == null || !dialogPane.peek().isPresent()) { - return; - } - Node currentDialog = dialogPane.peek().get(); - if (!(e.getTarget() instanceof Node)) { - return; + return; // event source can't be determined } - boolean targetInDialog = false; + Node newTarget; + if (dialogPane != null && dialogPane.peek().isPresent()) { + newTarget = dialogPane.peek().get(); // current dialog + } else { + newTarget = navigator.getCurrentPage(); // current page + } + + boolean needsRedirect = true; Node t = (Node) e.getTarget(); while (t != null) { - if (t == currentDialog) { - targetInDialog = true; + if (t == newTarget) { + // current event target is in newTarget + needsRedirect = false; break; } t = t.getParent(); } - if (targetInDialog) { + if (!needsRedirect) { return; } e.consume(); - currentDialog.fireEvent(e.copyFor(e.getSource(), currentDialog)); + newTarget.fireEvent(e.copyFor(e.getSource(), newTarget)); }); + + // press ESC to go back + onEscPressed(navigator, this::back); } public Decorator getDecorator() { @@ -229,10 +236,15 @@ public class DecoratorController { if (navigator.getCurrentPage() instanceof DecoratorPage) { DecoratorPage page = (DecoratorPage) navigator.getCurrentPage(); - if (page.back()) - navigator.close(); + if (page.back()) { + if (navigator.canGoBack()) { + navigator.close(); + } + } } else { - navigator.close(); + if (navigator.canGoBack()) { + navigator.close(); + } } } From e9bc1524f166acd6d8aac8a1ea0d1ac4f2db4d10 Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Fri, 27 Aug 2021 00:19:37 +0800 Subject: [PATCH 5/9] fix: no longer hide player id in game logs. --- HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index 7032f73a2..f43ed7183 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -597,8 +597,7 @@ public final class LauncherHelper { else forbiddenTokens = mapOf( pair(authInfo.getAccessToken(), ""), - pair(UUIDTypeAdapter.fromUUID(authInfo.getUUID()), ""), - pair(authInfo.getUsername(), "") + pair(UUIDTypeAdapter.fromUUID(authInfo.getUUID()), "") ); logs = new LinkedList<>(); From a560eff153846b6042468448338d2364cfbb71ff Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Sat, 28 Aug 2021 01:24:36 +0800 Subject: [PATCH 6/9] feat: title bar with color --- .../main/java/org/jackhuang/hmcl/Metadata.java | 2 +- .../java/org/jackhuang/hmcl/setting/Theme.java | 2 +- .../org/jackhuang/hmcl/ui/Controllers.java | 4 ++-- .../hmcl/ui/decorator/DecoratorPage.java | 10 ++-------- .../ui/decorator/DecoratorTransitionPage.java | 17 ++--------------- .../org/jackhuang/hmcl/ui/main/AboutPage.java | 13 ++++++------- .../hmcl/ui/main/LauncherSettingsPage.java | 2 +- .../org/jackhuang/hmcl/ui/main/MainPage.java | 17 ++++++++++++++++- .../org/jackhuang/hmcl/ui/main/RootPage.java | 1 - .../hmcl/ui/versions/GameListPage.java | 2 +- .../hmcl/ui/versions/VersionPage.java | 2 +- HMCL/src/main/resources/assets/css/root.css | 6 +++--- .../main/resources/assets/img/gamerteam.jpg | Bin 14487 -> 0 bytes .../main/resources/assets/lang/I18N.properties | 2 +- .../assets/lang/I18N_zh_CN.properties | 2 +- 15 files changed, 38 insertions(+), 44 deletions(-) delete mode 100644 HMCL/src/main/resources/assets/img/gamerteam.jpg diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java index 27c51c4df..5ab515bd1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java @@ -30,7 +30,7 @@ public final class Metadata { private Metadata() {} public static final String VERSION = System.getProperty("hmcl.version.override", JarUtils.thisJar().flatMap(JarUtils::getImplementationVersion).orElse("@develop@")); - public static final String NAME = "HMCL"; + public static final String NAME = "Hello Minecraft! Launcher"; public static final String TITLE = NAME + " " + VERSION; public static final String UPDATE_URL = System.getProperty("hmcl.update_source.override", "https://hmcl.huangyuhui.net/api/update_link"); 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 66a4cf112..47ecf8ae3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Theme.java @@ -41,7 +41,7 @@ public class Theme { public static final Theme BLUE = new Theme("blue", "#5C6BC0"); public static final Color BLACK = Color.web("#292929"); public static final Color[] SUGGESTED_COLORS = new Color[]{ - Color.web("#5C6BC0"), // blue + Color.web("#3D6DA3"), // blue Color.web("#283593"), // dark blue Color.web("#43A047"), // green Color.web("#E67E22"), // orange 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 2d9a8b0ad..e5db94849 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -172,8 +172,8 @@ public final class Controllers { scene = new Scene(decorator.getDecorator()); scene.setFill(Color.TRANSPARENT); - stage.setMinHeight(482 + 32); - stage.setMinWidth(802 + 32); + stage.setMinHeight(450 + 2 + 40 + 16); // bg height + border width*2 + toolbar height + shadow width*2 + 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()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorPage.java index 17d60eed2..5ffc22f7d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorPage.java @@ -50,20 +50,18 @@ public interface DecoratorPage extends Refreshable { private final boolean backable; private final boolean refreshable; private final boolean animate; - private final boolean titleBarTransparent; private final double leftPaneWidth; public State(String title, Node titleNode, boolean backable, boolean refreshable, boolean animate) { - this(title, titleNode, backable, refreshable, animate, false, 0); + this(title, titleNode, backable, refreshable, animate, 0); } - public State(String title, Node titleNode, boolean backable, boolean refreshable, boolean animate, boolean titleBarTransparent, double leftPaneWidth) { + public State(String title, Node titleNode, boolean backable, boolean refreshable, boolean animate, double leftPaneWidth) { this.title = title; this.titleNode = titleNode; this.backable = backable; this.refreshable = refreshable; this.animate = animate; - this.titleBarTransparent = titleBarTransparent; this.leftPaneWidth = leftPaneWidth; } @@ -95,10 +93,6 @@ public interface DecoratorPage extends Refreshable { return animate; } - public boolean isTitleBarTransparent() { - return titleBarTransparent; - } - public double getLeftPaneWidth() { return leftPaneWidth; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorTransitionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorTransitionPage.java index 3acddadd3..58b3ef42d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorTransitionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorTransitionPage.java @@ -31,7 +31,6 @@ import org.jackhuang.hmcl.ui.wizard.Refreshable; public abstract class DecoratorTransitionPage extends Control implements DecoratorPage { protected final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(State.fromTitle("")); private final DoubleProperty leftPaneWidth = new SimpleDoubleProperty(); - private final BooleanProperty titleBarTransparent = new SimpleBooleanProperty(false); private final BooleanProperty backable = new SimpleBooleanProperty(false); private final BooleanProperty refreshable = new SimpleBooleanProperty(false); private Node currentPage; @@ -57,11 +56,11 @@ public abstract class DecoratorTransitionPage extends Control implements Decorat if (to instanceof DecoratorPage) { state.bind(Bindings.createObjectBinding(() -> { State state = ((DecoratorPage) to).stateProperty().get(); - return new State(state.getTitle(), state.getTitleNode(), backable.get(), state.isRefreshable(), true, titleBarTransparent.get(), leftPaneWidth.get()); + return new State(state.getTitle(), state.getTitleNode(), backable.get(), state.isRefreshable(), true, leftPaneWidth.get()); }, ((DecoratorPage) to).stateProperty())); } else { state.unbind(); - state.set(new State("", null, backable.get(), false, true, titleBarTransparent.get(), leftPaneWidth.get())); + state.set(new State("", null, backable.get(), false, true, leftPaneWidth.get())); } if (to instanceof Region) { @@ -121,16 +120,4 @@ public abstract class DecoratorTransitionPage extends Control implements Decorat public void setLeftPaneWidth(double leftPaneWidth) { this.leftPaneWidth.set(leftPaneWidth); } - - public boolean isTitleBarTransparent() { - return titleBarTransparent.get(); - } - - public BooleanProperty titleBarTransparentProperty() { - return titleBarTransparent; - } - - public void setTitleBarTransparent(boolean titleBarTransparent) { - this.titleBarTransparent.set(titleBarTransparent); - } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java index d0ff59d78..b187efcb5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java @@ -63,11 +63,10 @@ public class AboutPage extends StackPane { bangbang93.setSubtitle(i18n("about.thanks_to.bangbang93.statement")); bangbang93.setExternalLink("https://bmclapi2.bangbang93.com/"); - IconedTwoLineListItem gamerteam = new IconedTwoLineListItem(); - gamerteam.setTitle("gamerteam"); - gamerteam.setImage(new Image("/assets/img/gamerteam.jpg", 32, 32, false, true)); - gamerteam.setSubtitle(i18n("about.thanks_to.gamerteam.statement")); - gamerteam.setExternalLink("http://www.zhaisoul.com/"); + IconedTwoLineListItem redLnn = new IconedTwoLineListItem(); + redLnn.setTitle("Red_lnn"); + redLnn.setImage(new Image("/assets/img/red_lnn.jpg", 32, 32, false, true)); + redLnn.setSubtitle(i18n("about.thanks_to.gamerteam.statement")); IconedTwoLineListItem mcbbs = new IconedTwoLineListItem(); mcbbs.setImage(new Image("/assets/img/chest.png", 32, 32, false, true)); @@ -80,14 +79,14 @@ public class AboutPage extends StackPane { contributors.setTitle(i18n("about.thanks_to.contributors")); contributors.setSubtitle(i18n("about.thanks_to.contributors.statement")); contributors.setExternalLink("https://github.com/huanghongxun/HMCL/graphs/contributors"); - contributors.setExternalLink("https://hmcl.huangyuhui.net/api/redirect/sponsor"); IconedTwoLineListItem users = new IconedTwoLineListItem(); users.setImage(new Image("/assets/img/craft_table.png", 32, 32, false, true)); users.setTitle(i18n("about.thanks_to.users")); users.setSubtitle(i18n("about.thanks_to.users.statement")); + users.setExternalLink("https://hmcl.huangyuhui.net/api/redirect/sponsor"); - thanks.getContent().setAll(yushijinhun, bangbang93, gamerteam, mcbbs, users, contributors); + thanks.getContent().setAll(yushijinhun, bangbang93, mcbbs, redLnn, users, contributors); } ComponentList dep = new ComponentList(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java index 77fca2189..447e70172 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java @@ -33,7 +33,7 @@ import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class LauncherSettingsPage extends BorderPane implements DecoratorPage { - private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(new State(i18n("settings.launcher"), null, true, false, true, false, 200)); + private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(new State(i18n("settings.launcher"), null, true, false, true, 200)); private final TabHeader tab; private final TabHeader.Tab settingsTab = new TabHeader.Tab<>("settingsPage"); private final TabHeader.Tab helpTab = new TabHeader.Tab<>("helpPage"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java index 2ad1fc248..a116ec90a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java @@ -30,6 +30,8 @@ import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; @@ -59,7 +61,7 @@ import static org.jackhuang.hmcl.ui.FXUtils.SINE; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public final class MainPage extends StackPane implements DecoratorPage { - private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(State.fromTitle("HMCL " + Metadata.VERSION)); + private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(); private final PopupMenu menu = new PopupMenu(); private final JFXPopup popup = new JFXPopup(menu); @@ -75,6 +77,19 @@ public final class MainPage extends StackPane implements DecoratorPage { private JFXButton menuButton; { + HBox titleNode = new HBox(8); + titleNode.setPadding(new Insets(0, 0, 0, 2)); + titleNode.setAlignment(Pos.CENTER_LEFT); + + ImageView titleIcon = new ImageView(); + titleIcon.setImage(new Image("/assets/img/icon.png", 20, 20, false, false)); + + Label titleLabel = new Label(Metadata.TITLE); + titleLabel.getStyleClass().add("jfx-decorator-title"); + titleNode.getChildren().setAll(titleIcon, titleLabel); + + state.setValue(State.fromTitleNode(titleNode)); + setPadding(new Insets(20)); updatePane = new StackPane(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java index 9bf5e77d9..739e8fd80 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java @@ -93,7 +93,6 @@ public class RootPage extends DecoratorTabPage { @Override protected void onNavigated(Node to) { backableProperty().set(!(to instanceof MainPage)); - setTitleBarTransparent(to instanceof MainPage); super.onNavigated(to); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java index 8a518b908..cc7c42bc6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java @@ -244,7 +244,7 @@ public class GameListPage extends ListPageBase implements Decorato public GameListSkin() { super(GameList.this); - state.set(new State(i18n("version.manage"), null, true, false, true, false, 200)); + state.set(new State(i18n("version.manage"), null, true, false, true, 200)); } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java index 133d7ea38..309663d48 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java @@ -388,7 +388,7 @@ public class VersionPage extends Control implements DecoratorPage, ModDownloadPa } control.state.bind(Bindings.createObjectBinding(() -> - new State(i18n("version.manage.manage.title", getSkinnable().getVersion()), null, true, false, true, false, 200), + new State(i18n("version.manage.manage.title", getSkinnable().getVersion()), null, true, false, true, 200), getSkinnable().version)); //control.transitionPane.getStyleClass().add("gray-background"); diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index c5879078d..c42898f69 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -398,7 +398,7 @@ -fx-font-weight: BOLD; } -.jfx-tool-bar .background { +.jfx-tool-bar.background { -fx-background-color: -fx-base-color; } @@ -1140,12 +1140,12 @@ .window { -fx-background-color: transparent; - -fx-padding: 16; + -fx-padding: 8; } .body { -fx-border-radius: 5; - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.4), 20, 0.3, 0.0, 0.0); + -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.4), 10, 0.3, 0.0, 0.0); } .debug-border { diff --git a/HMCL/src/main/resources/assets/img/gamerteam.jpg b/HMCL/src/main/resources/assets/img/gamerteam.jpg deleted file mode 100644 index 0ee98e3ce7d541b0bbc0f37a4c7705f762afcbe6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14487 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 diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index b14d6c2d8..ea6ca2f66 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -29,7 +29,7 @@ about.legal=Legal Acknowledgement about.thanks_to=Thanks to about.thanks_to.bangbang93.statement=BMCLAPI about.thanks_to.contributors=All contributors who participated in this project via issues, pull requests, etc. -about.thanks_to.gamerteam.statement=gamerteam (Default background image) +about.thanks_to.gamerteam.statement=Default background image about.thanks_to.mcbbs=MCBBS about.thanks_to.mcbbs.statement=Provide MCBBS download source about.thanks_to.users=Members of HMCL User Group diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 261909fad..c10223b42 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -30,7 +30,7 @@ about.thanks_to=鸣谢 about.thanks_to.bangbang93.statement=提供 BMCLAPI 下载源,请赞助支持 BMCLAPI! about.thanks_to.contributors=所有通过 Issues、Pull Requests 等方式参与本项目的贡献者 about.thanks_to.contributors.statement=没有开源社区的支持,Hello Minecraft! Launcher 无法走到今天 -about.thanks_to.gamerteam.statement=提供默认背景图与 HMCL 官方网站 CDN +about.thanks_to.gamerteam.statement=提供默认背景图 about.thanks_to.mcbbs=MCBBS 我的世界中文论坛 about.thanks_to.mcbbs.statement=提供 MCBBS 下载源 about.thanks_to.users=HMCL 用户群成员 From f1e4364ffa3dc74aeae0e3636a3e6a8a0f7bfdfb Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Sat, 28 Aug 2021 01:29:13 +0800 Subject: [PATCH 7/9] fix: background size --- .../hmcl/ui/decorator/DecoratorSkin.java | 101 ++++++++---------- 1 file changed, 42 insertions(+), 59 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java index d842399b6..f47fc57ea 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorSkin.java @@ -79,13 +79,8 @@ public class DecoratorSkin extends SkinBase { StackPane shadowContainer = new StackPane(); shadowContainer.getStyleClass().add("body"); - shadowContainer.setPickOnBounds(false); parent = new StackPane(); - parent.backgroundProperty().bind(skinnable.contentBackgroundProperty()); - parent.setPickOnBounds(false); - parent.prefHeightProperty().bind(control.prefHeightProperty()); - parent.prefWidthProperty().bind(control.prefWidthProperty()); Rectangle clip = new Rectangle(); clip.widthProperty().bind(parent.widthProperty()); clip.heightProperty().bind(parent.heightProperty()); @@ -100,16 +95,6 @@ public class DecoratorSkin extends SkinBase { shadowContainer.getChildren().setAll(parent); root.getChildren().setAll(shadowContainer); - // animation layer at bottom - { - HBox layer = new HBox(); - leftPane = new StackPane(); - leftPane.setPrefWidth(0); - leftPane.getStyleClass().add("jfx-decorator-drawer"); - layer.getChildren().setAll(leftPane); - parent.getChildren().add(layer); - } - StackPane wrapper = new StackPane(); BorderPane frame = new BorderPane(); frame.getStyleClass().addAll("jfx-decorator"); @@ -120,8 +105,19 @@ public class DecoratorSkin extends SkinBase { // center node with a animation layer at bottom, a container layer at middle and a "welcome" layer at top. StackPane container = new StackPane(); + container.backgroundProperty().bind(skinnable.contentBackgroundProperty()); FXUtils.setOverflowHidden(container); + // animation layer at bottom + { + HBox layer = new HBox(); + leftPane = new StackPane(); + leftPane.setPrefWidth(0); + leftPane.getStyleClass().add("jfx-decorator-drawer"); + layer.getChildren().setAll(leftPane); + container.getChildren().add(layer); + } + // content layer at middle { StackPane contentPlaceHolder = new StackPane(); @@ -154,29 +150,18 @@ public class DecoratorSkin extends SkinBase { titleContainer = new StackPane(); titleContainer.setPickOnBounds(false); - titleContainer.getStyleClass().addAll("jfx-tool-bar"); + titleContainer.getStyleClass().addAll("jfx-tool-bar", "background"); control.capableDraggingWindow(titleContainer); - StackPane titleBarBackground = new StackPane(); - titleBarBackground.getStyleClass().add("background"); - titleContainer.getChildren().add(titleBarBackground); - BorderPane titleBar = new BorderPane(); - titleBar.setPickOnBounds(false); titleContainer.getChildren().add(titleBar); - Rectangle rectangle = new Rectangle(0, 0, 0, 0); - rectangle.widthProperty().bind(titleContainer.widthProperty()); - rectangle.heightProperty().bind(titleContainer.heightProperty().add(100)); - titleContainer.setClip(rectangle); - Rectangle buttonsContainerPlaceHolder = new Rectangle(); { navBarPane = new TransitionPane(); FXUtils.onChangeAndOperate(skinnable.stateProperty(), s -> { if (s == null) return; - Node node = createNavBar(skinnable, s.isTitleBarTransparent(), s.getLeftPaneWidth(), s.isBackable(), skinnable.canCloseProperty().get(), skinnable.showCloseAsHomeProperty().get(), s.isRefreshable(), s.getTitle(), s.getTitleNode()); - double targetOpacity = s.isTitleBarTransparent() ? 0 : 1; + Node node = createNavBar(skinnable, s.getLeftPaneWidth(), s.isBackable(), skinnable.canCloseProperty().get(), skinnable.showCloseAsHomeProperty().get(), s.isRefreshable(), s.getTitle(), s.getTitleNode()); if (s.isAnimate()) { AnimationProducer animation; if (skinnable.getNavigationDirection() == Navigation.NavigationDirection.NEXT) { @@ -192,9 +177,6 @@ public class DecoratorSkin extends SkinBase { navBarPane.getChildren().setAll(node); } - FXUtils.playAnimation(titleBarBackground, "animation", - s.isAnimate() ? Duration.millis(160) : null, titleBarBackground.opacityProperty(), null, targetOpacity, FXUtils.SINE); - titleBarTransparent = s.isTitleBarTransparent(); FXUtils.playAnimation(leftPane, "animation", s.isAnimate() ? Duration.millis(160) : null, leftPane.prefWidthProperty(), null, s.getLeftPaneWidth(), FXUtils.SINE); }); @@ -234,51 +216,52 @@ public class DecoratorSkin extends SkinBase { getChildren().add(root); } - private Node createNavBar(Decorator skinnable, boolean titleBarTransparent, double leftPaneWidth, boolean canBack, boolean canClose, boolean showCloseAsHome, boolean canRefresh, String title, Node titleNode) { + private Node createNavBar(Decorator skinnable, double leftPaneWidth, boolean canBack, boolean canClose, boolean showCloseAsHome, boolean canRefresh, String title, Node titleNode) { BorderPane navBar = new BorderPane(); { HBox navLeft = new HBox(); navLeft.setAlignment(Pos.CENTER_LEFT); navLeft.setPadding(new Insets(0, 5, 0, 5)); - JFXButton backNavButton = new JFXButton(); - backNavButton.setGraphic(SVG.back(Theme.foregroundFillBinding(), -1, -1)); - backNavButton.getStyleClass().add("jfx-decorator-button"); - backNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding()); - backNavButton.onActionProperty().bind(skinnable.onBackNavButtonActionProperty()); - backNavButton.visibleProperty().set(canBack); + if (canBack) { + JFXButton backNavButton = new JFXButton(); + backNavButton.setGraphic(SVG.back(Theme.foregroundFillBinding(), -1, -1)); + backNavButton.getStyleClass().add("jfx-decorator-button"); + backNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding()); + backNavButton.onActionProperty().bind(skinnable.onBackNavButtonActionProperty()); + backNavButton.visibleProperty().set(canBack); - JFXButton closeNavButton = new JFXButton(); - closeNavButton.setGraphic(SVG.close(Theme.foregroundFillBinding(), -1, -1)); - closeNavButton.getStyleClass().add("jfx-decorator-button"); - closeNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding()); - closeNavButton.onActionProperty().bind(skinnable.onCloseNavButtonActionProperty()); + navLeft.getChildren().add(backNavButton); + } - if (canBack) navLeft.getChildren().add(backNavButton); - if (canClose) navLeft.getChildren().add(closeNavButton); - if (showCloseAsHome) - closeNavButton.setGraphic(SVG.home(Theme.foregroundFillBinding(), -1, -1)); - else + if (canClose) { + JFXButton closeNavButton = new JFXButton(); closeNavButton.setGraphic(SVG.close(Theme.foregroundFillBinding(), -1, -1)); - navBar.setLeft(navLeft); + closeNavButton.getStyleClass().add("jfx-decorator-button"); + closeNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding()); + closeNavButton.onActionProperty().bind(skinnable.onCloseNavButtonActionProperty()); + if (showCloseAsHome) + closeNavButton.setGraphic(SVG.home(Theme.foregroundFillBinding(), -1, -1)); + else + closeNavButton.setGraphic(SVG.close(Theme.foregroundFillBinding(), -1, -1)); + + navLeft.getChildren().add(closeNavButton); + } + + if (canBack || canClose) { + navBar.setLeft(navLeft); + } BorderPane center = new BorderPane(); if (title != null) { Label titleLabel = new Label(); BorderPane.setAlignment(titleLabel, Pos.CENTER_LEFT); titleLabel.getStyleClass().add("jfx-decorator-title"); - if (titleBarTransparent) titleLabel.pseudoClassStateChanged(TRANSPARENT, true); - if (!canClose && !canBack) { - titleLabel.setAlignment(Pos.CENTER); - // 20: in this case width of navLeft is 10, so to make the text center aligned, - // we should have width 2 * 10 reduced - titleLabel.setPrefWidth(leftPaneWidth - 20); - } if (titleNode != null) { titleLabel.prefWidthProperty().bind(Bindings.createDoubleBinding(() -> { - // 18 = 10 (horizontal padding of navLeft) + 8 (margin-left) - return leftPaneWidth - 18 - backNavButton.getWidth() - closeNavButton.getWidth(); - }, backNavButton.widthProperty(), closeNavButton.widthProperty())); + // 8 (margin-left) + return leftPaneWidth - 8 - navLeft.getWidth(); + }, navLeft.widthProperty())); } titleLabel.setText(title); center.setLeft(titleLabel); From 6e45ea835f1df3fdfaa856b8cd15423e510ad42b Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Sat, 28 Aug 2021 01:29:26 +0800 Subject: [PATCH 8/9] bulid: minimize jar --- HMCL/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HMCL/build.gradle b/HMCL/build.gradle index 2d9522fa9..7650ce941 100644 --- a/HMCL/build.gradle +++ b/HMCL/build.gradle @@ -121,6 +121,8 @@ jar { shadowJar { classifier = null + minimize() + manifest { attributes 'Created-By': 'Copyright(c) 2013-2021 huangyuhui.', 'Main-Class': mainClassName, From 9bef8b432b9095bdb470f7a1e7c1ce8fcab37c01 Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Sat, 28 Aug 2021 16:35:28 +0800 Subject: [PATCH 9/9] feat: account list page left pane --- .../org/jackhuang/hmcl/ui/Controllers.java | 13 +++ .../main/java/org/jackhuang/hmcl/ui/SVG.java | 32 +++++- .../hmcl/ui/account/AccountList.java | 58 ---------- .../hmcl/ui/account/AccountListPage.java | 104 ++++++++++++++++++ .../hmcl/ui/construct/AdvancedListBox.java | 10 ++ .../hmcl/ui/decorator/DecoratorPage.java | 4 + .../hmcl/ui/main/LauncherSettingsPage.java | 63 +++++------ .../org/jackhuang/hmcl/ui/main/RootPage.java | 96 +++++----------- .../hmcl/ui/versions/GameListPage.java | 2 +- .../hmcl/ui/versions/VersionPage.java | 2 +- .../src/main/resources/assets/img/red_lnn.jpg | Bin 0 -> 23469 bytes 11 files changed, 218 insertions(+), 166 deletions(-) delete mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountList.java create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java create mode 100644 HMCL/src/main/resources/assets/img/red_lnn.jpg 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 e5db94849..8ec86ddfb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -30,10 +30,12 @@ import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.download.java.JavaRepository; import org.jackhuang.hmcl.mod.curse.CurseModManager; +import org.jackhuang.hmcl.setting.Accounts; import org.jackhuang.hmcl.setting.EnumCommonDirectory; import org.jackhuang.hmcl.setting.Profiles; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.TaskExecutor; +import org.jackhuang.hmcl.ui.account.AccountListPage; import org.jackhuang.hmcl.ui.account.AuthlibInjectorServersPage; import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.construct.InputDialogPane; @@ -91,6 +93,12 @@ public final class Controllers { } }; }); + private static Lazy accountListPage = new Lazy<>(() -> { + AccountListPage accountListPage = new AccountListPage(); + accountListPage.selectedAccountProperty().bindBidirectional(Accounts.selectedAccountProperty()); + accountListPage.accountsProperty().bindContent(Accounts.accountsProperty()); + return accountListPage; + }); private static Lazy settingsPage = new Lazy<>(LauncherSettingsPage::new); private Controllers() { @@ -136,6 +144,11 @@ public final class Controllers { return settingsPage.get(); } + // FXThread + public static AccountListPage getAccountListPage() { + return accountListPage.get(); + } + // FXThread public static DecoratorController getDecorator() { return decorator; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java index f69d95746..4269efafb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java @@ -54,7 +54,7 @@ public final class SVG { return svg; } - // default fill: white, width: 20, height 20 + // default fill: white, width: 24, height 24 public static Node gear(ObjectBinding fill, double width, double height) { return createSVGPath( @@ -360,4 +360,34 @@ public final class SVG { "M16 3.23C16.71 2.41 17.61 2 18.7 2C19.61 2 20.37 2.33 21 3C21.63 3.67 21.96 4.43 22 5.3C22 6 21.67 6.81 21 7.76S19.68 9.5 19.03 10.15C18.38 10.79 17.37 11.74 16 13C14.61 11.74 13.59 10.79 12.94 10.15S11.63 8.71 10.97 7.76C10.31 6.81 10 6 10 5.3C10 4.39 10.32 3.63 10.97 3C11.62 2.37 12.4 2.04 13.31 2C14.38 2 15.27 2.41 16 3.23M22 19V20L14 22.5L7 20.56V22H1V11H8.97L15.13 13.3C16.25 13.72 17 14.8 17 16H19C20.66 16 22 17.34 22 19M5 20V13H3V20H5M19.9 18.57C19.74 18.24 19.39 18 19 18H13.65C13.11 18 12.58 17.92 12.07 17.75L9.69 16.96L10.32 15.06L12.7 15.85C13 15.95 15 16 15 16C15 15.63 14.77 15.3 14.43 15.17L8.61 13H7V18.5L13.97 20.41L19.9 18.57Z", fill, width, height); } + + public static Node mojang(ObjectBinding fill, double width, double height) { + return createSVGPath( + "m 13.965847,0 c -1.010612,0.82802228 -1.197232,2.1950303 -1.265172,3.4179557 0.02123,1.0021189 1.341654,1.2994159 1.953117,0.590289 C 15.091158,2.6579315 14.369242,1.2738804 13.965847,0 Z M 10.913012,2.9296764 C 10.755901,3.6982508 10.628413,4.4668999 10.492533,5.2354744 8.9893533,3.9913178 7.1504705,3.0824837 5.1419856,3.2947971 3.4944341,3.4646478 0.94293227,2.6961479 0.14038761,4.645185 -0.12288102,8.3139606 0.07223999,12.01236 0.03402357,15.689629 c -0.12314178,1.222925 0.86170213,2.420422 2.14407513,2.280295 4.2207899,0.03397 8.4502143,0.04723 12.6710043,-0.0038 1.265389,0.135918 1.957646,-1.010748 2.13599,-2.0893 C 13.269608,16.437357 9.1760813,16.929609 5.7111265,15.129192 2.5986124,13.58355 2.246023,8.3138817 5.5581114,6.7979639 9.3203049,5.1758896 13.859607,8.0382886 14.942405,11.787743 15.613316,12.11046 16.284433,12.424684 16.95959,12.743154 16.624135,10.348258 16.653651,7.800456 15.579346,5.5881508 15.057054,4.7473897 14.097531,6.2714182 13.379911,5.6217388 12.416008,4.865903 11.749527,3.8128948 10.913012,2.9296713 Z", + fill, width, height); + } + + public static Node microsoft(ObjectBinding fill, double width, double height) { + return createSVGPath( + "M2,3H11V12H2V3M11,22H2V13H11V22M21,3V12H12V3H21M21,22H12V13H21V22Z", + fill, width, height); + } + + public static Node accountOutline(ObjectBinding fill, double width, double height) { + return createSVGPath( + "M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,6A2,2 0 0,0 10,8A2,2 0 0,0 12,10A2,2 0 0,0 14,8A2,2 0 0,0 12,6M12,13C14.67,13 20,14.33 20,17V20H4V17C4,14.33 9.33,13 12,13M12,14.9C9.03,14.9 5.9,16.36 5.9,17V18.1H18.1V17C18.1,16.36 14.97,14.9 12,14.9Z", + fill, width, height); + } + + public static Node accountGroupOutline(ObjectBinding fill, double width, double height) { + return createSVGPath( + "M12,5A3.5,3.5 0 0,0 8.5,8.5A3.5,3.5 0 0,0 12,12A3.5,3.5 0 0,0 15.5,8.5A3.5,3.5 0 0,0 12,5M12,7A1.5,1.5 0 0,1 13.5,8.5A1.5,1.5 0 0,1 12,10A1.5,1.5 0 0,1 10.5,8.5A1.5,1.5 0 0,1 12,7M5.5,8A2.5,2.5 0 0,0 3,10.5C3,11.44 3.53,12.25 4.29,12.68C4.65,12.88 5.06,13 5.5,13C5.94,13 6.35,12.88 6.71,12.68C7.08,12.47 7.39,12.17 7.62,11.81C6.89,10.86 6.5,9.7 6.5,8.5C6.5,8.41 6.5,8.31 6.5,8.22C6.2,8.08 5.86,8 5.5,8M18.5,8C18.14,8 17.8,8.08 17.5,8.22C17.5,8.31 17.5,8.41 17.5,8.5C17.5,9.7 17.11,10.86 16.38,11.81C16.5,12 16.63,12.15 16.78,12.3C16.94,12.45 17.1,12.58 17.29,12.68C17.65,12.88 18.06,13 18.5,13C18.94,13 19.35,12.88 19.71,12.68C20.47,12.25 21,11.44 21,10.5A2.5,2.5 0 0,0 18.5,8M12,14C9.66,14 5,15.17 5,17.5V19H19V17.5C19,15.17 14.34,14 12,14M4.71,14.55C2.78,14.78 0,15.76 0,17.5V19H3V17.07C3,16.06 3.69,15.22 4.71,14.55M19.29,14.55C20.31,15.22 21,16.06 21,17.07V19H24V17.5C24,15.76 21.22,14.78 19.29,14.55M12,16C13.53,16 15.24,16.5 16.23,17H7.77C8.76,16.5 10.47,16 12,16Z", + fill, width, height); + } + + public static Node styleOutline(ObjectBinding fill, double width, double height) { + return createSVGPath( + "M2.5 19.6L3.8 20.2V11.2L1.4 17C1 18.1 1.5 19.2 2.5 19.6M15.2 4.8L20.2 16.8L12.9 19.8L7.9 7.9V7.8L15.2 4.8M15.3 2.8C15 2.8 14.8 2.8 14.5 2.9L7.1 6C6.4 6.3 5.9 7 5.9 7.8C5.9 8 5.9 8.3 6 8.6L11 20.5C11.3 21.3 12 21.7 12.8 21.7C13.1 21.7 13.3 21.7 13.6 21.6L21 18.5C22 18.1 22.5 16.9 22.1 15.9L17.1 4C16.8 3.2 16 2.8 15.3 2.8M10.5 9.9C9.9 9.9 9.5 9.5 9.5 8.9S9.9 7.9 10.5 7.9C11.1 7.9 11.5 8.4 11.5 8.9S11.1 9.9 10.5 9.9M5.9 19.8C5.9 20.9 6.8 21.8 7.9 21.8H9.3L5.9 13.5V19.8Z", + fill, width, height); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountList.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountList.java deleted file mode 100644 index e3848c3c5..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountList.java +++ /dev/null @@ -1,58 +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 . - */ -package org.jackhuang.hmcl.ui.account; - -import javafx.beans.property.*; -import javafx.collections.FXCollections; -import org.jackhuang.hmcl.auth.Account; -import org.jackhuang.hmcl.ui.Controllers; -import org.jackhuang.hmcl.ui.ListPage; -import org.jackhuang.hmcl.ui.decorator.DecoratorPage; -import org.jackhuang.hmcl.util.javafx.MappedObservableList; - -import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor; - -public class AccountList extends ListPage implements DecoratorPage { - private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("account.manage"))); - private final ListProperty accounts = new SimpleListProperty<>(this, "accounts", FXCollections.observableArrayList()); - private final ObjectProperty selectedAccount; - - public AccountList() { - setItems(MappedObservableList.create(accounts, AccountListItem::new)); - selectedAccount = createSelectedItemPropertyFor(getItems(), Account.class); - } - - public ObjectProperty selectedAccountProperty() { - return selectedAccount; - } - - public ListProperty accountsProperty() { - return accounts; - } - - @Override - public void add() { - Controllers.dialog(new AddAccountPane()); - } - - @Override - public ReadOnlyObjectProperty stateProperty() { - return state.getReadOnlyProperty(); - } -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java new file mode 100644 index 000000000..74932bbf8 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java @@ -0,0 +1,104 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 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 . + */ +package org.jackhuang.hmcl.ui.account; + +import com.jfoenix.controls.JFXScrollPane; +import javafx.beans.binding.Bindings; +import javafx.beans.property.*; +import javafx.collections.FXCollections; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.Skin; +import javafx.scene.control.SkinBase; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.VBox; +import org.jackhuang.hmcl.auth.Account; +import org.jackhuang.hmcl.ui.*; +import org.jackhuang.hmcl.ui.construct.AdvancedListBox; +import org.jackhuang.hmcl.ui.decorator.DecoratorPage; +import org.jackhuang.hmcl.util.javafx.MappedObservableList; + +import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap; +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor; + +public class AccountListPage extends ListPageBase implements DecoratorPage { + private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("account.manage"), 200)); + private final ListProperty accounts = new SimpleListProperty<>(this, "accounts", FXCollections.observableArrayList()); + private final ObjectProperty selectedAccount; + + public AccountListPage() { + setItems(MappedObservableList.create(accounts, AccountListItem::new)); + selectedAccount = createSelectedItemPropertyFor(getItems(), Account.class); + } + + public ObjectProperty selectedAccountProperty() { + return selectedAccount; + } + + public ListProperty accountsProperty() { + return accounts; + } + + @Override + public ReadOnlyObjectProperty stateProperty() { + return state.getReadOnlyProperty(); + } + + @Override + protected Skin createDefaultSkin() { + return new AccountListPageSkin(this); + } + + private static class AccountListPageSkin extends SkinBase { + public AccountListPageSkin(AccountListPage skinnable) { + super(skinnable); + + BorderPane root = new BorderPane(); + + { + AdvancedListBox sideBar = new AdvancedListBox() + .addNavigationDrawerItem(settingsItem -> { + settingsItem.setTitle(i18n("account.create")); + settingsItem.setLeftGraphic(wrap(SVG.plusCircleOutline(null, 20, 20))); + settingsItem.setOnAction(e -> Controllers.dialog(new AddAccountPane())); + }); + FXUtils.setLimitWidth(sideBar, 200); + root.setLeft(sideBar); + } + + ScrollPane scrollPane = new ScrollPane(); + VBox list = new VBox(); + { + scrollPane.setFitToWidth(true); + + list.maxWidthProperty().bind(scrollPane.widthProperty()); + list.setSpacing(10); + list.getStyleClass().add("card-list"); + + Bindings.bindContent(list.getChildren(), skinnable.itemsProperty()); + + scrollPane.setContent(list); + JFXScrollPane.smoothScrolling(scrollPane); + + root.setCenter(scrollPane); + } + + getChildren().setAll(root); + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListBox.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListBox.java index e16e6a5bb..31f33061e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListBox.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListBox.java @@ -25,6 +25,8 @@ import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import org.jackhuang.hmcl.ui.FXUtils; +import java.util.function.Consumer; + public class AdvancedListBox extends ScrollPane { private final VBox container = new VBox(); @@ -52,6 +54,14 @@ public class AdvancedListBox extends ScrollPane { return this; } + public AdvancedListBox addNavigationDrawerItem(Consumer fn) { + AdvancedListItem item = new AdvancedListItem(); + item.getStyleClass().add("navigation-drawer-item"); + item.setActionButtonVisible(false); + fn.accept(item); + return add(item); + } + public AdvancedListBox add(int index, Node child) { if (child instanceof Pane || child instanceof AdvancedListItem) container.getChildren().add(index, child); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorPage.java index 5ffc22f7d..babad5095 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorPage.java @@ -69,6 +69,10 @@ public interface DecoratorPage extends Refreshable { return new State(title, null, true, false, true); } + public static State fromTitle(String title, double leftPaneWidth) { + return new State(title, null, true, false, true, leftPaneWidth); + } + public static State fromTitleNode(Node titleNode) { return new State(null, titleNode, true, false, true); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java index 447e70172..4dabfc101 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java @@ -25,7 +25,6 @@ import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.animation.TransitionPane; import org.jackhuang.hmcl.ui.construct.AdvancedListBox; -import org.jackhuang.hmcl.ui.construct.AdvancedListItem; import org.jackhuang.hmcl.ui.construct.TabHeader; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; @@ -33,7 +32,7 @@ import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class LauncherSettingsPage extends BorderPane implements DecoratorPage { - private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(new State(i18n("settings.launcher"), null, true, false, true, 200)); + private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("settings.launcher"), 200)); private final TabHeader tab; private final TabHeader.Tab settingsTab = new TabHeader.Tab<>("settingsPage"); private final TabHeader.Tab helpTab = new TabHeader.Tab<>("helpPage"); @@ -55,43 +54,31 @@ public class LauncherSettingsPage extends BorderPane implements DecoratorPage { }); { - AdvancedListItem settingsItem = new AdvancedListItem(); - settingsItem.getStyleClass().add("navigation-drawer-item"); - settingsItem.setTitle(i18n("settings.launcher")); - settingsItem.setLeftGraphic(wrap(SVG.gearOutline(null, 20, 20))); - settingsItem.setActionButtonVisible(false); - settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(settingsTab)); - settingsItem.setOnAction(e -> tab.getSelectionModel().select(settingsTab)); - - AdvancedListItem helpItem = new AdvancedListItem(); - helpItem.getStyleClass().add("navigation-drawer-item"); - helpItem.setTitle(i18n("help")); - helpItem.setLeftGraphic(wrap(SVG.helpCircleOutline(null, 20, 20))); - helpItem.setActionButtonVisible(false); - helpItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(helpTab)); - helpItem.setOnAction(e -> tab.getSelectionModel().select(helpTab)); - - AdvancedListItem sponsorItem = new AdvancedListItem(); - sponsorItem.getStyleClass().add("navigation-drawer-item"); - sponsorItem.setTitle(i18n("sponsor")); - sponsorItem.setLeftGraphic(wrap(SVG.handHearOutline(null, 20, 20))); - sponsorItem.setActionButtonVisible(false); - sponsorItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(sponsorTab)); - sponsorItem.setOnAction(e -> tab.getSelectionModel().select(sponsorTab)); - - AdvancedListItem aboutItem = new AdvancedListItem(); - aboutItem.getStyleClass().add("navigation-drawer-item"); - aboutItem.setTitle(i18n("about")); - aboutItem.setLeftGraphic(wrap(SVG.informationOutline(null, 20, 20))); - aboutItem.setActionButtonVisible(false); - aboutItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(aboutTab)); - aboutItem.setOnAction(e -> tab.getSelectionModel().select(aboutTab)); - AdvancedListBox sideBar = new AdvancedListBox() - .add(settingsItem) - .add(helpItem) - .add(sponsorItem) - .add(aboutItem); + .addNavigationDrawerItem(settingsItem -> { + settingsItem.setTitle(i18n("settings.launcher")); + settingsItem.setLeftGraphic(wrap(SVG.gearOutline(null, 20, 20))); + settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(settingsTab)); + settingsItem.setOnAction(e -> tab.getSelectionModel().select(settingsTab)); + }) + .addNavigationDrawerItem(helpItem -> { + helpItem.setTitle(i18n("help")); + helpItem.setLeftGraphic(wrap(SVG.helpCircleOutline(null, 20, 20))); + helpItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(helpTab)); + helpItem.setOnAction(e -> tab.getSelectionModel().select(helpTab)); + }) + .addNavigationDrawerItem(sponsorItem -> { + sponsorItem.setTitle(i18n("sponsor")); + sponsorItem.setLeftGraphic(wrap(SVG.handHearOutline(null, 20, 20))); + sponsorItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(sponsorTab)); + sponsorItem.setOnAction(e -> tab.getSelectionModel().select(sponsorTab)); + }) + .addNavigationDrawerItem(aboutItem -> { + aboutItem.setTitle(i18n("about")); + aboutItem.setLeftGraphic(wrap(SVG.informationOutline(null, 20, 20))); + aboutItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(aboutTab)); + aboutItem.setOnAction(e -> tab.getSelectionModel().select(aboutTab)); + }); FXUtils.setLimitWidth(sideBar, 200); setLeft(sideBar); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java index 739e8fd80..1670695e1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java @@ -34,7 +34,6 @@ import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.account.AccountAdvancedListItem; -import org.jackhuang.hmcl.ui.account.AccountList; import org.jackhuang.hmcl.ui.account.AddAccountPane; import org.jackhuang.hmcl.ui.construct.AdvancedListBox; import org.jackhuang.hmcl.ui.construct.AdvancedListItem; @@ -61,29 +60,27 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class RootPage extends DecoratorTabPage { private MainPage mainPage = null; - private SettingsPage settingsPage = null; - private AccountList accountListPage = null; private final TabHeader.Tab mainTab = new TabHeader.Tab<>("main"); - private final TabHeader.Tab accountTab = new TabHeader.Tab<>("account"); public RootPage() { setLeftPaneWidth(200); - EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(event -> onRefreshedVersions((HMCLGameRepository) event.getSource())); + EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class) + .register(event -> onRefreshedVersions((HMCLGameRepository) event.getSource())); Profile profile = Profiles.getSelectedProfile(); if (profile != null && profile.getRepository().isLoaded()) onRefreshedVersions(Profiles.selectedProfileProperty().get().getRepository()); mainTab.setNodeSupplier(this::getMainPage); - accountTab.setNodeSupplier(this::getAccountListPage); - getTabs().setAll(mainTab, accountTab); + getTabs().setAll(mainTab); } @Override public boolean back() { - if (mainTab.isSelected()) return true; + if (mainTab.isSelected()) + return true; else { getSelectionModel().select(mainTab); return false; @@ -107,20 +104,23 @@ public class RootPage extends DecoratorTabPage { MainPage mainPage = new MainPage(); FXUtils.applyDragListener(mainPage, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> { File modpack = modpacks.get(0); - Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack), i18n("install.modpack")); + Controllers.getDecorator().startWizard( + new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack), + i18n("install.modpack")); }); FXUtils.onChangeAndOperate(Profiles.selectedVersionProperty(), mainPage::setCurrentGame); mainPage.showUpdateProperty().bind(UpdateChecker.outdatedProperty()); - mainPage.latestVersionProperty().bind( - BindingMapping.of(UpdateChecker.latestVersionProperty()) - .map(version -> version == null ? "" : i18n("update.bubble.title", version.getVersion()))); + mainPage.latestVersionProperty().bind(BindingMapping.of(UpdateChecker.latestVersionProperty()) + .map(version -> version == null ? "" : i18n("update.bubble.title", version.getVersion()))); Profiles.registerVersionsListener(profile -> { HMCLGameRepository repository = profile.getRepository(); List children = repository.getVersions().parallelStream() .filter(version -> !version.isHidden()) - .sorted(Comparator.comparing((Version version) -> version.getReleaseTime() == null ? new Date(0L) : version.getReleaseTime()) + .sorted(Comparator + .comparing((Version version) -> version.getReleaseTime() == null ? new Date(0L) + : version.getReleaseTime()) .thenComparing(a -> VersionNumber.asVersion(a.getId()))) .collect(Collectors.toList()); runInFX(() -> { @@ -133,42 +133,6 @@ public class RootPage extends DecoratorTabPage { return mainPage; } - private SettingsPage getSettingsPage() { - if (settingsPage == null) - settingsPage = new SettingsPage(); - return settingsPage; - } - - private AccountList getAccountListPage() { - if (accountListPage == null) { - accountListPage = new AccountList(); - accountListPage.selectedAccountProperty().bindBidirectional(Accounts.selectedAccountProperty()); - accountListPage.accountsProperty().bindContent(Accounts.accountsProperty()); - } - return accountListPage; - } - - public Tab getMainTab() { - return mainTab; - } - - public Tab getAccountTab() { - return accountTab; - } - - /** - * @return true if the tab is being opened, or false if the tab is being closed - */ - private boolean selectPage(Tab tab) { - if (getSelectionModel().getSelectedItem() == tab) { - getSelectionModel().select(getMainTab()); - return false; - } else { - getSelectionModel().select(tab); - return true; - } - } - private static class Skin extends SkinBase { protected Skin(RootPage control) { @@ -176,13 +140,11 @@ public class RootPage extends DecoratorTabPage { // first item in left sidebar AccountAdvancedListItem accountListItem = new AccountAdvancedListItem(); - accountListItem.activeProperty().bind(control.accountTab.selectedProperty()); accountListItem.setOnAction(e -> { - if (control.selectPage(control.accountTab)) { - // open create account dialog if no account exists - if (Accounts.getAccounts().isEmpty()) { - Controllers.dialog(new AddAccountPane()); - } + Controllers.navigate(Controllers.getAccountListPage()); + + if (Accounts.getAccounts().isEmpty()) { + Controllers.dialog(new AddAccountPane()); } }); accountListItem.accountProperty().bind(Accounts.selectedAccountProperty()); @@ -207,20 +169,16 @@ public class RootPage extends DecoratorTabPage { // fifth item in left sidebar AdvancedListItem launcherSettingsItem = new AdvancedListItem(); - launcherSettingsItem.setLeftGraphic(AdvancedListItem.createImageView(newImage("/assets/img/command.png")).getKey()); + launcherSettingsItem + .setLeftGraphic(AdvancedListItem.createImageView(newImage("/assets/img/command.png")).getKey()); launcherSettingsItem.setActionButtonVisible(false); launcherSettingsItem.setTitle(i18n("settings.launcher")); launcherSettingsItem.setOnAction(e -> Controllers.navigate(Controllers.getSettingsPage())); // the left sidebar - AdvancedListBox sideBar = new AdvancedListBox() - .startCategory(i18n("account").toUpperCase()) - .add(accountListItem) - .startCategory(i18n("version").toUpperCase()) - .add(gameListItem) - .add(gameItem) - .startCategory(i18n("launcher").toUpperCase()) - .add(launcherSettingsItem); + AdvancedListBox sideBar = new AdvancedListBox().startCategory(i18n("account").toUpperCase()) + .add(accountListItem).startCategory(i18n("version").toUpperCase()).add(gameListItem).add(gameItem) + .startCategory(i18n("launcher").toUpperCase()).add(launcherSettingsItem); // the root page, with the sidebar in left, navigator in center. BorderPane root = new BorderPane(); @@ -244,7 +202,8 @@ public class RootPage extends DecoratorTabPage { private boolean checkedAccont = false; public void checkAccount() { - if (checkedAccont) return; + if (checkedAccont) + return; checkedAccont = true; if (Accounts.getAccounts().isEmpty()) Platform.runLater(this::addNewAccount); @@ -266,8 +225,11 @@ public class RootPage extends DecoratorTabPage { File modpackFile = new File("modpack.zip").getAbsoluteFile(); if (modpackFile.exists()) { Task.supplyAsync(() -> CompressingUtils.findSuitableEncoding(modpackFile.toPath())) - .thenApplyAsync(encoding -> ModpackHelper.readModpackManifest(modpackFile.toPath(), encoding)) - .thenApplyAsync(modpack -> ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack) + .thenApplyAsync( + encoding -> ModpackHelper.readModpackManifest(modpackFile.toPath(), encoding)) + .thenApplyAsync(modpack -> ModpackHelper + .getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), + modpack) .withRunAsync(Schedulers.javafx(), this::checkAccount).executor()) .thenAcceptAsync(Schedulers.javafx(), executor -> { Controllers.taskDialog(executor, i18n("modpack.installing")); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java index cc7c42bc6..e5d23d17f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListPage.java @@ -244,7 +244,7 @@ public class GameListPage extends ListPageBase implements Decorato public GameListSkin() { super(GameList.this); - state.set(new State(i18n("version.manage"), null, true, false, true, 200)); + state.set(State.fromTitle(i18n("version.manage"), 200)); } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java index 309663d48..19e7b30a7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java @@ -388,7 +388,7 @@ public class VersionPage extends Control implements DecoratorPage, ModDownloadPa } control.state.bind(Bindings.createObjectBinding(() -> - new State(i18n("version.manage.manage.title", getSkinnable().getVersion()), null, true, false, true, 200), + State.fromTitle(i18n("version.manage.manage.title", getSkinnable().getVersion()), 200), getSkinnable().version)); //control.transitionPane.getStyleClass().add("gray-background"); diff --git a/HMCL/src/main/resources/assets/img/red_lnn.jpg b/HMCL/src/main/resources/assets/img/red_lnn.jpg new file mode 100644 index 0000000000000000000000000000000000000000..264cbf04296717224882264160d7548edbf842fa GIT binary patch literal 23469 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 literal 0 HcmV?d00001