mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-16 15:26:27 -04:00
alt: initial UI refactor
This commit is contained in:
parent
1ca4045a2f
commit
b117769711
@ -23,58 +23,36 @@ import javafx.scene.layout.Region;
|
|||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import org.jackhuang.hmcl.Launcher;
|
import org.jackhuang.hmcl.Launcher;
|
||||||
import org.jackhuang.hmcl.Metadata;
|
import org.jackhuang.hmcl.Metadata;
|
||||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
|
||||||
import org.jackhuang.hmcl.game.Version;
|
|
||||||
import org.jackhuang.hmcl.setting.Accounts;
|
|
||||||
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
||||||
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.AccountList;
|
|
||||||
import org.jackhuang.hmcl.ui.account.AuthlibInjectorServersPage;
|
import org.jackhuang.hmcl.ui.account.AuthlibInjectorServersPage;
|
||||||
|
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||||
import org.jackhuang.hmcl.ui.construct.InputDialogPane;
|
import org.jackhuang.hmcl.ui.construct.InputDialogPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||||
import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
|
||||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||||
import org.jackhuang.hmcl.ui.decorator.DecoratorController;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorController;
|
||||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
import org.jackhuang.hmcl.ui.main.RootPage;
|
||||||
import org.jackhuang.hmcl.ui.profile.ProfileList;
|
import org.jackhuang.hmcl.ui.versions.VersionRootPage;
|
||||||
import org.jackhuang.hmcl.ui.versions.GameItem;
|
|
||||||
import org.jackhuang.hmcl.ui.versions.GameList;
|
|
||||||
import org.jackhuang.hmcl.ui.versions.VersionPage;
|
|
||||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
|
||||||
import org.jackhuang.hmcl.util.FutureCallback;
|
import org.jackhuang.hmcl.util.FutureCallback;
|
||||||
import org.jackhuang.hmcl.util.Logging;
|
import org.jackhuang.hmcl.util.Logging;
|
||||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
|
||||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||||
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
||||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
public final class Controllers {
|
public final class Controllers {
|
||||||
|
|
||||||
private static Scene scene;
|
private static Scene scene;
|
||||||
private static Stage stage;
|
private static Stage stage;
|
||||||
private static MainPage mainPage = null;
|
private static VersionRootPage versionPage = null;
|
||||||
private static SettingsPage settingsPage = null;
|
|
||||||
private static VersionPage versionPage = null;
|
|
||||||
private static GameList gameListPage = null;
|
|
||||||
private static AccountList accountListPage = null;
|
|
||||||
private static ProfileList profileListPage = null;
|
|
||||||
private static AuthlibInjectorServersPage serversPage = null;
|
private static AuthlibInjectorServersPage serversPage = null;
|
||||||
private static LeftPaneController leftPaneController;
|
private static RootPage rootPage;
|
||||||
private static DecoratorController decorator;
|
private static DecoratorController decorator;
|
||||||
|
|
||||||
public static Scene getScene() {
|
public static Scene getScene() {
|
||||||
@ -86,53 +64,19 @@ public final class Controllers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FXThread
|
// FXThread
|
||||||
public static SettingsPage getSettingsPage() {
|
public static VersionRootPage getVersionPage() {
|
||||||
if (settingsPage == null)
|
|
||||||
settingsPage = new SettingsPage();
|
|
||||||
return settingsPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FXThread
|
|
||||||
public static GameList getGameListPage() {
|
|
||||||
if (gameListPage == null) {
|
|
||||||
gameListPage = new GameList();
|
|
||||||
FXUtils.applyDragListener(gameListPage, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
|
|
||||||
File modpack = modpacks.get(0);
|
|
||||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack), i18n("install.modpack"));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return gameListPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FXThread
|
|
||||||
public static AccountList getAccountListPage() {
|
|
||||||
if (accountListPage == null) {
|
|
||||||
AccountList accountListPage = new AccountList();
|
|
||||||
accountListPage.selectedAccountProperty().bindBidirectional(Accounts.selectedAccountProperty());
|
|
||||||
accountListPage.accountsProperty().bindContent(Accounts.accountsProperty());
|
|
||||||
Controllers.accountListPage = accountListPage;
|
|
||||||
}
|
|
||||||
return accountListPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FXThread
|
|
||||||
public static ProfileList getProfileListPage() {
|
|
||||||
if (profileListPage == null) {
|
|
||||||
ProfileList profileListPage = new ProfileList();
|
|
||||||
profileListPage.selectedProfileProperty().bindBidirectional(Profiles.selectedProfileProperty());
|
|
||||||
profileListPage.profilesProperty().bindContent(Profiles.profilesProperty());
|
|
||||||
Controllers.profileListPage = profileListPage;
|
|
||||||
}
|
|
||||||
return profileListPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FXThread
|
|
||||||
public static VersionPage getVersionPage() {
|
|
||||||
if (versionPage == null)
|
if (versionPage == null)
|
||||||
versionPage = new VersionPage();
|
versionPage = new VersionRootPage();
|
||||||
return versionPage;
|
return versionPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FXThread
|
||||||
|
public static RootPage getRootPage() {
|
||||||
|
if (rootPage == null)
|
||||||
|
rootPage = new RootPage();
|
||||||
|
return rootPage;
|
||||||
|
}
|
||||||
|
|
||||||
// FXThread
|
// FXThread
|
||||||
public static AuthlibInjectorServersPage getServersPage() {
|
public static AuthlibInjectorServersPage getServersPage() {
|
||||||
if (serversPage == null)
|
if (serversPage == null)
|
||||||
@ -145,51 +89,6 @@ public final class Controllers {
|
|||||||
return decorator;
|
return decorator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MainPage getMainPage() {
|
|
||||||
if (mainPage == null) {
|
|
||||||
MainPage mainPage = new MainPage();
|
|
||||||
FXUtils.applyDragListener(mainPage, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
|
|
||||||
File modpack = modpacks.get(0);
|
|
||||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack), i18n("install.modpack"));
|
|
||||||
});
|
|
||||||
|
|
||||||
FXUtils.onChangeAndOperate(Profiles.selectedVersionProperty(), version -> {
|
|
||||||
if (version != null) {
|
|
||||||
mainPage.setCurrentGame(version);
|
|
||||||
} else {
|
|
||||||
mainPage.setCurrentGame(i18n("version.empty"));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
mainPage.showUpdateProperty().bind(UpdateChecker.outdatedProperty());
|
|
||||||
mainPage.latestVersionProperty().bind(
|
|
||||||
BindingMapping.of(UpdateChecker.latestVersionProperty())
|
|
||||||
.map(version -> version == null ? "" : i18n("update.bubble.title", version.getVersion())));
|
|
||||||
|
|
||||||
Profiles.registerVersionsListener(profile -> {
|
|
||||||
HMCLGameRepository repository = profile.getRepository();
|
|
||||||
List<Node> children = repository.getVersions().parallelStream()
|
|
||||||
.filter(version -> !version.isHidden())
|
|
||||||
.sorted(Comparator.comparing((Version version) -> version.getReleaseTime() == null ? new Date(0L) : version.getReleaseTime())
|
|
||||||
.thenComparing(a -> VersionNumber.asVersion(a.getId())))
|
|
||||||
.map(version -> {
|
|
||||||
Node node = PopupMenu.wrapPopupMenuItem(new GameItem(profile, version.getId()));
|
|
||||||
node.setOnMouseClicked(e -> profile.setSelectedVersion(version.getId()));
|
|
||||||
return node;
|
|
||||||
})
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
runInFX(() -> {
|
|
||||||
if (profile == Profiles.getSelectedProfile())
|
|
||||||
mainPage.getVersions().setAll(children);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Controllers.mainPage = mainPage;
|
|
||||||
}
|
|
||||||
return mainPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static LeftPaneController getLeftPaneController() {
|
|
||||||
return leftPaneController;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void initialize(Stage stage) {
|
public static void initialize(Stage stage) {
|
||||||
Logging.LOG.info("Start initializing application");
|
Logging.LOG.info("Start initializing application");
|
||||||
@ -198,9 +97,7 @@ public final class Controllers {
|
|||||||
|
|
||||||
stage.setOnCloseRequest(e -> Launcher.stopApplication());
|
stage.setOnCloseRequest(e -> Launcher.stopApplication());
|
||||||
|
|
||||||
decorator = new DecoratorController(stage, getMainPage());
|
decorator = new DecoratorController(stage, getRootPage());
|
||||||
leftPaneController = new LeftPaneController();
|
|
||||||
decorator.getDecorator().drawerProperty().setAll(leftPaneController);
|
|
||||||
|
|
||||||
if (config().getCommonDirType() == EnumCommonDirectory.CUSTOM &&
|
if (config().getCommonDirType() == EnumCommonDirectory.CUSTOM &&
|
||||||
!FileUtils.canCreateDirectory(config().getCommonDirectory())) {
|
!FileUtils.canCreateDirectory(config().getCommonDirectory())) {
|
||||||
@ -261,7 +158,7 @@ public final class Controllers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void navigate(Node node) {
|
public static void navigate(Node node) {
|
||||||
decorator.getNavigator().navigate(node);
|
decorator.getNavigator().navigate(node, ContainerAnimations.FADE.getAnimationProducer());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isStopped() {
|
public static boolean isStopped() {
|
||||||
@ -269,15 +166,11 @@ public final class Controllers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void shutdown() {
|
public static void shutdown() {
|
||||||
mainPage = null;
|
rootPage = null;
|
||||||
settingsPage = null;
|
|
||||||
versionPage = null;
|
versionPage = null;
|
||||||
serversPage = null;
|
serversPage = null;
|
||||||
decorator = null;
|
decorator = null;
|
||||||
stage = null;
|
stage = null;
|
||||||
scene = null;
|
scene = null;
|
||||||
gameListPage = null;
|
|
||||||
accountListPage = null;
|
|
||||||
profileListPage = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,133 +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;
|
|
||||||
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import org.jackhuang.hmcl.event.EventBus;
|
|
||||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
|
||||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
|
||||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
|
||||||
import org.jackhuang.hmcl.setting.Accounts;
|
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
|
||||||
import org.jackhuang.hmcl.setting.Profiles;
|
|
||||||
import org.jackhuang.hmcl.task.Schedulers;
|
|
||||||
import org.jackhuang.hmcl.task.Task;
|
|
||||||
import org.jackhuang.hmcl.ui.account.AccountAdvancedListItem;
|
|
||||||
import org.jackhuang.hmcl.ui.account.AddAccountPane;
|
|
||||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
|
||||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
|
||||||
import org.jackhuang.hmcl.ui.profile.ProfileAdvancedListItem;
|
|
||||||
import org.jackhuang.hmcl.ui.versions.GameAdvancedListItem;
|
|
||||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
|
||||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
|
||||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
|
||||||
|
|
||||||
public final class LeftPaneController extends AdvancedListBox {
|
|
||||||
|
|
||||||
public LeftPaneController() {
|
|
||||||
|
|
||||||
AccountAdvancedListItem accountListItem = new AccountAdvancedListItem();
|
|
||||||
accountListItem.setOnAction(e -> Controllers.navigate(Controllers.getAccountListPage()));
|
|
||||||
accountListItem.accountProperty().bind(Accounts.selectedAccountProperty());
|
|
||||||
|
|
||||||
GameAdvancedListItem gameListItem = new GameAdvancedListItem();
|
|
||||||
gameListItem.actionButtonVisibleProperty().bind(Profiles.selectedVersionProperty().isNotNull());
|
|
||||||
gameListItem.setOnAction(e -> {
|
|
||||||
Profile profile = Profiles.getSelectedProfile();
|
|
||||||
String version = Profiles.getSelectedVersion();
|
|
||||||
if (version == null) {
|
|
||||||
Controllers.navigate(Controllers.getGameListPage());
|
|
||||||
} else {
|
|
||||||
Versions.modifyGameSettings(profile, version);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ProfileAdvancedListItem profileListItem = new ProfileAdvancedListItem();
|
|
||||||
profileListItem.setOnAction(e -> Controllers.navigate(Controllers.getProfileListPage()));
|
|
||||||
profileListItem.profileProperty().bind(Profiles.selectedProfileProperty());
|
|
||||||
|
|
||||||
AdvancedListItem gameItem = new AdvancedListItem();
|
|
||||||
gameItem.setImage(newImage("/assets/img/bookshelf.png"));
|
|
||||||
gameItem.setTitle(i18n("version.manage"));
|
|
||||||
gameItem.setOnAction(e -> Controllers.navigate(Controllers.getGameListPage()));
|
|
||||||
|
|
||||||
AdvancedListItem launcherSettingsItem = new AdvancedListItem();
|
|
||||||
launcherSettingsItem.setImage(newImage("/assets/img/command.png"));
|
|
||||||
launcherSettingsItem.setTitle(i18n("settings.launcher"));
|
|
||||||
launcherSettingsItem.setOnAction(e -> Controllers.navigate(Controllers.getSettingsPage()));
|
|
||||||
|
|
||||||
this
|
|
||||||
.startCategory(i18n("account").toUpperCase())
|
|
||||||
.add(accountListItem)
|
|
||||||
.startCategory(i18n("version").toUpperCase())
|
|
||||||
.add(gameListItem)
|
|
||||||
.add(gameItem)
|
|
||||||
.startCategory(i18n("profile.title").toUpperCase())
|
|
||||||
.add(profileListItem)
|
|
||||||
.startCategory(i18n("launcher").toUpperCase())
|
|
||||||
.add(launcherSettingsItem);
|
|
||||||
|
|
||||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(event -> onRefreshedVersions((HMCLGameRepository) event.getSource()));
|
|
||||||
|
|
||||||
Profile profile = Profiles.getSelectedProfile();
|
|
||||||
if (profile != null && profile.getRepository().isLoaded())
|
|
||||||
onRefreshedVersions(Profiles.selectedProfileProperty().get().getRepository());
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==== Accounts ====
|
|
||||||
public void checkAccount() {
|
|
||||||
if (Accounts.getAccounts().isEmpty())
|
|
||||||
Platform.runLater(this::addNewAccount);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addNewAccount() {
|
|
||||||
Controllers.dialog(new AddAccountPane());
|
|
||||||
}
|
|
||||||
// ====
|
|
||||||
|
|
||||||
private boolean checkedModpack = false;
|
|
||||||
|
|
||||||
private void onRefreshedVersions(HMCLGameRepository repository) {
|
|
||||||
runInFX(() -> {
|
|
||||||
if (!checkedModpack) {
|
|
||||||
checkedModpack = true;
|
|
||||||
|
|
||||||
if (repository.getVersionCount() == 0) {
|
|
||||||
File modpackFile = new File("modpack.zip").getAbsoluteFile();
|
|
||||||
if (modpackFile.exists()) {
|
|
||||||
Task.supplyAsync(() -> CompressingUtils.findSuitableEncoding(modpackFile.toPath()))
|
|
||||||
.thenApplyAsync(encoding -> ModpackHelper.readModpackManifest(modpackFile.toPath(), encoding))
|
|
||||||
.thenApplyAsync(modpack -> ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
|
|
||||||
.withRunAsync(Schedulers.javafx(), this::checkAccount).executor())
|
|
||||||
.thenAcceptAsync(Schedulers.javafx(), executor -> {
|
|
||||||
Controllers.taskDialog(executor, i18n("modpack.installing"));
|
|
||||||
executor.start();
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkAccount();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -20,7 +20,6 @@ package org.jackhuang.hmcl.ui.account;
|
|||||||
import com.jfoenix.controls.JFXButton;
|
import com.jfoenix.controls.JFXButton;
|
||||||
import com.jfoenix.controls.JFXDialogLayout;
|
import com.jfoenix.controls.JFXDialogLayout;
|
||||||
import com.jfoenix.controls.JFXTextField;
|
import com.jfoenix.controls.JFXTextField;
|
||||||
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
@ -28,7 +27,7 @@ import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
|||||||
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.ui.animation.ContainerAnimations;
|
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.DialogAware;
|
import org.jackhuang.hmcl.ui.construct.DialogAware;
|
||||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||||
@ -44,7 +43,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
|||||||
|
|
||||||
public class AddAuthlibInjectorServerPane extends StackPane implements DialogAware {
|
public class AddAuthlibInjectorServerPane extends StackPane implements DialogAware {
|
||||||
|
|
||||||
@FXML private StackPane addServerContainer;
|
@FXML private TransitionPane root;
|
||||||
@FXML private Label lblServerUrl;
|
@FXML private Label lblServerUrl;
|
||||||
@FXML private Label lblServerName;
|
@FXML private Label lblServerName;
|
||||||
@FXML private Label lblCreationWarning;
|
@FXML private Label lblCreationWarning;
|
||||||
@ -55,8 +54,6 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa
|
|||||||
@FXML private SpinnerPane nextPane;
|
@FXML private SpinnerPane nextPane;
|
||||||
@FXML private JFXButton btnAddNext;
|
@FXML private JFXButton btnAddNext;
|
||||||
|
|
||||||
private TransitionHandler transitionHandler;
|
|
||||||
|
|
||||||
private AuthlibInjectorServer serverBeingAdded;
|
private AuthlibInjectorServer serverBeingAdded;
|
||||||
|
|
||||||
public AddAuthlibInjectorServerPane(String url) {
|
public AddAuthlibInjectorServerPane(String url) {
|
||||||
@ -67,8 +64,7 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa
|
|||||||
|
|
||||||
public AddAuthlibInjectorServerPane() {
|
public AddAuthlibInjectorServerPane() {
|
||||||
loadFXML(this, "/assets/fxml/authlib-injector-server-add.fxml");
|
loadFXML(this, "/assets/fxml/authlib-injector-server-add.fxml");
|
||||||
transitionHandler = new TransitionHandler(addServerContainer);
|
root.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer());
|
||||||
transitionHandler.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer());
|
|
||||||
|
|
||||||
btnAddNext.disableProperty().bind(txtServerUrl.textProperty().isEmpty());
|
btnAddNext.disableProperty().bind(txtServerUrl.textProperty().isEmpty());
|
||||||
nextPane.hideSpinner();
|
nextPane.hideSpinner();
|
||||||
@ -116,7 +112,7 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa
|
|||||||
|
|
||||||
lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverBeingAdded.getUrl()).getProtocol()));
|
lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverBeingAdded.getUrl()).getProtocol()));
|
||||||
|
|
||||||
transitionHandler.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer());
|
root.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer());
|
||||||
} else {
|
} else {
|
||||||
LOG.log(Level.WARNING, "Failed to resolve auth server: " + url, exception);
|
LOG.log(Level.WARNING, "Failed to resolve auth server: " + url, exception);
|
||||||
lblCreationWarning.setText(resolveFetchExceptionMessage(exception));
|
lblCreationWarning.setText(resolveFetchExceptionMessage(exception));
|
||||||
@ -127,7 +123,7 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void onAddPrev() {
|
private void onAddPrev() {
|
||||||
transitionHandler.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer());
|
root.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer());
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
package org.jackhuang.hmcl.ui.animation;
|
package org.jackhuang.hmcl.ui.animation;
|
||||||
|
|
||||||
import javafx.animation.KeyFrame;
|
import javafx.animation.KeyFrame;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -25,4 +26,6 @@ public interface AnimationProducer {
|
|||||||
void init(AnimationHandler handler);
|
void init(AnimationHandler handler);
|
||||||
|
|
||||||
List<KeyFrame> animate(AnimationHandler handler);
|
List<KeyFrame> animate(AnimationHandler handler);
|
||||||
|
|
||||||
|
@Nullable AnimationProducer opposite();
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,8 @@ import javafx.animation.Interpolator;
|
|||||||
import javafx.animation.KeyFrame;
|
import javafx.animation.KeyFrame;
|
||||||
import javafx.animation.KeyValue;
|
import javafx.animation.KeyValue;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -41,6 +43,7 @@ public enum ContainerAnimations {
|
|||||||
c.getCurrentNode().setScaleY(1);
|
c.getCurrentNode().setScaleY(1);
|
||||||
c.getCurrentNode().setOpacity(1);
|
c.getCurrentNode().setOpacity(1);
|
||||||
}, c -> Collections.emptyList()),
|
}, c -> Collections.emptyList()),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fade between the old and new view
|
* A fade between the old and new view
|
||||||
*/
|
*/
|
||||||
@ -62,6 +65,36 @@ public enum ContainerAnimations {
|
|||||||
new KeyFrame(c.getDuration(),
|
new KeyFrame(c.getDuration(),
|
||||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH),
|
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH),
|
||||||
new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH)))),
|
new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH)))),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fade between the old and new view
|
||||||
|
*/
|
||||||
|
FADE_IN(c -> {
|
||||||
|
c.getCurrentNode().setTranslateX(0);
|
||||||
|
c.getCurrentNode().setTranslateY(0);
|
||||||
|
c.getCurrentNode().setScaleX(1);
|
||||||
|
c.getCurrentNode().setScaleY(1);
|
||||||
|
c.getCurrentNode().setOpacity(0);
|
||||||
|
}, c ->
|
||||||
|
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||||
|
new KeyValue(c.getCurrentNode().opacityProperty(), 0, FXUtils.SINE)),
|
||||||
|
new KeyFrame(c.getDuration(),
|
||||||
|
new KeyValue(c.getCurrentNode().opacityProperty(), 1, FXUtils.SINE)))),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fade between the old and new view
|
||||||
|
*/
|
||||||
|
FADE_OUT(c -> {
|
||||||
|
c.getCurrentNode().setTranslateX(0);
|
||||||
|
c.getCurrentNode().setTranslateY(0);
|
||||||
|
c.getCurrentNode().setScaleX(1);
|
||||||
|
c.getCurrentNode().setScaleY(1);
|
||||||
|
c.getCurrentNode().setOpacity(1);
|
||||||
|
}, c ->
|
||||||
|
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||||
|
new KeyValue(c.getCurrentNode().opacityProperty(), 1, FXUtils.SINE)),
|
||||||
|
new KeyFrame(c.getDuration(),
|
||||||
|
new KeyValue(c.getCurrentNode().opacityProperty(), 0, FXUtils.SINE)))),
|
||||||
/**
|
/**
|
||||||
* A zoom effect
|
* A zoom effect
|
||||||
*/
|
*/
|
||||||
@ -143,6 +176,7 @@ public enum ContainerAnimations {
|
|||||||
new KeyValue(c.getPreviousNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH))));
|
new KeyValue(c.getPreviousNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH))));
|
||||||
|
|
||||||
private final AnimationProducer animationProducer;
|
private final AnimationProducer animationProducer;
|
||||||
|
private ContainerAnimations opposite;
|
||||||
|
|
||||||
ContainerAnimations(Consumer<AnimationHandler> init, Function<AnimationHandler, List<KeyFrame>> animationProducer) {
|
ContainerAnimations(Consumer<AnimationHandler> init, Function<AnimationHandler, List<KeyFrame>> animationProducer) {
|
||||||
this.animationProducer = new AnimationProducer() {
|
this.animationProducer = new AnimationProducer() {
|
||||||
@ -155,10 +189,30 @@ public enum ContainerAnimations {
|
|||||||
public List<KeyFrame> animate(AnimationHandler handler) {
|
public List<KeyFrame> animate(AnimationHandler handler) {
|
||||||
return animationProducer.apply(handler);
|
return animationProducer.apply(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable AnimationProducer opposite() {
|
||||||
|
return opposite != null ? opposite.getAnimationProducer() : null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public AnimationProducer getAnimationProducer() {
|
public AnimationProducer getAnimationProducer() {
|
||||||
return animationProducer;
|
return animationProducer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ContainerAnimations getOpposite() {
|
||||||
|
return opposite;
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
NONE.opposite = NONE;
|
||||||
|
FADE.opposite = FADE;
|
||||||
|
SWIPE_LEFT.opposite = SWIPE_RIGHT;
|
||||||
|
SWIPE_RIGHT.opposite = SWIPE_LEFT;
|
||||||
|
FADE_IN.opposite = FADE_OUT;
|
||||||
|
FADE_OUT.opposite = FADE_IN;
|
||||||
|
ZOOM_IN.opposite = ZOOM_OUT;
|
||||||
|
ZOOM_OUT.opposite = ZOOM_IN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,29 +21,18 @@ import javafx.animation.KeyFrame;
|
|||||||
import javafx.animation.Timeline;
|
import javafx.animation.Timeline;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.Parent;
|
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.shape.Rectangle;
|
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
|
||||||
public final class TransitionHandler implements AnimationHandler {
|
public class TransitionPane extends StackPane implements AnimationHandler {
|
||||||
private final StackPane view;
|
|
||||||
private Timeline animation;
|
private Timeline animation;
|
||||||
private Duration duration;
|
private Duration duration;
|
||||||
private Node previousNode, currentNode;
|
private Node previousNode, currentNode;
|
||||||
|
|
||||||
/**
|
{
|
||||||
* @param view A stack pane that contains another control that is {@link Parent}
|
currentNode = getChildren().stream().findFirst().orElse(null);
|
||||||
*/
|
FXUtils.setOverflowHidden(this, true);
|
||||||
public TransitionHandler(StackPane view) {
|
|
||||||
this.view = view;
|
|
||||||
currentNode = view.getChildren().stream().findFirst().orElse(null);
|
|
||||||
|
|
||||||
// prevent content overflow
|
|
||||||
Rectangle clip = new Rectangle();
|
|
||||||
clip.widthProperty().bind(view.widthProperty());
|
|
||||||
clip.heightProperty().bind(view.heightProperty());
|
|
||||||
view.setClip(clip);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -58,7 +47,7 @@ public final class TransitionHandler implements AnimationHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StackPane getCurrentRoot() {
|
public StackPane getCurrentRoot() {
|
||||||
return view;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -86,8 +75,8 @@ public final class TransitionHandler implements AnimationHandler {
|
|||||||
Timeline nowAnimation = new Timeline();
|
Timeline nowAnimation = new Timeline();
|
||||||
nowAnimation.getKeyFrames().addAll(transition.animate(this));
|
nowAnimation.getKeyFrames().addAll(transition.animate(this));
|
||||||
nowAnimation.getKeyFrames().add(new KeyFrame(duration, e -> {
|
nowAnimation.getKeyFrames().add(new KeyFrame(duration, e -> {
|
||||||
view.setMouseTransparent(false);
|
setMouseTransparent(false);
|
||||||
view.getChildren().remove(previousNode);
|
getChildren().remove(previousNode);
|
||||||
}));
|
}));
|
||||||
nowAnimation.play();
|
nowAnimation.play();
|
||||||
animation = nowAnimation;
|
animation = nowAnimation;
|
||||||
@ -95,7 +84,7 @@ public final class TransitionHandler implements AnimationHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateContent(Node newView) {
|
private void updateContent(Node newView) {
|
||||||
if (view.getWidth() > 0 && view.getHeight() > 0) {
|
if (getWidth() > 0 && getHeight() > 0) {
|
||||||
previousNode = currentNode;
|
previousNode = currentNode;
|
||||||
if (previousNode == null)
|
if (previousNode == null)
|
||||||
previousNode = EMPTY_PANE;
|
previousNode = EMPTY_PANE;
|
||||||
@ -105,11 +94,11 @@ public final class TransitionHandler implements AnimationHandler {
|
|||||||
if (previousNode == newView)
|
if (previousNode == newView)
|
||||||
previousNode = EMPTY_PANE;
|
previousNode = EMPTY_PANE;
|
||||||
|
|
||||||
view.setMouseTransparent(true);
|
setMouseTransparent(true);
|
||||||
|
|
||||||
currentNode = newView;
|
currentNode = newView;
|
||||||
|
|
||||||
view.getChildren().setAll(previousNode, currentNode);
|
getChildren().setAll(previousNode, currentNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final StackPane EMPTY_PANE = new StackPane();
|
private final StackPane EMPTY_PANE = new StackPane();
|
@ -24,21 +24,20 @@ import javafx.event.EventHandler;
|
|||||||
import javafx.event.EventType;
|
import javafx.event.EventType;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.StackPane;
|
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
import org.jackhuang.hmcl.ui.animation.AnimationProducer;
|
||||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||||
import org.jackhuang.hmcl.util.Logging;
|
import org.jackhuang.hmcl.util.Logging;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
public class Navigator extends StackPane {
|
public class Navigator extends TransitionPane {
|
||||||
private static final String PROPERTY_DIALOG_CLOSE_HANDLER = Navigator.class.getName() + ".closeListener";
|
private static final String PROPERTY_DIALOG_CLOSE_HANDLER = Navigator.class.getName() + ".closeListener";
|
||||||
|
|
||||||
private final Stack<Node> stack = new Stack<>();
|
private final Stack<Node> stack = new Stack<>();
|
||||||
private final TransitionHandler animationHandler = new TransitionHandler(this);
|
|
||||||
private boolean initialized = false;
|
private boolean initialized = false;
|
||||||
|
|
||||||
public void init(Node init) {
|
public void init(Node init) {
|
||||||
@ -50,7 +49,7 @@ public class Navigator extends StackPane {
|
|||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void navigate(Node node) {
|
public void navigate(Node node, AnimationProducer animationProducer) {
|
||||||
FXUtils.checkFxUserThread();
|
FXUtils.checkFxUserThread();
|
||||||
|
|
||||||
if (!initialized)
|
if (!initialized)
|
||||||
@ -68,7 +67,8 @@ public class Navigator extends StackPane {
|
|||||||
fireEvent(navigating);
|
fireEvent(navigating);
|
||||||
node.fireEvent(navigating);
|
node.fireEvent(navigating);
|
||||||
|
|
||||||
setContent(node);
|
node.getProperties().put("hmcl.navigator.animation", animationProducer);
|
||||||
|
setContent(node, animationProducer);
|
||||||
|
|
||||||
NavigationEvent navigated = new NavigationEvent(this, node, NavigationEvent.NAVIGATED);
|
NavigationEvent navigated = new NavigationEvent(this, node, NavigationEvent.NAVIGATED);
|
||||||
fireEvent(navigated);
|
fireEvent(navigated);
|
||||||
@ -110,7 +110,12 @@ public class Navigator extends StackPane {
|
|||||||
fireEvent(navigating);
|
fireEvent(navigating);
|
||||||
node.fireEvent(navigating);
|
node.fireEvent(navigating);
|
||||||
|
|
||||||
setContent(node);
|
Object obj = from.getProperties().get("hmcl.navigator.animation");
|
||||||
|
if (obj instanceof AnimationProducer) {
|
||||||
|
setContent(node, (AnimationProducer) obj);
|
||||||
|
} else {
|
||||||
|
setContent(node, ContainerAnimations.NONE.getAnimationProducer());
|
||||||
|
}
|
||||||
|
|
||||||
NavigationEvent navigated = new NavigationEvent(this, node, NavigationEvent.NAVIGATED);
|
NavigationEvent navigated = new NavigationEvent(this, node, NavigationEvent.NAVIGATED);
|
||||||
fireEvent(navigated);
|
fireEvent(navigated);
|
||||||
@ -128,8 +133,8 @@ public class Navigator extends StackPane {
|
|||||||
return stack.size() > 1;
|
return stack.size() > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setContent(Node content) {
|
public void setContent(Node content, AnimationProducer animationProducer) {
|
||||||
animationHandler.setContent(content, ContainerAnimations.FADE.getAnimationProducer());
|
super.setContent(content, animationProducer);
|
||||||
|
|
||||||
if (content instanceof Region) {
|
if (content instanceof Region) {
|
||||||
((Region) content).setMinSize(0, 0);
|
((Region) content).setMinSize(0, 0);
|
||||||
@ -179,14 +184,21 @@ public class Navigator extends StackPane {
|
|||||||
public static final EventType<NavigationEvent> NAVIGATED = new EventType<>("NAVIGATED");
|
public static final EventType<NavigationEvent> NAVIGATED = new EventType<>("NAVIGATED");
|
||||||
public static final EventType<NavigationEvent> NAVIGATING = new EventType<>("NAVIGATING");
|
public static final EventType<NavigationEvent> NAVIGATING = new EventType<>("NAVIGATING");
|
||||||
|
|
||||||
|
private final Navigator source;
|
||||||
private final Node node;
|
private final Node node;
|
||||||
|
|
||||||
public NavigationEvent(Object source, Node target, EventType<? extends Event> eventType) {
|
public NavigationEvent(Navigator source, Node target, EventType<? extends Event> eventType) {
|
||||||
super(source, target, eventType);
|
super(source, target, eventType);
|
||||||
|
|
||||||
|
this.source = source;
|
||||||
this.node = target;
|
this.node = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Navigator getSource() {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
public Node getNode() {
|
public Node getNode() {
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
@ -18,38 +18,28 @@
|
|||||||
package org.jackhuang.hmcl.ui.construct;
|
package org.jackhuang.hmcl.ui.construct;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXSpinner;
|
import com.jfoenix.controls.JFXSpinner;
|
||||||
|
import javafx.animation.KeyFrame;
|
||||||
|
import javafx.animation.Timeline;
|
||||||
import javafx.beans.DefaultProperty;
|
import javafx.beans.DefaultProperty;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.Control;
|
||||||
|
import javafx.scene.control.SkinBase;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
import org.jackhuang.hmcl.ui.animation.AnimationHandler;
|
||||||
|
import org.jackhuang.hmcl.ui.animation.AnimationProducer;
|
||||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
|
||||||
|
|
||||||
@DefaultProperty("content")
|
@DefaultProperty("content")
|
||||||
public class SpinnerPane extends StackPane {
|
public class SpinnerPane extends Control {
|
||||||
private final TransitionHandler transitionHandler = new TransitionHandler(this);
|
|
||||||
private final JFXSpinner spinner = new JFXSpinner();
|
|
||||||
private final StackPane contentPane = new StackPane();
|
|
||||||
private final ObjectProperty<Node> content = new SimpleObjectProperty<>(this, "content");
|
private final ObjectProperty<Node> content = new SimpleObjectProperty<>(this, "content");
|
||||||
private final BooleanProperty loading = new SimpleBooleanProperty(this, "loading") {
|
private final BooleanProperty loading = new SimpleBooleanProperty(this, "loading");
|
||||||
protected void invalidated() {
|
|
||||||
if (get())
|
|
||||||
transitionHandler.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer());
|
|
||||||
else
|
|
||||||
transitionHandler.setContent(contentPane, ContainerAnimations.FADE.getAnimationProducer());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public SpinnerPane() {
|
|
||||||
getStyleClass().add("spinner-pane");
|
|
||||||
|
|
||||||
getChildren().setAll(contentPane);
|
|
||||||
|
|
||||||
content.addListener((a, b, newValue) -> contentPane.getChildren().setAll(newValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showSpinner() {
|
public void showSpinner() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@ -82,4 +72,72 @@ public class SpinnerPane extends StackPane {
|
|||||||
public void setLoading(boolean loading) {
|
public void setLoading(boolean loading) {
|
||||||
this.loading.set(loading);
|
this.loading.set(loading);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Skin createDefaultSkin() {
|
||||||
|
return new Skin(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Skin extends SkinBase<SpinnerPane> {
|
||||||
|
private final JFXSpinner spinner = new JFXSpinner();
|
||||||
|
private final StackPane contentPane = new StackPane();
|
||||||
|
private final StackPane topPane = new StackPane();
|
||||||
|
private final StackPane root = new StackPane();
|
||||||
|
private Timeline animation;
|
||||||
|
|
||||||
|
protected Skin(SpinnerPane control) {
|
||||||
|
super(control);
|
||||||
|
|
||||||
|
root.getStyleClass().add("spinner-pane");
|
||||||
|
topPane.getChildren().setAll(spinner);
|
||||||
|
root.getChildren().setAll(contentPane, topPane);
|
||||||
|
FXUtils.onChangeAndOperate(getSkinnable().content, newValue -> contentPane.getChildren().setAll(newValue));
|
||||||
|
getChildren().setAll(root);
|
||||||
|
|
||||||
|
FXUtils.onChangeAndOperate(getSkinnable().loadingProperty(), newValue -> {
|
||||||
|
Timeline prev = animation;
|
||||||
|
if (prev != null) prev.stop();
|
||||||
|
|
||||||
|
AnimationProducer transition;
|
||||||
|
topPane.setMouseTransparent(true);
|
||||||
|
topPane.setVisible(true);
|
||||||
|
topPane.getStyleClass().add("gray-background");
|
||||||
|
if (newValue)
|
||||||
|
transition = ContainerAnimations.FADE_IN.getAnimationProducer();
|
||||||
|
else
|
||||||
|
transition = ContainerAnimations.FADE_OUT.getAnimationProducer();
|
||||||
|
|
||||||
|
AnimationHandler handler = new AnimationHandler() {
|
||||||
|
@Override
|
||||||
|
public Duration getDuration() {
|
||||||
|
return Duration.millis(160);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pane getCurrentRoot() {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getPreviousNode() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getCurrentNode() {
|
||||||
|
return topPane;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Timeline now = new Timeline();
|
||||||
|
now.getKeyFrames().addAll(transition.animate(handler));
|
||||||
|
now.getKeyFrames().add(new KeyFrame(handler.getDuration(), e -> {
|
||||||
|
topPane.setMouseTransparent(!newValue);
|
||||||
|
topPane.setVisible(newValue);
|
||||||
|
}));
|
||||||
|
now.play();
|
||||||
|
animation = now;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
|||||||
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.AddAuthlibInjectorServerPane;
|
import org.jackhuang.hmcl.ui.account.AddAuthlibInjectorServerPane;
|
||||||
|
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||||
import org.jackhuang.hmcl.ui.construct.DialogAware;
|
import org.jackhuang.hmcl.ui.construct.DialogAware;
|
||||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||||
@ -220,8 +221,8 @@ public class DecoratorController {
|
|||||||
if (navigator.getCurrentPage() instanceof DecoratorPage) {
|
if (navigator.getCurrentPage() instanceof DecoratorPage) {
|
||||||
DecoratorPage page = (DecoratorPage) navigator.getCurrentPage();
|
DecoratorPage page = (DecoratorPage) navigator.getCurrentPage();
|
||||||
|
|
||||||
if (page.canForceToClose()) {
|
if (page.isPageCloseable()) {
|
||||||
page.onForceToClose();
|
page.closePage();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -232,7 +233,7 @@ public class DecoratorController {
|
|||||||
if (navigator.getCurrentPage() instanceof DecoratorPage) {
|
if (navigator.getCurrentPage() instanceof DecoratorPage) {
|
||||||
DecoratorPage page = (DecoratorPage) navigator.getCurrentPage();
|
DecoratorPage page = (DecoratorPage) navigator.getCurrentPage();
|
||||||
|
|
||||||
if (page.onClose())
|
if (page.back())
|
||||||
navigator.close();
|
navigator.close();
|
||||||
} else {
|
} else {
|
||||||
navigator.close();
|
navigator.close();
|
||||||
@ -243,23 +244,25 @@ public class DecoratorController {
|
|||||||
if (navigator.getCurrentPage() instanceof Refreshable) {
|
if (navigator.getCurrentPage() instanceof Refreshable) {
|
||||||
Refreshable refreshable = (Refreshable) navigator.getCurrentPage();
|
Refreshable refreshable = (Refreshable) navigator.getCurrentPage();
|
||||||
|
|
||||||
if (refreshable.canRefreshProperty().get())
|
if (refreshable.refreshableProperty().get())
|
||||||
refreshable.refresh();
|
refreshable.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onNavigating(Navigator.NavigationEvent event) {
|
private void onNavigating(Navigator.NavigationEvent event) {
|
||||||
|
if (event.getSource() != this.navigator) return;
|
||||||
Node from = event.getNode();
|
Node from = event.getNode();
|
||||||
|
|
||||||
if (from instanceof DecoratorPage)
|
if (from instanceof DecoratorPage)
|
||||||
((DecoratorPage) from).onClose();
|
((DecoratorPage) from).back();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onNavigated(Navigator.NavigationEvent event) {
|
private void onNavigated(Navigator.NavigationEvent event) {
|
||||||
|
if (event.getSource() != this.navigator) return;
|
||||||
Node to = event.getNode();
|
Node to = event.getNode();
|
||||||
|
|
||||||
if (to instanceof Refreshable) {
|
if (to instanceof Refreshable) {
|
||||||
decorator.canRefreshProperty().bind(((Refreshable) to).canRefreshProperty());
|
decorator.canRefreshProperty().bind(((Refreshable) to).refreshableProperty());
|
||||||
} else {
|
} else {
|
||||||
decorator.canRefreshProperty().unbind();
|
decorator.canRefreshProperty().unbind();
|
||||||
decorator.canRefreshProperty().set(false);
|
decorator.canRefreshProperty().set(false);
|
||||||
@ -267,14 +270,17 @@ public class DecoratorController {
|
|||||||
|
|
||||||
if (to instanceof DecoratorPage) {
|
if (to instanceof DecoratorPage) {
|
||||||
decorator.drawerTitleProperty().bind(((DecoratorPage) to).titleProperty());
|
decorator.drawerTitleProperty().bind(((DecoratorPage) to).titleProperty());
|
||||||
decorator.showCloseAsHomeProperty().set(!((DecoratorPage) to).canForceToClose());
|
decorator.showCloseAsHomeProperty().set(!((DecoratorPage) to).isPageCloseable());
|
||||||
|
decorator.canBackProperty().bind(Bindings.createBooleanBinding(() -> navigator.canGoBack() || ((DecoratorPage) to).backableProperty().get(),
|
||||||
|
((DecoratorPage) to).backableProperty()));
|
||||||
} else {
|
} else {
|
||||||
decorator.drawerTitleProperty().unbind();
|
decorator.drawerTitleProperty().unbind();
|
||||||
decorator.drawerTitleProperty().set("");
|
decorator.drawerTitleProperty().set("");
|
||||||
decorator.showCloseAsHomeProperty().set(true);
|
decorator.showCloseAsHomeProperty().set(true);
|
||||||
|
decorator.canBackProperty().unbind();
|
||||||
|
decorator.canBackProperty().setValue(navigator.canGoBack());
|
||||||
}
|
}
|
||||||
|
|
||||||
decorator.canBackProperty().set(navigator.canGoBack());
|
|
||||||
decorator.canCloseProperty().set(navigator.canGoBack());
|
decorator.canCloseProperty().set(navigator.canGoBack());
|
||||||
|
|
||||||
if (to instanceof Region) {
|
if (to instanceof Region) {
|
||||||
@ -359,7 +365,7 @@ public class DecoratorController {
|
|||||||
public void startWizard(WizardProvider wizardProvider, String category) {
|
public void startWizard(WizardProvider wizardProvider, String category) {
|
||||||
FXUtils.checkFxUserThread();
|
FXUtils.checkFxUserThread();
|
||||||
|
|
||||||
getNavigator().navigate(new DecoratorWizardDisplayer(wizardProvider, category));
|
getNavigator().navigate(new DecoratorWizardDisplayer(wizardProvider, category), ContainerAnimations.FADE.getAnimationProducer());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==== Authlib Injector DnD ====
|
// ==== Authlib Injector DnD ====
|
||||||
|
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* 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.decorator;
|
||||||
|
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import org.jackhuang.hmcl.ui.animation.AnimationProducer;
|
||||||
|
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||||
|
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
||||||
|
|
||||||
|
public abstract class DecoratorNavigatorPage extends DecoratorTransitionPage {
|
||||||
|
protected final Navigator navigator = new Navigator();
|
||||||
|
|
||||||
|
{
|
||||||
|
this.navigator.setOnNavigating(this::onNavigating);
|
||||||
|
this.navigator.setOnNavigated(this::onNavigated);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void navigate(Node page, AnimationProducer animationProducer) {
|
||||||
|
navigator.navigate(page, animationProducer);
|
||||||
|
setRefreshable(page instanceof Refreshable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean back() {
|
||||||
|
if (navigator.canGoBack()) {
|
||||||
|
navigator.close();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onNavigating(Navigator.NavigationEvent event) {
|
||||||
|
if (event.getSource() != this.navigator) return;
|
||||||
|
Node from = event.getNode();
|
||||||
|
|
||||||
|
if (from instanceof DecoratorPage)
|
||||||
|
((DecoratorPage) from).back();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onNavigated(Navigator.NavigationEvent event) {
|
||||||
|
if (event.getSource() != this.navigator) return;
|
||||||
|
Node to = event.getNode();
|
||||||
|
|
||||||
|
if (to instanceof Refreshable) {
|
||||||
|
refreshableProperty().bind(((Refreshable) to).refreshableProperty());
|
||||||
|
} else {
|
||||||
|
refreshableProperty().unbind();
|
||||||
|
refreshableProperty().set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to instanceof DecoratorPage) {
|
||||||
|
titleProperty().bind(((DecoratorPage) to).titleProperty());
|
||||||
|
} else {
|
||||||
|
titleProperty().unbind();
|
||||||
|
titleProperty().set("");
|
||||||
|
}
|
||||||
|
|
||||||
|
setBackable(navigator.canGoBack());
|
||||||
|
|
||||||
|
if (to instanceof Region) {
|
||||||
|
Region region = (Region) to;
|
||||||
|
// Let root pane fix window size.
|
||||||
|
StackPane parent = (StackPane) region.getParent();
|
||||||
|
region.prefWidthProperty().bind(parent.widthProperty());
|
||||||
|
region.prefHeightProperty().bind(parent.heightProperty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,22 +17,28 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui.decorator;
|
package org.jackhuang.hmcl.ui.decorator;
|
||||||
|
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.ReadOnlyStringProperty;
|
import javafx.beans.property.ReadOnlyStringProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||||
|
|
||||||
public interface DecoratorPage {
|
public interface DecoratorPage {
|
||||||
ReadOnlyStringProperty titleProperty();
|
ReadOnlyStringProperty titleProperty();
|
||||||
|
|
||||||
default boolean canForceToClose() {
|
default boolean isPageCloseable() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
default boolean onClose() {
|
default boolean back() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
default void onForceToClose() {
|
default BooleanProperty backableProperty() {
|
||||||
|
return new SimpleBooleanProperty(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
default void closePage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
default void onDecoratorPageNavigating(Navigator.NavigationEvent event) {
|
default void onDecoratorPageNavigating(Navigator.NavigationEvent event) {
|
||||||
|
@ -72,65 +72,41 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
|||||||
root.setMaxWidth(Region.USE_PREF_SIZE);
|
root.setMaxWidth(Region.USE_PREF_SIZE);
|
||||||
root.setMinWidth(Region.USE_PREF_SIZE);
|
root.setMinWidth(Region.USE_PREF_SIZE);
|
||||||
|
|
||||||
StackPane drawerWrapper = new StackPane();
|
// center node with a container node in bottom layer and a "welcome" layer at the top layer.
|
||||||
skinnable.setDrawerWrapper(drawerWrapper);
|
StackPane container = new StackPane();
|
||||||
drawerWrapper.getStyleClass().add("jfx-decorator-drawer");
|
skinnable.setDrawerWrapper(container);
|
||||||
drawerWrapper.backgroundProperty().bind(skinnable.backgroundProperty());
|
container.getStyleClass().add("jfx-decorator-drawer");
|
||||||
FXUtils.setOverflowHidden(drawerWrapper, true);
|
container.backgroundProperty().bind(skinnable.backgroundProperty());
|
||||||
{
|
FXUtils.setOverflowHidden(container, true);
|
||||||
BorderPane drawer = new BorderPane();
|
// bottom layer
|
||||||
|
|
||||||
{
|
|
||||||
BorderPane leftRootPane = new BorderPane();
|
|
||||||
FXUtils.setLimitWidth(leftRootPane, 200);
|
|
||||||
leftRootPane.getStyleClass().add("jfx-decorator-content-container");
|
|
||||||
|
|
||||||
StackPane drawerContainer = new StackPane();
|
|
||||||
drawerContainer.getStyleClass().add("gray-background");
|
|
||||||
Bindings.bindContent(drawerContainer.getChildren(), skinnable.drawerProperty());
|
|
||||||
leftRootPane.setCenter(drawerContainer);
|
|
||||||
|
|
||||||
Rectangle separator = new Rectangle();
|
|
||||||
separator.heightProperty().bind(drawer.heightProperty());
|
|
||||||
separator.setWidth(1);
|
|
||||||
separator.setFill(Color.GRAY);
|
|
||||||
|
|
||||||
leftRootPane.setRight(separator);
|
|
||||||
|
|
||||||
drawer.setLeft(leftRootPane);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
contentPlaceHolder = new StackPane();
|
contentPlaceHolder = new StackPane();
|
||||||
contentPlaceHolder.getStyleClass().add("jfx-decorator-content-container");
|
contentPlaceHolder.getStyleClass().add("jfx-decorator-content-container");
|
||||||
FXUtils.setOverflowHidden(contentPlaceHolder, true);
|
|
||||||
Bindings.bindContent(contentPlaceHolder.getChildren(), skinnable.contentProperty());
|
Bindings.bindContent(contentPlaceHolder.getChildren(), skinnable.contentProperty());
|
||||||
|
|
||||||
drawer.setCenter(contentPlaceHolder);
|
container.getChildren().add(contentPlaceHolder);
|
||||||
}
|
|
||||||
|
|
||||||
drawerWrapper.getChildren().add(drawer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// top layer for welcome and hint
|
||||||
{
|
{
|
||||||
StackPane container = new StackPane();
|
StackPane floatLayer = new StackPane();
|
||||||
Bindings.bindContent(container.getChildren(), skinnable.containerProperty());
|
Bindings.bindContent(floatLayer.getChildren(), skinnable.containerProperty());
|
||||||
ListChangeListener<Node> listener = c -> {
|
ListChangeListener<Node> listener = c -> {
|
||||||
if (skinnable.getContainer().isEmpty()) {
|
if (skinnable.getContainer().isEmpty()) {
|
||||||
container.setMouseTransparent(true);
|
floatLayer.setMouseTransparent(true);
|
||||||
container.setVisible(false);
|
floatLayer.setVisible(false);
|
||||||
} else {
|
} else {
|
||||||
container.setMouseTransparent(false);
|
floatLayer.setMouseTransparent(false);
|
||||||
container.setVisible(true);
|
floatLayer.setVisible(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
skinnable.containerProperty().addListener(listener);
|
skinnable.containerProperty().addListener(listener);
|
||||||
listener.onChanged(null);
|
listener.onChanged(null);
|
||||||
|
|
||||||
drawerWrapper.getChildren().add(container);
|
container.getChildren().add(floatLayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
root.setCenter(drawerWrapper);
|
root.setCenter(container);
|
||||||
|
|
||||||
titleContainer = new BorderPane();
|
titleContainer = new BorderPane();
|
||||||
titleContainer.setOnMouseReleased(this::onMouseReleased);
|
titleContainer.setOnMouseReleased(this::onMouseReleased);
|
||||||
|
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* 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.decorator;
|
||||||
|
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.Control;
|
||||||
|
import javafx.scene.control.Skin;
|
||||||
|
import org.jackhuang.hmcl.ui.animation.AnimationProducer;
|
||||||
|
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||||
|
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
||||||
|
|
||||||
|
public abstract class DecoratorTransitionPage extends Control implements DecoratorPage, Refreshable {
|
||||||
|
private final StringProperty title = new SimpleStringProperty();
|
||||||
|
private final BooleanProperty refreshable = new SimpleBooleanProperty(false);
|
||||||
|
private final BooleanProperty backable = new SimpleBooleanProperty();
|
||||||
|
private Node currentPage;
|
||||||
|
protected final TransitionPane transitionPane = new TransitionPane();
|
||||||
|
|
||||||
|
protected void navigate(Node page, AnimationProducer animation) {
|
||||||
|
transitionPane.setContent(currentPage = page, animation);
|
||||||
|
refreshable.setValue(page instanceof Refreshable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected abstract Skin<?> createDefaultSkin();
|
||||||
|
|
||||||
|
protected Node getCurrentPage() {
|
||||||
|
return currentPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StringProperty titleProperty() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title.set(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRefreshable() {
|
||||||
|
return refreshable.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refresh() {
|
||||||
|
// empty implementation for default
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BooleanProperty refreshableProperty() {
|
||||||
|
return refreshable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRefreshable(boolean refreshable) {
|
||||||
|
this.refreshable.set(refreshable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBackable() {
|
||||||
|
return backable.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BooleanProperty backableProperty() {
|
||||||
|
return backable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackable(boolean backable) {
|
||||||
|
this.backable.set(backable);
|
||||||
|
}
|
||||||
|
}
|
@ -17,13 +17,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui.decorator;
|
package org.jackhuang.hmcl.ui.decorator;
|
||||||
|
|
||||||
import javafx.beans.property.BooleanProperty;
|
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
|
||||||
import javafx.beans.property.StringProperty;
|
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.control.SkinBase;
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
|
||||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||||
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
||||||
import org.jackhuang.hmcl.ui.wizard.*;
|
import org.jackhuang.hmcl.ui.wizard.*;
|
||||||
@ -31,18 +26,12 @@ import org.jackhuang.hmcl.ui.wizard.*;
|
|||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
|
||||||
public class DecoratorWizardDisplayer extends StackPane implements TaskExecutorDialogWizardDisplayer, Refreshable, DecoratorPage {
|
public class DecoratorWizardDisplayer extends DecoratorTransitionPage implements TaskExecutorDialogWizardDisplayer {
|
||||||
private final StringProperty title = new SimpleStringProperty();
|
|
||||||
private final BooleanProperty canRefresh = new SimpleBooleanProperty();
|
|
||||||
|
|
||||||
private final TransitionHandler transitionHandler = new TransitionHandler(this);
|
|
||||||
private final WizardController wizardController = new WizardController(this);
|
private final WizardController wizardController = new WizardController(this);
|
||||||
private final Queue<Object> cancelQueue = new ConcurrentLinkedQueue<>();
|
private final Queue<Object> cancelQueue = new ConcurrentLinkedQueue<>();
|
||||||
|
|
||||||
private final String category;
|
private final String category;
|
||||||
|
|
||||||
private Node nowPage;
|
|
||||||
|
|
||||||
public DecoratorWizardDisplayer(WizardProvider provider) {
|
public DecoratorWizardDisplayer(WizardProvider provider) {
|
||||||
this(provider, null);
|
this(provider, null);
|
||||||
}
|
}
|
||||||
@ -56,16 +45,6 @@ public class DecoratorWizardDisplayer extends StackPane implements TaskExecutorD
|
|||||||
addEventHandler(Navigator.NavigationEvent.NAVIGATING, this::onDecoratorPageNavigating);
|
addEventHandler(Navigator.NavigationEvent.NAVIGATING, this::onDecoratorPageNavigating);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public StringProperty titleProperty() {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BooleanProperty canRefreshProperty() {
|
|
||||||
return canRefresh;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WizardController getWizardController() {
|
public WizardController getWizardController() {
|
||||||
return wizardController;
|
return wizardController;
|
||||||
@ -88,30 +67,26 @@ public class DecoratorWizardDisplayer extends StackPane implements TaskExecutorD
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void navigateTo(Node page, Navigation.NavigationDirection nav) {
|
public void navigateTo(Node page, Navigation.NavigationDirection nav) {
|
||||||
nowPage = page;
|
navigate(page, nav.getAnimation().getAnimationProducer());
|
||||||
|
|
||||||
transitionHandler.setContent(page, nav.getAnimation().getAnimationProducer());
|
|
||||||
|
|
||||||
canRefresh.set(page instanceof Refreshable);
|
|
||||||
|
|
||||||
String prefix = category == null ? "" : category + " - ";
|
String prefix = category == null ? "" : category + " - ";
|
||||||
|
|
||||||
if (page instanceof WizardPage)
|
if (page instanceof WizardPage)
|
||||||
title.set(prefix + ((WizardPage) page).getTitle());
|
setTitle(prefix + ((WizardPage) page).getTitle());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canForceToClose() {
|
public boolean isPageCloseable() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onForceToClose() {
|
public void closePage() {
|
||||||
wizardController.onCancel();
|
wizardController.onCancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onClose() {
|
public boolean back() {
|
||||||
if (wizardController.canPrev()) {
|
if (wizardController.canPrev()) {
|
||||||
wizardController.onPrev(true);
|
wizardController.onPrev(true);
|
||||||
return false;
|
return false;
|
||||||
@ -121,6 +96,20 @@ public class DecoratorWizardDisplayer extends StackPane implements TaskExecutorD
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void refresh() {
|
public void refresh() {
|
||||||
((Refreshable) nowPage).refresh();
|
((Refreshable) getCurrentPage()).refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Skin createDefaultSkin() {
|
||||||
|
return new Skin(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Skin extends SkinBase<DecoratorWizardDisplayer> {
|
||||||
|
|
||||||
|
protected Skin(DecoratorWizardDisplayer control) {
|
||||||
|
super(control);
|
||||||
|
|
||||||
|
getChildren().setAll(control.transitionPane);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion;
|
|||||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.FloatListCell;
|
import org.jackhuang.hmcl.ui.construct.FloatListCell;
|
||||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||||
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
||||||
@ -72,7 +72,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
|||||||
@FXML
|
@FXML
|
||||||
private StackPane emptyPane;
|
private StackPane emptyPane;
|
||||||
@FXML
|
@FXML
|
||||||
private StackPane root;
|
private TransitionPane root;
|
||||||
@FXML
|
@FXML
|
||||||
private JFXCheckBox chkRelease;
|
private JFXCheckBox chkRelease;
|
||||||
@FXML
|
@FXML
|
||||||
@ -84,7 +84,6 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
|||||||
@FXML
|
@FXML
|
||||||
private VBox centrePane;
|
private VBox centrePane;
|
||||||
|
|
||||||
private final TransitionHandler transitionHandler;
|
|
||||||
private final VersionList<?> versionList;
|
private final VersionList<?> versionList;
|
||||||
private TaskExecutor executor;
|
private TaskExecutor executor;
|
||||||
|
|
||||||
@ -97,8 +96,6 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
|||||||
|
|
||||||
FXUtils.loadFXML(this, "/assets/fxml/download/versions.fxml");
|
FXUtils.loadFXML(this, "/assets/fxml/download/versions.fxml");
|
||||||
|
|
||||||
transitionHandler = new TransitionHandler(root);
|
|
||||||
|
|
||||||
if (versionList.hasType()) {
|
if (versionList.hasType()) {
|
||||||
centrePane.getChildren().setAll(checkPane, list);
|
centrePane.getChildren().setAll(checkPane, list);
|
||||||
} else
|
} else
|
||||||
@ -187,14 +184,14 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void refresh() {
|
public void refresh() {
|
||||||
transitionHandler.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer());
|
root.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer());
|
||||||
executor = versionList.refreshAsync(gameVersion).whenComplete(exception -> {
|
executor = versionList.refreshAsync(gameVersion).whenComplete(exception -> {
|
||||||
if (exception == null) {
|
if (exception == null) {
|
||||||
List<RemoteVersion> items = loadVersions();
|
List<RemoteVersion> items = loadVersions();
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
if (versionList.getVersions(gameVersion).isEmpty()) {
|
if (versionList.getVersions(gameVersion).isEmpty()) {
|
||||||
transitionHandler.setContent(emptyPane, ContainerAnimations.FADE.getAnimationProducer());
|
root.setContent(emptyPane, ContainerAnimations.FADE.getAnimationProducer());
|
||||||
} else {
|
} else {
|
||||||
if (items.isEmpty()) {
|
if (items.isEmpty()) {
|
||||||
chkRelease.setSelected(true);
|
chkRelease.setSelected(true);
|
||||||
@ -203,13 +200,13 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
|||||||
} else {
|
} else {
|
||||||
list.getItems().setAll(items);
|
list.getItems().setAll(items);
|
||||||
}
|
}
|
||||||
transitionHandler.setContent(centrePane, ContainerAnimations.FADE.getAnimationProducer());
|
root.setContent(centrePane, ContainerAnimations.FADE.getAnimationProducer());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
LOG.log(Level.WARNING, "Failed to fetch versions list", exception);
|
LOG.log(Level.WARNING, "Failed to fetch versions list", exception);
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
transitionHandler.setContent(failedPane, ContainerAnimations.FADE.getAnimationProducer());
|
root.setContent(failedPane, ContainerAnimations.FADE.getAnimationProducer());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).executor().start();
|
}).executor().start();
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui;
|
package org.jackhuang.hmcl.ui.main;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
import com.jfoenix.controls.JFXButton;
|
||||||
import com.jfoenix.controls.JFXPopup;
|
import com.jfoenix.controls.JFXPopup;
|
||||||
@ -23,12 +23,7 @@ import javafx.animation.KeyFrame;
|
|||||||
import javafx.animation.KeyValue;
|
import javafx.animation.KeyValue;
|
||||||
import javafx.animation.Timeline;
|
import javafx.animation.Timeline;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.*;
|
||||||
import javafx.beans.property.ReadOnlyStringProperty;
|
|
||||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
|
||||||
import javafx.beans.property.StringProperty;
|
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
@ -44,6 +39,8 @@ import org.jackhuang.hmcl.Metadata;
|
|||||||
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.setting.Theme;
|
||||||
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
||||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
287
HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java
Normal file
287
HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
/*
|
||||||
|
* 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.main;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.SkinBase;
|
||||||
|
import javafx.scene.layout.BorderPane;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.shape.Rectangle;
|
||||||
|
import org.jackhuang.hmcl.event.EventBus;
|
||||||
|
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||||
|
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||||
|
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||||
|
import org.jackhuang.hmcl.game.Version;
|
||||||
|
import org.jackhuang.hmcl.setting.Accounts;
|
||||||
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
|
import org.jackhuang.hmcl.setting.Profiles;
|
||||||
|
import org.jackhuang.hmcl.task.Schedulers;
|
||||||
|
import org.jackhuang.hmcl.task.Task;
|
||||||
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
import org.jackhuang.hmcl.ui.account.AccountAdvancedListItem;
|
||||||
|
import org.jackhuang.hmcl.ui.account.AccountList;
|
||||||
|
import org.jackhuang.hmcl.ui.account.AddAccountPane;
|
||||||
|
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||||
|
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||||
|
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||||
|
import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
||||||
|
import org.jackhuang.hmcl.ui.decorator.DecoratorNavigatorPage;
|
||||||
|
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||||
|
import org.jackhuang.hmcl.ui.profile.ProfileAdvancedListItem;
|
||||||
|
import org.jackhuang.hmcl.ui.profile.ProfileList;
|
||||||
|
import org.jackhuang.hmcl.ui.versions.GameAdvancedListItem;
|
||||||
|
import org.jackhuang.hmcl.ui.versions.GameItem;
|
||||||
|
import org.jackhuang.hmcl.ui.versions.GameList;
|
||||||
|
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||||
|
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||||
|
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||||
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
|
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||||
|
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
||||||
|
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||||
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
|
public class RootPage extends DecoratorNavigatorPage {
|
||||||
|
private MainPage mainPage = null;
|
||||||
|
private SettingsPage settingsPage = null;
|
||||||
|
private GameList gameListPage = null;
|
||||||
|
private AccountList accountListPage = null;
|
||||||
|
private ProfileList profileListPage = null;
|
||||||
|
|
||||||
|
public RootPage() {
|
||||||
|
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(event -> onRefreshedVersions((HMCLGameRepository) event.getSource()));
|
||||||
|
|
||||||
|
Profile profile = Profiles.getSelectedProfile();
|
||||||
|
if (profile != null && profile.getRepository().isLoaded())
|
||||||
|
onRefreshedVersions(Profiles.selectedProfileProperty().get().getRepository());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Skin createDefaultSkin() {
|
||||||
|
return new Skin(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MainPage getMainPage() {
|
||||||
|
if (mainPage == null) {
|
||||||
|
mainPage = new MainPage();
|
||||||
|
FXUtils.applyDragListener(mainPage, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
|
||||||
|
File modpack = modpacks.get(0);
|
||||||
|
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack), i18n("install.modpack"));
|
||||||
|
});
|
||||||
|
|
||||||
|
FXUtils.onChangeAndOperate(Profiles.selectedVersionProperty(), version -> {
|
||||||
|
if (version != null) {
|
||||||
|
mainPage.setCurrentGame(version);
|
||||||
|
} else {
|
||||||
|
mainPage.setCurrentGame(i18n("version.empty"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mainPage.showUpdateProperty().bind(UpdateChecker.outdatedProperty());
|
||||||
|
mainPage.latestVersionProperty().bind(
|
||||||
|
BindingMapping.of(UpdateChecker.latestVersionProperty())
|
||||||
|
.map(version -> version == null ? "" : i18n("update.bubble.title", version.getVersion())));
|
||||||
|
|
||||||
|
Profiles.registerVersionsListener(profile -> {
|
||||||
|
HMCLGameRepository repository = profile.getRepository();
|
||||||
|
List<Node> children = repository.getVersions().parallelStream()
|
||||||
|
.filter(version -> !version.isHidden())
|
||||||
|
.sorted(Comparator.comparing((Version version) -> version.getReleaseTime() == null ? new Date(0L) : version.getReleaseTime())
|
||||||
|
.thenComparing(a -> VersionNumber.asVersion(a.getId())))
|
||||||
|
.map(version -> {
|
||||||
|
Node node = PopupMenu.wrapPopupMenuItem(new GameItem(profile, version.getId()));
|
||||||
|
node.setOnMouseClicked(e -> profile.setSelectedVersion(version.getId()));
|
||||||
|
return node;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
runInFX(() -> {
|
||||||
|
if (profile == Profiles.getSelectedProfile())
|
||||||
|
mainPage.getVersions().setAll(children);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return mainPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SettingsPage getSettingsPage() {
|
||||||
|
if (settingsPage == null)
|
||||||
|
settingsPage = new SettingsPage();
|
||||||
|
return settingsPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GameList getGameListPage() {
|
||||||
|
if (gameListPage == null) {
|
||||||
|
gameListPage = new GameList();
|
||||||
|
FXUtils.applyDragListener(gameListPage, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
|
||||||
|
File modpack = modpacks.get(0);
|
||||||
|
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack), i18n("install.modpack"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return gameListPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AccountList getAccountListPage() {
|
||||||
|
if (accountListPage == null) {
|
||||||
|
accountListPage = new AccountList();
|
||||||
|
accountListPage.selectedAccountProperty().bindBidirectional(Accounts.selectedAccountProperty());
|
||||||
|
accountListPage.accountsProperty().bindContent(Accounts.accountsProperty());
|
||||||
|
}
|
||||||
|
return accountListPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProfileList getProfileListPage() {
|
||||||
|
if (profileListPage == null) {
|
||||||
|
profileListPage = new ProfileList();
|
||||||
|
profileListPage.selectedProfileProperty().bindBidirectional(Profiles.selectedProfileProperty());
|
||||||
|
profileListPage.profilesProperty().bindContent(Profiles.profilesProperty());
|
||||||
|
}
|
||||||
|
return profileListPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Skin extends SkinBase<RootPage> {
|
||||||
|
|
||||||
|
protected Skin(RootPage control) {
|
||||||
|
super(control);
|
||||||
|
|
||||||
|
// first item in left sidebar
|
||||||
|
AccountAdvancedListItem accountListItem = new AccountAdvancedListItem();
|
||||||
|
accountListItem.setOnAction(e -> getSkinnable().navigate(getSkinnable().getAccountListPage(), ContainerAnimations.FADE.getAnimationProducer()));
|
||||||
|
accountListItem.accountProperty().bind(Accounts.selectedAccountProperty());
|
||||||
|
|
||||||
|
// second item in left sidebar
|
||||||
|
GameAdvancedListItem gameListItem = new GameAdvancedListItem();
|
||||||
|
gameListItem.actionButtonVisibleProperty().bind(Profiles.selectedVersionProperty().isNotNull());
|
||||||
|
gameListItem.setOnAction(e -> {
|
||||||
|
Profile profile = Profiles.getSelectedProfile();
|
||||||
|
String version = Profiles.getSelectedVersion();
|
||||||
|
if (version == null) {
|
||||||
|
getSkinnable().navigate(getSkinnable().getGameListPage(), ContainerAnimations.FADE.getAnimationProducer());
|
||||||
|
} else {
|
||||||
|
Versions.modifyGameSettings(profile, version);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// third item in left sidebar
|
||||||
|
AdvancedListItem gameItem = new AdvancedListItem();
|
||||||
|
gameItem.setImage(newImage("/assets/img/bookshelf.png"));
|
||||||
|
gameItem.setTitle(i18n("version.manage"));
|
||||||
|
gameItem.setOnAction(e -> getSkinnable().navigate(getSkinnable().getGameListPage(), ContainerAnimations.FADE.getAnimationProducer()));
|
||||||
|
|
||||||
|
// forth item in left sidebar
|
||||||
|
ProfileAdvancedListItem profileListItem = new ProfileAdvancedListItem();
|
||||||
|
profileListItem.setOnAction(e -> getSkinnable().navigate(getSkinnable().getProfileListPage(), ContainerAnimations.FADE.getAnimationProducer()));
|
||||||
|
profileListItem.profileProperty().bind(Profiles.selectedProfileProperty());
|
||||||
|
|
||||||
|
// fifth item in left sidebar
|
||||||
|
AdvancedListItem launcherSettingsItem = new AdvancedListItem();
|
||||||
|
launcherSettingsItem.setImage(newImage("/assets/img/command.png"));
|
||||||
|
launcherSettingsItem.setTitle(i18n("settings.launcher"));
|
||||||
|
launcherSettingsItem.setOnAction(e -> getSkinnable().navigate(getSkinnable().getSettingsPage(), ContainerAnimations.FADE.getAnimationProducer()));
|
||||||
|
|
||||||
|
// the left sidebar
|
||||||
|
AdvancedListBox sideBar = new AdvancedListBox()
|
||||||
|
.startCategory(i18n("account").toUpperCase())
|
||||||
|
.add(accountListItem)
|
||||||
|
.startCategory(i18n("version").toUpperCase())
|
||||||
|
.add(gameListItem)
|
||||||
|
.add(gameItem)
|
||||||
|
.startCategory(i18n("profile.title").toUpperCase())
|
||||||
|
.add(profileListItem)
|
||||||
|
.startCategory(i18n("launcher").toUpperCase())
|
||||||
|
.add(launcherSettingsItem);
|
||||||
|
|
||||||
|
// the root page, with the sidebar in left, navigator in center.
|
||||||
|
BorderPane root = new BorderPane();
|
||||||
|
|
||||||
|
{
|
||||||
|
BorderPane leftRootPane = new BorderPane();
|
||||||
|
FXUtils.setLimitWidth(leftRootPane, 200);
|
||||||
|
|
||||||
|
StackPane drawerContainer = new StackPane();
|
||||||
|
drawerContainer.getStyleClass().add("gray-background");
|
||||||
|
drawerContainer.getChildren().setAll(sideBar);
|
||||||
|
leftRootPane.setCenter(drawerContainer);
|
||||||
|
|
||||||
|
Rectangle separator = new Rectangle();
|
||||||
|
separator.heightProperty().bind(root.heightProperty());
|
||||||
|
separator.setWidth(1);
|
||||||
|
separator.setFill(Color.GRAY);
|
||||||
|
|
||||||
|
leftRootPane.setRight(separator);
|
||||||
|
|
||||||
|
root.setLeft(leftRootPane);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
control.navigator.getStyleClass().add("jfx-decorator-content-container");
|
||||||
|
control.navigator.init(getSkinnable().getMainPage());
|
||||||
|
root.setCenter(control.navigator);
|
||||||
|
}
|
||||||
|
|
||||||
|
getChildren().setAll(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==== Accounts ====
|
||||||
|
public void checkAccount() {
|
||||||
|
if (Accounts.getAccounts().isEmpty())
|
||||||
|
Platform.runLater(this::addNewAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addNewAccount() {
|
||||||
|
Controllers.dialog(new AddAccountPane());
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
|
||||||
|
private boolean checkedModpack = false;
|
||||||
|
|
||||||
|
private void onRefreshedVersions(HMCLGameRepository repository) {
|
||||||
|
runInFX(() -> {
|
||||||
|
if (!checkedModpack) {
|
||||||
|
checkedModpack = true;
|
||||||
|
|
||||||
|
if (repository.getVersionCount() == 0) {
|
||||||
|
File modpackFile = new File("modpack.zip").getAbsoluteFile();
|
||||||
|
if (modpackFile.exists()) {
|
||||||
|
Task.supplyAsync(() -> CompressingUtils.findSuitableEncoding(modpackFile.toPath()))
|
||||||
|
.thenApplyAsync(encoding -> ModpackHelper.readModpackManifest(modpackFile.toPath(), encoding))
|
||||||
|
.thenApplyAsync(modpack -> ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
|
||||||
|
.withRunAsync(Schedulers.javafx(), this::checkAccount).executor())
|
||||||
|
.thenAcceptAsync(Schedulers.javafx(), executor -> {
|
||||||
|
Controllers.taskDialog(executor, i18n("modpack.installing"));
|
||||||
|
executor.start();
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAccount();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui;
|
package org.jackhuang.hmcl.ui.main;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXColorPicker;
|
import com.jfoenix.controls.JFXColorPicker;
|
||||||
import com.jfoenix.effects.JFXDepthManager;
|
import com.jfoenix.effects.JFXDepthManager;
|
||||||
@ -31,6 +31,8 @@ import javafx.scene.paint.Color;
|
|||||||
import javafx.scene.text.Font;
|
import javafx.scene.text.Font;
|
||||||
import org.jackhuang.hmcl.Metadata;
|
import org.jackhuang.hmcl.Metadata;
|
||||||
import org.jackhuang.hmcl.setting.*;
|
import org.jackhuang.hmcl.setting.*;
|
||||||
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
import org.jackhuang.hmcl.ui.construct.Validator;
|
@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui;
|
package org.jackhuang.hmcl.ui.main;
|
||||||
|
|
||||||
import com.jfoenix.controls.*;
|
import com.jfoenix.controls.*;
|
||||||
import javafx.geometry.HPos;
|
import javafx.geometry.HPos;
|
||||||
@ -31,6 +31,8 @@ import javafx.scene.text.TextAlignment;
|
|||||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||||
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
||||||
import org.jackhuang.hmcl.setting.Theme;
|
import org.jackhuang.hmcl.setting.Theme;
|
||||||
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
import org.jackhuang.hmcl.ui.construct.*;
|
import org.jackhuang.hmcl.ui.construct.*;
|
||||||
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
|
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
|
||||||
|
|
@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui.versions;
|
package org.jackhuang.hmcl.ui.versions;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Skin;
|
import javafx.scene.control.Skin;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
@ -28,12 +29,7 @@ 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.task.TaskListener;
|
import org.jackhuang.hmcl.task.TaskListener;
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.*;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
|
||||||
import org.jackhuang.hmcl.ui.InstallerItem;
|
|
||||||
import org.jackhuang.hmcl.ui.ListPageBase;
|
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
|
||||||
import org.jackhuang.hmcl.ui.ToolbarListPageSkin;
|
|
||||||
import org.jackhuang.hmcl.ui.download.InstallerWizardProvider;
|
import org.jackhuang.hmcl.ui.download.InstallerWizardProvider;
|
||||||
import org.jackhuang.hmcl.ui.download.UpdateInstallerWizardProvider;
|
import org.jackhuang.hmcl.ui.download.UpdateInstallerWizardProvider;
|
||||||
import org.jackhuang.hmcl.util.Lang;
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
@ -43,6 +39,7 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
@ -67,17 +64,17 @@ public class InstallerListPage extends ListPageBase<InstallerItem> {
|
|||||||
return new InstallerListPageSkin();
|
return new InstallerListPageSkin();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadVersion(Profile profile, String versionId) {
|
public CompletableFuture<?> loadVersion(Profile profile, String versionId) {
|
||||||
this.profile = profile;
|
this.profile = profile;
|
||||||
this.versionId = versionId;
|
this.versionId = versionId;
|
||||||
this.version = profile.getRepository().getVersion(versionId);
|
this.version = profile.getRepository().getVersion(versionId);
|
||||||
this.gameVersion = null;
|
this.gameVersion = null;
|
||||||
|
|
||||||
Task.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse(null);
|
gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse(null);
|
||||||
|
|
||||||
return LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(versionId));
|
return LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(versionId));
|
||||||
}).thenAcceptAsync(Schedulers.javafx(), analyzer -> {
|
}).thenAcceptAsync(analyzer -> {
|
||||||
Function<String, Consumer<InstallerItem>> removeAction = libraryId -> x -> {
|
Function<String, Consumer<InstallerItem>> removeAction = libraryId -> x -> {
|
||||||
profile.getDependency().removeLibraryAsync(version, libraryId)
|
profile.getDependency().removeLibraryAsync(version, libraryId)
|
||||||
.thenComposeAsync(profile.getRepository()::save)
|
.thenComposeAsync(profile.getRepository()::save)
|
||||||
@ -100,7 +97,7 @@ public class InstallerListPage extends ListPageBase<InstallerItem> {
|
|||||||
else
|
else
|
||||||
itemsProperty().add(new InstallerItem(title, libraryVersion, null, action));
|
itemsProperty().add(new InstallerItem(title, libraryVersion, null, action));
|
||||||
}
|
}
|
||||||
}).start();
|
}, Platform::runLater);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void installOnline() {
|
public void installOnline() {
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
package org.jackhuang.hmcl.ui.versions;
|
package org.jackhuang.hmcl.ui.versions;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXTabPane;
|
import com.jfoenix.controls.JFXTabPane;
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
@ -37,10 +38,12 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -77,28 +80,32 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
|||||||
loadMods(modManager);
|
loadMods(modManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadVersion(Profile profile, String id) {
|
public CompletableFuture<?> loadVersion(Profile profile, String id) {
|
||||||
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));
|
return loadMods(profile.getRepository().getModManager(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadMods(ModManager modManager) {
|
private CompletableFuture<?> loadMods(ModManager modManager) {
|
||||||
this.modManager = modManager;
|
this.modManager = modManager;
|
||||||
Task.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
|
try {
|
||||||
synchronized (ModListPage.this) {
|
synchronized (ModListPage.this) {
|
||||||
runInFX(() -> loadingProperty().set(true));
|
runInFX(() -> loadingProperty().set(true));
|
||||||
modManager.refreshMods();
|
modManager.refreshMods();
|
||||||
return new LinkedList<>(modManager.getMods());
|
return new LinkedList<>(modManager.getMods());
|
||||||
}
|
}
|
||||||
}).whenComplete(Schedulers.javafx(), (list, exception) -> {
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}).whenCompleteAsync((list, exception) -> {
|
||||||
loadingProperty().set(false);
|
loadingProperty().set(false);
|
||||||
if (exception == null)
|
if (exception == null)
|
||||||
FXUtils.onWeakChangeAndOperate(parentTab.getSelectionModel().selectedItemProperty(), newValue -> {
|
FXUtils.onWeakChangeAndOperate(parentTab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||||
if (newValue != null && newValue.getUserData() == ModListPage.this)
|
if (newValue != null && newValue.getUserData() == ModListPage.this)
|
||||||
itemsProperty().setAll(list.stream().map(ModListPageSkin.ModInfoObject::new).collect(Collectors.toList()));
|
itemsProperty().setAll(list.stream().map(ModListPageSkin.ModInfoObject::new).collect(Collectors.toList()));
|
||||||
});
|
});
|
||||||
}).start();
|
}, Platform::runLater);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add() {
|
public void add() {
|
||||||
|
@ -21,6 +21,8 @@ import com.jfoenix.controls.JFXButton;
|
|||||||
import com.jfoenix.controls.JFXPopup;
|
import com.jfoenix.controls.JFXPopup;
|
||||||
import com.jfoenix.controls.JFXTabPane;
|
import com.jfoenix.controls.JFXTabPane;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||||
import javafx.beans.property.ReadOnlyStringProperty;
|
import javafx.beans.property.ReadOnlyStringProperty;
|
||||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
@ -34,15 +36,16 @@ import org.jackhuang.hmcl.ui.construct.IconedMenuItem;
|
|||||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||||
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
||||||
import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
||||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
|
||||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
public final class VersionPage extends StackPane implements DecoratorPage {
|
public final class VersionPage extends StackPane {
|
||||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", null);
|
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", null);
|
||||||
|
private final ReadOnlyBooleanWrapper loading = new ReadOnlyBooleanWrapper(this, "loading", false);
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private VersionSettingsPage versionSettings;
|
private VersionSettingsPage versionSettings;
|
||||||
@ -65,8 +68,6 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
|||||||
@FXML
|
@FXML
|
||||||
private JFXButton btnTestGame;
|
private JFXButton btnTestGame;
|
||||||
@FXML
|
@FXML
|
||||||
private StackPane rootPane;
|
|
||||||
@FXML
|
|
||||||
private StackPane contentPane;
|
private StackPane contentPane;
|
||||||
@FXML
|
@FXML
|
||||||
private JFXTabPane tabPane;
|
private JFXTabPane tabPane;
|
||||||
@ -107,7 +108,6 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
|||||||
btnTestGame.setGraphic(SVG.launch(Theme.whiteFillBinding(), 20, 20));
|
btnTestGame.setGraphic(SVG.launch(Theme.whiteFillBinding(), 20, 20));
|
||||||
FXUtils.installFastTooltip(btnTestGame, i18n("version.launch.test"));
|
FXUtils.installFastTooltip(btnTestGame, i18n("version.launch.test"));
|
||||||
|
|
||||||
addEventHandler(Navigator.NavigationEvent.NAVIGATING, this::onDecoratorPageNavigating);
|
|
||||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
|
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,9 +120,13 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
|||||||
versionSettings.loadVersion(profile, id);
|
versionSettings.loadVersion(profile, id);
|
||||||
mod.setParentTab(tabPane);
|
mod.setParentTab(tabPane);
|
||||||
modTab.setUserData(mod);
|
modTab.setUserData(mod);
|
||||||
mod.loadVersion(profile, id);
|
loading.set(true);
|
||||||
installer.loadVersion(profile, id);
|
|
||||||
world.loadVersion(profile, id);
|
CompletableFuture.allOf(
|
||||||
|
mod.loadVersion(profile, id),
|
||||||
|
installer.loadVersion(profile, id),
|
||||||
|
world.loadVersion(profile, id))
|
||||||
|
.whenCompleteAsync((result, exception) -> loading.set(false), Platform::runLater);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onNavigated(Navigator.NavigationEvent event) {
|
private void onNavigated(Navigator.NavigationEvent event) {
|
||||||
@ -163,7 +167,6 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
|||||||
return title.get();
|
return title.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public ReadOnlyStringProperty titleProperty() {
|
public ReadOnlyStringProperty titleProperty() {
|
||||||
return title.getReadOnlyProperty();
|
return title.getReadOnlyProperty();
|
||||||
}
|
}
|
||||||
@ -171,4 +174,12 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
|||||||
public void setTitle(String title) {
|
public void setTitle(String title) {
|
||||||
this.title.set(title);
|
this.title.set(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isLoading() {
|
||||||
|
return loading.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyBooleanProperty loadingProperty() {
|
||||||
|
return loading.getReadOnlyProperty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
* 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.versions;
|
||||||
|
|
||||||
|
import com.jfoenix.controls.JFXListView;
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyStringProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.scene.control.Control;
|
||||||
|
import javafx.scene.control.SelectionMode;
|
||||||
|
import javafx.scene.control.SkinBase;
|
||||||
|
import javafx.scene.layout.BorderPane;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.shape.Rectangle;
|
||||||
|
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||||
|
import org.jackhuang.hmcl.game.Version;
|
||||||
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
|
import org.jackhuang.hmcl.setting.Profiles;
|
||||||
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||||
|
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||||
|
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||||
|
|
||||||
|
public class VersionRootPage extends Control implements DecoratorPage {
|
||||||
|
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper();
|
||||||
|
private final BooleanProperty loading = new SimpleBooleanProperty();
|
||||||
|
private final JFXListView<String> listView = new JFXListView<>();
|
||||||
|
private final VersionPage versionPage = new VersionPage();
|
||||||
|
private Profile profile;
|
||||||
|
|
||||||
|
{
|
||||||
|
Profiles.registerVersionsListener(this::loadVersions);
|
||||||
|
|
||||||
|
listView.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> {
|
||||||
|
loadVersion(newValue, profile);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadVersions(Profile profile) {
|
||||||
|
HMCLGameRepository repository = profile.getRepository();
|
||||||
|
List<String> children = repository.getVersions().parallelStream()
|
||||||
|
.filter(version -> !version.isHidden())
|
||||||
|
.sorted(Comparator.comparing((Version version) -> version.getReleaseTime() == null ? new Date(0L) : version.getReleaseTime())
|
||||||
|
.thenComparing(a -> VersionNumber.asVersion(a.getId())))
|
||||||
|
.map(Version::getId)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
runInFX(() -> {
|
||||||
|
if (profile == Profiles.getSelectedProfile()) {
|
||||||
|
this.profile = profile;
|
||||||
|
loading.set(false);
|
||||||
|
listView.getItems().setAll(children);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadVersion(String version, Profile profile) {
|
||||||
|
listView.getSelectionModel().select(version);
|
||||||
|
versionPage.load(version, profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Skin createDefaultSkin() {
|
||||||
|
return new Skin(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReadOnlyStringProperty titleProperty() {
|
||||||
|
return title.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Skin extends SkinBase<VersionRootPage> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for all SkinBase instances.
|
||||||
|
*
|
||||||
|
* @param control The control for which this Skin should attach to.
|
||||||
|
*/
|
||||||
|
protected Skin(VersionRootPage control) {
|
||||||
|
super(control);
|
||||||
|
|
||||||
|
control.listView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||||
|
|
||||||
|
SpinnerPane spinnerPane = new SpinnerPane();
|
||||||
|
spinnerPane.getStyleClass().add("large-spinner-pane");
|
||||||
|
|
||||||
|
// the root page, with the sidebar in left, navigator in center.
|
||||||
|
BorderPane root = new BorderPane();
|
||||||
|
root.getStyleClass().add("gray-background");
|
||||||
|
|
||||||
|
{
|
||||||
|
BorderPane leftRootPane = new BorderPane();
|
||||||
|
FXUtils.setLimitWidth(leftRootPane, 200);
|
||||||
|
|
||||||
|
StackPane drawerContainer = new StackPane();
|
||||||
|
drawerContainer.getChildren().setAll(control.listView);
|
||||||
|
leftRootPane.setCenter(drawerContainer);
|
||||||
|
|
||||||
|
Rectangle separator = new Rectangle();
|
||||||
|
separator.heightProperty().bind(root.heightProperty());
|
||||||
|
separator.setWidth(1);
|
||||||
|
separator.setFill(Color.GRAY);
|
||||||
|
|
||||||
|
leftRootPane.setRight(separator);
|
||||||
|
|
||||||
|
root.setLeft(leftRootPane);
|
||||||
|
}
|
||||||
|
|
||||||
|
root.setCenter(control.versionPage);
|
||||||
|
control.title.bind(control.versionPage.titleProperty());
|
||||||
|
|
||||||
|
spinnerPane.loadingProperty().bind(control.versionPage.loadingProperty());
|
||||||
|
spinnerPane.setContent(root);
|
||||||
|
getChildren().setAll(spinnerPane);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -121,7 +121,7 @@ public class Versions {
|
|||||||
|
|
||||||
private static boolean checkForLaunching(Profile profile, String id) {
|
private static boolean checkForLaunching(Profile profile, String id) {
|
||||||
if (Accounts.getSelectedAccount() == null)
|
if (Accounts.getSelectedAccount() == null)
|
||||||
Controllers.getLeftPaneController().checkAccount();
|
Controllers.getRootPage().checkAccount();
|
||||||
else if (id == null || !profile.getRepository().isLoaded() || !profile.getRepository().hasVersion(id))
|
else if (id == null || !profile.getRepository().isLoaded() || !profile.getRepository().hasVersion(id))
|
||||||
Controllers.dialog(i18n("version.empty.launch"));
|
Controllers.dialog(i18n("version.empty.launch"));
|
||||||
else
|
else
|
||||||
@ -136,7 +136,7 @@ public class Versions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void modifyGameSettings(Profile profile, String version) {
|
public static void modifyGameSettings(Profile profile, String version) {
|
||||||
Controllers.getVersionPage().load(version, profile);
|
Controllers.getVersionPage().loadVersion(version, profile);
|
||||||
Controllers.navigate(Controllers.getVersionPage());
|
Controllers.navigate(Controllers.getVersionPage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
package org.jackhuang.hmcl.ui.versions;
|
package org.jackhuang.hmcl.ui.versions;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXCheckBox;
|
import com.jfoenix.controls.JFXCheckBox;
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
@ -39,6 +40,7 @@ import java.nio.file.InvalidPathException;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -71,29 +73,29 @@ public class WorldListPage extends ListPageBase<WorldListItem> {
|
|||||||
return new WorldListPageSkin();
|
return new WorldListPageSkin();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadVersion(Profile profile, String id) {
|
public CompletableFuture<?> loadVersion(Profile profile, String id) {
|
||||||
this.profile = profile;
|
this.profile = profile;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.savesDir = profile.getRepository().getRunDirectory(id).toPath().resolve("saves");
|
this.savesDir = profile.getRepository().getRunDirectory(id).toPath().resolve("saves");
|
||||||
refresh();
|
return refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refresh() {
|
public CompletableFuture<?> refresh() {
|
||||||
if (profile == null || id == null)
|
if (profile == null || id == null)
|
||||||
return;
|
return CompletableFuture.completedFuture(null);
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
Task
|
return CompletableFuture
|
||||||
.runAsync(() -> gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse(null))
|
.runAsync(() -> gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse(null))
|
||||||
.thenSupplyAsync(() -> World.getWorlds(savesDir).parallel().collect(Collectors.toList()))
|
.thenApplyAsync(unused -> World.getWorlds(savesDir).parallel().collect(Collectors.toList()))
|
||||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
.whenCompleteAsync((result, exception) -> {
|
||||||
worlds = result;
|
worlds = result;
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
if (exception == null)
|
if (exception == null)
|
||||||
itemsProperty().setAll(result.stream()
|
itemsProperty().setAll(result.stream()
|
||||||
.filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
|
.filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
|
||||||
.map(WorldListItem::new).collect(Collectors.toList()));
|
.map(WorldListItem::new).collect(Collectors.toList()));
|
||||||
}).start();
|
}, Platform::runLater);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add() {
|
public void add() {
|
||||||
|
@ -24,7 +24,7 @@ import javafx.scene.Node;
|
|||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
@ -38,10 +38,8 @@ public class DefaultWizardDisplayer extends StackPane implements AbstractWizardD
|
|||||||
|
|
||||||
private Node nowPage;
|
private Node nowPage;
|
||||||
|
|
||||||
private TransitionHandler transitionHandler;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private StackPane root;
|
private TransitionPane root;
|
||||||
@FXML
|
@FXML
|
||||||
private JFXButton backButton;
|
private JFXButton backButton;
|
||||||
@FXML
|
@FXML
|
||||||
@ -86,7 +84,7 @@ public class DefaultWizardDisplayer extends StackPane implements AbstractWizardD
|
|||||||
@Override
|
@Override
|
||||||
public void navigateTo(Node page, Navigation.NavigationDirection nav) {
|
public void navigateTo(Node page, Navigation.NavigationDirection nav) {
|
||||||
backButton.setDisable(!wizardController.canPrev());
|
backButton.setDisable(!wizardController.canPrev());
|
||||||
transitionHandler.setContent(page, nav.getAnimation().getAnimationProducer());
|
root.setContent(page, nav.getAnimation().getAnimationProducer());
|
||||||
String title = StringUtils.isBlank(prefix) ? "" : prefix + " - ";
|
String title = StringUtils.isBlank(prefix) ? "" : prefix + " - ";
|
||||||
if (page instanceof WizardPage)
|
if (page instanceof WizardPage)
|
||||||
titleLabel.setText(title + ((WizardPage) page).getTitle());
|
titleLabel.setText(title + ((WizardPage) page).getTitle());
|
||||||
@ -96,7 +94,6 @@ public class DefaultWizardDisplayer extends StackPane implements AbstractWizardD
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void initialize() {
|
private void initialize() {
|
||||||
transitionHandler = new TransitionHandler(root);
|
|
||||||
wizardController.onStart();
|
wizardController.onStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ import javafx.beans.property.SimpleBooleanProperty;
|
|||||||
public interface Refreshable {
|
public interface Refreshable {
|
||||||
void refresh();
|
void refresh();
|
||||||
|
|
||||||
default BooleanProperty canRefreshProperty() {
|
default BooleanProperty refreshableProperty() {
|
||||||
return new SimpleBooleanProperty(false);
|
return new SimpleBooleanProperty(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,11 @@
|
|||||||
<?import com.jfoenix.controls.*?>
|
<?import com.jfoenix.controls.*?>
|
||||||
<?import org.jackhuang.hmcl.ui.construct.SpinnerPane?>
|
<?import org.jackhuang.hmcl.ui.construct.SpinnerPane?>
|
||||||
|
|
||||||
|
<?import org.jackhuang.hmcl.ui.animation.TransitionPane?>
|
||||||
<fx:root xmlns="http://javafx.com/javafx"
|
<fx:root xmlns="http://javafx.com/javafx"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
type="StackPane">
|
type="StackPane">
|
||||||
<StackPane fx:id="addServerContainer">
|
<TransitionPane fx:id="root">
|
||||||
<JFXDialogLayout fx:id="addServerPane">
|
<JFXDialogLayout fx:id="addServerPane">
|
||||||
<heading>
|
<heading>
|
||||||
<Label text="%account.injector.add" />
|
<Label text="%account.injector.add" />
|
||||||
@ -51,5 +52,5 @@
|
|||||||
<JFXButton onMouseClicked="#onAddFinish" text="%wizard.finish" styleClass="dialog-accept" />
|
<JFXButton onMouseClicked="#onAddFinish" text="%wizard.finish" styleClass="dialog-accept" />
|
||||||
</actions>
|
</actions>
|
||||||
</JFXDialogLayout>
|
</JFXDialogLayout>
|
||||||
</StackPane>
|
</TransitionPane>
|
||||||
</fx:root>
|
</fx:root>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<?import com.jfoenix.controls.*?>
|
<?import com.jfoenix.controls.*?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?import org.jackhuang.hmcl.ui.animation.TransitionPane?>
|
||||||
<fx:root xmlns="http://javafx.com/javafx"
|
<fx:root xmlns="http://javafx.com/javafx"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
type="BorderPane"
|
type="BorderPane"
|
||||||
@ -13,7 +14,7 @@
|
|||||||
</StackPane>
|
</StackPane>
|
||||||
</top>
|
</top>
|
||||||
<center>
|
<center>
|
||||||
<StackPane fx:id="root">
|
<TransitionPane fx:id="root">
|
||||||
<JFXSpinner fx:id="spinner" styleClass="first-spinner" />
|
<JFXSpinner fx:id="spinner" styleClass="first-spinner" />
|
||||||
<VBox fx:id="centrePane">
|
<VBox fx:id="centrePane">
|
||||||
<HBox fx:id="checkPane" spacing="10" style="-fx-padding: 10;">
|
<HBox fx:id="checkPane" spacing="10" style="-fx-padding: 10;">
|
||||||
@ -30,6 +31,6 @@
|
|||||||
<StackPane fx:id="emptyPane" styleClass="notice-pane">
|
<StackPane fx:id="emptyPane" styleClass="notice-pane">
|
||||||
<Label onMouseClicked="#onBack" text="%download.failed.empty" />
|
<Label onMouseClicked="#onBack" text="%download.failed.empty" />
|
||||||
</StackPane>
|
</StackPane>
|
||||||
</StackPane>
|
</TransitionPane>
|
||||||
</center>
|
</center>
|
||||||
</fx:root>
|
</fx:root>
|
||||||
|
@ -10,9 +10,7 @@
|
|||||||
<?import org.jackhuang.hmcl.ui.versions.WorldListPage?>
|
<?import org.jackhuang.hmcl.ui.versions.WorldListPage?>
|
||||||
<fx:root xmlns="http://javafx.com/javafx"
|
<fx:root xmlns="http://javafx.com/javafx"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
fx:id="rootPane"
|
|
||||||
type="StackPane">
|
type="StackPane">
|
||||||
<JFXRippler />
|
|
||||||
<StackPane fx:id="contentPane">
|
<StackPane fx:id="contentPane">
|
||||||
<JFXTabPane fx:id="tabPane">
|
<JFXTabPane fx:id="tabPane">
|
||||||
<Tab text="%settings">
|
<Tab text="%settings">
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?import org.jackhuang.hmcl.ui.animation.TransitionPane?>
|
||||||
<fx:root xmlns="http://javafx.com/javafx"
|
<fx:root xmlns="http://javafx.com/javafx"
|
||||||
type="StackPane"
|
type="StackPane"
|
||||||
style="-fx-background-color: gray;"
|
style="-fx-background-color: gray;"
|
||||||
@ -43,6 +44,6 @@
|
|||||||
</rightItems>
|
</rightItems>
|
||||||
</JFXToolbar>
|
</JFXToolbar>
|
||||||
|
|
||||||
<StackPane fx:id="root"/>
|
<TransitionPane fx:id="root"/>
|
||||||
</VBox>
|
</VBox>
|
||||||
</fx:root>
|
</fx:root>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user