diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 6f0e014c4..6a3a65dc9 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -9,19 +9,21 @@ jobs: steps: - uses: actions/checkout@v2 + - name: Checkout submodules + run: git submodule update --init --recursive - name: Set up JDK 1.8 uses: actions/setup-java@v1 with: java-version: 1.8 java-package: jdk+fx - - name: Check style main - run: ./gradlew checkstyleMain - - name: Check style test - run: ./gradlew checkstyleTest - name: Build with Gradle run: ./gradlew build - name: Upload Artifacts uses: actions/upload-artifact@v2 with: name: HMCL - path: HMCL/build/libs \ No newline at end of file + path: HMCL/build/libs + - name: Check style main + run: ./gradlew checkstyleMain + - name: Check style test + run: ./gradlew checkstyleTest diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..c96b42156 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "JSTUN"] + path = JSTUN + url = https://github.com/huanghongxun/JSTUN diff --git a/HMCL/build.gradle b/HMCL/build.gradle index be822d9f7..714294810 100644 --- a/HMCL/build.gradle +++ b/HMCL/build.gradle @@ -44,6 +44,7 @@ mainClassName = 'org.jackhuang.hmcl.Main' dependencies { implementation project(":HMCLCore") + implementation project(":JSTUN") implementation rootProject.files("lib/JFoenix.jar") } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java index 91beb47db..8688afbe3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java @@ -365,6 +365,8 @@ public final class Accounts { } else { return i18n("account.methods.microsoft.error.unknown", errorCode); } + } else if (exception instanceof MicrosoftService.NoMinecraftJavaEditionProfileException) { + return i18n("account.methods.microsoft.error.no_character"); } else if (exception.getClass() == AuthenticationException.class) { return exception.getLocalizedMessage(); } else { 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 ea0651887..6a77deabd 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -36,7 +36,6 @@ 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; import org.jackhuang.hmcl.ui.construct.MessageDialogPane; @@ -48,6 +47,7 @@ import org.jackhuang.hmcl.ui.download.DownloadPage; import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider; import org.jackhuang.hmcl.ui.main.LauncherSettingsPage; import org.jackhuang.hmcl.ui.main.RootPage; +import org.jackhuang.hmcl.ui.multiplayer.MultiplayerPage; import org.jackhuang.hmcl.ui.versions.GameListPage; import org.jackhuang.hmcl.ui.versions.ModDownloadListPage; import org.jackhuang.hmcl.ui.versions.VersionPage; @@ -84,7 +84,6 @@ public final class Controllers { }); return gameListPage; }); - private static AuthlibInjectorServersPage serversPage = null; private static Lazy rootPage = new Lazy<>(RootPage::new); private static DecoratorController decorator; private static Lazy modDownloadListPage = new Lazy<>(() -> { @@ -99,8 +98,10 @@ public final class Controllers { AccountListPage accountListPage = new AccountListPage(); accountListPage.selectedAccountProperty().bindBidirectional(Accounts.selectedAccountProperty()); accountListPage.accountsProperty().bindContent(Accounts.accountsProperty()); + accountListPage.authServersProperty().bindContentBidirectional(config().getAuthlibInjectorServers()); return accountListPage; }); + private static Lazy multiplayerPage = new Lazy<>(MultiplayerPage::new); private static Lazy settingsPage = new Lazy<>(LauncherSettingsPage::new); private Controllers() { @@ -130,15 +131,13 @@ public final class Controllers { } // FXThread - public static AuthlibInjectorServersPage getServersPage() { - if (serversPage == null) - serversPage = new AuthlibInjectorServersPage(); - return serversPage; + public static ModDownloadListPage getModpackDownloadListPage() { + return modDownloadListPage.get(); } // FXThread - public static ModDownloadListPage getModpackDownloadListPage() { - return modDownloadListPage.get(); + public static MultiplayerPage getMultiplayerPage() { + return multiplayerPage.get(); } // FXThread @@ -280,7 +279,6 @@ public final class Controllers { public static void shutdown() { rootPage = null; versionPage = null; - serversPage = null; gameListPage = null; settingsPage = null; modDownloadListPage = null; 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 54465b9a5..2c5b98f51 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -59,6 +59,7 @@ import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.URI; +import java.nio.file.Path; import java.util.List; import java.util.Objects; import java.util.function.BooleanSupplier; @@ -363,6 +364,21 @@ public final class FXUtils { } } + public static void showFileInExplorer(Path file) { + switch (OperatingSystem.CURRENT_OS) { + case WINDOWS: + try { + Runtime.getRuntime().exec(new String[]{"explorer.exe", "/select,", file.toAbsolutePath().toString()}); + } catch (IOException e) { + Logging.LOG.log(Level.SEVERE, "Unable to open " + file + " by executing explorer /select", e); + } + break; + default: + // Currently unsupported. + break; + } + } + private static final String[] linuxBrowsers = { "xdg-open", "google-chrome", 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 5273d9765..3e3d56393 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java @@ -391,6 +391,12 @@ public final class SVG { fill, width, height); } + public static Node accountArrowRightOutline(ObjectBinding fill, double width, double height) { + return createSVGPath( + "M19,21V19H15V17H19V15L22,18L19,21M13,18C13,18.71 13.15,19.39 13.42,20H2V17C2,14.79 5.58,13 10,13C11,13 11.96,13.09 12.85,13.26C13.68,13.42 14.44,13.64 15.11,13.92C13.83,14.83 13,16.32 13,18M4,17V18H11C11,16.96 11.23,15.97 11.64,15.08L10,15C6.69,15 4,15.9 4,17M10,4A4,4 0 0,1 14,8A4,4 0 0,1 10,12A4,4 0 0,1 6,8A4,4 0 0,1 10,4M10,6A2,2 0 0,0 8,8A2,2 0 0,0 10,10A2,2 0 0,0 12,8A2,2 0 0,0 10,6Z", + 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", @@ -432,4 +438,10 @@ public final class SVG { "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,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z", fill, width, height); } + + public static Node server(ObjectBinding fill, double width, double height) { + return createSVGPath( + "M13,19H14A1,1 0 0,1 15,20H22V22H15A1,1 0 0,1 14,23H10A1,1 0 0,1 9,22H2V20H9A1,1 0 0,1 10,19H11V17H4A1,1 0 0,1 3,16V12A1,1 0 0,1 4,11H20A1,1 0 0,1 21,12V16A1,1 0 0,1 20,17H13V19M4,3H20A1,1 0 0,1 21,4V8A1,1 0 0,1 20,9H4A1,1 0 0,1 3,8V4A1,1 0 0,1 4,3M9,7H10V5H9V7M9,15H10V13H9V15M5,5V7H7V5H5M5,13V15H7V13H5Z", + fill, width, height); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java index f398060ab..0033552bd 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ToolbarListPageSkin.java @@ -99,6 +99,15 @@ public abstract class ToolbarListPageSkin return ret; } + public static JFXButton createToolbarButton2(String text, SVG.SVGIcon creator, Runnable onClick) { + JFXButton ret = new JFXButton(); + ret.getStyleClass().add("jfx-tool-bar-button"); + ret.setGraphic(wrap(creator.createIcon(Theme.blackFillBinding(), -1, -1))); + ret.setText(text); + ret.setOnMouseClicked(e -> onClick.run()); + return ret; + } + public static JFXButton createDecoratorButton(String tooltip, SVG.SVGIcon creator, Runnable onClick) { JFXButton ret = new JFXButton(); ret.getStyleClass().add("jfx-decorator-button"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java index 9a4af5470..b84d29583 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java @@ -90,6 +90,7 @@ public class AccountListItemSkin extends SkinBase { JFXButton btnRefresh = new JFXButton(); SpinnerPane spinnerRefresh = new SpinnerPane(); + spinnerRefresh.getStyleClass().setAll("small-spinner-pane"); btnRefresh.setOnMouseClicked(e -> { spinnerRefresh.showSpinner(); skinnable.refreshAsync() 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 index 3885df38e..28c307b6c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListPage.java @@ -17,29 +17,44 @@ */ package org.jackhuang.hmcl.ui.account; +import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXScrollPane; import javafx.beans.binding.Bindings; import javafx.beans.property.*; +import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Insets; import javafx.scene.control.ScrollPane; import javafx.scene.control.Skin; import javafx.scene.control.SkinBase; +import javafx.scene.control.Tooltip; import javafx.scene.layout.BorderPane; import javafx.scene.layout.VBox; import org.jackhuang.hmcl.auth.Account; +import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; import org.jackhuang.hmcl.setting.Accounts; -import org.jackhuang.hmcl.ui.*; -import org.jackhuang.hmcl.ui.construct.AdvancedListBox; +import org.jackhuang.hmcl.setting.Theme; +import org.jackhuang.hmcl.ui.Controllers; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.ListPageBase; +import org.jackhuang.hmcl.ui.SVG; +import org.jackhuang.hmcl.ui.construct.AdvancedListItem; +import org.jackhuang.hmcl.ui.construct.ClassTitle; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; +import org.jackhuang.hmcl.util.javafx.BindingMapping; 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; +import java.net.URI; + public class AccountListPage extends ListPageBase implements DecoratorPage { private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("account.manage"), -1)); private final ListProperty accounts = new SimpleListProperty<>(this, "accounts", FXCollections.observableArrayList()); + private final ListProperty authServers = new SimpleListProperty<>(this, "authServers", FXCollections.observableArrayList()); private final ObjectProperty selectedAccount; public AccountListPage() { @@ -60,47 +75,109 @@ public class AccountListPage extends ListPageBase implements De return state.getReadOnlyProperty(); } + public ListProperty authServersProperty() { + return authServers; + } + @Override protected Skin createDefaultSkin() { return new AccountListPageSkin(this); } private static class AccountListPageSkin extends SkinBase { + + private final ObservableList authServerItems; + public AccountListPageSkin(AccountListPage skinnable) { super(skinnable); BorderPane root = new BorderPane(); { - AdvancedListBox sideBar = new AdvancedListBox() - .startCategory(i18n("account.create")) - .addNavigationDrawerItem(settingsItem -> { - settingsItem.setTitle(i18n("account.methods.offline")); - settingsItem.setLeftGraphic(wrap(SVG.account(null, 20, 20))); - settingsItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_OFFLINE))); - }) - .addNavigationDrawerItem(settingsItem -> { - settingsItem.setTitle(i18n("account.methods.yggdrasil")); - settingsItem.setLeftGraphic(wrap(SVG.mojang(null, 20, 20))); - settingsItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_MOJANG))); - }) - .addNavigationDrawerItem(settingsItem -> { - settingsItem.setTitle(i18n("account.methods.microsoft")); - settingsItem.setLeftGraphic(wrap(SVG.microsoft(null, 20, 20))); - settingsItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_MICROSOFT))); - }) - .addNavigationDrawerItem(settingsItem -> { - settingsItem.setTitle(i18n("account.methods.authlib_injector")); - settingsItem.setLeftGraphic(wrap(SVG.gear(null, 20, 20))); - settingsItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_AUTHLIB_INJECTOR))); - }) - .addNavigationDrawerItem(settingsItem -> { - settingsItem.setTitle(i18n("account.create")); - settingsItem.setLeftGraphic(wrap(SVG.plus(null, 20, 20))); - settingsItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane())); + BorderPane left = new BorderPane(); + FXUtils.setLimitWidth(left, 200); + + { + VBox boxItemList = new VBox(); + boxItemList.getStyleClass().add("advanced-list-box-content"); + + boxItemList.getChildren().add(new ClassTitle(i18n("account.create"))); + + { + VBox boxMethods = new VBox(); + FXUtils.setLimitWidth(boxMethods, 200); + + AdvancedListItem offlineItem = new AdvancedListItem(); + offlineItem.getStyleClass().add("navigation-drawer-item"); + offlineItem.setActionButtonVisible(false); + offlineItem.setTitle(i18n("account.methods.offline")); + offlineItem.setLeftGraphic(wrap(SVG.account(Theme.blackFillBinding(), 24, 24))); + offlineItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_OFFLINE))); + boxMethods.getChildren().add(offlineItem); + + AdvancedListItem mojangItem = new AdvancedListItem(); + mojangItem.getStyleClass().add("navigation-drawer-item"); + mojangItem.setActionButtonVisible(false); + mojangItem.setTitle(i18n("account.methods.yggdrasil")); + mojangItem.setLeftGraphic(wrap(SVG.mojang(Theme.blackFillBinding(), 24, 24))); + mojangItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_MOJANG))); + boxMethods.getChildren().add(mojangItem); + + AdvancedListItem microsoftItem = new AdvancedListItem(); + microsoftItem.getStyleClass().add("navigation-drawer-item"); + microsoftItem.setActionButtonVisible(false); + microsoftItem.setTitle(i18n("account.methods.microsoft")); + microsoftItem.setLeftGraphic(wrap(SVG.microsoft(Theme.blackFillBinding(), 24, 24))); + microsoftItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_MICROSOFT))); + boxMethods.getChildren().add(microsoftItem); + + VBox boxAuthServers = new VBox(); + authServerItems = MappedObservableList.create(skinnable.authServersProperty(), server -> { + AdvancedListItem item = new AdvancedListItem(); + item.getStyleClass().add("navigation-drawer-item"); + item.setLeftGraphic(wrap(SVG.server(Theme.blackFillBinding(), 24, 24))); + item.setOnAction(e -> Controllers.dialog(new CreateAccountPane(server))); + + JFXButton btnRemove = new JFXButton(); + btnRemove.setOnAction(e -> { + skinnable.authServersProperty().remove(server); + e.consume(); + }); + btnRemove.getStyleClass().add("toggle-icon4"); + btnRemove.setGraphic(SVG.close(Theme.blackFillBinding(), 14, 14)); + item.setRightGraphic(btnRemove); + + ObservableValue title = BindingMapping.of(server, AuthlibInjectorServer::getName); + item.titleProperty().bind(title); + item.subtitleProperty().set(URI.create(server.getUrl()).getHost()); + Tooltip tooltip = new Tooltip(); + tooltip.textProperty().bind(Bindings.format("%s (%s)", title, server.getUrl())); + FXUtils.installFastTooltip(item, tooltip); + + return item; }); - FXUtils.setLimitWidth(sideBar, 200); - root.setLeft(sideBar); + Bindings.bindContent(boxAuthServers.getChildren(), authServerItems); + boxMethods.getChildren().add(boxAuthServers); + + boxItemList.getChildren().add(new ScrollPane(boxMethods)); + } + + left.setCenter(boxItemList); + } + + { + AdvancedListItem addAuthServerItem = new AdvancedListItem(); + addAuthServerItem.getStyleClass().add("navigation-drawer-item"); + addAuthServerItem.setTitle(i18n("account.injector.add")); + addAuthServerItem.setSubtitle(i18n("account.methods.authlib_injector")); + addAuthServerItem.setActionButtonVisible(false); + addAuthServerItem.setLeftGraphic(wrap(SVG.plusCircleOutline(Theme.blackFillBinding(), 24, 24))); + addAuthServerItem.setOnAction(e -> Controllers.dialog(new AddAuthlibInjectorServerPane())); + BorderPane.setMargin(addAuthServerItem, new Insets(0, 0, 12, 0)); + left.setBottom(addAuthServerItem); + } + + root.setLeft(left); } ScrollPane scrollPane = new ScrollPane(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAuthlibInjectorServerPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAuthlibInjectorServerPane.java index 59d05cd72..d59e2c4eb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAuthlibInjectorServerPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAuthlibInjectorServerPane.java @@ -132,7 +132,7 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa @FXML private void onAddFinish() { if (!config().getAuthlibInjectorServers().contains(serverBeingAdded)) { - config().getAuthlibInjectorServers().add(0, serverBeingAdded); + config().getAuthlibInjectorServers().add(serverBeingAdded); } fireEvent(new DialogCloseEvent()); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AuthlibInjectorServerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AuthlibInjectorServerItem.java deleted file mode 100644 index ebae6ad26..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AuthlibInjectorServerItem.java +++ /dev/null @@ -1,67 +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 com.jfoenix.controls.JFXButton; -import com.jfoenix.effects.JFXDepthManager; - -import javafx.beans.binding.Bindings; -import javafx.geometry.Pos; -import javafx.scene.control.Label; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.VBox; -import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; -import org.jackhuang.hmcl.setting.Theme; -import org.jackhuang.hmcl.ui.SVG; - -import java.util.function.Consumer; - -public final class AuthlibInjectorServerItem extends BorderPane { - private final AuthlibInjectorServer server; - - private final Label lblServerName = new Label(); - private final Label lblServerUrl = new Label(); - - public AuthlibInjectorServerItem(AuthlibInjectorServer server, Consumer deleteCallback) { - this.server = server; - - lblServerName.setStyle("-fx-font-size: 15;"); - lblServerUrl.setStyle("-fx-font-size: 10;"); - - VBox center = new VBox(); - BorderPane.setAlignment(center, Pos.CENTER); - center.getChildren().addAll(lblServerName, lblServerUrl); - setCenter(center); - - JFXButton right = new JFXButton(); - right.setOnMouseClicked(e -> deleteCallback.accept(this)); - right.getStyleClass().add("toggle-icon4"); - BorderPane.setAlignment(right, Pos.CENTER); - right.setGraphic(SVG.close(Theme.blackFillBinding(), 15, 15)); - setRight(right); - - setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;"); - JFXDepthManager.setDepth(this, 1); - lblServerName.textProperty().bind(Bindings.createStringBinding(server::getName, server)); - lblServerUrl.setText(server.getUrl()); - } - - public AuthlibInjectorServer getServer() { - return server; - } -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AuthlibInjectorServersPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AuthlibInjectorServersPage.java deleted file mode 100644 index d10aa329e..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AuthlibInjectorServersPage.java +++ /dev/null @@ -1,56 +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.binding.Bindings; -import javafx.beans.property.ReadOnlyObjectWrapper; -import javafx.collections.ObservableList; -import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; -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.setting.ConfigHolder.config; -import static org.jackhuang.hmcl.util.i18n.I18n.i18n; - -public class AuthlibInjectorServersPage extends ListPage implements DecoratorPage { - private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("account.injector.manage.title"))); - - private final ObservableList serverItems; - - public AuthlibInjectorServersPage() { - serverItems = MappedObservableList.create(config().getAuthlibInjectorServers(), this::createServerItem); - Bindings.bindContent(itemsProperty(), serverItems); - } - - private AuthlibInjectorServerItem createServerItem(AuthlibInjectorServer server) { - return new AuthlibInjectorServerItem(server, - item -> config().getAuthlibInjectorServers().remove(item.getServer())); - } - - @Override - public void add() { - Controllers.dialog(new AddAuthlibInjectorServerPane()); - } - - @Override - public ReadOnlyObjectWrapper stateProperty() { - return state; - } -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java index 9d68190d3..a3186791e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java @@ -17,26 +17,17 @@ */ package org.jackhuang.hmcl.ui.account; -import static java.util.Collections.emptyList; -import static java.util.Collections.unmodifiableList; -import static javafx.beans.binding.Bindings.bindContent; -import static javafx.beans.binding.Bindings.createBooleanBinding; -import static org.jackhuang.hmcl.setting.ConfigHolder.config; -import static org.jackhuang.hmcl.ui.FXUtils.jfxListCellFactory; -import static org.jackhuang.hmcl.ui.FXUtils.onChange; -import static org.jackhuang.hmcl.ui.FXUtils.onChangeAndOperate; -import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; -import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating; -import static org.jackhuang.hmcl.ui.FXUtils.setValidateWhileTextChanged; -import static org.jackhuang.hmcl.ui.FXUtils.stringConverter; -import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.classPropertyFor; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CountDownLatch; - +import com.jfoenix.controls.*; +import javafx.application.Platform; +import javafx.beans.binding.BooleanBinding; +import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Hyperlink; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.*; import org.jackhuang.hmcl.auth.AccountFactory; import org.jackhuang.hmcl.auth.CharacterSelector; import org.jackhuang.hmcl.auth.NoSelectedCharacterException; @@ -47,48 +38,31 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService; import org.jackhuang.hmcl.game.TexturesLoader; import org.jackhuang.hmcl.setting.Accounts; +import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.TaskExecutor; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; -import org.jackhuang.hmcl.ui.construct.AdvancedListBox; -import org.jackhuang.hmcl.ui.construct.DialogCloseEvent; -import org.jackhuang.hmcl.ui.construct.IconedItem; -import org.jackhuang.hmcl.ui.construct.RequiredValidator; -import org.jackhuang.hmcl.ui.construct.SpinnerPane; -import org.jackhuang.hmcl.ui.construct.TabControl; -import org.jackhuang.hmcl.ui.construct.TabHeader; -import org.jackhuang.hmcl.ui.construct.TwoLineListItem; -import org.jackhuang.hmcl.ui.construct.Validator; +import org.jackhuang.hmcl.ui.construct.*; import org.jetbrains.annotations.Nullable; -import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.JFXComboBox; -import com.jfoenix.controls.JFXDialogLayout; -import com.jfoenix.controls.JFXPasswordField; -import com.jfoenix.controls.JFXTextField; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; -import javafx.application.Platform; -import javafx.beans.binding.BooleanBinding; -import javafx.geometry.HPos; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.control.Hyperlink; -import javafx.scene.control.Label; -import javafx.scene.image.ImageView; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.ColumnConstraints; -import javafx.scene.layout.GridPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Pane; -import javafx.scene.layout.Priority; -import javafx.scene.layout.StackPane; -import javafx.scene.layout.VBox; +import static java.util.Collections.emptyList; +import static java.util.Collections.unmodifiableList; +import static javafx.beans.binding.Bindings.bindContent; +import static javafx.beans.binding.Bindings.createBooleanBinding; +import static org.jackhuang.hmcl.setting.ConfigHolder.config; +import static org.jackhuang.hmcl.ui.FXUtils.*; +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.classPropertyFor; -public class CreateAccountPane extends JFXDialogLayout { +public class CreateAccountPane extends JFXDialogLayout implements DialogAware { private boolean showMethodSwitcher; private AccountFactory factory; @@ -105,7 +79,7 @@ public class CreateAccountPane extends JFXDialogLayout { private TaskExecutor loginTask; public CreateAccountPane() { - this(null); + this((AccountFactory) null); } public CreateAccountPane(AccountFactory factory) { @@ -197,6 +171,11 @@ public class CreateAccountPane extends JFXDialogLayout { setPrefWidth(560); } + public CreateAccountPane(AuthlibInjectorServer authserver) { + this(Accounts.FACTORY_AUTHLIB_INJECTOR); + ((AccountDetailsInputPane) detailsPane).selectAuthServer(authserver); + } + private void onAccept() { spinner.showSpinner(); lblErrorMessage.setText(""); @@ -216,7 +195,7 @@ public class CreateAccountPane extends JFXDialogLayout { additionalData = null; } - loginTask = Task.supplyAsync(() -> factory.create(new DialogCharacterSelector(), username, password, additionalData)) + loginTask = Task.supplyAsync(() -> factory.create(new DialogCharacterSelector(), username, password, null, additionalData)) .whenComplete(Schedulers.javafx(), account -> { int oldIndex = Accounts.getAccounts().indexOf(account); if (oldIndex == -1) { @@ -303,7 +282,7 @@ public class CreateAccountPane extends JFXDialogLayout { public AccountDetailsInputPane(AccountFactory factory, Runnable onAction) { this.factory = factory; - setVgap(15); + setVgap(22); setHgap(15); setAlignment(Pos.CENTER); @@ -334,30 +313,22 @@ public class CreateAccountPane extends JFXDialogLayout { classPropertyFor(cboServers, "jfx-combo-box-warning").bind(noServers); classPropertyFor(cboServers, "jfx-combo-box").bind(noServers.not()); HBox.setHgrow(cboServers, Priority.ALWAYS); + HBox.setMargin(cboServers, new Insets(0, 10, 0, 0)); cboServers.setMaxWidth(Double.MAX_VALUE); HBox linksContainer = new HBox(); linksContainer.setAlignment(Pos.CENTER); - linksContainer.setPadding(new Insets(0, 5, 0, 15)); onChangeAndOperate(cboServers.valueProperty(), server -> linksContainer.getChildren().setAll(createHyperlinks(server))); linksContainer.setMinWidth(USE_PREF_SIZE); JFXButton btnAddServer = new JFXButton(); - btnAddServer.setGraphic(SVG.plus(null, 20, 20)); + btnAddServer.setGraphic(SVG.plus(Theme.blackFillBinding(), 20, 20)); btnAddServer.getStyleClass().add("toggle-icon4"); btnAddServer.setOnAction(e -> { Controllers.dialog(new AddAuthlibInjectorServerPane()); }); - JFXButton btnManageServers = new JFXButton(); - btnManageServers.setGraphic(SVG.gear(null, 20, 20)); - btnManageServers.getStyleClass().add("toggle-icon4"); - btnManageServers.setOnAction(e -> { - fireEvent(new DialogCloseEvent()); - Controllers.navigate(Controllers.getServersPage()); - }); - - HBox boxServers = new HBox(cboServers, linksContainer, btnAddServer, btnManageServers); + HBox boxServers = new HBox(cboServers, linksContainer, btnAddServer); add(boxServers, 1, rowIndex); rowIndex++; @@ -449,6 +420,16 @@ public class CreateAccountPane extends JFXDialogLayout { public BooleanBinding validProperty() { return valid; } + + public void selectAuthServer(AuthlibInjectorServer authserver) { + cboServers.getSelectionModel().select(authserver); + } + + public void focus() { + if (txtUsername != null) { + txtUsername.requestFocus(); + } + } } private static class DialogCharacterSelector extends BorderPane implements CharacterSelector { @@ -511,4 +492,11 @@ public class CreateAccountPane extends JFXDialogLayout { } } } + + @Override + public void onDialogShown() { + if (detailsPane instanceof AccountDetailsInputPane) { + ((AccountDetailsInputPane) detailsPane).focus(); + } + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListItemSkin.java index 074a73bd1..148e74291 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/AdvancedListItemSkin.java @@ -62,7 +62,6 @@ public class AdvancedListItemSkin extends SkinBase { HBox right = new HBox(); right.setAlignment(Pos.CENTER); - right.setMouseTransparent(true); right.getStyleClass().add("toggle-icon4"); FXUtils.setLimitWidth(right, 40); FXUtils.onChangeAndOperate(skinnable.rightGraphicProperty(), diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java index 5e6b93f66..9c6d04875 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java @@ -34,6 +34,7 @@ import javafx.scene.control.Control; import javafx.scene.control.Label; import javafx.scene.control.SkinBase; import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import org.jackhuang.hmcl.util.javafx.MappedObservableList; @@ -132,6 +133,9 @@ public class ComponentList extends Control { list = MappedObservableList.create(control.getContent(), node -> { ComponentListCell cell = new ComponentListCell(node); cell.getStyleClass().add("options-list-item"); + if (node.getProperties().containsKey("ComponentList.vgrow")) { + VBox.setVgrow(cell, (Priority) node.getProperties().get("ComponentList.vgrow")); + } return cell; }); @@ -172,4 +176,8 @@ public class ComponentList extends Control { } return node; } + + public static void setVgrow(Node node, Priority priority) { + node.getProperties().put("ComponentList.vgrow", priority); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MDListCell.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MDListCell.java new file mode 100644 index 000000000..18c262c2e --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MDListCell.java @@ -0,0 +1,62 @@ +/* + * 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.construct; + +import javafx.css.PseudoClass; +import javafx.scene.control.ListCell; +import javafx.scene.layout.StackPane; +import org.jackhuang.hmcl.ui.FXUtils; + +public abstract class MDListCell extends ListCell { + private final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected"); + + private final StackPane container = new StackPane(); + private final StackPane root = new StackPane(); + + public MDListCell() { + setText(null); + setGraphic(null); + + root.getStyleClass().add("md-list-cell"); + RipplerContainer ripplerContainer = new RipplerContainer(container); + root.getChildren().setAll(ripplerContainer); + } + + @Override + protected void updateItem(T item, boolean empty) { + super.updateItem(item, empty); + updateControl(item, empty); + if (empty) { + setGraphic(null); + } else { + setGraphic(root); + } + } + + protected StackPane getContainer() { + return container; + } + + protected void setSelectable() { + FXUtils.onChangeAndOperate(selectedProperty(), selected -> { + root.pseudoClassStateChanged(SELECTED, selected); + }); + } + + protected abstract void updateControl(T item, boolean empty); +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/SpinnerPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/SpinnerPane.java index 68671de95..0d2dd38b9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/SpinnerPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/SpinnerPane.java @@ -36,6 +36,10 @@ public class SpinnerPane extends Control { private final BooleanProperty loading = new SimpleBooleanProperty(this, "loading"); private final StringProperty failedReason = new SimpleStringProperty(this, "failedReason"); + public SpinnerPane() { + getStyleClass().add("spinner-pane"); + } + public void showSpinner() { setLoading(true); } @@ -99,7 +103,6 @@ public class SpinnerPane extends Control { protected Skin(SpinnerPane control) { super(control); - root.getStyleClass().add("spinner-pane"); topPane.getChildren().setAll(spinner); topPane.getStyleClass().add("notice-pane"); failedPane.getChildren().setAll(failedReasonLabel); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java index 2402d3a85..c06aefd41 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java @@ -24,6 +24,7 @@ import org.jackhuang.hmcl.mod.curse.CurseAddon; import org.jackhuang.hmcl.mod.curse.CurseModManager; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profiles; +import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.TaskExecutor; @@ -85,37 +86,37 @@ public class DownloadPage extends BorderPane implements DecoratorPage { AdvancedListBox sideBar = new AdvancedListBox() .addNavigationDrawerItem(item -> { item.setTitle(i18n("install.new_game")); - item.setLeftGraphic(wrap(SVG.gamepad(null, 20, 20))); + item.setLeftGraphic(wrap(SVG.gamepad(Theme.blackFillBinding(), 24, 24))); item.setOnAction(e -> Versions.addNewGame()); }) .startCategory(i18n("download")) .addNavigationDrawerItem(item -> { item.setTitle(i18n("mods")); - item.setLeftGraphic(wrap(SVG.puzzle(null, 20, 20))); + item.setLeftGraphic(wrap(SVG.puzzle(Theme.blackFillBinding(), 24, 24))); item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(modTab)); item.setOnAction(e -> tab.getSelectionModel().select(modTab)); }) .addNavigationDrawerItem(settingsItem -> { settingsItem.setTitle(i18n("modpack")); - settingsItem.setLeftGraphic(wrap(SVG.pack(null, 20, 20))); + settingsItem.setLeftGraphic(wrap(SVG.pack(Theme.blackFillBinding(), 24, 24))); settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(modpackTab)); settingsItem.setOnAction(e -> tab.getSelectionModel().select(modpackTab)); }) .addNavigationDrawerItem(item -> { item.setTitle(i18n("resourcepack")); - item.setLeftGraphic(wrap(SVG.textureBox(null, 20, 20))); + item.setLeftGraphic(wrap(SVG.textureBox(Theme.blackFillBinding(), 24, 24))); item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(resourcePackTab)); item.setOnAction(e -> tab.getSelectionModel().select(resourcePackTab)); }) // .addNavigationDrawerItem(item -> { // item.setTitle(i18n("download.curseforge.customization")); -// item.setLeftGraphic(wrap(SVG.script(null, 20, 20))); +// item.setLeftGraphic(wrap(SVG.script(Theme.blackFillBinding(), 24, 24))); // item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(customizationTab)); // item.setOnAction(e -> tab.getSelectionModel().select(customizationTab)); // }) .addNavigationDrawerItem(item -> { item.setTitle(i18n("world")); - item.setLeftGraphic(wrap(SVG.earth(null, 20, 20))); + item.setLeftGraphic(wrap(SVG.earth(Theme.blackFillBinding(), 24, 24))); item.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(worldTab)); item.setOnAction(e -> tab.getSelectionModel().select(worldTab)); }); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java index d6c8faa67..875eb010b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java @@ -28,6 +28,7 @@ import javafx.scene.control.ListCell; import javafx.scene.image.Image; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; import javafx.scene.layout.StackPane; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.download.RemoteVersion; @@ -105,6 +106,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres } else { centrePane.getContent().setAll(list); } + ComponentList.setVgrow(list, Priority.ALWAYS); InvalidationListener listener = o -> list.getItems().setAll(loadVersions()); chkRelease.selectedProperty().addListener(listener); 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 747c2924e..18882e491 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 @@ -22,6 +22,7 @@ import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.scene.layout.BorderPane; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profiles; +import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.animation.ContainerAnimations; @@ -67,46 +68,46 @@ public class LauncherSettingsPage extends BorderPane implements DecoratorPage { { AdvancedListBox sideBar = new AdvancedListBox() .addNavigationDrawerItem(settingsItem -> { - settingsItem.setTitle(i18n("settings.game.current")); - settingsItem.setLeftGraphic(wrap(SVG.gamepad(null, 20, 20))); + settingsItem.setTitle(i18n("settings.type.global.manage")); + settingsItem.setLeftGraphic(wrap(SVG.gamepad(Theme.blackFillBinding(), 24, 24))); settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(gameTab)); settingsItem.setOnAction(e -> tab.getSelectionModel().select(gameTab)); }) .startCategory(i18n("launcher")) .addNavigationDrawerItem(settingsItem -> { settingsItem.setTitle(i18n("settings.launcher.general")); - settingsItem.setLeftGraphic(wrap(SVG.applicationOutline(null, 20, 20))); + settingsItem.setLeftGraphic(wrap(SVG.applicationOutline(Theme.blackFillBinding(), 24, 24))); settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(settingsTab)); settingsItem.setOnAction(e -> tab.getSelectionModel().select(settingsTab)); }) .addNavigationDrawerItem(personalizationItem -> { personalizationItem.setTitle(i18n("settings.launcher.appearance")); - personalizationItem.setLeftGraphic(wrap(SVG.styleOutline(null, 20, 20))); + personalizationItem.setLeftGraphic(wrap(SVG.styleOutline(Theme.blackFillBinding(), 24, 24))); personalizationItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(personalizationTab)); personalizationItem.setOnAction(e -> tab.getSelectionModel().select(personalizationTab)); }) .addNavigationDrawerItem(downloadItem -> { downloadItem.setTitle(i18n("download")); - downloadItem.setLeftGraphic(wrap(SVG.downloadOutline(null, 20, 20))); + downloadItem.setLeftGraphic(wrap(SVG.downloadOutline(Theme.blackFillBinding(), 24, 24))); downloadItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(downloadTab)); downloadItem.setOnAction(e -> tab.getSelectionModel().select(downloadTab)); }) .startCategory(i18n("help")) .addNavigationDrawerItem(helpItem -> { helpItem.setTitle(i18n("help")); - helpItem.setLeftGraphic(wrap(SVG.helpCircleOutline(null, 20, 20))); + helpItem.setLeftGraphic(wrap(SVG.helpCircleOutline(Theme.blackFillBinding(), 24, 24))); 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.setLeftGraphic(wrap(SVG.handHearOutline(Theme.blackFillBinding(), 24, 24))); 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.setLeftGraphic(wrap(SVG.informationOutline(Theme.blackFillBinding(), 24, 24))); aboutItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(aboutTab)); aboutItem.setOnAction(e -> tab.getSelectionModel().select(aboutTab)); }); 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 d5f3c39e0..c0e17e2f2 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 @@ -17,7 +17,6 @@ */ package org.jackhuang.hmcl.ui.main; -import javafx.application.Platform; import javafx.scene.Node; import javafx.scene.control.SkinBase; import javafx.scene.layout.BorderPane; @@ -34,7 +33,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.CreateAccountPane; import org.jackhuang.hmcl.ui.construct.AdvancedListBox; import org.jackhuang.hmcl.ui.construct.AdvancedListItem; import org.jackhuang.hmcl.ui.construct.TabHeader; @@ -140,13 +138,7 @@ public class RootPage extends DecoratorTabPage { // first item in left sidebar AccountAdvancedListItem accountListItem = new AccountAdvancedListItem(); - accountListItem.setOnAction(e -> { - Controllers.navigate(Controllers.getAccountListPage()); - - if (Accounts.getAccounts().isEmpty()) { - Controllers.dialog(new CreateAccountPane()); - } - }); + accountListItem.setOnAction(e -> Controllers.navigate(Controllers.getAccountListPage())); accountListItem.accountProperty().bind(Accounts.selectedAccountProperty()); // second item in left sidebar @@ -176,6 +168,14 @@ public class RootPage extends DecoratorTabPage { downloadItem.setOnAction(e -> Controllers.navigate(Controllers.getDownloadPage())); // fifth item in left sidebar + AdvancedListItem multiplayerItem = new AdvancedListItem(); + multiplayerItem + .setLeftGraphic(AdvancedListItem.createImageView(newImage("/assets/img/command.png")).getKey()); + multiplayerItem.setActionButtonVisible(false); + multiplayerItem.setTitle(i18n("multiplayer")); + multiplayerItem.setOnAction(e -> Controllers.navigate(Controllers.getMultiplayerPage())); + + // sixth item in left sidebar AdvancedListItem launcherSettingsItem = new AdvancedListItem(); launcherSettingsItem .setLeftGraphic(AdvancedListItem.createImageView(newImage("/assets/img/command.png")).getKey()); @@ -191,6 +191,8 @@ public class RootPage extends DecoratorTabPage { .add(gameListItem) .add(gameItem) .add(downloadItem) + .startCategory(i18n("settings.launcher.general").toLowerCase()) +// .add(multiplayerItem) .add(launcherSettingsItem); // the root page, with the sidebar in left, navigator in center. @@ -210,27 +212,6 @@ public class RootPage extends DecoratorTabPage { } - // ==== Accounts ==== - - private boolean checkedAccont = false; - - public void checkAccount() { - if (checkedAccont) - return; - checkedAccont = true; - checkAccountForcibly(); - } - - public void checkAccountForcibly() { - if (Accounts.getAccounts().isEmpty()) - Platform.runLater(this::addNewAccount); - } - - private void addNewAccount() { - Controllers.dialog(new CreateAccountPane()); - } - // ==== - private boolean checkedModpack = false; private void onRefreshedVersions(HMCLGameRepository repository) { @@ -247,7 +228,7 @@ public class RootPage extends DecoratorTabPage { .thenApplyAsync(modpack -> ModpackHelper .getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack) - .withRunAsync(Schedulers.javafx(), this::checkAccount).executor()) + .executor()) .thenAcceptAsync(Schedulers.javafx(), executor -> { Controllers.taskDialog(executor, i18n("modpack.installing")); executor.start(); @@ -255,8 +236,6 @@ public class RootPage extends DecoratorTabPage { } } } - - checkAccount(); }); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsView.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsView.java index 9717de6d9..2b0645110 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsView.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsView.java @@ -69,6 +69,7 @@ public abstract class SettingsView extends StackPane { StackPane sponsorPane = new StackPane(); sponsorPane.setCursor(Cursor.HAND); sponsorPane.setOnMouseClicked(e -> onSponsor()); + sponsorPane.setPadding(new Insets(8, 0, 8, 0)); GridPane gridPane = new GridPane(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerManager.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerManager.java new file mode 100644 index 000000000..7c15f012c --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerManager.java @@ -0,0 +1,27 @@ +/* + * 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.multiplayer; + +public class MultiplayerManager { + + enum State { + DISCONNECTED, + MASTER, + SLAVE + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java new file mode 100644 index 000000000..e3edf4e58 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java @@ -0,0 +1,86 @@ +/* + * 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.multiplayer; + +import de.javawi.jstun.test.DiscoveryInfo; +import de.javawi.jstun.test.DiscoveryTest; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.control.Control; +import javafx.scene.control.Skin; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.ui.decorator.DecoratorPage; + +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public class MultiplayerPage extends Control implements DecoratorPage { + private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("multiplayer"), -1)); + + private final ObjectProperty multiplayerState = new SimpleObjectProperty<>(); + private final ReadOnlyObjectWrapper natState = new ReadOnlyObjectWrapper<>(); + + public MultiplayerPage() { + testNAT(); + } + + @Override + protected Skin createDefaultSkin() { + return new MultiplayerPageSkin(this); + } + + public MultiplayerManager.State getMultiplayerState() { + return multiplayerState.get(); + } + + public ObjectProperty multiplayerStateProperty() { + return multiplayerState; + } + + public void setMultiplayerState(MultiplayerManager.State multiplayerState) { + this.multiplayerState.set(multiplayerState); + } + + public DiscoveryInfo getNatState() { + return natState.get(); + } + + public ReadOnlyObjectProperty natStateProperty() { + return natState.getReadOnlyProperty(); + } + + public void testNAT() { + Task.supplyAsync(() -> { + DiscoveryTest tester = new DiscoveryTest(null, 0, "stun.qq.com", 3478); + return tester.test(); + }).whenComplete(Schedulers.javafx(), (info, exception) -> { + if (exception == null) { + natState.set(info); + } else { + natState.set(null); + } + }).start(); + } + + @Override + public ReadOnlyObjectProperty stateProperty() { + return state; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPageSkin.java new file mode 100644 index 000000000..dc39acc0f --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPageSkin.java @@ -0,0 +1,174 @@ +/* + * 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.multiplayer; + +import de.javawi.jstun.test.DiscoveryInfo; +import javafx.geometry.Insets; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.SkinBase; +import javafx.scene.layout.*; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.SVG; +import org.jackhuang.hmcl.ui.construct.*; +import org.jackhuang.hmcl.util.javafx.BindingMapping; + +import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap; +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public class MultiplayerPageSkin extends SkinBase { + + /** + * Constructor for all SkinBase instances. + * + * @param control The control for which this Skin should attach to. + */ + protected MultiplayerPageSkin(MultiplayerPage control) { + super(control); + + BorderPane root = new BorderPane(); + root.setPadding(new Insets(10)); + getChildren().setAll(root); + { + VBox roomPane = new VBox(); + { + AdvancedListItem createRoomItem = new AdvancedListItem(); + createRoomItem.setTitle(i18n("multiplayer.room.create")); + createRoomItem.setLeftGraphic(wrap(SVG.plusCircleOutline(null, 24, 24))); + createRoomItem.setOnAction(e -> FXUtils.openLink("")); + + AdvancedListItem joinRoomItem = new AdvancedListItem(); + joinRoomItem.setTitle(i18n("multiplayer.room.join")); + joinRoomItem.setLeftGraphic(wrap(SVG.accountArrowRightOutline(null, 24, 24))); + joinRoomItem.setOnAction(e -> FXUtils.openLink("")); + + AdvancedListItem copyLinkItem = new AdvancedListItem(); + copyLinkItem.setTitle(i18n("multiplayer.room.copy_room_code")); + copyLinkItem.setLeftGraphic(wrap(SVG.accountArrowRightOutline(null, 24, 24))); + copyLinkItem.setOnAction(e -> FXUtils.openLink("")); + + AdvancedListItem quitItem = new AdvancedListItem(); + quitItem.setTitle(i18n("multiplayer.room.quit")); + quitItem.setLeftGraphic(wrap(SVG.closeCircle(null, 24, 24))); + quitItem.setOnAction(e -> FXUtils.openLink("")); + + AdvancedListItem closeRoomItem = new AdvancedListItem(); + closeRoomItem.setTitle(i18n("multiplayer.room.quit")); + closeRoomItem.setLeftGraphic(wrap(SVG.closeCircle(null, 24, 24))); + closeRoomItem.setOnAction(e -> FXUtils.openLink("")); + + FXUtils.onChangeAndOperate(getSkinnable().multiplayerStateProperty(), state -> { + if (state == MultiplayerManager.State.DISCONNECTED) { + roomPane.getChildren().setAll(createRoomItem, joinRoomItem); + } else if (state == MultiplayerManager.State.MASTER) { + roomPane.getChildren().setAll(copyLinkItem); + roomPane.getChildren().setAll(closeRoomItem); + } else if (state == MultiplayerManager.State.SLAVE) { + roomPane.getChildren().setAll(copyLinkItem); + roomPane.getChildren().setAll(quitItem); + } + }); + } + + AdvancedListBox sideBar = new AdvancedListBox() + .startCategory("multiplayer.room") + .add(roomPane) + .startCategory("help") + .addNavigationDrawerItem(settingsItem -> { + settingsItem.setTitle(i18n("help")); + settingsItem.setLeftGraphic(wrap(SVG.gamepad(null, 20, 20))); + settingsItem.setOnAction(e -> FXUtils.openLink("")); + }); + FXUtils.setLimitWidth(sideBar, 200); + root.setLeft(sideBar); + } + + { + VBox content = new VBox(16); + content.setFillWidth(true); + ScrollPane scrollPane = new ScrollPane(content); + scrollPane.setFitToWidth(true); + scrollPane.setFitToHeight(true); + root.setCenter(scrollPane); + + ComponentList roomPane = new ComponentList(); + { + VBox pane = new VBox(); + + } + + ComponentList natDetectionPane = new ComponentList(); + { + GridPane pane = new GridPane(); + ColumnConstraints title = new ColumnConstraints(); + ColumnConstraints value = new ColumnConstraints(); + pane.getColumnConstraints().setAll(title, value); + value.setFillWidth(true); + value.setHgrow(Priority.ALWAYS); + pane.setHgap(16); + pane.setVgap(8); + + HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFORMATION); + hintPane.setText(i18n("multiplayer.nat.hint")); + GridPane.setColumnSpan(hintPane, 2); + pane.addRow(0, hintPane); + + Label natResult = new Label(); + natResult.textProperty().bind(BindingMapping.of(getSkinnable().natStateProperty()) + .map(MultiplayerPageSkin::getNATType)); + pane.addRow(1, new Label(i18n("multiplayer.nat.type")), natResult); + +// Label natResult = new Label(); +// natResult.textProperty().bind(BindingMapping.of(getSkinnable().natStateProperty()) +// .map(MultiplayerPageSkin::getNATType)); +// pane.addRow(1, new Label(i18n("multiplayer.nat.latency")), natResult); + + natDetectionPane.getContent().add(pane); + } + + content.getChildren().setAll( + ComponentList.createComponentListTitle(i18n("multiplayer.room")), + roomPane, + ComponentList.createComponentListTitle(i18n("multiplayer.nat")), + natDetectionPane + ); + } + } + + private static String getNATType(DiscoveryInfo info) { + if (info == null) { + return i18n("multiplayer.nat.testing"); + } else if (info.isBlockedUDP()) { + return i18n("multiplayer.nat.type.blocked_udp"); + } else if (info.isFullCone()) { + return i18n("multiplayer.nat.type.full_cone"); + } else if (info.isOpenAccess()) { + return i18n("multiplayer.nat.type.open_access"); + } else if (info.isPortRestrictedCone()) { + return i18n("multiplayer.nat.type.port_restricted_cone"); + } else if (info.isRestrictedCone()) { + return i18n("multiplayer.nat.type.restricted_cone"); + } else if (info.isSymmetric()) { + return i18n("multiplayer.nat.type.symmetric"); + } else if (info.isSymmetricUDPFirewall()) { + return i18n("multiplayer.nat.type.symmetric_udp_firewall"); + } else { + return i18n("multiplayer.nat.type.unknown"); + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileAdvancedListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileAdvancedListItem.java deleted file mode 100644 index 811e42053..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/profile/ProfileAdvancedListItem.java +++ /dev/null @@ -1,52 +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.profile; - -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import org.jackhuang.hmcl.setting.Profile; -import org.jackhuang.hmcl.setting.Profiles; -import org.jackhuang.hmcl.setting.Theme; -import org.jackhuang.hmcl.ui.SVG; -import org.jackhuang.hmcl.ui.construct.AdvancedListItem; - -import static org.jackhuang.hmcl.ui.FXUtils.newImage; - -public class ProfileAdvancedListItem extends AdvancedListItem { - private ObjectProperty profile = new SimpleObjectProperty() { - - @Override - protected void invalidated() { - Profile profile = get(); - if (profile == null) { - } else { - setTitle(Profiles.getProfileDisplayName(profile)); - setSubtitle(profile.getGameDir().toString()); - } - } - }; - - public ProfileAdvancedListItem() { - setLeftGraphic(createImageView(newImage("/assets/img/craft_table.png")).getKey()); - setRightGraphic(SVG.viewList(Theme.blackFillBinding(), -1, -1)); - } - - public ObjectProperty profileProperty() { - return profile; - } -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadPage.java index e7abf648c..9270b9d8e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadPage.java @@ -89,6 +89,7 @@ public class ModDownloadPage extends Control implements DecoratorPage { List files = CurseModManager.getFiles(addon); items.setAll(files.stream() .filter(file -> file.getGameVersion().contains(gameVersion.get())) + .sorted(Comparator.comparing(CurseAddon.LatestFile::getParsedFileDate).reversed()) .collect(Collectors.toList())); return; } @@ -276,6 +277,18 @@ public class ModDownloadPage extends Control implements DecoratorPage { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL).withLocale(Locale.getDefault()).withZone(ZoneId.systemDefault()); + public interface Project { + + } + + public interface ProjectVersion { + + } + + public interface DownloadSource { + + } + public interface DownloadCallback { void download(Profile profile, @Nullable String version, CurseAddon.LatestFile file); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java index 4d33ac1a1..667d466a4 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java @@ -54,6 +54,8 @@ public final class ModListPage extends ListPageBase Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it)), mods -> { @@ -79,6 +81,9 @@ public final class ModListPage extends ListPageBase info.setActive(false)); } + public void openModFolder() { + FXUtils.openFolder(new File(profile.getRepository().getRunDirectory(versionId), "mods")); + } + public boolean isModded() { return modded.get(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java index 093cb3cd2..b1face0c8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -22,16 +22,15 @@ import com.jfoenix.controls.JFXCheckBox; import com.jfoenix.controls.JFXDialogLayout; import com.jfoenix.controls.JFXListView; import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject; -import com.jfoenix.effects.JFXDepthManager; import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; +import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.control.SelectionMode; import javafx.scene.control.SkinBase; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.StackPane; @@ -42,10 +41,7 @@ import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; -import org.jackhuang.hmcl.ui.construct.DialogCloseEvent; -import org.jackhuang.hmcl.ui.construct.FloatListCell; -import org.jackhuang.hmcl.ui.construct.SpinnerPane; -import org.jackhuang.hmcl.ui.construct.TwoLineListItem; +import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; @@ -59,7 +55,7 @@ import java.nio.file.Files; import java.nio.file.Path; import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; -import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton; +import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Pair.pair; import static org.jackhuang.hmcl.util.StringUtils.isNotBlank; @@ -71,42 +67,44 @@ class ModListPageSkin extends SkinBase { super(skinnable); StackPane pane = new StackPane(); + pane.setPadding(new Insets(10)); pane.getStyleClass().addAll("notice-pane"); - BorderPane root = new BorderPane(); + ComponentList root = new ComponentList(); + root.getStyleClass().add("no-padding"); JFXListView listView = new JFXListView<>(); { HBox toolbar = new HBox(); - toolbar.getStyleClass().add("jfx-tool-bar-second"); - JFXDepthManager.setDepth(toolbar, 1); - toolbar.setPickOnBounds(false); - - toolbar.getChildren().add(createToolbarButton(i18n("button.refresh"), SVG::refresh, skinnable::refresh)); - toolbar.getChildren().add(createToolbarButton(i18n("mods.add"), SVG::plus, skinnable::add)); - toolbar.getChildren().add(createToolbarButton(i18n("button.remove"), SVG::delete, () -> { - Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), () -> { - skinnable.removeSelected(listView.getSelectionModel().getSelectedItems()); - }, null); - })); - toolbar.getChildren().add(createToolbarButton(i18n("mods.enable"), SVG::check, () -> - skinnable.enableSelected(listView.getSelectionModel().getSelectedItems()))); - toolbar.getChildren().add(createToolbarButton(i18n("mods.disable"), SVG::close, () -> - skinnable.disableSelected(listView.getSelectionModel().getSelectedItems()))); - root.setTop(toolbar); + toolbar.getChildren().setAll( + createToolbarButton2(i18n("button.refresh"), SVG::refresh, skinnable::refresh), + createToolbarButton2(i18n("mods.add"), SVG::plus, skinnable::add), + createToolbarButton2(i18n("button.remove"), SVG::delete, () -> { + Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), () -> { + skinnable.removeSelected(listView.getSelectionModel().getSelectedItems()); + }, null); + }), + createToolbarButton2(i18n("mods.enable"), SVG::check, () -> + skinnable.enableSelected(listView.getSelectionModel().getSelectedItems())), + createToolbarButton2(i18n("mods.disable"), SVG::close, () -> + skinnable.disableSelected(listView.getSelectionModel().getSelectedItems())), + createToolbarButton2(i18n("folder.mod"), SVG::folderOpen, () -> + skinnable.openModFolder())); + root.getContent().add(toolbar); } { SpinnerPane center = new SpinnerPane(); + ComponentList.setVgrow(center, Priority.ALWAYS); center.getStyleClass().add("large-spinner-pane"); center.loadingProperty().bind(skinnable.loadingProperty()); - listView.setCellFactory(x -> new ModInfoListCell(listView)); + listView.setCellFactory(x -> new ModInfoListCell()); listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); Bindings.bindContent(listView.getItems(), skinnable.getItems()); center.setContent(listView); - root.setCenter(center); + root.getContent().add(center); } Label label = new Label(i18n("mods.not_modded")); @@ -231,23 +229,31 @@ class ModListPageSkin extends SkinBase { } } - static class ModInfoListCell extends FloatListCell { + static class ModInfoListCell extends MDListCell { JFXCheckBox checkBox = new JFXCheckBox(); TwoLineListItem content = new TwoLineListItem(); JFXButton infoButton = new JFXButton(); + JFXButton revealButton = new JFXButton(); BooleanProperty booleanProperty; - ModInfoListCell(JFXListView listView) { - super(listView); + ModInfoListCell() { HBox container = new HBox(8); + container.setPickOnBounds(false); container.setAlignment(Pos.CENTER_LEFT); - pane.getChildren().add(container); HBox.setHgrow(content, Priority.ALWAYS); + content.setMouseTransparent(true); + setSelectable(); + + revealButton.getStyleClass().add("toggle-icon4"); + revealButton.setGraphic(FXUtils.limitingSize(SVG.folderOutline(Theme.blackFillBinding(), 24, 24), 24, 24)); infoButton.getStyleClass().add("toggle-icon4"); infoButton.setGraphic(FXUtils.limitingSize(SVG.informationOutline(Theme.blackFillBinding(), 24, 24), 24, 24)); - container.getChildren().setAll(checkBox, content, infoButton); + container.getChildren().setAll(checkBox, content, revealButton, infoButton); + + StackPane.setMargin(container, new Insets(10, 16, 10, 16)); + getContainer().getChildren().setAll(container); } @Override @@ -259,6 +265,9 @@ class ModListPageSkin extends SkinBase { checkBox.selectedProperty().unbindBidirectional(booleanProperty); } checkBox.selectedProperty().bindBidirectional(booleanProperty = dataItem.active); + revealButton.setOnMouseClicked(e -> { + FXUtils.showFileInExplorer(dataItem.getModInfo().getFile()); + }); infoButton.setOnMouseClicked(e -> { Controllers.dialog(new ModInfoDialog(dataItem)); }); 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 2f69851e1..68c7ce050 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 @@ -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 @@ -244,7 +244,7 @@ public class VersionPage extends Control implements DecoratorPage, ModDownloadPa AdvancedListItem versionSettingsItem = new AdvancedListItem(); versionSettingsItem.getStyleClass().add("navigation-drawer-item"); versionSettingsItem.setTitle(i18n("settings.game")); - versionSettingsItem.setLeftGraphic(wrap(SVG.gearOutline(null, 20, 20))); + versionSettingsItem.setLeftGraphic(wrap(SVG.gearOutline(Theme.blackFillBinding(), 24, 24))); versionSettingsItem.setActionButtonVisible(false); versionSettingsItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.versionSettingsTab)); versionSettingsItem.setOnAction(e -> control.tab.getSelectionModel().select(control.versionSettingsTab)); @@ -252,7 +252,7 @@ public class VersionPage extends Control implements DecoratorPage, ModDownloadPa AdvancedListItem modListItem = new AdvancedListItem(); modListItem.getStyleClass().add("navigation-drawer-item"); modListItem.setTitle(i18n("mods.manage")); - modListItem.setLeftGraphic(wrap(SVG.puzzle(null, 20, 20))); + modListItem.setLeftGraphic(wrap(SVG.puzzle(Theme.blackFillBinding(), 24, 24))); modListItem.setActionButtonVisible(false); modListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.modListTab)); modListItem.setOnAction(e -> control.tab.getSelectionModel().select(control.modListTab)); @@ -260,7 +260,7 @@ public class VersionPage extends Control implements DecoratorPage, ModDownloadPa AdvancedListItem curseModListItem = new AdvancedListItem(); curseModListItem.getStyleClass().add("navigation-drawer-item"); curseModListItem.setTitle(i18n("mods.download")); - curseModListItem.setLeftGraphic(wrap(SVG.fire(null, 20, 20))); + curseModListItem.setLeftGraphic(wrap(SVG.fire(Theme.blackFillBinding(), 24, 24))); curseModListItem.setActionButtonVisible(false); curseModListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.curseModListTab)); curseModListItem.setOnAction(e -> control.tab.getSelectionModel().select(control.curseModListTab)); @@ -268,7 +268,7 @@ public class VersionPage extends Control implements DecoratorPage, ModDownloadPa AdvancedListItem installerListItem = new AdvancedListItem(); installerListItem.getStyleClass().add("navigation-drawer-item"); installerListItem.setTitle(i18n("settings.tabs.installers")); - installerListItem.setLeftGraphic(wrap(SVG.cube(null, 20, 20))); + installerListItem.setLeftGraphic(wrap(SVG.cube(Theme.blackFillBinding(), 24, 24))); installerListItem.setActionButtonVisible(false); installerListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.installerListTab)); installerListItem.setOnAction(e -> control.tab.getSelectionModel().select(control.installerListTab)); @@ -276,7 +276,7 @@ public class VersionPage extends Control implements DecoratorPage, ModDownloadPa AdvancedListItem worldListItem = new AdvancedListItem(); worldListItem.getStyleClass().add("navigation-drawer-item"); worldListItem.setTitle(i18n("world.manage")); - worldListItem.setLeftGraphic(wrap(SVG.earth(null, 20, 20))); + worldListItem.setLeftGraphic(wrap(SVG.earth(Theme.blackFillBinding(), 24, 24))); worldListItem.setActionButtonVisible(false); worldListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.worldListTab)); worldListItem.setOnAction(e -> control.tab.getSelectionModel().select(control.worldListTab)); @@ -320,7 +320,7 @@ public class VersionPage extends Control implements DecoratorPage, ModDownloadPa AdvancedListItem upgradeItem = new AdvancedListItem(); upgradeItem.getStyleClass().add("navigation-drawer-item"); upgradeItem.setTitle(i18n("version.update")); - upgradeItem.setLeftGraphic(wrap(SVG.update(Theme.blackFillBinding(), 20, 20))); + upgradeItem.setLeftGraphic(wrap(SVG.update(Theme.blackFillBinding(), 24, 24))); upgradeItem.setActionButtonVisible(false); upgradeItem.visibleProperty().bind(control.currentVersionUpgradable); upgradeItem.setOnAction(e -> control.updateGame()); @@ -328,21 +328,21 @@ public class VersionPage extends Control implements DecoratorPage, ModDownloadPa AdvancedListItem testGameItem = new AdvancedListItem(); testGameItem.getStyleClass().add("navigation-drawer-item"); testGameItem.setTitle(i18n("version.launch.test")); - testGameItem.setLeftGraphic(wrap(SVG.rocketLaunchOutline(Theme.blackFillBinding(), 20, 20))); + testGameItem.setLeftGraphic(wrap(SVG.rocketLaunchOutline(Theme.blackFillBinding(), 24, 24))); testGameItem.setActionButtonVisible(false); testGameItem.setOnAction(e -> control.testGame()); AdvancedListItem browseMenuItem = new AdvancedListItem(); browseMenuItem.getStyleClass().add("navigation-drawer-item"); browseMenuItem.setTitle(i18n("settings.game.exploration")); - browseMenuItem.setLeftGraphic(wrap(SVG.folderOutline(Theme.blackFillBinding(), 20, 20))); + browseMenuItem.setLeftGraphic(wrap(SVG.folderOutline(Theme.blackFillBinding(), 24, 24))); browseMenuItem.setActionButtonVisible(false); browseMenuItem.setOnAction(e -> browsePopup.show(browseMenuItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, browseMenuItem.getWidth(), 0)); AdvancedListItem managementItem = new AdvancedListItem(); managementItem.getStyleClass().add("navigation-drawer-item"); managementItem.setTitle(i18n("settings.game.management")); - managementItem.setLeftGraphic(wrap(SVG.wrenchOutline(Theme.blackFillBinding(), 20, 20))); + managementItem.setLeftGraphic(wrap(SVG.wrenchOutline(Theme.blackFillBinding(), 24, 24))); managementItem.setActionButtonVisible(false); managementItem.setOnAction(e -> managementPopup.show(managementItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, managementItem.getWidth(), 0)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java index 55812f1f4..1cd3c1271 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java @@ -18,7 +18,11 @@ package org.jackhuang.hmcl.ui.versions; import com.jfoenix.controls.JFXButton; + +import javafx.application.Platform; import javafx.stage.FileChooser; + +import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.download.game.GameAssetDownloadTask; import org.jackhuang.hmcl.game.GameDirectoryType; import org.jackhuang.hmcl.game.GameRepository; @@ -33,6 +37,8 @@ import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.TaskExecutor; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.account.CreateAccountPane; +import org.jackhuang.hmcl.ui.construct.DialogCloseEvent; import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.PromptDialogPane; import org.jackhuang.hmcl.ui.construct.Validator; @@ -50,6 +56,7 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; import java.util.logging.Level; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -185,7 +192,9 @@ public final class Versions { } public static void generateLaunchScript(Profile profile, String id) { - if (checkForLaunching(profile, id)) { + if (!checkVersionForLaunching(profile, id)) + return; + ensureSelectedAccount(account -> { GameRepository repository = profile.getRepository(); FileChooser chooser = new FileChooser(); if (repository.getRunDirectory(id).isDirectory()) @@ -196,33 +205,55 @@ public final class Versions { : new FileChooser.ExtensionFilter(i18n("extension.sh"), "*.sh")); File file = chooser.showSaveDialog(Controllers.getStage()); if (file != null) - new LauncherHelper(profile, Accounts.getSelectedAccount(), id).makeLaunchScript(file); - } + new LauncherHelper(profile, account, id).makeLaunchScript(file); + }); } public static void launch(Profile profile, String id) { - if (checkForLaunching(profile, id)) - new LauncherHelper(profile, Accounts.getSelectedAccount(), id).launch(); + if (!checkVersionForLaunching(profile, id)) + return; + ensureSelectedAccount(account -> { + new LauncherHelper(profile, account, id).launch(); + }); } public static void testGame(Profile profile, String id) { - if (checkForLaunching(profile, id)) { - LauncherHelper helper = new LauncherHelper(profile, Accounts.getSelectedAccount(), id); + if (!checkVersionForLaunching(profile, id)) + return; + ensureSelectedAccount(account -> { + LauncherHelper helper = new LauncherHelper(profile, account, id); helper.setTestMode(); helper.launch(); - } + }); } - private static boolean checkForLaunching(Profile profile, String id) { - if (Accounts.getSelectedAccount() == null) - Controllers.getRootPage().checkAccountForcibly(); - else if (id == null || !profile.getRepository().isLoaded() || !profile.getRepository().hasVersion(id)) + private static boolean checkVersionForLaunching(Profile profile, String id) { + if (id == null || !profile.getRepository().isLoaded() || !profile.getRepository().hasVersion(id)) { Controllers.dialog(i18n("version.empty.launch"), i18n("launch.failed"), MessageDialogPane.MessageType.ERROR, () -> { Controllers.navigate(Controllers.getGameListPage()); }); - else + return false; + } else { return true; - return false; + } + } + + private static void ensureSelectedAccount(Consumer action) { + Account account = Accounts.getSelectedAccount(); + if (account == null) { + CreateAccountPane dialog = new CreateAccountPane(); + dialog.addEventHandler(DialogCloseEvent.CLOSE, e -> { + Account newAccount = Accounts.getSelectedAccount(); + if (newAccount == null) { + // user cancelled operation + } else { + Platform.runLater(() -> action.accept(newAccount)); + } + }); + Controllers.dialog(dialog); + } else { + action.accept(account); + } } public static void modifyGlobalSettings(Profile profile) { diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index afa9ff8ea..5f15611eb 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -162,7 +162,7 @@ } .advanced-list-box-content { - -fx-padding: 12 0 12 0; + -fx-padding: 12 0 0 0; -fx-spacing: 0; } @@ -793,6 +793,10 @@ -fx-border-width: 0 0 1 0; } +.md-list-cell:selected { + -fx-background-color: derive(-fx-base-color, 60%); +} + .options-sublist { -fx-background-color: white; } diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index d90226ac9..2f1fb97e6 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -68,6 +68,7 @@ account.methods.microsoft=Microsoft Account account.methods.microsoft.close_page=Microsoft account authorization has been finished. There are some remaining logging-in steps to be finished later. You can close this page right now. account.methods.microsoft.error.add_family=Since you are not yet 18 years old, an adult must add you to a family in order for you to play Minecraft. account.methods.microsoft.error.missing_xbox_account=Your Microsoft account is not connected to an Xbox account. Please create one before continuing. +account.methods.microsoft.error.no_character=Account is missing a Minecraft Java profile. While the Microsoft account is valid, it does not own the game. account.methods.microsoft.error.unknown=Failed to log in. Microsoft respond with error code %d. account.methods.microsoft.logging_in=Logging in... account.methods.microsoft.manual=You should finish authorization in the newly opened browser window. If the browser window failed to show, you can click here to copy the URL, and manually open it in your browser. @@ -449,6 +450,35 @@ mods.name=Name mods.not_modded=You should install a modloader first (Fabric, Forge or LiteLoader) mods.url=Official Page +multiplayer=Multiplayer +multiplayer.nat=Network Type Detection +multiplayer.nat.hint=Network type detection will make it clear whether your network fulfills our requirement for multiplayer mode. +multiplayer.nat.latency=Latency +multiplayer.nat.not_yet_tested=Not yet testsed +multiplayer.nat.packet_loss_ratio=Packet Loss Ratio +multiplayer.nat.testing=Testing +multiplayer.nat.type=NAT Type +multiplayer.nat.type.blocked_udp=Very bad (Blocked UDP) +multiplayer.nat.type.full_cone=Medium (Full Cone) +multiplayer.nat.type.open_access=Good (Open Access) +multiplayer.nat.type.port_restricted_cone=Medium (Port Restricted Cone) +multiplayer.nat.type.restricted_cone=Medium (Restricted Cone) +multiplayer.nat.type.symmetric=Bad (Symmetric) +multiplayer.nat.type.symmetric_udp_firewall=Bad (Symmetric with UDP Firewall) +multiplayer.nat.type.unknown=Unknownn +multiplayer.room=Room +multiplayer.room.name.format=%1$s's Room +multiplayer.room.close=Close Room +multiplayer.room.copy_room_code=Copy Invitation Code +multiplayer.room.create=Create Room +multiplayer.room.error.port=Cannot detect game port, you must click "Open LAN Server" in game to enable multiplayer functionality. +multiplayer.room.hint=You must click "Open LAN Server" in game in order to enable multiplayer functionality. +multiplayer.room.join=Join Room +multiplayer.room.members=Room Members +multiplayer.room.port.prompt=Enter game port +multiplayer.room.quit=Quit Room +multiplayer.room.username=Username + datapack=Datapacks datapack.add=Install datapack datapack.choose_datapack=Choose the datapack zip to import @@ -506,6 +536,7 @@ selector.custom=Custom settings=Settings settings.advanced=Advanced Settings +settings.advanced.custom_commands=Custom Commands settings.advanced.dont_check_game_completeness=Do not scan game files settings.advanced.dont_check_jvm_validity=Don't check whether JVM can launch the game or not settings.advanced.game_dir.default=Standard (.minecraft/) diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 31647f297..a20b3951e 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -58,6 +58,15 @@ account.manage=帳戶列表 account.methods=登入方式 account.methods.authlib_injector=authlib-injector 登入 account.methods.microsoft=微軟帳戶 +account.methods.microsoft.close_page=已完成微軟帳號授權,接下來啟動器還需要完成剩餘登錄步驟。你已經可以關閉本頁面了。 +account.methods.microsoft.error.add_family=由於你未滿 18 歲,你的帳號必須被加入到家庭中才能登錄遊戲。 +account.methods.microsoft.error.missing_xbox_account=你的微軟帳號尚未關聯 XBox 帳號,你必須先創建 XBox 帳號,才能登錄遊戲。 +account.methods.microsoft.error.no_character=該帳號沒有包含 Minecraft Java 版購買記錄 +account.methods.microsoft.error.unknown=登錄失敗,錯誤碼:%d +account.methods.microsoft.logging_in=登錄中... +account.methods.microsoft.hint=點擊確定以登錄 +account.methods.microsoft.manual=您需要在新打開的瀏覽器窗口中完成登錄。若頁面未能打開,您可以點擊此處複製連結,並手動在瀏覽器中打開網頁。 +account.methods.microsoft.waiting_browser=等待在新打開的瀏覽器窗口中完成登錄... account.methods.offline=離線模式 account.methods.yggdrasil=正版登入 account.missing=沒有遊戲帳戶 @@ -65,7 +74,7 @@ account.missing.add=按一下此處加入帳戶 account.password=密碼 account.skin.file=皮膚圖片檔案 account.skin.upload=上傳皮膚 -account.skin.upload=皮膚上傳失敗 +account.skin.upload.failed=皮膚上傳失敗 account.skin.invalid_skin=無法識別的皮膚文件 account.username=使用者名稱 @@ -98,6 +107,112 @@ color.custom=自訂顏色 crash.NoClassDefFound=請確認 Hello Minecraft! Launcher 本體是否完整,或更新您的 Java。 crash.user_fault=您的系統或 Java 環境可能安裝不當導致本軟體當機,請檢查您的 Java 環境或您的電腦! 可以嘗試重新安裝 Java。 +curse.category.0=全部 + +# https://addons-ecs.forgesvc.net/api/v2/category/section/4471 +curse.category.4474=科幻 +curse.category.4481=輕量整合包 +curse.category.4483=戰鬥 PVP +curse.category.4477=小遊戲 +curse.category.4478=任務 +curse.category.4484=多人 +curse.category.4476=探索 +curse.category.4736=空島 +curse.category.4475=冒險 RPG +curse.category.4487=FTB 整合包 +curse.category.4480=有特定地圖 +curse.category.4479=高難度 +curse.category.4482=大型整合包 +curse.category.4472=科技 +curse.category.4473=魔法 + +# https://addons-ecs.forgesvc.net/api/v2/category/section/6 +curse.category.423=訊息展示 +curse.category.426=模組擴展 +curse.category.434=裝備武器 +curse.category.409=自然生成 +curse.category.4485=血魔法 +curse.category.420=儲存 +curse.category.429=工業 (Industrialcraft) +curse.category.419=魔法 +curse.category.412=科技 +curse.category.4557=紅石 +curse.category.428=匠魂 +curse.category.414=交通運輸 +curse.category.4486=幸運方塊 (Lucky Blocks) +curse.category.432=建築 (Buildcraft) +curse.category.418=基因 +curse.category.4671=Twitch +curse.category.408=礦物資源 +curse.category.4773=CraftTweaker +curse.category.430=神秘 (Thaumcraft) +curse.category.422=冒險 RPG +curse.category.413=機器處理 +curse.category.417=能源 +curse.category.415=物流運輸 +curse.category.433=林業 (Forestry) +curse.category.425=其他 +curse.category.4545=應用能源 2 (Applied Energistics 2) +curse.category.416=農業 +curse.category.421=支持庫 +curse.category.4780=Fabric +curse.category.424=裝飾 +curse.category.406=世界生成 +curse.category.435=伺服器 +curse.category.411=生物 +curse.category.407=生物群系 +curse.category.427=熱力膨脹 (Thermal Expansion) +curse.category.410=維度 +curse.category.436=食物 +curse.category.4558=紅石 +curse.category.4843=自動化 +curse.category.4906=MCreator + +# https://addons-ecs.forgesvc.net/api/v2/category/section/6 +curse.category.399=蒸汽朋克 +curse.category.396=128x +curse.category.398=512x 及更高 +curse.category.397=256x +curse.category.405=其他 +curse.category.395=64x +curse.category.400=模擬 +curse.category.393=16x +curse.category.403=傳統 +curse.category.394=32x +curse.category.404=動態效果 +curse.category.4465=模組支持 +curse.category.402=中世紀風格 +curse.category.401=現代風格 + +# https://addons-ecs.forgesvc.net/api/v2/category/section/17 +curse.category.4464=模組 +curse.category.250=遊戲挑戰 +curse.category.249=創造模式 +curse.category.251=跑酷 +curse.category.253=生存模式 +curse.category.248=冒險模式 +curse.category.252=解謎類 + +# https://addons-ecs.forgesvc.net/api/v2/category/section/4546 +curse.category.4551=硬核任務模式 +curse.category.4548=幸運方塊 (Lucky Blocks) +curse.category.4556=任務進度 +curse.category.4752=小物件 +curse.category.4553=CraftTweaker +curse.category.4554=合成表 +curse.category.4549=指引書 +curse.category.4547=配置 +curse.category.4550=任務 +curse.category.4555=世界生成 +curse.category.4552=腳本 + +curse.sort.author=作者 +curse.sort.date_created=創建日期 +curse.sort.last_updated=最近更新 +curse.sort.name=名稱 +curse.sort.popularity=熱度 +curse.sort.total_downloads=下載量 + download=下載 download.code.404=遠端伺服器沒有需要下載的檔案: %s download.failed=下載失敗: %1$s,錯誤碼:%2$d @@ -129,7 +244,8 @@ folder.resourcepacks=資源包資料夾 folder.saves=遊戲存檔資料夾 folder.screenshots=截圖資料夾 -help=Hello Minecraft! Launcher 說明文件 +help=說明 +help.doc=Hello Minecraft! Launcher 說明文件 help.detail=可查閱資料包、整合包製作指南等內容。 input.email=[使用者名稱] 必須是電子信箱格式 @@ -222,6 +338,7 @@ logwindow.show_lines=顯示行數 logwindow.terminate_game=結束遊戲處理程序 logwindow.title=記錄 logwindow.autoscroll=自動滾動 +logwindow.export_game_crash_logs=導出遊戲崩潰訊息 main_page=首頁 @@ -309,6 +426,35 @@ mods.mangage=模組管理 mods.name=名稱 mods.not_modded=你需要先在自動安裝頁面安裝 Fabric、Forge 或 LiteLoader 才能進行模組管理。 +multiplayer=聯機 +multiplayer.nat=網路檢測 +multiplayer.nat.hint=執行網路檢測可以讓你更清楚裡的網路狀況是否符合聯機功能的需求。不符合聯機功能運行條件的網路狀況將可能導致聯機失敗。 +multiplayer.nat.latency=延遲 +multiplayer.nat.not_yet_tested=尚未檢測 +multiplayer.nat.packet_loss_ratio=丟包率 +multiplayer.nat.testing=檢測中 +multiplayer.nat.type=NAT 類型 +multiplayer.nat.type.blocked_udp=極差(網路禁止 UDP 協議) +multiplayer.nat.type.full_cone=中(完全圓錐型) +multiplayer.nat.type.open_access=好(公網開放型) +multiplayer.nat.type.port_restricted_cone=中(埠受限圓錐型) +multiplayer.nat.type.restricted_cone=中(受限圓錐型) +multiplayer.nat.type.symmetric=差(對稱型) +multiplayer.nat.type.symmetric_udp_firewall=差(對稱型+防火牆) +multiplayer.nat.type.unknown=未知 +multiplayer.room=房間 +multiplayer.room.name.format=%1$s 的房間 +multiplayer.room.close=關閉房間 +multiplayer.room.copy_room_code=複製邀請碼 +multiplayer.room.create=創建房間 +multiplayer.room.error.port=無法檢測遊戲埠號,你必須先啟動遊戲並在遊戲內打開對區域網路開放選項後才能啟動聯機。 +multiplayer.room.hint=你必須先啟動遊戲並在遊戲內打開對區域網路開放選項後才能創建房間 +multiplayer.room.join=加入房間 +multiplayer.room.members=房間成員 +multiplayer.room.port.prompt=輸入埠號 +multiplayer.room.quit=退出房間 +multiplayer.room.username=使用者名稱 + datapack=資料包 datapack.add=加入資料包 datapack.choose_datapack=選擇要匯入的資料包壓縮檔 @@ -357,6 +503,11 @@ repositories.chooser=缺少 JavaFX 運行環境。是否需要從網絡下載並 repositories.chooser.linux_arm32=缺少 JavaFX 運行環境。是否需要從網絡下載並加載 JavaFX 運行時組件?\n選擇“是”從指定下載源下載 JavaFX 運行時組件並啟動HMCL,選擇“否”退出程式。\n注意:Linux ARM32 平臺下暫時無法下載部分組件,運行時可能造成HMCL崩潰。\n下載源: repositories.chooser.title=是否下載 JavaFX? +resourcepack=資源包 + +search=搜索 +search.sort=排序 + selector.choose=選擇 selector.choose_file=選擇檔案 selector.custom=自訂 @@ -419,8 +570,16 @@ settings.icon=遊戲圖示 settings.launcher=啟動器設定 settings.launcher.common_path.tooltip=啟動器將所有遊戲資源及相依元件庫檔案放於此集中管理,如果遊戲資料夾內有現成的將不會使用公共庫檔案 +settings.launcher.debug=除錯 +settings.launcher.download=下載 +settings.launcher.download.threads=並發數 +settings.launcher.download.threads.auto=自動選擇並發數 +settings.launcher.download.threads.hint=並發數過大可能導致系統卡頓。你的下載速度會受到寬頻運營商、伺服器等方面的影響,調大下載並發數不一定能大幅提升總下載速度。 settings.launcher.download_source=下載來源 +settings.launcher.download_source.auto=自動選擇下載來源 settings.launcher.enable_game_list=在首頁內顯示遊戲列表 +settings.launcher.font=字體 +settings.launcher.general=通用 settings.launcher.language=語言 settings.launcher.launcher_log.export=匯出啟動器日誌 settings.launcher.launcher_log.export.failed=無法匯出日誌 @@ -437,8 +596,16 @@ settings.launcher.proxy.port=連線埠 settings.launcher.proxy.socks=Socks settings.launcher.proxy.username=帳戶 settings.launcher.theme=主題 +settings.launcher.version_list_source=版本列表來源 -settings.max_memory=最大記憶體(MB) +settings.memory=遊戲記憶體 +settings.memory.allocate.auto=最低分配 %1$.1f GB / 實際分配 %2$.1f GB +settings.memory.allocate.auto.exceeded=最低分配 %1$.1f GB / 實際分配 %2$.1f GB (%3$.1f GB 可用) +settings.memory.allocate.manual=遊戲分配 %1$.1f GB +settings.memory.allocate.manual.exceeded=遊戲分配 %1$.1f GB (%3$.1f GB 可用) +settings.memory.auto_allocate=自動分配 +settings.memory.lower_bound=最低分配 +settings.memory.used_per_total=已使用 %1$.1f GB / 總記憶體 %2$.1f GB settings.physical_memory=實體記憶體大小 settings.show_log=查看記錄 settings.tabs.installers=自動安裝 @@ -448,6 +615,7 @@ settings.type.global.manage=全域遊戲設定 settings.type.global.edit=編輯全域遊戲設定 settings.type.special.enable=啟用遊戲特別設定(不影響其他遊戲版本) +sponsor=贊助 sponsor.bmclapi=大中華區下載源由 BMCLAPI 和我的世界中文論壇 (MCBBS) 提供高速下載服務 sponsor.hmcl=Hello Minecraft! Launcher 是一個免費、開源的 Minecraft 啟動器,允許玩家方便快捷地安裝、管理、執行遊戲。您的贊助將幫助 Hello Minecraft! Launcher 獲得更好的發展、支援穩定高速的遊戲安裝與文件下載服務。點選此處查閱更多詳細訊息。 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 27e70633f..229b47908 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -59,8 +59,6 @@ account.failed.no_character=该帐号没有角色 account.failed.server_response_malformed=无法解析认证服务器响应,可能是服务器故障 account.injector.add=添加认证服务器 account.injector.empty=无(点击右侧加号添加) -account.injector.manage=管理认证服务器 -account.injector.manage.title=认证服务器 account.injector.http=警告:此服务器使用不安全的 HTTP 协议,您的密码在登录时会被明文传输。 account.injector.link.register=注册 account.injector.server=认证服务器 @@ -73,8 +71,10 @@ account.methods.microsoft=微软账户 account.methods.microsoft.close_page=已完成微软账号授权,接下来启动器还需要完成剩余登录步骤。你已经可以关闭本页面了。 account.methods.microsoft.error.add_family=由于你未满 18 岁,你的账号必须被加入到家庭中才能登录游戏。 account.methods.microsoft.error.missing_xbox_account=你的微软账号尚未关联 XBox 账号,你必须先创建 XBox 账号,才能登录游戏。 +account.methods.microsoft.error.no_character=该账号没有包含 Minecraft Java 版购买记录 account.methods.microsoft.error.unknown=登录失败,错误码:%d account.methods.microsoft.logging_in=登录中... +account.methods.microsoft.hint=点击确定以登录 account.methods.microsoft.manual=您需要在新打开的浏览器窗口中完成登录。若页面未能打开,您可以点击此处复制链接,并手动在浏览器中打开网页。 account.methods.microsoft.waiting_browser=等待在新打开的浏览器窗口中完成登录... account.methods.offline=离线模式 @@ -458,6 +458,35 @@ mods.name=名称 mods.not_modded=你需要先在自动安装页面安装 Fabric、Forge 或 LiteLoader 才能进行模组管理。 mods.url=官方页面 +multiplayer=联机 +multiplayer.nat=网络检测 +multiplayer.nat.hint=执行网络检测可以让你更清楚里的网络状况是否符合联机功能的需求。不符合联机功能运行条件的网络状况将可能导致联机失败。 +multiplayer.nat.latency=延迟 +multiplayer.nat.not_yet_tested=尚未检测 +multiplayer.nat.packet_loss_ratio=丢包率 +multiplayer.nat.testing=检测中 +multiplayer.nat.type=NAT 类型 +multiplayer.nat.type.blocked_udp=极差(网络禁止 UDP 协议) +multiplayer.nat.type.full_cone=中(完全圆锥型) +multiplayer.nat.type.open_access=好(公网开放型) +multiplayer.nat.type.port_restricted_cone=中(端口受限圆锥型) +multiplayer.nat.type.restricted_cone=中(受限圆锥型) +multiplayer.nat.type.symmetric=差(对称型) +multiplayer.nat.type.symmetric_udp_firewall=差(对称型+防火墙) +multiplayer.nat.type.unknown=未知 +multiplayer.room=房间 +multiplayer.room.name.format=%1$s 的房间 +multiplayer.room.close=关闭房间 +multiplayer.room.copy_room_code=复制邀请码 +multiplayer.room.create=创建房间 +multiplayer.room.error.port=无法检测游戏端口号,你必须先启动游戏并在游戏内打开对局域网开放选项后才能启动联机。 +multiplayer.room.hint=你必须先启动游戏并在游戏内打开对局域网开放选项后才能创建房间 +multiplayer.room.join=加入房间 +multiplayer.room.members=房间成员 +multiplayer.room.port.prompt=输入端口号 +multiplayer.room.quit=退出房间 +multiplayer.room.username=用户名 + datapack=数据包 datapack.add=添加数据包 datapack.choose_datapack=选择要导入的数据包压缩包 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AccountFactory.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AccountFactory.java index 5e5db757a..7670ae27f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AccountFactory.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AccountFactory.java @@ -50,25 +50,33 @@ public abstract class AccountFactory { } } + public interface ProgressCallback { + void onProgressChanged(String stageName); + } + /** * Informs how this account factory verifies user's credential. + * * @see AccountLoginType */ public abstract AccountLoginType getLoginType(); /** * Create a new(to be verified via network) account, and log in. - * @param selector for character selection if multiple characters belong to single account. Pick out which character to act as. - * @param username username of the account if needed. - * @param password password of the account if needed. - * @param additionalData extra data for specific account factory. + * + * @param selector for character selection if multiple characters belong to single account. Pick out which character to act as. + * @param username username of the account if needed. + * @param password password of the account if needed. + * @param progressCallback notify login progress. + * @param additionalData extra data for specific account factory. * @return logged-in account. * @throws AuthenticationException if an error occurs when logging in. */ - public abstract T create(CharacterSelector selector, String username, String password, Object additionalData) throws AuthenticationException; + public abstract T create(CharacterSelector selector, String username, String password, ProgressCallback progressCallback, Object additionalData) throws AuthenticationException; /** * Create a existing(stored in local files) account. + * * @param storage serialized account data. * @return account stored in local storage. Credentials may expired, and you should refresh account state later. */ diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorAccountFactory.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorAccountFactory.java index 2e135c541..04eb219ea 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorAccountFactory.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorAccountFactory.java @@ -50,7 +50,7 @@ public class AuthlibInjectorAccountFactory extends AccountFactory>> getTextures() { - return BindingMapping.of(service.getProfileRepository().binding(session.getAuthorization())) - .map(profile -> profile.flatMap(MicrosoftService::getTextures)); - + return BindingMapping.of(service.getProfileRepository().binding(getUUID())) + .map(profile -> profile.flatMap(it -> { + try { + return YggdrasilService.getTextures(it); + } catch (ServerResponseMalformedException e) { + LOG.log(Level.WARNING, "Failed to parse texture payload", e); + return Optional.empty(); + } + })); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccountFactory.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccountFactory.java index c404535dc..e795ec236 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccountFactory.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccountFactory.java @@ -38,7 +38,7 @@ public class MicrosoftAccountFactory extends AccountFactory { } @Override - public MicrosoftAccount create(CharacterSelector selector, String username, String password, Object additionalData) throws AuthenticationException { + public MicrosoftAccount create(CharacterSelector selector, String username, String password, ProgressCallback progressCallback, Object additionalData) throws AuthenticationException { Objects.requireNonNull(selector); return new MicrosoftAccount(service, selector); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java index a322446a8..5db3aedd2 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java @@ -17,15 +17,16 @@ */ package org.jackhuang.hmcl.auth.microsoft; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.google.gson.JsonParseException; import com.google.gson.annotations.SerializedName; import org.jackhuang.hmcl.auth.*; +import org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile; import org.jackhuang.hmcl.auth.yggdrasil.RemoteAuthenticationException; import org.jackhuang.hmcl.auth.yggdrasil.Texture; import org.jackhuang.hmcl.auth.yggdrasil.TextureType; -import org.jackhuang.hmcl.util.gson.JsonUtils; -import org.jackhuang.hmcl.util.gson.TolerableValidationException; -import org.jackhuang.hmcl.util.gson.Validation; +import org.jackhuang.hmcl.util.gson.*; import org.jackhuang.hmcl.util.io.HttpRequest; import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jackhuang.hmcl.util.io.ResponseCodeException; @@ -60,17 +61,17 @@ public class MicrosoftService { private final OAuthCallback callback; - private final ObservableOptionalCache profileRepository; + private final ObservableOptionalCache profileRepository; public MicrosoftService(OAuthCallback callback) { this.callback = requireNonNull(callback); - this.profileRepository = new ObservableOptionalCache<>(authorization -> { - LOG.info("Fetching properties"); - return getCompleteProfile(authorization); + this.profileRepository = new ObservableOptionalCache<>(uuid -> { + LOG.info("Fetching properties of " + uuid); + return getCompleteGameProfile(uuid); }, (uuid, e) -> LOG.log(Level.WARNING, "Failed to fetch properties of " + uuid, e), POOL); } - public ObservableOptionalCache getProfileRepository() { + public ObservableOptionalCache getProfileRepository() { return profileRepository; } @@ -213,10 +214,14 @@ public class MicrosoftService { } } - public boolean validate(String tokenType, String accessToken) throws AuthenticationException { + public boolean validate(long notAfter, String tokenType, String accessToken) throws AuthenticationException { requireNonNull(tokenType); requireNonNull(accessToken); + if (System.currentTimeMillis() > notAfter) { + return false; + } + try { getMinecraftProfile(tokenType, accessToken); return true; @@ -266,7 +271,7 @@ public class MicrosoftService { .createConnection(); int responseCode = conn.getResponseCode(); if (responseCode == HTTP_NOT_FOUND) { - throw new NoCharacterException(); + throw new NoMinecraftJavaEditionProfileException(); } else if (responseCode != 200) { throw new ResponseCodeException(new URL("https://api.minecraftservices.com/minecraft/profile"), responseCode); } @@ -275,6 +280,31 @@ public class MicrosoftService { return JsonUtils.fromNonNullJson(result, MinecraftProfileResponse.class); } + public Optional getCompleteGameProfile(UUID uuid) throws AuthenticationException { + Objects.requireNonNull(uuid); + + return Optional.ofNullable(GSON.fromJson(request(NetworkUtils.toURL("https://sessionserver.mojang.com/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid)), null), CompleteGameProfile.class)); + } + + private static String request(URL url, Object payload) throws AuthenticationException { + try { + if (payload == null) + return NetworkUtils.doGet(url); + else + return NetworkUtils.doPost(url, payload instanceof String ? (String) payload : GSON.toJson(payload), "application/json"); + } catch (IOException e) { + throw new ServerDisconnectException(e); + } + } + + private static T fromJson(String text, Class typeOfT) throws ServerResponseMalformedException { + try { + return GSON.fromJson(text, typeOfT); + } catch (JsonParseException e) { + throw new ServerResponseMalformedException(text, e); + } + } + public static class XboxAuthorizationException extends AuthenticationException { private final long errorCode; @@ -290,6 +320,9 @@ public class MicrosoftService { public static final long ADD_FAMILY = 2148916238L; } + public static class NoMinecraftJavaEditionProfileException extends AuthenticationException { + } + /** * Error response: {"error":"invalid_grant","error_description":"The provided * value for the 'redirect_uri' is not valid. The value must exactly match the @@ -487,4 +520,9 @@ public class MicrosoftService { String waitFor() throws InterruptedException, ExecutionException; } + private static final Gson GSON = new GsonBuilder() + .registerTypeAdapter(UUID.class, UUIDTypeAdapter.INSTANCE) + .registerTypeAdapterFactory(ValidationTypeAdapterFactory.INSTANCE) + .create(); + } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/OfflineAccountFactory.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/OfflineAccountFactory.java index 92d319f4a..7871171d0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/OfflineAccountFactory.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/OfflineAccountFactory.java @@ -47,7 +47,7 @@ public final class OfflineAccountFactory extends AccountFactory } @Override - public OfflineAccount create(CharacterSelector selector, String username, String password, Object additionalData) { + public OfflineAccount create(CharacterSelector selector, String username, String password, ProgressCallback progressCallback, Object additionalData) { return new OfflineAccount(username, getUUIDFromUserName(username)); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccountFactory.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccountFactory.java index 91c9e8f4d..0d5955be2 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccountFactory.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccountFactory.java @@ -48,7 +48,7 @@ public class YggdrasilAccountFactory extends AccountFactory { } @Override - public YggdrasilAccount create(CharacterSelector selector, String username, String password, Object additionalData) throws AuthenticationException { + public YggdrasilAccount create(CharacterSelector selector, String username, String password, ProgressCallback progressCallback, Object additionalData) throws AuthenticationException { Objects.requireNonNull(selector); Objects.requireNonNull(username); Objects.requireNonNull(password); diff --git a/JSTUN b/JSTUN new file mode 160000 index 000000000..08ab1f848 --- /dev/null +++ b/JSTUN @@ -0,0 +1 @@ +Subproject commit 08ab1f8483aba307931494e695e27cbde0cc2657 diff --git a/build.gradle b/build.gradle index adc6ceaf1..d2221bed1 100644 --- a/build.gradle +++ b/build.gradle @@ -31,6 +31,10 @@ subprojects { sourceSets = [] } + tasks.withType(Checkstyle) { + exclude 'de/javawi/jstun' + } + sourceCompatibility = 1.8 compileJava.options.encoding = "UTF-8" compileTestJava.options.encoding = "UTF-8" diff --git a/settings.gradle b/settings.gradle index 28fbb873a..d5019c6d4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,4 +2,4 @@ rootProject.name = 'HMCL3' include ':HMCL' include ':HMCLCore' include ':HMCLTransformerDiscoveryService' - +include ':JSTUN'