Merge branch 'javafx' into aarch64
This commit is contained in:
12
.github/workflows/gradle.yml
vendored
12
.github/workflows/gradle.yml
vendored
@@ -9,19 +9,21 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
- name: Checkout submodules
|
||||||
|
run: git submodule update --init --recursive
|
||||||
- name: Set up JDK 1.8
|
- name: Set up JDK 1.8
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 1.8
|
java-version: 1.8
|
||||||
java-package: jdk+fx
|
java-package: jdk+fx
|
||||||
- name: Check style main
|
|
||||||
run: ./gradlew checkstyleMain
|
|
||||||
- name: Check style test
|
|
||||||
run: ./gradlew checkstyleTest
|
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
run: ./gradlew build
|
run: ./gradlew build
|
||||||
- name: Upload Artifacts
|
- name: Upload Artifacts
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: HMCL
|
name: HMCL
|
||||||
path: HMCL/build/libs
|
path: HMCL/build/libs
|
||||||
|
- name: Check style main
|
||||||
|
run: ./gradlew checkstyleMain
|
||||||
|
- name: Check style test
|
||||||
|
run: ./gradlew checkstyleTest
|
||||||
|
|||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "JSTUN"]
|
||||||
|
path = JSTUN
|
||||||
|
url = https://github.com/huanghongxun/JSTUN
|
||||||
@@ -44,6 +44,7 @@ mainClassName = 'org.jackhuang.hmcl.Main'
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(":HMCLCore")
|
implementation project(":HMCLCore")
|
||||||
|
implementation project(":JSTUN")
|
||||||
implementation rootProject.files("lib/JFoenix.jar")
|
implementation rootProject.files("lib/JFoenix.jar")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -365,6 +365,8 @@ public final class Accounts {
|
|||||||
} else {
|
} else {
|
||||||
return i18n("account.methods.microsoft.error.unknown", errorCode);
|
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) {
|
} else if (exception.getClass() == AuthenticationException.class) {
|
||||||
return exception.getLocalizedMessage();
|
return exception.getLocalizedMessage();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import org.jackhuang.hmcl.setting.Profiles;
|
|||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||||
import org.jackhuang.hmcl.ui.account.AccountListPage;
|
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.animation.ContainerAnimations;
|
||||||
import org.jackhuang.hmcl.ui.construct.InputDialogPane;
|
import org.jackhuang.hmcl.ui.construct.InputDialogPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
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.download.ModpackInstallWizardProvider;
|
||||||
import org.jackhuang.hmcl.ui.main.LauncherSettingsPage;
|
import org.jackhuang.hmcl.ui.main.LauncherSettingsPage;
|
||||||
import org.jackhuang.hmcl.ui.main.RootPage;
|
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.GameListPage;
|
||||||
import org.jackhuang.hmcl.ui.versions.ModDownloadListPage;
|
import org.jackhuang.hmcl.ui.versions.ModDownloadListPage;
|
||||||
import org.jackhuang.hmcl.ui.versions.VersionPage;
|
import org.jackhuang.hmcl.ui.versions.VersionPage;
|
||||||
@@ -84,7 +84,6 @@ public final class Controllers {
|
|||||||
});
|
});
|
||||||
return gameListPage;
|
return gameListPage;
|
||||||
});
|
});
|
||||||
private static AuthlibInjectorServersPage serversPage = null;
|
|
||||||
private static Lazy<RootPage> rootPage = new Lazy<>(RootPage::new);
|
private static Lazy<RootPage> rootPage = new Lazy<>(RootPage::new);
|
||||||
private static DecoratorController decorator;
|
private static DecoratorController decorator;
|
||||||
private static Lazy<ModDownloadListPage> modDownloadListPage = new Lazy<>(() -> {
|
private static Lazy<ModDownloadListPage> modDownloadListPage = new Lazy<>(() -> {
|
||||||
@@ -99,8 +98,10 @@ public final class Controllers {
|
|||||||
AccountListPage accountListPage = new AccountListPage();
|
AccountListPage accountListPage = new AccountListPage();
|
||||||
accountListPage.selectedAccountProperty().bindBidirectional(Accounts.selectedAccountProperty());
|
accountListPage.selectedAccountProperty().bindBidirectional(Accounts.selectedAccountProperty());
|
||||||
accountListPage.accountsProperty().bindContent(Accounts.accountsProperty());
|
accountListPage.accountsProperty().bindContent(Accounts.accountsProperty());
|
||||||
|
accountListPage.authServersProperty().bindContentBidirectional(config().getAuthlibInjectorServers());
|
||||||
return accountListPage;
|
return accountListPage;
|
||||||
});
|
});
|
||||||
|
private static Lazy<MultiplayerPage> multiplayerPage = new Lazy<>(MultiplayerPage::new);
|
||||||
private static Lazy<LauncherSettingsPage> settingsPage = new Lazy<>(LauncherSettingsPage::new);
|
private static Lazy<LauncherSettingsPage> settingsPage = new Lazy<>(LauncherSettingsPage::new);
|
||||||
|
|
||||||
private Controllers() {
|
private Controllers() {
|
||||||
@@ -130,15 +131,13 @@ public final class Controllers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FXThread
|
// FXThread
|
||||||
public static AuthlibInjectorServersPage getServersPage() {
|
public static ModDownloadListPage getModpackDownloadListPage() {
|
||||||
if (serversPage == null)
|
return modDownloadListPage.get();
|
||||||
serversPage = new AuthlibInjectorServersPage();
|
|
||||||
return serversPage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FXThread
|
// FXThread
|
||||||
public static ModDownloadListPage getModpackDownloadListPage() {
|
public static MultiplayerPage getMultiplayerPage() {
|
||||||
return modDownloadListPage.get();
|
return multiplayerPage.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
// FXThread
|
// FXThread
|
||||||
@@ -280,7 +279,6 @@ public final class Controllers {
|
|||||||
public static void shutdown() {
|
public static void shutdown() {
|
||||||
rootPage = null;
|
rootPage = null;
|
||||||
versionPage = null;
|
versionPage = null;
|
||||||
serversPage = null;
|
|
||||||
gameListPage = null;
|
gameListPage = null;
|
||||||
settingsPage = null;
|
settingsPage = null;
|
||||||
modDownloadListPage = null;
|
modDownloadListPage = null;
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ import java.io.*;
|
|||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.BooleanSupplier;
|
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 = {
|
private static final String[] linuxBrowsers = {
|
||||||
"xdg-open",
|
"xdg-open",
|
||||||
"google-chrome",
|
"google-chrome",
|
||||||
|
|||||||
@@ -391,6 +391,12 @@ public final class SVG {
|
|||||||
fill, width, height);
|
fill, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Node accountArrowRightOutline(ObjectBinding<? extends Paint> 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<? extends Paint> fill, double width, double height) {
|
public static Node styleOutline(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||||
return createSVGPath(
|
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",
|
"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",
|
"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);
|
fill, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Node server(ObjectBinding<? extends Paint> 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,15 @@ public abstract class ToolbarListPageSkin<T extends ListPageBase<? extends Node>
|
|||||||
return ret;
|
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) {
|
public static JFXButton createDecoratorButton(String tooltip, SVG.SVGIcon creator, Runnable onClick) {
|
||||||
JFXButton ret = new JFXButton();
|
JFXButton ret = new JFXButton();
|
||||||
ret.getStyleClass().add("jfx-decorator-button");
|
ret.getStyleClass().add("jfx-decorator-button");
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ public class AccountListItemSkin extends SkinBase<AccountListItem> {
|
|||||||
|
|
||||||
JFXButton btnRefresh = new JFXButton();
|
JFXButton btnRefresh = new JFXButton();
|
||||||
SpinnerPane spinnerRefresh = new SpinnerPane();
|
SpinnerPane spinnerRefresh = new SpinnerPane();
|
||||||
|
spinnerRefresh.getStyleClass().setAll("small-spinner-pane");
|
||||||
btnRefresh.setOnMouseClicked(e -> {
|
btnRefresh.setOnMouseClicked(e -> {
|
||||||
spinnerRefresh.showSpinner();
|
spinnerRefresh.showSpinner();
|
||||||
skinnable.refreshAsync()
|
skinnable.refreshAsync()
|
||||||
|
|||||||
@@ -17,29 +17,44 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui.account;
|
package org.jackhuang.hmcl.ui.account;
|
||||||
|
|
||||||
|
import com.jfoenix.controls.JFXButton;
|
||||||
import com.jfoenix.controls.JFXScrollPane;
|
import com.jfoenix.controls.JFXScrollPane;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.*;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.control.ScrollPane;
|
import javafx.scene.control.ScrollPane;
|
||||||
import javafx.scene.control.Skin;
|
import javafx.scene.control.Skin;
|
||||||
import javafx.scene.control.SkinBase;
|
import javafx.scene.control.SkinBase;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import org.jackhuang.hmcl.auth.Account;
|
import org.jackhuang.hmcl.auth.Account;
|
||||||
|
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||||
import org.jackhuang.hmcl.setting.Accounts;
|
import org.jackhuang.hmcl.setting.Accounts;
|
||||||
import org.jackhuang.hmcl.ui.*;
|
import org.jackhuang.hmcl.setting.Theme;
|
||||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
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.ui.decorator.DecoratorPage;
|
||||||
|
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
|
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
|
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
public class AccountListPage extends ListPageBase<AccountListItem> implements DecoratorPage {
|
public class AccountListPage extends ListPageBase<AccountListItem> implements DecoratorPage {
|
||||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("account.manage"), -1));
|
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("account.manage"), -1));
|
||||||
private final ListProperty<Account> accounts = new SimpleListProperty<>(this, "accounts", FXCollections.observableArrayList());
|
private final ListProperty<Account> accounts = new SimpleListProperty<>(this, "accounts", FXCollections.observableArrayList());
|
||||||
|
private final ListProperty<AuthlibInjectorServer> authServers = new SimpleListProperty<>(this, "authServers", FXCollections.observableArrayList());
|
||||||
private final ObjectProperty<Account> selectedAccount;
|
private final ObjectProperty<Account> selectedAccount;
|
||||||
|
|
||||||
public AccountListPage() {
|
public AccountListPage() {
|
||||||
@@ -60,47 +75,109 @@ public class AccountListPage extends ListPageBase<AccountListItem> implements De
|
|||||||
return state.getReadOnlyProperty();
|
return state.getReadOnlyProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ListProperty<AuthlibInjectorServer> authServersProperty() {
|
||||||
|
return authServers;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Skin<?> createDefaultSkin() {
|
protected Skin<?> createDefaultSkin() {
|
||||||
return new AccountListPageSkin(this);
|
return new AccountListPageSkin(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class AccountListPageSkin extends SkinBase<AccountListPage> {
|
private static class AccountListPageSkin extends SkinBase<AccountListPage> {
|
||||||
|
|
||||||
|
private final ObservableList<AdvancedListItem> authServerItems;
|
||||||
|
|
||||||
public AccountListPageSkin(AccountListPage skinnable) {
|
public AccountListPageSkin(AccountListPage skinnable) {
|
||||||
super(skinnable);
|
super(skinnable);
|
||||||
|
|
||||||
BorderPane root = new BorderPane();
|
BorderPane root = new BorderPane();
|
||||||
|
|
||||||
{
|
{
|
||||||
AdvancedListBox sideBar = new AdvancedListBox()
|
BorderPane left = new BorderPane();
|
||||||
.startCategory(i18n("account.create"))
|
FXUtils.setLimitWidth(left, 200);
|
||||||
.addNavigationDrawerItem(settingsItem -> {
|
|
||||||
settingsItem.setTitle(i18n("account.methods.offline"));
|
{
|
||||||
settingsItem.setLeftGraphic(wrap(SVG.account(null, 20, 20)));
|
VBox boxItemList = new VBox();
|
||||||
settingsItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_OFFLINE)));
|
boxItemList.getStyleClass().add("advanced-list-box-content");
|
||||||
})
|
|
||||||
.addNavigationDrawerItem(settingsItem -> {
|
boxItemList.getChildren().add(new ClassTitle(i18n("account.create")));
|
||||||
settingsItem.setTitle(i18n("account.methods.yggdrasil"));
|
|
||||||
settingsItem.setLeftGraphic(wrap(SVG.mojang(null, 20, 20)));
|
{
|
||||||
settingsItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_MOJANG)));
|
VBox boxMethods = new VBox();
|
||||||
})
|
FXUtils.setLimitWidth(boxMethods, 200);
|
||||||
.addNavigationDrawerItem(settingsItem -> {
|
|
||||||
settingsItem.setTitle(i18n("account.methods.microsoft"));
|
AdvancedListItem offlineItem = new AdvancedListItem();
|
||||||
settingsItem.setLeftGraphic(wrap(SVG.microsoft(null, 20, 20)));
|
offlineItem.getStyleClass().add("navigation-drawer-item");
|
||||||
settingsItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_MICROSOFT)));
|
offlineItem.setActionButtonVisible(false);
|
||||||
})
|
offlineItem.setTitle(i18n("account.methods.offline"));
|
||||||
.addNavigationDrawerItem(settingsItem -> {
|
offlineItem.setLeftGraphic(wrap(SVG.account(Theme.blackFillBinding(), 24, 24)));
|
||||||
settingsItem.setTitle(i18n("account.methods.authlib_injector"));
|
offlineItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_OFFLINE)));
|
||||||
settingsItem.setLeftGraphic(wrap(SVG.gear(null, 20, 20)));
|
boxMethods.getChildren().add(offlineItem);
|
||||||
settingsItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_AUTHLIB_INJECTOR)));
|
|
||||||
})
|
AdvancedListItem mojangItem = new AdvancedListItem();
|
||||||
.addNavigationDrawerItem(settingsItem -> {
|
mojangItem.getStyleClass().add("navigation-drawer-item");
|
||||||
settingsItem.setTitle(i18n("account.create"));
|
mojangItem.setActionButtonVisible(false);
|
||||||
settingsItem.setLeftGraphic(wrap(SVG.plus(null, 20, 20)));
|
mojangItem.setTitle(i18n("account.methods.yggdrasil"));
|
||||||
settingsItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane()));
|
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<String> 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);
|
Bindings.bindContent(boxAuthServers.getChildren(), authServerItems);
|
||||||
root.setLeft(sideBar);
|
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();
|
ScrollPane scrollPane = new ScrollPane();
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa
|
|||||||
@FXML
|
@FXML
|
||||||
private void onAddFinish() {
|
private void onAddFinish() {
|
||||||
if (!config().getAuthlibInjectorServers().contains(serverBeingAdded)) {
|
if (!config().getAuthlibInjectorServers().contains(serverBeingAdded)) {
|
||||||
config().getAuthlibInjectorServers().add(0, serverBeingAdded);
|
config().getAuthlibInjectorServers().add(serverBeingAdded);
|
||||||
}
|
}
|
||||||
fireEvent(new DialogCloseEvent());
|
fireEvent(new DialogCloseEvent());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
/*
|
|
||||||
* Hello Minecraft! Launcher
|
|
||||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
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<AuthlibInjectorServerItem> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* Hello Minecraft! Launcher
|
|
||||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
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<AuthlibInjectorServerItem> implements DecoratorPage {
|
|
||||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("account.injector.manage.title")));
|
|
||||||
|
|
||||||
private final ObservableList<AuthlibInjectorServerItem> 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<State> stateProperty() {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,26 +17,17 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui.account;
|
package org.jackhuang.hmcl.ui.account;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
import com.jfoenix.controls.*;
|
||||||
import static java.util.Collections.unmodifiableList;
|
import javafx.application.Platform;
|
||||||
import static javafx.beans.binding.Bindings.bindContent;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
import static javafx.beans.binding.Bindings.createBooleanBinding;
|
import javafx.geometry.HPos;
|
||||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
import javafx.geometry.Insets;
|
||||||
import static org.jackhuang.hmcl.ui.FXUtils.jfxListCellFactory;
|
import javafx.geometry.Pos;
|
||||||
import static org.jackhuang.hmcl.ui.FXUtils.onChange;
|
import javafx.scene.Node;
|
||||||
import static org.jackhuang.hmcl.ui.FXUtils.onChangeAndOperate;
|
import javafx.scene.control.Hyperlink;
|
||||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
import javafx.scene.control.Label;
|
||||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
import javafx.scene.image.ImageView;
|
||||||
import static org.jackhuang.hmcl.ui.FXUtils.setValidateWhileTextChanged;
|
import javafx.scene.layout.*;
|
||||||
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 org.jackhuang.hmcl.auth.AccountFactory;
|
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||||
import org.jackhuang.hmcl.auth.CharacterSelector;
|
import org.jackhuang.hmcl.auth.CharacterSelector;
|
||||||
import org.jackhuang.hmcl.auth.NoSelectedCharacterException;
|
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.auth.yggdrasil.YggdrasilService;
|
||||||
import org.jackhuang.hmcl.game.TexturesLoader;
|
import org.jackhuang.hmcl.game.TexturesLoader;
|
||||||
import org.jackhuang.hmcl.setting.Accounts;
|
import org.jackhuang.hmcl.setting.Accounts;
|
||||||
|
import org.jackhuang.hmcl.setting.Theme;
|
||||||
import org.jackhuang.hmcl.task.Schedulers;
|
import org.jackhuang.hmcl.task.Schedulers;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
import org.jackhuang.hmcl.ui.construct.*;
|
||||||
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.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
import java.util.ArrayList;
|
||||||
import com.jfoenix.controls.JFXComboBox;
|
import java.util.List;
|
||||||
import com.jfoenix.controls.JFXDialogLayout;
|
import java.util.Map;
|
||||||
import com.jfoenix.controls.JFXPasswordField;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import com.jfoenix.controls.JFXTextField;
|
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import static java.util.Collections.emptyList;
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import static java.util.Collections.unmodifiableList;
|
||||||
import javafx.geometry.HPos;
|
import static javafx.beans.binding.Bindings.bindContent;
|
||||||
import javafx.geometry.Insets;
|
import static javafx.beans.binding.Bindings.createBooleanBinding;
|
||||||
import javafx.geometry.Pos;
|
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||||
import javafx.scene.Node;
|
import static org.jackhuang.hmcl.ui.FXUtils.*;
|
||||||
import javafx.scene.control.Hyperlink;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
import javafx.scene.control.Label;
|
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.classPropertyFor;
|
||||||
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;
|
|
||||||
|
|
||||||
public class CreateAccountPane extends JFXDialogLayout {
|
public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||||
|
|
||||||
private boolean showMethodSwitcher;
|
private boolean showMethodSwitcher;
|
||||||
private AccountFactory<?> factory;
|
private AccountFactory<?> factory;
|
||||||
@@ -105,7 +79,7 @@ public class CreateAccountPane extends JFXDialogLayout {
|
|||||||
private TaskExecutor loginTask;
|
private TaskExecutor loginTask;
|
||||||
|
|
||||||
public CreateAccountPane() {
|
public CreateAccountPane() {
|
||||||
this(null);
|
this((AccountFactory<?>) null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CreateAccountPane(AccountFactory<?> factory) {
|
public CreateAccountPane(AccountFactory<?> factory) {
|
||||||
@@ -197,6 +171,11 @@ public class CreateAccountPane extends JFXDialogLayout {
|
|||||||
setPrefWidth(560);
|
setPrefWidth(560);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CreateAccountPane(AuthlibInjectorServer authserver) {
|
||||||
|
this(Accounts.FACTORY_AUTHLIB_INJECTOR);
|
||||||
|
((AccountDetailsInputPane) detailsPane).selectAuthServer(authserver);
|
||||||
|
}
|
||||||
|
|
||||||
private void onAccept() {
|
private void onAccept() {
|
||||||
spinner.showSpinner();
|
spinner.showSpinner();
|
||||||
lblErrorMessage.setText("");
|
lblErrorMessage.setText("");
|
||||||
@@ -216,7 +195,7 @@ public class CreateAccountPane extends JFXDialogLayout {
|
|||||||
additionalData = null;
|
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 -> {
|
.whenComplete(Schedulers.javafx(), account -> {
|
||||||
int oldIndex = Accounts.getAccounts().indexOf(account);
|
int oldIndex = Accounts.getAccounts().indexOf(account);
|
||||||
if (oldIndex == -1) {
|
if (oldIndex == -1) {
|
||||||
@@ -303,7 +282,7 @@ public class CreateAccountPane extends JFXDialogLayout {
|
|||||||
public AccountDetailsInputPane(AccountFactory<?> factory, Runnable onAction) {
|
public AccountDetailsInputPane(AccountFactory<?> factory, Runnable onAction) {
|
||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
|
|
||||||
setVgap(15);
|
setVgap(22);
|
||||||
setHgap(15);
|
setHgap(15);
|
||||||
setAlignment(Pos.CENTER);
|
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-warning").bind(noServers);
|
||||||
classPropertyFor(cboServers, "jfx-combo-box").bind(noServers.not());
|
classPropertyFor(cboServers, "jfx-combo-box").bind(noServers.not());
|
||||||
HBox.setHgrow(cboServers, Priority.ALWAYS);
|
HBox.setHgrow(cboServers, Priority.ALWAYS);
|
||||||
|
HBox.setMargin(cboServers, new Insets(0, 10, 0, 0));
|
||||||
cboServers.setMaxWidth(Double.MAX_VALUE);
|
cboServers.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
|
||||||
HBox linksContainer = new HBox();
|
HBox linksContainer = new HBox();
|
||||||
linksContainer.setAlignment(Pos.CENTER);
|
linksContainer.setAlignment(Pos.CENTER);
|
||||||
linksContainer.setPadding(new Insets(0, 5, 0, 15));
|
|
||||||
onChangeAndOperate(cboServers.valueProperty(), server -> linksContainer.getChildren().setAll(createHyperlinks(server)));
|
onChangeAndOperate(cboServers.valueProperty(), server -> linksContainer.getChildren().setAll(createHyperlinks(server)));
|
||||||
linksContainer.setMinWidth(USE_PREF_SIZE);
|
linksContainer.setMinWidth(USE_PREF_SIZE);
|
||||||
|
|
||||||
JFXButton btnAddServer = new JFXButton();
|
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.getStyleClass().add("toggle-icon4");
|
||||||
btnAddServer.setOnAction(e -> {
|
btnAddServer.setOnAction(e -> {
|
||||||
Controllers.dialog(new AddAuthlibInjectorServerPane());
|
Controllers.dialog(new AddAuthlibInjectorServerPane());
|
||||||
});
|
});
|
||||||
|
|
||||||
JFXButton btnManageServers = new JFXButton();
|
HBox boxServers = new HBox(cboServers, linksContainer, btnAddServer);
|
||||||
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);
|
|
||||||
add(boxServers, 1, rowIndex);
|
add(boxServers, 1, rowIndex);
|
||||||
|
|
||||||
rowIndex++;
|
rowIndex++;
|
||||||
@@ -449,6 +420,16 @@ public class CreateAccountPane extends JFXDialogLayout {
|
|||||||
public BooleanBinding validProperty() {
|
public BooleanBinding validProperty() {
|
||||||
return valid;
|
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 {
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
|
|||||||
|
|
||||||
HBox right = new HBox();
|
HBox right = new HBox();
|
||||||
right.setAlignment(Pos.CENTER);
|
right.setAlignment(Pos.CENTER);
|
||||||
right.setMouseTransparent(true);
|
|
||||||
right.getStyleClass().add("toggle-icon4");
|
right.getStyleClass().add("toggle-icon4");
|
||||||
FXUtils.setLimitWidth(right, 40);
|
FXUtils.setLimitWidth(right, 40);
|
||||||
FXUtils.onChangeAndOperate(skinnable.rightGraphicProperty(),
|
FXUtils.onChangeAndOperate(skinnable.rightGraphicProperty(),
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import javafx.scene.control.Control;
|
|||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.SkinBase;
|
import javafx.scene.control.SkinBase;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||||
|
|
||||||
@@ -132,6 +133,9 @@ public class ComponentList extends Control {
|
|||||||
list = MappedObservableList.create(control.getContent(), node -> {
|
list = MappedObservableList.create(control.getContent(), node -> {
|
||||||
ComponentListCell cell = new ComponentListCell(node);
|
ComponentListCell cell = new ComponentListCell(node);
|
||||||
cell.getStyleClass().add("options-list-item");
|
cell.getStyleClass().add("options-list-item");
|
||||||
|
if (node.getProperties().containsKey("ComponentList.vgrow")) {
|
||||||
|
VBox.setVgrow(cell, (Priority) node.getProperties().get("ComponentList.vgrow"));
|
||||||
|
}
|
||||||
return cell;
|
return cell;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -172,4 +176,8 @@ public class ComponentList extends Control {
|
|||||||
}
|
}
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setVgrow(Node node, Priority priority) {
|
||||||
|
node.getProperties().put("ComponentList.vgrow", priority);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher
|
||||||
|
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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<T> extends ListCell<T> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
@@ -36,6 +36,10 @@ public class SpinnerPane extends Control {
|
|||||||
private final BooleanProperty loading = new SimpleBooleanProperty(this, "loading");
|
private final BooleanProperty loading = new SimpleBooleanProperty(this, "loading");
|
||||||
private final StringProperty failedReason = new SimpleStringProperty(this, "failedReason");
|
private final StringProperty failedReason = new SimpleStringProperty(this, "failedReason");
|
||||||
|
|
||||||
|
public SpinnerPane() {
|
||||||
|
getStyleClass().add("spinner-pane");
|
||||||
|
}
|
||||||
|
|
||||||
public void showSpinner() {
|
public void showSpinner() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
}
|
}
|
||||||
@@ -99,7 +103,6 @@ public class SpinnerPane extends Control {
|
|||||||
protected Skin(SpinnerPane control) {
|
protected Skin(SpinnerPane control) {
|
||||||
super(control);
|
super(control);
|
||||||
|
|
||||||
root.getStyleClass().add("spinner-pane");
|
|
||||||
topPane.getChildren().setAll(spinner);
|
topPane.getChildren().setAll(spinner);
|
||||||
topPane.getStyleClass().add("notice-pane");
|
topPane.getStyleClass().add("notice-pane");
|
||||||
failedPane.getChildren().setAll(failedReasonLabel);
|
failedPane.getChildren().setAll(failedReasonLabel);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import org.jackhuang.hmcl.mod.curse.CurseAddon;
|
|||||||
import org.jackhuang.hmcl.mod.curse.CurseModManager;
|
import org.jackhuang.hmcl.mod.curse.CurseModManager;
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.setting.Profiles;
|
import org.jackhuang.hmcl.setting.Profiles;
|
||||||
|
import org.jackhuang.hmcl.setting.Theme;
|
||||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||||
@@ -85,37 +86,37 @@ public class DownloadPage extends BorderPane implements DecoratorPage {
|
|||||||
AdvancedListBox sideBar = new AdvancedListBox()
|
AdvancedListBox sideBar = new AdvancedListBox()
|
||||||
.addNavigationDrawerItem(item -> {
|
.addNavigationDrawerItem(item -> {
|
||||||
item.setTitle(i18n("install.new_game"));
|
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());
|
item.setOnAction(e -> Versions.addNewGame());
|
||||||
})
|
})
|
||||||
.startCategory(i18n("download"))
|
.startCategory(i18n("download"))
|
||||||
.addNavigationDrawerItem(item -> {
|
.addNavigationDrawerItem(item -> {
|
||||||
item.setTitle(i18n("mods"));
|
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.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(modTab));
|
||||||
item.setOnAction(e -> tab.getSelectionModel().select(modTab));
|
item.setOnAction(e -> tab.getSelectionModel().select(modTab));
|
||||||
})
|
})
|
||||||
.addNavigationDrawerItem(settingsItem -> {
|
.addNavigationDrawerItem(settingsItem -> {
|
||||||
settingsItem.setTitle(i18n("modpack"));
|
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.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(modpackTab));
|
||||||
settingsItem.setOnAction(e -> tab.getSelectionModel().select(modpackTab));
|
settingsItem.setOnAction(e -> tab.getSelectionModel().select(modpackTab));
|
||||||
})
|
})
|
||||||
.addNavigationDrawerItem(item -> {
|
.addNavigationDrawerItem(item -> {
|
||||||
item.setTitle(i18n("resourcepack"));
|
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.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(resourcePackTab));
|
||||||
item.setOnAction(e -> tab.getSelectionModel().select(resourcePackTab));
|
item.setOnAction(e -> tab.getSelectionModel().select(resourcePackTab));
|
||||||
})
|
})
|
||||||
// .addNavigationDrawerItem(item -> {
|
// .addNavigationDrawerItem(item -> {
|
||||||
// item.setTitle(i18n("download.curseforge.customization"));
|
// 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.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(customizationTab));
|
||||||
// item.setOnAction(e -> tab.getSelectionModel().select(customizationTab));
|
// item.setOnAction(e -> tab.getSelectionModel().select(customizationTab));
|
||||||
// })
|
// })
|
||||||
.addNavigationDrawerItem(item -> {
|
.addNavigationDrawerItem(item -> {
|
||||||
item.setTitle(i18n("world"));
|
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.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(worldTab));
|
||||||
item.setOnAction(e -> tab.getSelectionModel().select(worldTab));
|
item.setOnAction(e -> tab.getSelectionModel().select(worldTab));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import javafx.scene.control.ListCell;
|
|||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||||
@@ -105,6 +106,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
|||||||
} else {
|
} else {
|
||||||
centrePane.getContent().setAll(list);
|
centrePane.getContent().setAll(list);
|
||||||
}
|
}
|
||||||
|
ComponentList.setVgrow(list, Priority.ALWAYS);
|
||||||
|
|
||||||
InvalidationListener listener = o -> list.getItems().setAll(loadVersions());
|
InvalidationListener listener = o -> list.getItems().setAll(loadVersions());
|
||||||
chkRelease.selectedProperty().addListener(listener);
|
chkRelease.selectedProperty().addListener(listener);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import javafx.beans.property.ReadOnlyObjectWrapper;
|
|||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.setting.Profiles;
|
import org.jackhuang.hmcl.setting.Profiles;
|
||||||
|
import org.jackhuang.hmcl.setting.Theme;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||||
@@ -67,46 +68,46 @@ public class LauncherSettingsPage extends BorderPane implements DecoratorPage {
|
|||||||
{
|
{
|
||||||
AdvancedListBox sideBar = new AdvancedListBox()
|
AdvancedListBox sideBar = new AdvancedListBox()
|
||||||
.addNavigationDrawerItem(settingsItem -> {
|
.addNavigationDrawerItem(settingsItem -> {
|
||||||
settingsItem.setTitle(i18n("settings.game.current"));
|
settingsItem.setTitle(i18n("settings.type.global.manage"));
|
||||||
settingsItem.setLeftGraphic(wrap(SVG.gamepad(null, 20, 20)));
|
settingsItem.setLeftGraphic(wrap(SVG.gamepad(Theme.blackFillBinding(), 24, 24)));
|
||||||
settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(gameTab));
|
settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(gameTab));
|
||||||
settingsItem.setOnAction(e -> tab.getSelectionModel().select(gameTab));
|
settingsItem.setOnAction(e -> tab.getSelectionModel().select(gameTab));
|
||||||
})
|
})
|
||||||
.startCategory(i18n("launcher"))
|
.startCategory(i18n("launcher"))
|
||||||
.addNavigationDrawerItem(settingsItem -> {
|
.addNavigationDrawerItem(settingsItem -> {
|
||||||
settingsItem.setTitle(i18n("settings.launcher.general"));
|
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.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(settingsTab));
|
||||||
settingsItem.setOnAction(e -> tab.getSelectionModel().select(settingsTab));
|
settingsItem.setOnAction(e -> tab.getSelectionModel().select(settingsTab));
|
||||||
})
|
})
|
||||||
.addNavigationDrawerItem(personalizationItem -> {
|
.addNavigationDrawerItem(personalizationItem -> {
|
||||||
personalizationItem.setTitle(i18n("settings.launcher.appearance"));
|
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.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(personalizationTab));
|
||||||
personalizationItem.setOnAction(e -> tab.getSelectionModel().select(personalizationTab));
|
personalizationItem.setOnAction(e -> tab.getSelectionModel().select(personalizationTab));
|
||||||
})
|
})
|
||||||
.addNavigationDrawerItem(downloadItem -> {
|
.addNavigationDrawerItem(downloadItem -> {
|
||||||
downloadItem.setTitle(i18n("download"));
|
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.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(downloadTab));
|
||||||
downloadItem.setOnAction(e -> tab.getSelectionModel().select(downloadTab));
|
downloadItem.setOnAction(e -> tab.getSelectionModel().select(downloadTab));
|
||||||
})
|
})
|
||||||
.startCategory(i18n("help"))
|
.startCategory(i18n("help"))
|
||||||
.addNavigationDrawerItem(helpItem -> {
|
.addNavigationDrawerItem(helpItem -> {
|
||||||
helpItem.setTitle(i18n("help"));
|
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.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(helpTab));
|
||||||
helpItem.setOnAction(e -> tab.getSelectionModel().select(helpTab));
|
helpItem.setOnAction(e -> tab.getSelectionModel().select(helpTab));
|
||||||
})
|
})
|
||||||
.addNavigationDrawerItem(sponsorItem -> {
|
.addNavigationDrawerItem(sponsorItem -> {
|
||||||
sponsorItem.setTitle(i18n("sponsor"));
|
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.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(sponsorTab));
|
||||||
sponsorItem.setOnAction(e -> tab.getSelectionModel().select(sponsorTab));
|
sponsorItem.setOnAction(e -> tab.getSelectionModel().select(sponsorTab));
|
||||||
})
|
})
|
||||||
.addNavigationDrawerItem(aboutItem -> {
|
.addNavigationDrawerItem(aboutItem -> {
|
||||||
aboutItem.setTitle(i18n("about"));
|
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.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(aboutTab));
|
||||||
aboutItem.setOnAction(e -> tab.getSelectionModel().select(aboutTab));
|
aboutItem.setOnAction(e -> tab.getSelectionModel().select(aboutTab));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui.main;
|
package org.jackhuang.hmcl.ui.main;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.SkinBase;
|
import javafx.scene.control.SkinBase;
|
||||||
import javafx.scene.layout.BorderPane;
|
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.Controllers;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.account.AccountAdvancedListItem;
|
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.AdvancedListBox;
|
||||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||||
import org.jackhuang.hmcl.ui.construct.TabHeader;
|
import org.jackhuang.hmcl.ui.construct.TabHeader;
|
||||||
@@ -140,13 +138,7 @@ public class RootPage extends DecoratorTabPage {
|
|||||||
|
|
||||||
// first item in left sidebar
|
// first item in left sidebar
|
||||||
AccountAdvancedListItem accountListItem = new AccountAdvancedListItem();
|
AccountAdvancedListItem accountListItem = new AccountAdvancedListItem();
|
||||||
accountListItem.setOnAction(e -> {
|
accountListItem.setOnAction(e -> Controllers.navigate(Controllers.getAccountListPage()));
|
||||||
Controllers.navigate(Controllers.getAccountListPage());
|
|
||||||
|
|
||||||
if (Accounts.getAccounts().isEmpty()) {
|
|
||||||
Controllers.dialog(new CreateAccountPane());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
accountListItem.accountProperty().bind(Accounts.selectedAccountProperty());
|
accountListItem.accountProperty().bind(Accounts.selectedAccountProperty());
|
||||||
|
|
||||||
// second item in left sidebar
|
// second item in left sidebar
|
||||||
@@ -176,6 +168,14 @@ public class RootPage extends DecoratorTabPage {
|
|||||||
downloadItem.setOnAction(e -> Controllers.navigate(Controllers.getDownloadPage()));
|
downloadItem.setOnAction(e -> Controllers.navigate(Controllers.getDownloadPage()));
|
||||||
|
|
||||||
// fifth item in left sidebar
|
// 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();
|
AdvancedListItem launcherSettingsItem = new AdvancedListItem();
|
||||||
launcherSettingsItem
|
launcherSettingsItem
|
||||||
.setLeftGraphic(AdvancedListItem.createImageView(newImage("/assets/img/command.png")).getKey());
|
.setLeftGraphic(AdvancedListItem.createImageView(newImage("/assets/img/command.png")).getKey());
|
||||||
@@ -191,6 +191,8 @@ public class RootPage extends DecoratorTabPage {
|
|||||||
.add(gameListItem)
|
.add(gameListItem)
|
||||||
.add(gameItem)
|
.add(gameItem)
|
||||||
.add(downloadItem)
|
.add(downloadItem)
|
||||||
|
.startCategory(i18n("settings.launcher.general").toLowerCase())
|
||||||
|
// .add(multiplayerItem)
|
||||||
.add(launcherSettingsItem);
|
.add(launcherSettingsItem);
|
||||||
|
|
||||||
// the root page, with the sidebar in left, navigator in center.
|
// 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 boolean checkedModpack = false;
|
||||||
|
|
||||||
private void onRefreshedVersions(HMCLGameRepository repository) {
|
private void onRefreshedVersions(HMCLGameRepository repository) {
|
||||||
@@ -247,7 +228,7 @@ public class RootPage extends DecoratorTabPage {
|
|||||||
.thenApplyAsync(modpack -> ModpackHelper
|
.thenApplyAsync(modpack -> ModpackHelper
|
||||||
.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(),
|
.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(),
|
||||||
modpack)
|
modpack)
|
||||||
.withRunAsync(Schedulers.javafx(), this::checkAccount).executor())
|
.executor())
|
||||||
.thenAcceptAsync(Schedulers.javafx(), executor -> {
|
.thenAcceptAsync(Schedulers.javafx(), executor -> {
|
||||||
Controllers.taskDialog(executor, i18n("modpack.installing"));
|
Controllers.taskDialog(executor, i18n("modpack.installing"));
|
||||||
executor.start();
|
executor.start();
|
||||||
@@ -255,8 +236,6 @@ public class RootPage extends DecoratorTabPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkAccount();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ public abstract class SettingsView extends StackPane {
|
|||||||
StackPane sponsorPane = new StackPane();
|
StackPane sponsorPane = new StackPane();
|
||||||
sponsorPane.setCursor(Cursor.HAND);
|
sponsorPane.setCursor(Cursor.HAND);
|
||||||
sponsorPane.setOnMouseClicked(e -> onSponsor());
|
sponsorPane.setOnMouseClicked(e -> onSponsor());
|
||||||
|
sponsorPane.setPadding(new Insets(8, 0, 8, 0));
|
||||||
|
|
||||||
GridPane gridPane = new GridPane();
|
GridPane gridPane = new GridPane();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher
|
||||||
|
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.ui.multiplayer;
|
||||||
|
|
||||||
|
public class MultiplayerManager {
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
DISCONNECTED,
|
||||||
|
MASTER,
|
||||||
|
SLAVE
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher
|
||||||
|
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("multiplayer"), -1));
|
||||||
|
|
||||||
|
private final ObjectProperty<MultiplayerManager.State> multiplayerState = new SimpleObjectProperty<>();
|
||||||
|
private final ReadOnlyObjectWrapper<DiscoveryInfo> natState = new ReadOnlyObjectWrapper<>();
|
||||||
|
|
||||||
|
public MultiplayerPage() {
|
||||||
|
testNAT();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Skin<?> createDefaultSkin() {
|
||||||
|
return new MultiplayerPageSkin(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultiplayerManager.State getMultiplayerState() {
|
||||||
|
return multiplayerState.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<MultiplayerManager.State> multiplayerStateProperty() {
|
||||||
|
return multiplayerState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMultiplayerState(MultiplayerManager.State multiplayerState) {
|
||||||
|
this.multiplayerState.set(multiplayerState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscoveryInfo getNatState() {
|
||||||
|
return natState.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyObjectProperty<DiscoveryInfo> 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<State> stateProperty() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher
|
||||||
|
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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<MultiplayerPage> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
* Hello Minecraft! Launcher
|
|
||||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
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> profile = new SimpleObjectProperty<Profile>() {
|
|
||||||
|
|
||||||
@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<Profile> profileProperty() {
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -89,6 +89,7 @@ public class ModDownloadPage extends Control implements DecoratorPage {
|
|||||||
List<CurseAddon.LatestFile> files = CurseModManager.getFiles(addon);
|
List<CurseAddon.LatestFile> files = CurseModManager.getFiles(addon);
|
||||||
items.setAll(files.stream()
|
items.setAll(files.stream()
|
||||||
.filter(file -> file.getGameVersion().contains(gameVersion.get()))
|
.filter(file -> file.getGameVersion().contains(gameVersion.get()))
|
||||||
|
.sorted(Comparator.comparing(CurseAddon.LatestFile::getParsedFileDate).reversed())
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
return;
|
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());
|
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 {
|
public interface DownloadCallback {
|
||||||
void download(Profile profile, @Nullable String version, CurseAddon.LatestFile file);
|
void download(Profile profile, @Nullable String version, CurseAddon.LatestFile file);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
|||||||
|
|
||||||
private ModManager modManager;
|
private ModManager modManager;
|
||||||
private LibraryAnalyzer libraryAnalyzer;
|
private LibraryAnalyzer libraryAnalyzer;
|
||||||
|
private Profile profile;
|
||||||
|
private String versionId;
|
||||||
|
|
||||||
public ModListPage() {
|
public ModListPage() {
|
||||||
FXUtils.applyDragListener(this, it -> Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it)), mods -> {
|
FXUtils.applyDragListener(this, it -> Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it)), mods -> {
|
||||||
@@ -79,6 +81,9 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void loadVersion(Profile profile, String id) {
|
public void loadVersion(Profile profile, String id) {
|
||||||
|
this.profile = profile;
|
||||||
|
this.versionId = versionId;
|
||||||
|
|
||||||
libraryAnalyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(id));
|
libraryAnalyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(id));
|
||||||
modded.set(libraryAnalyzer.hasModLoader());
|
modded.set(libraryAnalyzer.hasModLoader());
|
||||||
loadMods(profile.getRepository().getModManager(id));
|
loadMods(profile.getRepository().getModManager(id));
|
||||||
@@ -167,6 +172,10 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
|||||||
.forEach(info -> info.setActive(false));
|
.forEach(info -> info.setActive(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void openModFolder() {
|
||||||
|
FXUtils.openFolder(new File(profile.getRepository().getRunDirectory(versionId), "mods"));
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isModded() {
|
public boolean isModded() {
|
||||||
return modded.get();
|
return modded.get();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,16 +22,15 @@ import com.jfoenix.controls.JFXCheckBox;
|
|||||||
import com.jfoenix.controls.JFXDialogLayout;
|
import com.jfoenix.controls.JFXDialogLayout;
|
||||||
import com.jfoenix.controls.JFXListView;
|
import com.jfoenix.controls.JFXListView;
|
||||||
import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject;
|
import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject;
|
||||||
import com.jfoenix.effects.JFXDepthManager;
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.SelectionMode;
|
import javafx.scene.control.SelectionMode;
|
||||||
import javafx.scene.control.SkinBase;
|
import javafx.scene.control.SkinBase;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.layout.BorderPane;
|
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.StackPane;
|
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.Controllers;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
import org.jackhuang.hmcl.ui.construct.*;
|
||||||
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.util.StringUtils;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
@@ -59,7 +55,7 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
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.Lang.mapOf;
|
||||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||||
import static org.jackhuang.hmcl.util.StringUtils.isNotBlank;
|
import static org.jackhuang.hmcl.util.StringUtils.isNotBlank;
|
||||||
@@ -71,42 +67,44 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
|||||||
super(skinnable);
|
super(skinnable);
|
||||||
|
|
||||||
StackPane pane = new StackPane();
|
StackPane pane = new StackPane();
|
||||||
|
pane.setPadding(new Insets(10));
|
||||||
pane.getStyleClass().addAll("notice-pane");
|
pane.getStyleClass().addAll("notice-pane");
|
||||||
|
|
||||||
BorderPane root = new BorderPane();
|
ComponentList root = new ComponentList();
|
||||||
|
root.getStyleClass().add("no-padding");
|
||||||
JFXListView<ModInfoObject> listView = new JFXListView<>();
|
JFXListView<ModInfoObject> listView = new JFXListView<>();
|
||||||
|
|
||||||
{
|
{
|
||||||
HBox toolbar = new HBox();
|
HBox toolbar = new HBox();
|
||||||
toolbar.getStyleClass().add("jfx-tool-bar-second");
|
toolbar.getChildren().setAll(
|
||||||
JFXDepthManager.setDepth(toolbar, 1);
|
createToolbarButton2(i18n("button.refresh"), SVG::refresh, skinnable::refresh),
|
||||||
toolbar.setPickOnBounds(false);
|
createToolbarButton2(i18n("mods.add"), SVG::plus, skinnable::add),
|
||||||
|
createToolbarButton2(i18n("button.remove"), SVG::delete, () -> {
|
||||||
toolbar.getChildren().add(createToolbarButton(i18n("button.refresh"), SVG::refresh, skinnable::refresh));
|
Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), () -> {
|
||||||
toolbar.getChildren().add(createToolbarButton(i18n("mods.add"), SVG::plus, skinnable::add));
|
skinnable.removeSelected(listView.getSelectionModel().getSelectedItems());
|
||||||
toolbar.getChildren().add(createToolbarButton(i18n("button.remove"), SVG::delete, () -> {
|
}, null);
|
||||||
Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), () -> {
|
}),
|
||||||
skinnable.removeSelected(listView.getSelectionModel().getSelectedItems());
|
createToolbarButton2(i18n("mods.enable"), SVG::check, () ->
|
||||||
}, null);
|
skinnable.enableSelected(listView.getSelectionModel().getSelectedItems())),
|
||||||
}));
|
createToolbarButton2(i18n("mods.disable"), SVG::close, () ->
|
||||||
toolbar.getChildren().add(createToolbarButton(i18n("mods.enable"), SVG::check, () ->
|
skinnable.disableSelected(listView.getSelectionModel().getSelectedItems())),
|
||||||
skinnable.enableSelected(listView.getSelectionModel().getSelectedItems())));
|
createToolbarButton2(i18n("folder.mod"), SVG::folderOpen, () ->
|
||||||
toolbar.getChildren().add(createToolbarButton(i18n("mods.disable"), SVG::close, () ->
|
skinnable.openModFolder()));
|
||||||
skinnable.disableSelected(listView.getSelectionModel().getSelectedItems())));
|
root.getContent().add(toolbar);
|
||||||
root.setTop(toolbar);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
SpinnerPane center = new SpinnerPane();
|
SpinnerPane center = new SpinnerPane();
|
||||||
|
ComponentList.setVgrow(center, Priority.ALWAYS);
|
||||||
center.getStyleClass().add("large-spinner-pane");
|
center.getStyleClass().add("large-spinner-pane");
|
||||||
center.loadingProperty().bind(skinnable.loadingProperty());
|
center.loadingProperty().bind(skinnable.loadingProperty());
|
||||||
|
|
||||||
listView.setCellFactory(x -> new ModInfoListCell(listView));
|
listView.setCellFactory(x -> new ModInfoListCell());
|
||||||
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||||
Bindings.bindContent(listView.getItems(), skinnable.getItems());
|
Bindings.bindContent(listView.getItems(), skinnable.getItems());
|
||||||
|
|
||||||
center.setContent(listView);
|
center.setContent(listView);
|
||||||
root.setCenter(center);
|
root.getContent().add(center);
|
||||||
}
|
}
|
||||||
|
|
||||||
Label label = new Label(i18n("mods.not_modded"));
|
Label label = new Label(i18n("mods.not_modded"));
|
||||||
@@ -231,23 +229,31 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class ModInfoListCell extends FloatListCell<ModInfoObject> {
|
static class ModInfoListCell extends MDListCell<ModInfoObject> {
|
||||||
JFXCheckBox checkBox = new JFXCheckBox();
|
JFXCheckBox checkBox = new JFXCheckBox();
|
||||||
TwoLineListItem content = new TwoLineListItem();
|
TwoLineListItem content = new TwoLineListItem();
|
||||||
JFXButton infoButton = new JFXButton();
|
JFXButton infoButton = new JFXButton();
|
||||||
|
JFXButton revealButton = new JFXButton();
|
||||||
BooleanProperty booleanProperty;
|
BooleanProperty booleanProperty;
|
||||||
|
|
||||||
ModInfoListCell(JFXListView<ModInfoObject> listView) {
|
ModInfoListCell() {
|
||||||
super(listView);
|
|
||||||
HBox container = new HBox(8);
|
HBox container = new HBox(8);
|
||||||
|
container.setPickOnBounds(false);
|
||||||
container.setAlignment(Pos.CENTER_LEFT);
|
container.setAlignment(Pos.CENTER_LEFT);
|
||||||
pane.getChildren().add(container);
|
|
||||||
HBox.setHgrow(content, Priority.ALWAYS);
|
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.getStyleClass().add("toggle-icon4");
|
||||||
infoButton.setGraphic(FXUtils.limitingSize(SVG.informationOutline(Theme.blackFillBinding(), 24, 24), 24, 24));
|
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
|
@Override
|
||||||
@@ -259,6 +265,9 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
|||||||
checkBox.selectedProperty().unbindBidirectional(booleanProperty);
|
checkBox.selectedProperty().unbindBidirectional(booleanProperty);
|
||||||
}
|
}
|
||||||
checkBox.selectedProperty().bindBidirectional(booleanProperty = dataItem.active);
|
checkBox.selectedProperty().bindBidirectional(booleanProperty = dataItem.active);
|
||||||
|
revealButton.setOnMouseClicked(e -> {
|
||||||
|
FXUtils.showFileInExplorer(dataItem.getModInfo().getFile());
|
||||||
|
});
|
||||||
infoButton.setOnMouseClicked(e -> {
|
infoButton.setOnMouseClicked(e -> {
|
||||||
Controllers.dialog(new ModInfoDialog(dataItem));
|
Controllers.dialog(new ModInfoDialog(dataItem));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Hello Minecraft! Launcher
|
* Hello Minecraft! Launcher
|
||||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* 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();
|
AdvancedListItem versionSettingsItem = new AdvancedListItem();
|
||||||
versionSettingsItem.getStyleClass().add("navigation-drawer-item");
|
versionSettingsItem.getStyleClass().add("navigation-drawer-item");
|
||||||
versionSettingsItem.setTitle(i18n("settings.game"));
|
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.setActionButtonVisible(false);
|
||||||
versionSettingsItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.versionSettingsTab));
|
versionSettingsItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.versionSettingsTab));
|
||||||
versionSettingsItem.setOnAction(e -> control.tab.getSelectionModel().select(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();
|
AdvancedListItem modListItem = new AdvancedListItem();
|
||||||
modListItem.getStyleClass().add("navigation-drawer-item");
|
modListItem.getStyleClass().add("navigation-drawer-item");
|
||||||
modListItem.setTitle(i18n("mods.manage"));
|
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.setActionButtonVisible(false);
|
||||||
modListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.modListTab));
|
modListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.modListTab));
|
||||||
modListItem.setOnAction(e -> control.tab.getSelectionModel().select(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();
|
AdvancedListItem curseModListItem = new AdvancedListItem();
|
||||||
curseModListItem.getStyleClass().add("navigation-drawer-item");
|
curseModListItem.getStyleClass().add("navigation-drawer-item");
|
||||||
curseModListItem.setTitle(i18n("mods.download"));
|
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.setActionButtonVisible(false);
|
||||||
curseModListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.curseModListTab));
|
curseModListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.curseModListTab));
|
||||||
curseModListItem.setOnAction(e -> control.tab.getSelectionModel().select(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();
|
AdvancedListItem installerListItem = new AdvancedListItem();
|
||||||
installerListItem.getStyleClass().add("navigation-drawer-item");
|
installerListItem.getStyleClass().add("navigation-drawer-item");
|
||||||
installerListItem.setTitle(i18n("settings.tabs.installers"));
|
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.setActionButtonVisible(false);
|
||||||
installerListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.installerListTab));
|
installerListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.installerListTab));
|
||||||
installerListItem.setOnAction(e -> control.tab.getSelectionModel().select(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();
|
AdvancedListItem worldListItem = new AdvancedListItem();
|
||||||
worldListItem.getStyleClass().add("navigation-drawer-item");
|
worldListItem.getStyleClass().add("navigation-drawer-item");
|
||||||
worldListItem.setTitle(i18n("world.manage"));
|
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.setActionButtonVisible(false);
|
||||||
worldListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.worldListTab));
|
worldListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.worldListTab));
|
||||||
worldListItem.setOnAction(e -> control.tab.getSelectionModel().select(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();
|
AdvancedListItem upgradeItem = new AdvancedListItem();
|
||||||
upgradeItem.getStyleClass().add("navigation-drawer-item");
|
upgradeItem.getStyleClass().add("navigation-drawer-item");
|
||||||
upgradeItem.setTitle(i18n("version.update"));
|
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.setActionButtonVisible(false);
|
||||||
upgradeItem.visibleProperty().bind(control.currentVersionUpgradable);
|
upgradeItem.visibleProperty().bind(control.currentVersionUpgradable);
|
||||||
upgradeItem.setOnAction(e -> control.updateGame());
|
upgradeItem.setOnAction(e -> control.updateGame());
|
||||||
@@ -328,21 +328,21 @@ public class VersionPage extends Control implements DecoratorPage, ModDownloadPa
|
|||||||
AdvancedListItem testGameItem = new AdvancedListItem();
|
AdvancedListItem testGameItem = new AdvancedListItem();
|
||||||
testGameItem.getStyleClass().add("navigation-drawer-item");
|
testGameItem.getStyleClass().add("navigation-drawer-item");
|
||||||
testGameItem.setTitle(i18n("version.launch.test"));
|
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.setActionButtonVisible(false);
|
||||||
testGameItem.setOnAction(e -> control.testGame());
|
testGameItem.setOnAction(e -> control.testGame());
|
||||||
|
|
||||||
AdvancedListItem browseMenuItem = new AdvancedListItem();
|
AdvancedListItem browseMenuItem = new AdvancedListItem();
|
||||||
browseMenuItem.getStyleClass().add("navigation-drawer-item");
|
browseMenuItem.getStyleClass().add("navigation-drawer-item");
|
||||||
browseMenuItem.setTitle(i18n("settings.game.exploration"));
|
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.setActionButtonVisible(false);
|
||||||
browseMenuItem.setOnAction(e -> browsePopup.show(browseMenuItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, browseMenuItem.getWidth(), 0));
|
browseMenuItem.setOnAction(e -> browsePopup.show(browseMenuItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, browseMenuItem.getWidth(), 0));
|
||||||
|
|
||||||
AdvancedListItem managementItem = new AdvancedListItem();
|
AdvancedListItem managementItem = new AdvancedListItem();
|
||||||
managementItem.getStyleClass().add("navigation-drawer-item");
|
managementItem.getStyleClass().add("navigation-drawer-item");
|
||||||
managementItem.setTitle(i18n("settings.game.management"));
|
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.setActionButtonVisible(false);
|
||||||
managementItem.setOnAction(e -> managementPopup.show(managementItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, managementItem.getWidth(), 0));
|
managementItem.setOnAction(e -> managementPopup.show(managementItem, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT, managementItem.getWidth(), 0));
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,11 @@
|
|||||||
package org.jackhuang.hmcl.ui.versions;
|
package org.jackhuang.hmcl.ui.versions;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
import com.jfoenix.controls.JFXButton;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
|
|
||||||
|
import org.jackhuang.hmcl.auth.Account;
|
||||||
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
|
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
|
||||||
import org.jackhuang.hmcl.game.GameDirectoryType;
|
import org.jackhuang.hmcl.game.GameDirectoryType;
|
||||||
import org.jackhuang.hmcl.game.GameRepository;
|
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.task.TaskExecutor;
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
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.MessageDialogPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.PromptDialogPane;
|
import org.jackhuang.hmcl.ui.construct.PromptDialogPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||||
@@ -50,6 +56,7 @@ import java.net.URL;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
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) {
|
public static void generateLaunchScript(Profile profile, String id) {
|
||||||
if (checkForLaunching(profile, id)) {
|
if (!checkVersionForLaunching(profile, id))
|
||||||
|
return;
|
||||||
|
ensureSelectedAccount(account -> {
|
||||||
GameRepository repository = profile.getRepository();
|
GameRepository repository = profile.getRepository();
|
||||||
FileChooser chooser = new FileChooser();
|
FileChooser chooser = new FileChooser();
|
||||||
if (repository.getRunDirectory(id).isDirectory())
|
if (repository.getRunDirectory(id).isDirectory())
|
||||||
@@ -196,33 +205,55 @@ public final class Versions {
|
|||||||
: new FileChooser.ExtensionFilter(i18n("extension.sh"), "*.sh"));
|
: new FileChooser.ExtensionFilter(i18n("extension.sh"), "*.sh"));
|
||||||
File file = chooser.showSaveDialog(Controllers.getStage());
|
File file = chooser.showSaveDialog(Controllers.getStage());
|
||||||
if (file != null)
|
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) {
|
public static void launch(Profile profile, String id) {
|
||||||
if (checkForLaunching(profile, id))
|
if (!checkVersionForLaunching(profile, id))
|
||||||
new LauncherHelper(profile, Accounts.getSelectedAccount(), id).launch();
|
return;
|
||||||
|
ensureSelectedAccount(account -> {
|
||||||
|
new LauncherHelper(profile, account, id).launch();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void testGame(Profile profile, String id) {
|
public static void testGame(Profile profile, String id) {
|
||||||
if (checkForLaunching(profile, id)) {
|
if (!checkVersionForLaunching(profile, id))
|
||||||
LauncherHelper helper = new LauncherHelper(profile, Accounts.getSelectedAccount(), id);
|
return;
|
||||||
|
ensureSelectedAccount(account -> {
|
||||||
|
LauncherHelper helper = new LauncherHelper(profile, account, id);
|
||||||
helper.setTestMode();
|
helper.setTestMode();
|
||||||
helper.launch();
|
helper.launch();
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean checkForLaunching(Profile profile, String id) {
|
private static boolean checkVersionForLaunching(Profile profile, String id) {
|
||||||
if (Accounts.getSelectedAccount() == null)
|
if (id == null || !profile.getRepository().isLoaded() || !profile.getRepository().hasVersion(id)) {
|
||||||
Controllers.getRootPage().checkAccountForcibly();
|
|
||||||
else if (id == null || !profile.getRepository().isLoaded() || !profile.getRepository().hasVersion(id))
|
|
||||||
Controllers.dialog(i18n("version.empty.launch"), i18n("launch.failed"), MessageDialogPane.MessageType.ERROR, () -> {
|
Controllers.dialog(i18n("version.empty.launch"), i18n("launch.failed"), MessageDialogPane.MessageType.ERROR, () -> {
|
||||||
Controllers.navigate(Controllers.getGameListPage());
|
Controllers.navigate(Controllers.getGameListPage());
|
||||||
});
|
});
|
||||||
else
|
return false;
|
||||||
|
} else {
|
||||||
return true;
|
return true;
|
||||||
return false;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ensureSelectedAccount(Consumer<Account> 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) {
|
public static void modifyGlobalSettings(Profile profile) {
|
||||||
|
|||||||
@@ -162,7 +162,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.advanced-list-box-content {
|
.advanced-list-box-content {
|
||||||
-fx-padding: 12 0 12 0;
|
-fx-padding: 12 0 0 0;
|
||||||
-fx-spacing: 0;
|
-fx-spacing: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -793,6 +793,10 @@
|
|||||||
-fx-border-width: 0 0 1 0;
|
-fx-border-width: 0 0 1 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.md-list-cell:selected {
|
||||||
|
-fx-background-color: derive(-fx-base-color, 60%);
|
||||||
|
}
|
||||||
|
|
||||||
.options-sublist {
|
.options-sublist {
|
||||||
-fx-background-color: white;
|
-fx-background-color: white;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.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.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.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.error.unknown=Failed to log in. Microsoft respond with error code %d.
|
||||||
account.methods.microsoft.logging_in=Logging in...
|
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.
|
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.not_modded=You should install a modloader first (Fabric, Forge or LiteLoader)
|
||||||
mods.url=Official Page
|
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=Datapacks
|
||||||
datapack.add=Install datapack
|
datapack.add=Install datapack
|
||||||
datapack.choose_datapack=Choose the datapack zip to import
|
datapack.choose_datapack=Choose the datapack zip to import
|
||||||
@@ -506,6 +536,7 @@ selector.custom=Custom
|
|||||||
settings=Settings
|
settings=Settings
|
||||||
|
|
||||||
settings.advanced=Advanced 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_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.dont_check_jvm_validity=Don't check whether JVM can launch the game or not
|
||||||
settings.advanced.game_dir.default=Standard (.minecraft/)
|
settings.advanced.game_dir.default=Standard (.minecraft/)
|
||||||
|
|||||||
@@ -58,6 +58,15 @@ account.manage=帳戶列表
|
|||||||
account.methods=登入方式
|
account.methods=登入方式
|
||||||
account.methods.authlib_injector=authlib-injector 登入
|
account.methods.authlib_injector=authlib-injector 登入
|
||||||
account.methods.microsoft=微軟帳戶
|
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.offline=離線模式
|
||||||
account.methods.yggdrasil=正版登入
|
account.methods.yggdrasil=正版登入
|
||||||
account.missing=沒有遊戲帳戶
|
account.missing=沒有遊戲帳戶
|
||||||
@@ -65,7 +74,7 @@ account.missing.add=按一下此處加入帳戶
|
|||||||
account.password=密碼
|
account.password=密碼
|
||||||
account.skin.file=皮膚圖片檔案
|
account.skin.file=皮膚圖片檔案
|
||||||
account.skin.upload=上傳皮膚
|
account.skin.upload=上傳皮膚
|
||||||
account.skin.upload=皮膚上傳失敗
|
account.skin.upload.failed=皮膚上傳失敗
|
||||||
account.skin.invalid_skin=無法識別的皮膚文件
|
account.skin.invalid_skin=無法識別的皮膚文件
|
||||||
account.username=使用者名稱
|
account.username=使用者名稱
|
||||||
|
|
||||||
@@ -98,6 +107,112 @@ color.custom=自訂顏色
|
|||||||
crash.NoClassDefFound=請確認 Hello Minecraft! Launcher 本體是否完整,或更新您的 Java。
|
crash.NoClassDefFound=請確認 Hello Minecraft! Launcher 本體是否完整,或更新您的 Java。
|
||||||
crash.user_fault=您的系統或 Java 環境可能安裝不當導致本軟體當機,請檢查您的 Java 環境或您的電腦! 可以嘗試重新安裝 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=下載
|
||||||
download.code.404=遠端伺服器沒有需要下載的檔案: %s
|
download.code.404=遠端伺服器沒有需要下載的檔案: %s
|
||||||
download.failed=下載失敗: %1$s,錯誤碼:%2$d
|
download.failed=下載失敗: %1$s,錯誤碼:%2$d
|
||||||
@@ -129,7 +244,8 @@ folder.resourcepacks=資源包資料夾
|
|||||||
folder.saves=遊戲存檔資料夾
|
folder.saves=遊戲存檔資料夾
|
||||||
folder.screenshots=截圖資料夾
|
folder.screenshots=截圖資料夾
|
||||||
|
|
||||||
help=Hello Minecraft! Launcher 說明文件
|
help=說明
|
||||||
|
help.doc=Hello Minecraft! Launcher 說明文件
|
||||||
help.detail=可查閱資料包、整合包製作指南等內容。
|
help.detail=可查閱資料包、整合包製作指南等內容。
|
||||||
|
|
||||||
input.email=[使用者名稱] 必須是電子信箱格式
|
input.email=[使用者名稱] 必須是電子信箱格式
|
||||||
@@ -222,6 +338,7 @@ logwindow.show_lines=顯示行數
|
|||||||
logwindow.terminate_game=結束遊戲處理程序
|
logwindow.terminate_game=結束遊戲處理程序
|
||||||
logwindow.title=記錄
|
logwindow.title=記錄
|
||||||
logwindow.autoscroll=自動滾動
|
logwindow.autoscroll=自動滾動
|
||||||
|
logwindow.export_game_crash_logs=導出遊戲崩潰訊息
|
||||||
|
|
||||||
main_page=首頁
|
main_page=首頁
|
||||||
|
|
||||||
@@ -309,6 +426,35 @@ mods.mangage=模組管理
|
|||||||
mods.name=名稱
|
mods.name=名稱
|
||||||
mods.not_modded=你需要先在自動安裝頁面安裝 Fabric、Forge 或 LiteLoader 才能進行模組管理。
|
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=資料包
|
||||||
datapack.add=加入資料包
|
datapack.add=加入資料包
|
||||||
datapack.choose_datapack=選擇要匯入的資料包壓縮檔
|
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.linux_arm32=缺少 JavaFX 運行環境。是否需要從網絡下載並加載 JavaFX 運行時組件?\n選擇“是”從指定下載源下載 JavaFX 運行時組件並啟動HMCL,選擇“否”退出程式。\n注意:Linux ARM32 平臺下暫時無法下載部分組件,運行時可能造成HMCL崩潰。\n下載源:
|
||||||
repositories.chooser.title=是否下載 JavaFX?
|
repositories.chooser.title=是否下載 JavaFX?
|
||||||
|
|
||||||
|
resourcepack=資源包
|
||||||
|
|
||||||
|
search=搜索
|
||||||
|
search.sort=排序
|
||||||
|
|
||||||
selector.choose=選擇
|
selector.choose=選擇
|
||||||
selector.choose_file=選擇檔案
|
selector.choose_file=選擇檔案
|
||||||
selector.custom=自訂
|
selector.custom=自訂
|
||||||
@@ -419,8 +570,16 @@ settings.icon=遊戲圖示
|
|||||||
|
|
||||||
settings.launcher=啟動器設定
|
settings.launcher=啟動器設定
|
||||||
settings.launcher.common_path.tooltip=啟動器將所有遊戲資源及相依元件庫檔案放於此集中管理,如果遊戲資料夾內有現成的將不會使用公共庫檔案
|
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=下載來源
|
||||||
|
settings.launcher.download_source.auto=自動選擇下載來源
|
||||||
settings.launcher.enable_game_list=在首頁內顯示遊戲列表
|
settings.launcher.enable_game_list=在首頁內顯示遊戲列表
|
||||||
|
settings.launcher.font=字體
|
||||||
|
settings.launcher.general=通用
|
||||||
settings.launcher.language=語言
|
settings.launcher.language=語言
|
||||||
settings.launcher.launcher_log.export=匯出啟動器日誌
|
settings.launcher.launcher_log.export=匯出啟動器日誌
|
||||||
settings.launcher.launcher_log.export.failed=無法匯出日誌
|
settings.launcher.launcher_log.export.failed=無法匯出日誌
|
||||||
@@ -437,8 +596,16 @@ settings.launcher.proxy.port=連線埠
|
|||||||
settings.launcher.proxy.socks=Socks
|
settings.launcher.proxy.socks=Socks
|
||||||
settings.launcher.proxy.username=帳戶
|
settings.launcher.proxy.username=帳戶
|
||||||
settings.launcher.theme=主題
|
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.physical_memory=實體記憶體大小
|
||||||
settings.show_log=查看記錄
|
settings.show_log=查看記錄
|
||||||
settings.tabs.installers=自動安裝
|
settings.tabs.installers=自動安裝
|
||||||
@@ -448,6 +615,7 @@ settings.type.global.manage=全域遊戲設定
|
|||||||
settings.type.global.edit=編輯全域遊戲設定
|
settings.type.global.edit=編輯全域遊戲設定
|
||||||
settings.type.special.enable=啟用遊戲特別設定(不影響其他遊戲版本)
|
settings.type.special.enable=啟用遊戲特別設定(不影響其他遊戲版本)
|
||||||
|
|
||||||
|
sponsor=贊助
|
||||||
sponsor.bmclapi=大中華區下載源由 BMCLAPI 和我的世界中文論壇 (MCBBS) 提供高速下載服務
|
sponsor.bmclapi=大中華區下載源由 BMCLAPI 和我的世界中文論壇 (MCBBS) 提供高速下載服務
|
||||||
sponsor.hmcl=Hello Minecraft! Launcher 是一個免費、開源的 Minecraft 啟動器,允許玩家方便快捷地安裝、管理、執行遊戲。您的贊助將幫助 Hello Minecraft! Launcher 獲得更好的發展、支援穩定高速的遊戲安裝與文件下載服務。點選此處查閱更多詳細訊息。
|
sponsor.hmcl=Hello Minecraft! Launcher 是一個免費、開源的 Minecraft 啟動器,允許玩家方便快捷地安裝、管理、執行遊戲。您的贊助將幫助 Hello Minecraft! Launcher 獲得更好的發展、支援穩定高速的遊戲安裝與文件下載服務。點選此處查閱更多詳細訊息。
|
||||||
|
|
||||||
|
|||||||
@@ -59,8 +59,6 @@ account.failed.no_character=该帐号没有角色
|
|||||||
account.failed.server_response_malformed=无法解析认证服务器响应,可能是服务器故障
|
account.failed.server_response_malformed=无法解析认证服务器响应,可能是服务器故障
|
||||||
account.injector.add=添加认证服务器
|
account.injector.add=添加认证服务器
|
||||||
account.injector.empty=无(点击右侧加号添加)
|
account.injector.empty=无(点击右侧加号添加)
|
||||||
account.injector.manage=管理认证服务器
|
|
||||||
account.injector.manage.title=认证服务器
|
|
||||||
account.injector.http=警告:此服务器使用不安全的 HTTP 协议,您的密码在登录时会被明文传输。
|
account.injector.http=警告:此服务器使用不安全的 HTTP 协议,您的密码在登录时会被明文传输。
|
||||||
account.injector.link.register=注册
|
account.injector.link.register=注册
|
||||||
account.injector.server=认证服务器
|
account.injector.server=认证服务器
|
||||||
@@ -73,8 +71,10 @@ account.methods.microsoft=微软账户
|
|||||||
account.methods.microsoft.close_page=已完成微软账号授权,接下来启动器还需要完成剩余登录步骤。你已经可以关闭本页面了。
|
account.methods.microsoft.close_page=已完成微软账号授权,接下来启动器还需要完成剩余登录步骤。你已经可以关闭本页面了。
|
||||||
account.methods.microsoft.error.add_family=由于你未满 18 岁,你的账号必须被加入到家庭中才能登录游戏。
|
account.methods.microsoft.error.add_family=由于你未满 18 岁,你的账号必须被加入到家庭中才能登录游戏。
|
||||||
account.methods.microsoft.error.missing_xbox_account=你的微软账号尚未关联 XBox 账号,你必须先创建 XBox 账号,才能登录游戏。
|
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.error.unknown=登录失败,错误码:%d
|
||||||
account.methods.microsoft.logging_in=登录中...
|
account.methods.microsoft.logging_in=登录中...
|
||||||
|
account.methods.microsoft.hint=点击确定以登录
|
||||||
account.methods.microsoft.manual=您需要在新打开的浏览器窗口中完成登录。若页面未能打开,您可以点击此处复制链接,并手动在浏览器中打开网页。
|
account.methods.microsoft.manual=您需要在新打开的浏览器窗口中完成登录。若页面未能打开,您可以点击此处复制链接,并手动在浏览器中打开网页。
|
||||||
account.methods.microsoft.waiting_browser=等待在新打开的浏览器窗口中完成登录...
|
account.methods.microsoft.waiting_browser=等待在新打开的浏览器窗口中完成登录...
|
||||||
account.methods.offline=离线模式
|
account.methods.offline=离线模式
|
||||||
@@ -458,6 +458,35 @@ mods.name=名称
|
|||||||
mods.not_modded=你需要先在自动安装页面安装 Fabric、Forge 或 LiteLoader 才能进行模组管理。
|
mods.not_modded=你需要先在自动安装页面安装 Fabric、Forge 或 LiteLoader 才能进行模组管理。
|
||||||
mods.url=官方页面
|
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=数据包
|
||||||
datapack.add=添加数据包
|
datapack.add=添加数据包
|
||||||
datapack.choose_datapack=选择要导入的数据包压缩包
|
datapack.choose_datapack=选择要导入的数据包压缩包
|
||||||
|
|||||||
@@ -50,25 +50,33 @@ public abstract class AccountFactory<T extends Account> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface ProgressCallback {
|
||||||
|
void onProgressChanged(String stageName);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Informs how this account factory verifies user's credential.
|
* Informs how this account factory verifies user's credential.
|
||||||
|
*
|
||||||
* @see AccountLoginType
|
* @see AccountLoginType
|
||||||
*/
|
*/
|
||||||
public abstract AccountLoginType getLoginType();
|
public abstract AccountLoginType getLoginType();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new(to be verified via network) account, and log in.
|
* 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 selector for character selection if multiple characters belong to single account. Pick out which character to act as.
|
||||||
* @param password password of the account if needed.
|
* @param username username of the account if needed.
|
||||||
* @param additionalData extra data for specific account factory.
|
* @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.
|
* @return logged-in account.
|
||||||
* @throws AuthenticationException if an error occurs when logging in.
|
* @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.
|
* Create a existing(stored in local files) account.
|
||||||
|
*
|
||||||
* @param storage serialized account data.
|
* @param storage serialized account data.
|
||||||
* @return account stored in local storage. Credentials may expired, and you should refresh account state later.
|
* @return account stored in local storage. Credentials may expired, and you should refresh account state later.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjecto
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthlibInjectorAccount create(CharacterSelector selector, String username, String password, Object additionalData) throws AuthenticationException {
|
public AuthlibInjectorAccount create(CharacterSelector selector, String username, String password, ProgressCallback progressCallback, Object additionalData) throws AuthenticationException {
|
||||||
Objects.requireNonNull(selector);
|
Objects.requireNonNull(selector);
|
||||||
Objects.requireNonNull(username);
|
Objects.requireNonNull(username);
|
||||||
Objects.requireNonNull(password);
|
Objects.requireNonNull(password);
|
||||||
|
|||||||
@@ -21,14 +21,17 @@ import javafx.beans.binding.ObjectBinding;
|
|||||||
import org.jackhuang.hmcl.auth.*;
|
import org.jackhuang.hmcl.auth.*;
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.Texture;
|
import org.jackhuang.hmcl.auth.yggdrasil.Texture;
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
|
import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
|
||||||
|
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
|
||||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||||
|
|
||||||
public class MicrosoftAccount extends Account {
|
public class MicrosoftAccount extends Account {
|
||||||
|
|
||||||
@@ -77,7 +80,7 @@ public class MicrosoftAccount extends Account {
|
|||||||
@Override
|
@Override
|
||||||
public AuthInfo logIn() throws AuthenticationException {
|
public AuthInfo logIn() throws AuthenticationException {
|
||||||
if (!authenticated) {
|
if (!authenticated) {
|
||||||
if (service.validate(session.getTokenType(), session.getAccessToken())) {
|
if (service.validate(session.getNotAfter(), session.getTokenType(), session.getAccessToken())) {
|
||||||
authenticated = true;
|
authenticated = true;
|
||||||
} else {
|
} else {
|
||||||
MicrosoftSession acquiredSession = service.refresh(session);
|
MicrosoftSession acquiredSession = service.refresh(session);
|
||||||
@@ -116,9 +119,15 @@ public class MicrosoftAccount extends Account {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ObjectBinding<Optional<Map<TextureType, Texture>>> getTextures() {
|
public ObjectBinding<Optional<Map<TextureType, Texture>>> getTextures() {
|
||||||
return BindingMapping.of(service.getProfileRepository().binding(session.getAuthorization()))
|
return BindingMapping.of(service.getProfileRepository().binding(getUUID()))
|
||||||
.map(profile -> profile.flatMap(MicrosoftService::getTextures));
|
.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
|
@Override
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public class MicrosoftAccountFactory extends AccountFactory<MicrosoftAccount> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
Objects.requireNonNull(selector);
|
||||||
|
|
||||||
return new MicrosoftAccount(service, selector);
|
return new MicrosoftAccount(service, selector);
|
||||||
|
|||||||
@@ -17,15 +17,16 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.auth.microsoft;
|
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.JsonParseException;
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
import org.jackhuang.hmcl.auth.*;
|
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.RemoteAuthenticationException;
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.Texture;
|
import org.jackhuang.hmcl.auth.yggdrasil.Texture;
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
|
import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
|
||||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
import org.jackhuang.hmcl.util.gson.*;
|
||||||
import org.jackhuang.hmcl.util.gson.TolerableValidationException;
|
|
||||||
import org.jackhuang.hmcl.util.gson.Validation;
|
|
||||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||||
import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||||
@@ -60,17 +61,17 @@ public class MicrosoftService {
|
|||||||
|
|
||||||
private final OAuthCallback callback;
|
private final OAuthCallback callback;
|
||||||
|
|
||||||
private final ObservableOptionalCache<String, MinecraftProfileResponse, AuthenticationException> profileRepository;
|
private final ObservableOptionalCache<UUID, CompleteGameProfile, AuthenticationException> profileRepository;
|
||||||
|
|
||||||
public MicrosoftService(OAuthCallback callback) {
|
public MicrosoftService(OAuthCallback callback) {
|
||||||
this.callback = requireNonNull(callback);
|
this.callback = requireNonNull(callback);
|
||||||
this.profileRepository = new ObservableOptionalCache<>(authorization -> {
|
this.profileRepository = new ObservableOptionalCache<>(uuid -> {
|
||||||
LOG.info("Fetching properties");
|
LOG.info("Fetching properties of " + uuid);
|
||||||
return getCompleteProfile(authorization);
|
return getCompleteGameProfile(uuid);
|
||||||
}, (uuid, e) -> LOG.log(Level.WARNING, "Failed to fetch properties of " + uuid, e), POOL);
|
}, (uuid, e) -> LOG.log(Level.WARNING, "Failed to fetch properties of " + uuid, e), POOL);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObservableOptionalCache<String, MinecraftProfileResponse, AuthenticationException> getProfileRepository() {
|
public ObservableOptionalCache<UUID, CompleteGameProfile, AuthenticationException> getProfileRepository() {
|
||||||
return profileRepository;
|
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(tokenType);
|
||||||
requireNonNull(accessToken);
|
requireNonNull(accessToken);
|
||||||
|
|
||||||
|
if (System.currentTimeMillis() > notAfter) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
getMinecraftProfile(tokenType, accessToken);
|
getMinecraftProfile(tokenType, accessToken);
|
||||||
return true;
|
return true;
|
||||||
@@ -266,7 +271,7 @@ public class MicrosoftService {
|
|||||||
.createConnection();
|
.createConnection();
|
||||||
int responseCode = conn.getResponseCode();
|
int responseCode = conn.getResponseCode();
|
||||||
if (responseCode == HTTP_NOT_FOUND) {
|
if (responseCode == HTTP_NOT_FOUND) {
|
||||||
throw new NoCharacterException();
|
throw new NoMinecraftJavaEditionProfileException();
|
||||||
} else if (responseCode != 200) {
|
} else if (responseCode != 200) {
|
||||||
throw new ResponseCodeException(new URL("https://api.minecraftservices.com/minecraft/profile"), responseCode);
|
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);
|
return JsonUtils.fromNonNullJson(result, MinecraftProfileResponse.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<CompleteGameProfile> 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> T fromJson(String text, Class<T> typeOfT) throws ServerResponseMalformedException {
|
||||||
|
try {
|
||||||
|
return GSON.fromJson(text, typeOfT);
|
||||||
|
} catch (JsonParseException e) {
|
||||||
|
throw new ServerResponseMalformedException(text, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class XboxAuthorizationException extends AuthenticationException {
|
public static class XboxAuthorizationException extends AuthenticationException {
|
||||||
private final long errorCode;
|
private final long errorCode;
|
||||||
|
|
||||||
@@ -290,6 +320,9 @@ public class MicrosoftService {
|
|||||||
public static final long ADD_FAMILY = 2148916238L;
|
public static final long ADD_FAMILY = 2148916238L;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class NoMinecraftJavaEditionProfileException extends AuthenticationException {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error response: {"error":"invalid_grant","error_description":"The provided
|
* Error response: {"error":"invalid_grant","error_description":"The provided
|
||||||
* value for the 'redirect_uri' is not valid. The value must exactly match the
|
* 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;
|
String waitFor() throws InterruptedException, ExecutionException;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Gson GSON = new GsonBuilder()
|
||||||
|
.registerTypeAdapter(UUID.class, UUIDTypeAdapter.INSTANCE)
|
||||||
|
.registerTypeAdapterFactory(ValidationTypeAdapterFactory.INSTANCE)
|
||||||
|
.create();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public final class OfflineAccountFactory extends AccountFactory<OfflineAccount>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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));
|
return new OfflineAccount(username, getUUIDFromUserName(username));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccount> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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(selector);
|
||||||
Objects.requireNonNull(username);
|
Objects.requireNonNull(username);
|
||||||
Objects.requireNonNull(password);
|
Objects.requireNonNull(password);
|
||||||
|
|||||||
1
JSTUN
Submodule
1
JSTUN
Submodule
Submodule JSTUN added at 08ab1f8483
@@ -31,6 +31,10 @@ subprojects {
|
|||||||
sourceSets = []
|
sourceSets = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.withType(Checkstyle) {
|
||||||
|
exclude 'de/javawi/jstun'
|
||||||
|
}
|
||||||
|
|
||||||
sourceCompatibility = 1.8
|
sourceCompatibility = 1.8
|
||||||
compileJava.options.encoding = "UTF-8"
|
compileJava.options.encoding = "UTF-8"
|
||||||
compileTestJava.options.encoding = "UTF-8"
|
compileTestJava.options.encoding = "UTF-8"
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ rootProject.name = 'HMCL3'
|
|||||||
include ':HMCL'
|
include ':HMCL'
|
||||||
include ':HMCLCore'
|
include ':HMCLCore'
|
||||||
include ':HMCLTransformerDiscoveryService'
|
include ':HMCLTransformerDiscoveryService'
|
||||||
|
include ':JSTUN'
|
||||||
|
|||||||
Reference in New Issue
Block a user