Merge branch 'gui' into javafx
This commit is contained in:
@@ -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.GameItem;
|
|
||||||
import org.jackhuang.hmcl.ui.versions.GameList;
|
|
||||||
import org.jackhuang.hmcl.ui.versions.VersionPage;
|
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 SettingsPage settingsPage = null;
|
|
||||||
private static VersionPage versionPage = 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() {
|
||||||
@@ -85,47 +63,6 @@ public final class Controllers {
|
|||||||
return stage;
|
return stage;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FXThread
|
|
||||||
public static SettingsPage getSettingsPage() {
|
|
||||||
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
|
// FXThread
|
||||||
public static VersionPage getVersionPage() {
|
public static VersionPage getVersionPage() {
|
||||||
if (versionPage == null)
|
if (versionPage == null)
|
||||||
@@ -133,6 +70,13 @@ public final class Controllers {
|
|||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import javafx.scene.shape.Rectangle;
|
|||||||
import javafx.util.Callback;
|
import javafx.util.Callback;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
import org.jackhuang.hmcl.util.Logging;
|
import org.jackhuang.hmcl.util.Logging;
|
||||||
import org.jackhuang.hmcl.util.ResourceNotFoundError;
|
import org.jackhuang.hmcl.util.ResourceNotFoundError;
|
||||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||||
@@ -100,8 +101,10 @@ public final class FXUtils {
|
|||||||
value.addListener((a, b, c) -> consumer.accept(c));
|
value.addListener((a, b, c) -> consumer.accept(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> void onWeakChange(ObservableValue<T> value, Consumer<T> consumer) {
|
public static <T> WeakChangeListener<T> onWeakChange(ObservableValue<T> value, Consumer<T> consumer) {
|
||||||
value.addListener(new WeakChangeListener<>((a, b, c) -> consumer.accept(c)));
|
WeakChangeListener<T> listener = new WeakChangeListener<>((a, b, c) -> consumer.accept(c));
|
||||||
|
value.addListener(listener);
|
||||||
|
return listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> void onChangeAndOperate(ObservableValue<T> value, Consumer<T> consumer) {
|
public static <T> void onChangeAndOperate(ObservableValue<T> value, Consumer<T> consumer) {
|
||||||
@@ -109,9 +112,9 @@ public final class FXUtils {
|
|||||||
onChange(value, consumer);
|
onChange(value, consumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> void onWeakChangeAndOperate(ObservableValue<T> value, Consumer<T> consumer) {
|
public static <T> WeakChangeListener<T> onWeakChangeAndOperate(ObservableValue<T> value, Consumer<T> consumer) {
|
||||||
consumer.accept(value.getValue());
|
consumer.accept(value.getValue());
|
||||||
onWeakChange(value, consumer);
|
return onWeakChange(value, consumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void runLaterIf(BooleanSupplier condition, Runnable runnable) {
|
public static void runLaterIf(BooleanSupplier condition, Runnable runnable) {
|
||||||
@@ -201,19 +204,19 @@ public final class FXUtils {
|
|||||||
return field.getProperties().containsKey("FXUtils.validation");
|
return field.getProperties().containsKey("FXUtils.validation");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setOverflowHidden(Region region, boolean hidden) {
|
public static Rectangle setOverflowHidden(Region region) {
|
||||||
if (hidden) {
|
|
||||||
Rectangle rectangle = new Rectangle();
|
Rectangle rectangle = new Rectangle();
|
||||||
rectangle.widthProperty().bind(region.widthProperty());
|
rectangle.widthProperty().bind(region.widthProperty());
|
||||||
rectangle.heightProperty().bind(region.heightProperty());
|
rectangle.heightProperty().bind(region.heightProperty());
|
||||||
region.setClip(rectangle);
|
region.setClip(rectangle);
|
||||||
} else {
|
return rectangle;
|
||||||
region.setClip(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean getOverflowHidden(Region region) {
|
public static Rectangle setOverflowHidden(Region region, double arc) {
|
||||||
return region.getClip() != null;
|
Rectangle rectangle = setOverflowHidden(region);
|
||||||
|
rectangle.setArcWidth(arc);
|
||||||
|
rectangle.setArcHeight(arc);
|
||||||
|
return rectangle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setLimitWidth(Region region, double width) {
|
public static void setLimitWidth(Region region, double width) {
|
||||||
@@ -452,7 +455,7 @@ public final class FXUtils {
|
|||||||
derivatives[i] += derivatives[i - 1];
|
derivatives[i] += derivatives[i - 1];
|
||||||
double dy = derivatives[derivatives.length - 1];
|
double dy = derivatives[derivatives.length - 1];
|
||||||
double height = listView.getLayoutBounds().getHeight();
|
double height = listView.getLayoutBounds().getHeight();
|
||||||
bar.setValue(Math.min(Math.max(bar.getValue() + dy / height, 0), 1));
|
bar.setValue(Lang.clamp(0, bar.getValue() + dy / height, 1));
|
||||||
if (Math.abs(dy) < 0.001)
|
if (Math.abs(dy) < 0.001)
|
||||||
timeline.stop();
|
timeline.stop();
|
||||||
listView.requestLayout();
|
listView.requestLayout();
|
||||||
|
|||||||
@@ -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();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -178,4 +178,8 @@ public final class SVG {
|
|||||||
public static Node arrowRight(ObjectBinding<? extends Paint> fill, double width, double height) {
|
public static Node arrowRight(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||||
return createSVGPath("M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z", fill, width, height);
|
return createSVGPath("M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z", fill, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Node wrench(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||||
|
return createSVGPath("M22.7,19L13.6,9.9C14.5,7.6 14,4.9 12.1,3C10.1,1 7.1,0.6 4.7,1.7L9,6L6,9L1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1C4.8,14 7.5,14.5 9.8,13.6L18.9,22.7C19.3,23.1 19.9,23.1 20.3,22.7L22.6,20.4C23.1,20 23.1,19.3 22.7,19Z", fill, width, height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,12 +44,13 @@ public abstract class ToolbarListPageSkin<T extends ListPageBase<? extends Node>
|
|||||||
|
|
||||||
BorderPane root = new BorderPane();
|
BorderPane root = new BorderPane();
|
||||||
|
|
||||||
{
|
List<Node> toolbarButtons = initializeToolbar(skinnable);
|
||||||
|
if (!toolbarButtons.isEmpty()) {
|
||||||
HBox toolbar = new HBox();
|
HBox toolbar = new HBox();
|
||||||
toolbar.getStyleClass().add("jfx-tool-bar-second");
|
toolbar.getStyleClass().add("jfx-tool-bar-second");
|
||||||
JFXDepthManager.setDepth(toolbar, 1);
|
JFXDepthManager.setDepth(toolbar, 1);
|
||||||
toolbar.setPickOnBounds(false);
|
toolbar.setPickOnBounds(false);
|
||||||
toolbar.getChildren().setAll(initializeToolbar(skinnable));
|
toolbar.getChildren().setAll(toolbarButtons);
|
||||||
root.setTop(toolbar);
|
root.setTop(toolbar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,23 +15,6 @@
|
|||||||
* 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/>.
|
||||||
*/
|
*/
|
||||||
/*
|
|
||||||
* Hello Minecraft! Launcher
|
|
||||||
* Copyright (C) 2019 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;
|
package org.jackhuang.hmcl.ui;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
import com.jfoenix.controls.JFXButton;
|
||||||
|
|||||||
@@ -70,9 +70,9 @@ public class AccountAdvancedListItem extends AdvancedListItem {
|
|||||||
ObservableList<Account> accounts = Accounts.getAccounts();
|
ObservableList<Account> accounts = Accounts.getAccounts();
|
||||||
int currentIndex = accounts.indexOf(account.get());
|
int currentIndex = accounts.indexOf(account.get());
|
||||||
if (event.getDeltaY() > 0) { // up
|
if (event.getDeltaY() > 0) { // up
|
||||||
currentIndex += 1;
|
currentIndex--;
|
||||||
} else { // down
|
} else { // down
|
||||||
currentIndex -= 1;
|
currentIndex++;
|
||||||
}
|
}
|
||||||
Accounts.setSelectedAccount(accounts.get((currentIndex + accounts.size()) % accounts.size()));
|
Accounts.setSelectedAccount(accounts.get((currentIndex + accounts.size()) % accounts.size()));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,11 +24,12 @@ import org.jackhuang.hmcl.ui.Controllers;
|
|||||||
import org.jackhuang.hmcl.ui.ListPage;
|
import org.jackhuang.hmcl.ui.ListPage;
|
||||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
|
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
|
||||||
|
|
||||||
public class AccountList extends ListPage<AccountListItem> implements DecoratorPage {
|
public class AccountList extends ListPage<AccountListItem> implements DecoratorPage {
|
||||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("account.manage"));
|
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("account.manage")));
|
||||||
private final ListProperty<Account> accounts = new SimpleListProperty<>(this, "accounts", FXCollections.observableArrayList());
|
private final ListProperty<Account> accounts = new SimpleListProperty<>(this, "accounts", FXCollections.observableArrayList());
|
||||||
private final ObjectProperty<Account> selectedAccount;
|
private final ObjectProperty<Account> selectedAccount;
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ public class AccountList extends ListPage<AccountListItem> implements DecoratorP
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReadOnlyStringProperty titleProperty() {
|
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||||
return title.getReadOnlyProperty();
|
return state.getReadOnlyProperty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,8 +18,7 @@
|
|||||||
package org.jackhuang.hmcl.ui.account;
|
package org.jackhuang.hmcl.ui.account;
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.ReadOnlyStringProperty;
|
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
@@ -31,7 +30,7 @@ import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
|||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
public class AuthlibInjectorServersPage extends ListPage<AuthlibInjectorServerItem> implements DecoratorPage {
|
public class AuthlibInjectorServersPage extends ListPage<AuthlibInjectorServerItem> implements DecoratorPage {
|
||||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("account.injector.manage.title"));
|
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("account.injector.manage.title")));
|
||||||
|
|
||||||
private final ObservableList<AuthlibInjectorServerItem> serverItems;
|
private final ObservableList<AuthlibInjectorServerItem> serverItems;
|
||||||
|
|
||||||
@@ -50,16 +49,8 @@ public class AuthlibInjectorServersPage extends ListPage<AuthlibInjectorServerIt
|
|||||||
Controllers.dialog(new AddAuthlibInjectorServerPane());
|
Controllers.dialog(new AddAuthlibInjectorServerPane());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTitle() {
|
|
||||||
return title.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReadOnlyStringProperty titleProperty() {
|
public ReadOnlyObjectWrapper<State> stateProperty() {
|
||||||
return title.getReadOnlyProperty();
|
return state;
|
||||||
}
|
|
||||||
|
|
||||||
public void setTitle(String title) {
|
|
||||||
this.title.set(title);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
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();
|
||||||
@@ -126,7 +126,7 @@ class ComponentListCell extends StackPane {
|
|||||||
VBox container = new VBox();
|
VBox container = new VBox();
|
||||||
container.setPadding(new Insets(8, 0, 0, 0));
|
container.setPadding(new Insets(8, 0, 0, 0));
|
||||||
FXUtils.setLimitHeight(container, 0);
|
FXUtils.setLimitHeight(container, 0);
|
||||||
FXUtils.setOverflowHidden(container, true);
|
FXUtils.setOverflowHidden(container);
|
||||||
container.getChildren().setAll(content);
|
container.getChildren().setAll(content);
|
||||||
groupNode.setBottom(container);
|
groupNode.setBottom(container);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,195 @@
|
|||||||
|
/**
|
||||||
|
* 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.construct;
|
||||||
|
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.binding.NumberBinding;
|
||||||
|
import javafx.geometry.Orientation;
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.ScrollBar;
|
||||||
|
import javafx.scene.control.Skin;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.shape.Rectangle;
|
||||||
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
|
|
||||||
|
public class FloatScrollBarSkin implements Skin<ScrollBar> {
|
||||||
|
private ScrollBar scrollBar;
|
||||||
|
private Region group;
|
||||||
|
private Rectangle track = new Rectangle();
|
||||||
|
private Rectangle thumb = new Rectangle();
|
||||||
|
|
||||||
|
public FloatScrollBarSkin(final ScrollBar scrollBar) {
|
||||||
|
this.scrollBar = scrollBar;
|
||||||
|
scrollBar.setPrefHeight(1e-18);
|
||||||
|
scrollBar.setPrefWidth(1e-18);
|
||||||
|
|
||||||
|
this.group = new Region() {
|
||||||
|
Point2D dragStart;
|
||||||
|
double preDragThumbPos;
|
||||||
|
|
||||||
|
NumberBinding range = Bindings.subtract(scrollBar.maxProperty(), scrollBar.minProperty());
|
||||||
|
NumberBinding position = Bindings.divide(Bindings.subtract(scrollBar.valueProperty(), scrollBar.minProperty()), range);
|
||||||
|
|
||||||
|
{
|
||||||
|
// Children are added unmanaged because for some reason the height of the bar keeps changing
|
||||||
|
// if they're managed in certain situations... not sure about the cause.
|
||||||
|
getChildren().addAll(track, thumb);
|
||||||
|
|
||||||
|
track.setManaged(false);
|
||||||
|
track.getStyleClass().add("track");
|
||||||
|
|
||||||
|
thumb.setManaged(false);
|
||||||
|
thumb.getStyleClass().add("thumb");
|
||||||
|
|
||||||
|
scrollBar.orientationProperty().addListener(obs -> setup());
|
||||||
|
|
||||||
|
setup();
|
||||||
|
|
||||||
|
|
||||||
|
thumb.setOnMousePressed(me -> {
|
||||||
|
if (me.isSynthesized()) {
|
||||||
|
// touch-screen events handled by Scroll handler
|
||||||
|
me.consume();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
** if max isn't greater than min then there is nothing to do here
|
||||||
|
*/
|
||||||
|
if (getSkinnable().getMax() > getSkinnable().getMin()) {
|
||||||
|
dragStart = thumb.localToParent(me.getX(), me.getY());
|
||||||
|
double clampedValue = Lang.clamp(getSkinnable().getMin(), getSkinnable().getValue(), getSkinnable().getMax());
|
||||||
|
preDragThumbPos = (clampedValue - getSkinnable().getMin()) / (getSkinnable().getMax() - getSkinnable().getMin());
|
||||||
|
me.consume();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
thumb.setOnMouseDragged(me -> {
|
||||||
|
if (me.isSynthesized()) {
|
||||||
|
// touch-screen events handled by Scroll handler
|
||||||
|
me.consume();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
** if max isn't greater than min then there is nothing to do here
|
||||||
|
*/
|
||||||
|
if (getSkinnable().getMax() > getSkinnable().getMin()) {
|
||||||
|
/*
|
||||||
|
** if the tracklength isn't greater then do nothing....
|
||||||
|
*/
|
||||||
|
if (trackLength() > thumbLength()) {
|
||||||
|
Point2D cur = thumb.localToParent(me.getX(), me.getY());
|
||||||
|
if (dragStart == null) {
|
||||||
|
// we're getting dragged without getting a mouse press
|
||||||
|
dragStart = thumb.localToParent(me.getX(), me.getY());
|
||||||
|
}
|
||||||
|
double dragPos = getSkinnable().getOrientation() == Orientation.VERTICAL ? cur.getY() - dragStart.getY(): cur.getX() - dragStart.getX();
|
||||||
|
double position = preDragThumbPos + dragPos / (trackLength() - thumbLength());
|
||||||
|
if (!getSkinnable().isFocused() && getSkinnable().isFocusTraversable()) getSkinnable().requestFocus();
|
||||||
|
double newValue = (position * (getSkinnable().getMax() - getSkinnable().getMin())) + getSkinnable().getMin();
|
||||||
|
if (!Double.isNaN(newValue)) {
|
||||||
|
getSkinnable().setValue(Lang.clamp(getSkinnable().getMin(), newValue, getSkinnable().getMax()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
me.consume();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private double trackLength() {
|
||||||
|
return getSkinnable().getOrientation() == Orientation.VERTICAL ? track.getHeight() : track.getWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
private double thumbLength() {
|
||||||
|
return getSkinnable().getOrientation() == Orientation.VERTICAL ? thumb.getHeight() : thumb.getWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
private double boundedSize(double min, double value, double max) {
|
||||||
|
return Math.min(Math.max(value, min), Math.max(min, max));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setup() {
|
||||||
|
track.widthProperty().unbind();
|
||||||
|
track.heightProperty().unbind();
|
||||||
|
|
||||||
|
if (scrollBar.getOrientation() == Orientation.HORIZONTAL) {
|
||||||
|
track.relocate(0, -5);
|
||||||
|
track.widthProperty().bind(scrollBar.widthProperty());
|
||||||
|
track.setHeight(5);
|
||||||
|
} else {
|
||||||
|
track.relocate(-5, 0);
|
||||||
|
track.setWidth(5);
|
||||||
|
track.heightProperty().bind(scrollBar.heightProperty());
|
||||||
|
}
|
||||||
|
|
||||||
|
thumb.xProperty().unbind();
|
||||||
|
thumb.yProperty().unbind();
|
||||||
|
thumb.widthProperty().unbind();
|
||||||
|
thumb.heightProperty().unbind();
|
||||||
|
|
||||||
|
if (scrollBar.getOrientation() == Orientation.HORIZONTAL) {
|
||||||
|
thumb.relocate(0, -5);
|
||||||
|
thumb.widthProperty().bind(Bindings.max(5, scrollBar.visibleAmountProperty().divide(range).multiply(scrollBar.widthProperty())));
|
||||||
|
thumb.setHeight(5);
|
||||||
|
thumb.xProperty().bind(Bindings.subtract(scrollBar.widthProperty(), thumb.widthProperty()).multiply(position));
|
||||||
|
} else {
|
||||||
|
thumb.relocate(-5, 0);
|
||||||
|
thumb.setWidth(5);
|
||||||
|
thumb.heightProperty().bind(Bindings.max(5, scrollBar.visibleAmountProperty().divide(range).multiply(scrollBar.heightProperty())));
|
||||||
|
thumb.yProperty().bind(Bindings.subtract(scrollBar.heightProperty(), thumb.heightProperty()).multiply(position));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected double computeMaxWidth(double height) {
|
||||||
|
if (scrollBar.getOrientation() == Orientation.HORIZONTAL) {
|
||||||
|
return Double.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected double computeMaxHeight(double width) {
|
||||||
|
if (scrollBar.getOrientation() == Orientation.VERTICAL) {
|
||||||
|
return Double.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
scrollBar = null;
|
||||||
|
group = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getNode() {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScrollBar getSkinnable() {
|
||||||
|
return scrollBar;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,10 +67,10 @@ 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);
|
|
||||||
node.fireEvent(navigated);
|
node.fireEvent(navigated);
|
||||||
|
|
||||||
EventHandler<PageCloseEvent> handler = event -> close(node);
|
EventHandler<PageCloseEvent> handler = event -> close(node);
|
||||||
@@ -110,10 +109,14 @@ 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);
|
|
||||||
node.fireEvent(navigated);
|
node.fireEvent(navigated);
|
||||||
|
|
||||||
Optional.ofNullable(from.getProperties().get(PROPERTY_DIALOG_CLOSE_HANDLER))
|
Optional.ofNullable(from.getProperties().get(PROPERTY_DIALOG_CLOSE_HANDLER))
|
||||||
@@ -128,12 +131,16 @@ public class Navigator extends StackPane {
|
|||||||
return stack.size() > 1;
|
return stack.size() > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setContent(Node content) {
|
public int size() {
|
||||||
animationHandler.setContent(content, ContainerAnimations.FADE.getAnimationProducer());
|
return stack.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContent(Node content, AnimationProducer animationProducer) {
|
||||||
|
super.setContent(content, animationProducer);
|
||||||
|
|
||||||
if (content instanceof Region) {
|
if (content instanceof Region) {
|
||||||
((Region) content).setMinSize(0, 0);
|
((Region) content).setMinSize(0, 0);
|
||||||
FXUtils.setOverflowHidden((Region) content, true);
|
FXUtils.setOverflowHidden((Region) content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,14 +186,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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,504 @@
|
|||||||
|
/*
|
||||||
|
* 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.construct;
|
||||||
|
|
||||||
|
import com.jfoenix.controls.JFXRippler;
|
||||||
|
import javafx.animation.*;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.property.*;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ListChangeListener;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.css.PseudoClass;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Side;
|
||||||
|
import javafx.scene.AccessibleAttribute;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.input.MouseButton;
|
||||||
|
import javafx.scene.layout.*;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.transform.Rotate;
|
||||||
|
import javafx.scene.transform.Scale;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||||
|
|
||||||
|
public class TabHeader extends Control {
|
||||||
|
|
||||||
|
public TabHeader(Tab... tabs) {
|
||||||
|
getStyleClass().setAll("tab-header");
|
||||||
|
if (tabs != null) {
|
||||||
|
getTabs().addAll(tabs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObservableList<Tab> tabs = FXCollections.observableArrayList();
|
||||||
|
|
||||||
|
public ObservableList<Tab> getTabs() {
|
||||||
|
return tabs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ObjectProperty<SingleSelectionModel<Tab>> selectionModel = new SimpleObjectProperty<>(this, "selectionModel", new TabHeaderSelectionModel(this));
|
||||||
|
|
||||||
|
public SingleSelectionModel<Tab> getSelectionModel() {
|
||||||
|
return selectionModel.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<SingleSelectionModel<Tab>> selectionModelProperty() {
|
||||||
|
return selectionModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectionModel(SingleSelectionModel<Tab> selectionModel) {
|
||||||
|
this.selectionModel.set(selectionModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TabHeaderSelectionModel extends SingleSelectionModel<Tab> {
|
||||||
|
private final TabHeader tabHeader;
|
||||||
|
|
||||||
|
public TabHeaderSelectionModel(final TabHeader t) {
|
||||||
|
if (t == null) {
|
||||||
|
throw new NullPointerException("TabPane can not be null");
|
||||||
|
}
|
||||||
|
this.tabHeader = t;
|
||||||
|
|
||||||
|
// watching for changes to the items list content
|
||||||
|
final ListChangeListener<Tab> itemsContentObserver = c -> {
|
||||||
|
while (c.next()) {
|
||||||
|
for (Tab tab : c.getRemoved()) {
|
||||||
|
if (tab != null && !tabHeader.getTabs().contains(tab)) {
|
||||||
|
if (tab.isSelected()) {
|
||||||
|
tab.setSelected(false);
|
||||||
|
final int tabIndex = c.getFrom();
|
||||||
|
|
||||||
|
// we always try to select the nearest, non-disabled
|
||||||
|
// tab from the position of the closed tab.
|
||||||
|
findNearestAvailableTab(tabIndex, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (c.wasAdded() || c.wasRemoved()) {
|
||||||
|
// The selected tab index can be out of sync with the list of tab if
|
||||||
|
// we add or remove tabs before the selected tab.
|
||||||
|
if (getSelectedIndex() != tabHeader.getTabs().indexOf(getSelectedItem())) {
|
||||||
|
clearAndSelect(tabHeader.getTabs().indexOf(getSelectedItem()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (getSelectedIndex() == -1 && getSelectedItem() == null && tabHeader.getTabs().size() > 0) {
|
||||||
|
// we go looking for the first non-disabled tab, as opposed to
|
||||||
|
// just selecting the first tab (fix for RT-36908)
|
||||||
|
findNearestAvailableTab(0, true);
|
||||||
|
} else if (tabHeader.getTabs().isEmpty()) {
|
||||||
|
clearSelection();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (this.tabHeader.getTabs() != null) {
|
||||||
|
this.tabHeader.getTabs().addListener(itemsContentObserver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API Implementation
|
||||||
|
@Override public void select(int index) {
|
||||||
|
if (index < 0 || (getItemCount() > 0 && index >= getItemCount()) ||
|
||||||
|
(index == getSelectedIndex() && getModelItem(index).isSelected())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unselect the old tab
|
||||||
|
if (getSelectedIndex() >= 0 && getSelectedIndex() < tabHeader.getTabs().size()) {
|
||||||
|
tabHeader.getTabs().get(getSelectedIndex()).setSelected(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedIndex(index);
|
||||||
|
|
||||||
|
Tab tab = getModelItem(index);
|
||||||
|
if (tab != null) {
|
||||||
|
setSelectedItem(tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select the new tab
|
||||||
|
if (getSelectedIndex() >= 0 && getSelectedIndex() < tabHeader.getTabs().size()) {
|
||||||
|
tabHeader.getTabs().get(getSelectedIndex()).setSelected(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Does this get all the change events */
|
||||||
|
tabHeader.notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUS_ITEM);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void select(Tab tab) {
|
||||||
|
final int itemCount = getItemCount();
|
||||||
|
|
||||||
|
for (int i = 0; i < itemCount; i++) {
|
||||||
|
final Tab value = getModelItem(i);
|
||||||
|
if (value != null && value.equals(tab)) {
|
||||||
|
select(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tab != null) {
|
||||||
|
setSelectedItem(tab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected Tab getModelItem(int index) {
|
||||||
|
final ObservableList<Tab> items = tabHeader.getTabs();
|
||||||
|
if (items == null) return null;
|
||||||
|
if (index < 0 || index >= items.size()) return null;
|
||||||
|
return items.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected int getItemCount() {
|
||||||
|
final ObservableList<Tab> items = tabHeader.getTabs();
|
||||||
|
return items == null ? 0 : items.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Tab findNearestAvailableTab(int tabIndex, boolean doSelect) {
|
||||||
|
// we always try to select the nearest, non-disabled
|
||||||
|
// tab from the position of the closed tab.
|
||||||
|
final int tabCount = getItemCount();
|
||||||
|
int i = 1;
|
||||||
|
Tab bestTab = null;
|
||||||
|
while (true) {
|
||||||
|
// look leftwards
|
||||||
|
int downPos = tabIndex - i;
|
||||||
|
if (downPos >= 0) {
|
||||||
|
Tab _tab = getModelItem(downPos);
|
||||||
|
if (_tab != null) {
|
||||||
|
bestTab = _tab;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// look rightwards. We subtract one as we need
|
||||||
|
// to take into account that a tab has been removed
|
||||||
|
// and if we don't do this we'll miss the tab
|
||||||
|
// to the right of the tab (as it has moved into
|
||||||
|
// the removed tabs position).
|
||||||
|
int upPos = tabIndex + i - 1;
|
||||||
|
if (upPos < tabCount) {
|
||||||
|
Tab _tab = getModelItem(upPos);
|
||||||
|
if (_tab != null) {
|
||||||
|
bestTab = _tab;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downPos < 0 && upPos >= tabCount) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doSelect && bestTab != null) {
|
||||||
|
select(bestTab);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestTab;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Skin<?> createDefaultSkin() {
|
||||||
|
return new TabHeaderSkin(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TabHeaderSkin extends SkinBase<TabHeader> {
|
||||||
|
|
||||||
|
private static final PseudoClass SELECTED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("selected");
|
||||||
|
|
||||||
|
private final Color ripplerColor = Color.valueOf("#FFFF8D");
|
||||||
|
|
||||||
|
private final HeaderContainer header;
|
||||||
|
private boolean isSelectingTab = false;
|
||||||
|
private Tab selectedTab;
|
||||||
|
|
||||||
|
protected TabHeaderSkin(TabHeader control) {
|
||||||
|
super(control);
|
||||||
|
|
||||||
|
header = new HeaderContainer();
|
||||||
|
getChildren().setAll(header);
|
||||||
|
|
||||||
|
FXUtils.onChangeAndOperate(control.getSelectionModel().selectedItemProperty(), item -> {
|
||||||
|
isSelectingTab = true;
|
||||||
|
selectedTab = item;
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
header.setNeedsLayout2(true);
|
||||||
|
header.layout();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.selectedTab = control.getSelectionModel().getSelectedItem();
|
||||||
|
if (this.selectedTab == null && control.getSelectionModel().getSelectedIndex() != -1) {
|
||||||
|
control.getSelectionModel().select(control.getSelectionModel().getSelectedIndex());
|
||||||
|
this.selectedTab = control.getSelectionModel().getSelectedItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.selectedTab == null) {
|
||||||
|
control.getSelectionModel().selectFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedTab = control.getSelectionModel().getSelectedItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class HeaderContainer extends StackPane {
|
||||||
|
private Timeline timeline;
|
||||||
|
private StackPane selectedTabLine;
|
||||||
|
private StackPane headersRegion;
|
||||||
|
private Scale scale = new Scale(1, 1, 0, 0);
|
||||||
|
private Rotate rotate = new Rotate(0, 0, 1);
|
||||||
|
private double selectedTabLineOffset;
|
||||||
|
private ObservableList<Node> binding;
|
||||||
|
|
||||||
|
public HeaderContainer() {
|
||||||
|
getStyleClass().add("tab-header-area");
|
||||||
|
setPickOnBounds(false);
|
||||||
|
|
||||||
|
headersRegion = new StackPane() {
|
||||||
|
@Override
|
||||||
|
protected double computePrefWidth(double height) {
|
||||||
|
double width = 0;
|
||||||
|
for (Node child : getChildren()) {
|
||||||
|
if (!(child instanceof TabHeaderContainer) || !child.isVisible()) continue;
|
||||||
|
width += child.prefWidth(height);
|
||||||
|
}
|
||||||
|
return snapSize(width) + snappedLeftInset() + snappedRightInset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected double computePrefHeight(double width) {
|
||||||
|
double height = 0;
|
||||||
|
for (Node child : getChildren()) {
|
||||||
|
if (!(child instanceof TabHeaderContainer) || !child.isVisible()) continue;
|
||||||
|
height = Math.max(height, child.prefHeight(width));
|
||||||
|
}
|
||||||
|
return snapSize(height) + snappedTopInset() + snappedBottomInset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void layoutChildren() {
|
||||||
|
if (isSelectingTab) {
|
||||||
|
animateSelectionLine();
|
||||||
|
isSelectingTab = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
double headerHeight = snapSize(prefHeight(-1));
|
||||||
|
double tabStartX = 0;
|
||||||
|
for (Node node : getChildren()) {
|
||||||
|
if (!(node instanceof TabHeaderContainer)) continue;
|
||||||
|
TabHeaderContainer child = (TabHeaderContainer) node;
|
||||||
|
double w = snapSize(child.prefWidth(-1));
|
||||||
|
double h = snapSize(child.prefHeight(-1));
|
||||||
|
child.resize(w, h);
|
||||||
|
|
||||||
|
child.relocate(tabStartX, headerHeight - h - snappedBottomInset());
|
||||||
|
tabStartX += w;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedTabLine.resizeRelocate(0,
|
||||||
|
headerHeight - selectedTabLine.prefHeight(-1),
|
||||||
|
snapSize(selectedTabLine.prefWidth(-1)),
|
||||||
|
snapSize(selectedTabLine.prefHeight(-1)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
selectedTabLine = new StackPane();
|
||||||
|
selectedTabLine.setManaged(false);
|
||||||
|
selectedTabLine.getTransforms().addAll(scale, rotate);
|
||||||
|
selectedTabLine.setCache(true);
|
||||||
|
selectedTabLine.getStyleClass().addAll("tab-selected-line");
|
||||||
|
selectedTabLine.setPrefHeight(2);
|
||||||
|
selectedTabLine.setPrefWidth(1);
|
||||||
|
selectedTabLine.setBackground(new Background(new BackgroundFill(ripplerColor, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||||
|
getChildren().setAll(headersRegion, selectedTabLine);
|
||||||
|
headersRegion.setPickOnBounds(false);
|
||||||
|
headersRegion.prefHeightProperty().bind(heightProperty());
|
||||||
|
prefWidthProperty().bind(headersRegion.widthProperty());
|
||||||
|
|
||||||
|
Bindings.bindContent(headersRegion.getChildren(), binding = MappedObservableList.create(getSkinnable().getTabs(), tab -> {
|
||||||
|
TabHeaderContainer container = new TabHeaderContainer(tab);
|
||||||
|
container.setVisible(true);
|
||||||
|
return container;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNeedsLayout2(boolean value) {
|
||||||
|
setNeedsLayout(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runTimeline(double newTransX, double newWidth) {
|
||||||
|
double tempScaleX = 0.0D;
|
||||||
|
double tempWidth = 0.0D;
|
||||||
|
double lineWidth = this.selectedTabLine.prefWidth(-1.0D);
|
||||||
|
if (this.isAnimating()) {
|
||||||
|
this.timeline.stop();
|
||||||
|
tempScaleX = this.scale.getX();
|
||||||
|
if (this.rotate.getAngle() != 0.0D) {
|
||||||
|
this.rotate.setAngle(0.0D);
|
||||||
|
tempWidth = tempScaleX * lineWidth;
|
||||||
|
this.selectedTabLine.setTranslateX(this.selectedTabLine.getTranslateX() - tempWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double oldScaleX = this.scale.getX();
|
||||||
|
double oldWidth = lineWidth * oldScaleX;
|
||||||
|
double oldTransX = this.selectedTabLine.getTranslateX();
|
||||||
|
double newScaleX = newWidth * oldScaleX / oldWidth;
|
||||||
|
this.selectedTabLineOffset = newTransX;
|
||||||
|
// newTransX += offsetStart * (double)this.direction;
|
||||||
|
double transDiff = newTransX - oldTransX;
|
||||||
|
double midScaleX = tempScaleX != 0.0D ? tempScaleX : (Math.abs(transDiff) / 1.3D + oldWidth) * oldScaleX / oldWidth;
|
||||||
|
if (transDiff < 0.0D) {
|
||||||
|
this.selectedTabLine.setTranslateX(this.selectedTabLine.getTranslateX() + oldWidth);
|
||||||
|
newTransX += newWidth;
|
||||||
|
this.rotate.setAngle(180.0D);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.timeline = new Timeline(new KeyFrame(Duration.ZERO, new KeyValue(this.selectedTabLine.translateXProperty(), this.selectedTabLine.getTranslateX(), Interpolator.EASE_BOTH)), new KeyFrame(Duration.seconds(0.12D), new KeyValue(this.scale.xProperty(), midScaleX, Interpolator.EASE_BOTH), new KeyValue(this.selectedTabLine.translateXProperty(), this.selectedTabLine.getTranslateX(), Interpolator.EASE_BOTH)), new KeyFrame(Duration.seconds(0.24D), new KeyValue(this.scale.xProperty(), newScaleX, Interpolator.EASE_BOTH), new KeyValue(this.selectedTabLine.translateXProperty(), newTransX, Interpolator.EASE_BOTH)));
|
||||||
|
this.timeline.setOnFinished((finish) -> {
|
||||||
|
if (this.rotate.getAngle() != 0.0D) {
|
||||||
|
this.rotate.setAngle(0.0D);
|
||||||
|
this.selectedTabLine.setTranslateX(this.selectedTabLine.getTranslateX() - newWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
this.timeline.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAnimating() {
|
||||||
|
return this.timeline != null && this.timeline.getStatus() == Animation.Status.RUNNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void layoutChildren() {
|
||||||
|
super.layoutChildren();
|
||||||
|
|
||||||
|
if (isSelectingTab) {
|
||||||
|
animateSelectionLine();
|
||||||
|
isSelectingTab = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void animateSelectionLine() {
|
||||||
|
double offset = 0.0D;
|
||||||
|
double selectedTabOffset = 0.0D;
|
||||||
|
double selectedTabWidth = 0.0D;
|
||||||
|
Side side = Side.TOP;
|
||||||
|
|
||||||
|
for (Node node : headersRegion.getChildren()) {
|
||||||
|
if (node instanceof TabHeaderContainer) {
|
||||||
|
TabHeaderContainer tabHeader = (TabHeaderContainer)node;
|
||||||
|
double tabHeaderPrefWidth = this.snapSize(tabHeader.prefWidth(-1.0D));
|
||||||
|
if (selectedTab != null && selectedTab.equals(tabHeader.tab)) {
|
||||||
|
selectedTabOffset = side != Side.LEFT && side != Side.BOTTOM ? offset : -offset - tabHeaderPrefWidth;
|
||||||
|
selectedTabWidth = tabHeaderPrefWidth;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += tabHeaderPrefWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.runTimeline(selectedTabOffset, selectedTabWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class TabHeaderContainer extends StackPane {
|
||||||
|
|
||||||
|
private final Tab tab;
|
||||||
|
private final Label tabText;
|
||||||
|
private final BorderPane inner;
|
||||||
|
private final JFXRippler rippler;
|
||||||
|
|
||||||
|
public TabHeaderContainer(Tab tab) {
|
||||||
|
this.tab = tab;
|
||||||
|
|
||||||
|
tabText = new Label();
|
||||||
|
tabText.textProperty().bind(tab.textProperty());
|
||||||
|
tabText.getStyleClass().add("tab-label");
|
||||||
|
inner = new BorderPane();
|
||||||
|
inner.setCenter(tabText);
|
||||||
|
inner.getStyleClass().add("tab-container");
|
||||||
|
rippler = new JFXRippler(inner, JFXRippler.RipplerPos.FRONT);
|
||||||
|
rippler.setRipplerFill(ripplerColor);
|
||||||
|
getChildren().setAll(rippler);
|
||||||
|
|
||||||
|
FXUtils.onChangeAndOperate(tab.selectedProperty(), selected -> inner.pseudoClassStateChanged(SELECTED_PSEUDOCLASS_STATE, selected));
|
||||||
|
|
||||||
|
this.setOnMouseClicked(event -> {
|
||||||
|
if (event.getButton() == MouseButton.PRIMARY) {
|
||||||
|
this.setOpacity(1);
|
||||||
|
getSkinnable().getSelectionModel().select(tab);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Tab {
|
||||||
|
private final StringProperty id = new SimpleStringProperty(this, "id");
|
||||||
|
private final StringProperty text = new SimpleStringProperty(this, "text");
|
||||||
|
private final ReadOnlyBooleanWrapper selected = new ReadOnlyBooleanWrapper(this, "selected");
|
||||||
|
|
||||||
|
public Tab(String id) {
|
||||||
|
setId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tab(String id, String text) {
|
||||||
|
setId(id);
|
||||||
|
setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringProperty idProperty() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id.set(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText() {
|
||||||
|
return text.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringProperty textProperty() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setText(String text) {
|
||||||
|
this.text.set(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSelected() {
|
||||||
|
return selected.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyBooleanProperty selectedProperty() {
|
||||||
|
return selected.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSelected(boolean selected) {
|
||||||
|
this.selected.set(selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ public class Decorator extends Control {
|
|||||||
private final ListProperty<Node> content = new SimpleListProperty<>(FXCollections.observableArrayList());
|
private final ListProperty<Node> content = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||||
private final ListProperty<Node> container = new SimpleListProperty<>(FXCollections.observableArrayList());
|
private final ListProperty<Node> container = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||||
private final ObjectProperty<Background> contentBackground = new SimpleObjectProperty<>();
|
private final ObjectProperty<Background> contentBackground = new SimpleObjectProperty<>();
|
||||||
private final StringProperty title = new SimpleStringProperty();
|
private final ObjectProperty<DecoratorPage.State> state = new SimpleObjectProperty<>();
|
||||||
private final StringProperty drawerTitle = new SimpleStringProperty();
|
private final StringProperty drawerTitle = new SimpleStringProperty();
|
||||||
private final ObjectProperty<Runnable> onCloseButtonAction = new SimpleObjectProperty<>();
|
private final ObjectProperty<Runnable> onCloseButtonAction = new SimpleObjectProperty<>();
|
||||||
private final ObjectProperty<EventHandler<ActionEvent>> onCloseNavButtonAction = new SimpleObjectProperty<>();
|
private final ObjectProperty<EventHandler<ActionEvent>> onCloseNavButtonAction = new SimpleObjectProperty<>();
|
||||||
@@ -90,16 +90,16 @@ public class Decorator extends Control {
|
|||||||
this.content.set(content);
|
this.content.set(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTitle() {
|
public DecoratorPage.State getState() {
|
||||||
return title.get();
|
return state.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringProperty titleProperty() {
|
public ObjectProperty<DecoratorPage.State> stateProperty() {
|
||||||
return title;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTitle(String title) {
|
public void setState(DecoratorPage.State state) {
|
||||||
this.title.set(title);
|
this.state.set(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDrawerTitle() {
|
public String getDrawerTitle() {
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -70,14 +71,11 @@ public class DecoratorController {
|
|||||||
private final Decorator decorator;
|
private final Decorator decorator;
|
||||||
private final ImageView welcomeView;
|
private final ImageView welcomeView;
|
||||||
private final Navigator navigator;
|
private final Navigator navigator;
|
||||||
private final Node mainPage;
|
|
||||||
|
|
||||||
private JFXDialog dialog;
|
private JFXDialog dialog;
|
||||||
private StackContainerPane dialogPane;
|
private StackContainerPane dialogPane;
|
||||||
|
|
||||||
public DecoratorController(Stage stage, Node mainPage) {
|
public DecoratorController(Stage stage, Node mainPage) {
|
||||||
this.mainPage = mainPage;
|
|
||||||
|
|
||||||
decorator = new Decorator(stage);
|
decorator = new Decorator(stage);
|
||||||
decorator.setOnCloseButtonAction(Launcher::stopApplication);
|
decorator.setOnCloseButtonAction(Launcher::stopApplication);
|
||||||
|
|
||||||
@@ -220,8 +218,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 +230,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,39 +241,45 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decorator.canCloseProperty().set(navigator.size() > 2);
|
||||||
|
|
||||||
if (to instanceof DecoratorPage) {
|
if (to instanceof DecoratorPage) {
|
||||||
decorator.drawerTitleProperty().bind(((DecoratorPage) to).titleProperty());
|
decorator.showCloseAsHomeProperty().set(!((DecoratorPage) to).isPageCloseable());
|
||||||
decorator.showCloseAsHomeProperty().set(!((DecoratorPage) to).canForceToClose());
|
|
||||||
} else {
|
} else {
|
||||||
decorator.drawerTitleProperty().unbind();
|
|
||||||
decorator.drawerTitleProperty().set("");
|
|
||||||
decorator.showCloseAsHomeProperty().set(true);
|
decorator.showCloseAsHomeProperty().set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
decorator.canBackProperty().set(navigator.canGoBack());
|
// state property should be updated at last.
|
||||||
decorator.canCloseProperty().set(navigator.canGoBack());
|
if (to instanceof DecoratorPage) {
|
||||||
|
decorator.stateProperty().bind(((DecoratorPage) to).stateProperty());
|
||||||
|
} else {
|
||||||
|
decorator.stateProperty().unbind();
|
||||||
|
decorator.stateProperty().set(new DecoratorPage.State("", null, navigator.canGoBack(), false, true));
|
||||||
|
}
|
||||||
|
|
||||||
if (to instanceof Region) {
|
if (to instanceof Region) {
|
||||||
Region region = (Region) to;
|
Region region = (Region) to;
|
||||||
@@ -359,7 +363,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,88 @@
|
|||||||
|
/*
|
||||||
|
* 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.binding.Bindings;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
state.bind(Bindings.createObjectBinding(() -> {
|
||||||
|
State state = ((DecoratorPage) to).stateProperty().get();
|
||||||
|
return new State(state.getTitle(), state.getTitleNode(), navigator.canGoBack(), state.isRefreshable(), true);
|
||||||
|
}, ((DecoratorPage) to).stateProperty()));
|
||||||
|
} else {
|
||||||
|
state.unbind();
|
||||||
|
state.set(new State("", null, navigator.canGoBack(), false, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
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,25 +17,74 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui.decorator;
|
package org.jackhuang.hmcl.ui.decorator;
|
||||||
|
|
||||||
import javafx.beans.property.ReadOnlyStringProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||||
|
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
||||||
|
|
||||||
public interface DecoratorPage {
|
public interface DecoratorPage extends Refreshable {
|
||||||
ReadOnlyStringProperty titleProperty();
|
ReadOnlyObjectProperty<State> stateProperty();
|
||||||
|
|
||||||
default boolean canForceToClose() {
|
default boolean isPageCloseable() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
default boolean onClose() {
|
default boolean back() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
default void onForceToClose() {
|
@Override
|
||||||
|
default void refresh() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void closePage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
default void onDecoratorPageNavigating(Navigator.NavigationEvent event) {
|
default void onDecoratorPageNavigating(Navigator.NavigationEvent event) {
|
||||||
((Node) this).getStyleClass().add("content-background");
|
((Node) this).getStyleClass().add("content-background");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class State {
|
||||||
|
private final String title;
|
||||||
|
private final Node titleNode;
|
||||||
|
private final boolean backable;
|
||||||
|
private final boolean refreshable;
|
||||||
|
private final boolean animate;
|
||||||
|
|
||||||
|
public State(String title, Node titleNode, boolean backable, boolean refreshable, boolean animate) {
|
||||||
|
this.title = title;
|
||||||
|
this.titleNode = titleNode;
|
||||||
|
this.backable = backable;
|
||||||
|
this.refreshable = refreshable;
|
||||||
|
this.animate = animate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static State fromTitle(String title) {
|
||||||
|
return new State(title, null, true, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static State fromTitleNode(Node titleNode) {
|
||||||
|
return new State(null, titleNode, true, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Node getTitleNode() {
|
||||||
|
return titleNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBackable() {
|
||||||
|
return backable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRefreshable() {
|
||||||
|
return refreshable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAnimate() {
|
||||||
|
return animate;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,13 +29,18 @@ import javafx.scene.Node;
|
|||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.SkinBase;
|
import javafx.scene.control.SkinBase;
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.BorderPane;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.scene.shape.Rectangle;
|
import javafx.scene.shape.Rectangle;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import org.jackhuang.hmcl.setting.Theme;
|
import org.jackhuang.hmcl.setting.Theme;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
|
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||||
|
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||||
import org.jackhuang.hmcl.util.Lang;
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
|
|
||||||
public class DecoratorSkin extends SkinBase<Decorator> {
|
public class DecoratorSkin extends SkinBase<Decorator> {
|
||||||
@@ -45,6 +50,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
|||||||
private final BorderPane titleContainer;
|
private final BorderPane titleContainer;
|
||||||
private final StackPane contentPlaceHolder;
|
private final StackPane contentPlaceHolder;
|
||||||
private final Stage primaryStage;
|
private final Stage primaryStage;
|
||||||
|
private final TransitionPane navBarPane;
|
||||||
|
|
||||||
private double xOffset, yOffset, newX, newY, initX, initY;
|
private double xOffset, yOffset, newX, newY, initX, initY;
|
||||||
private boolean allowMove, isDragging;
|
private boolean allowMove, isDragging;
|
||||||
@@ -72,65 +78,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);
|
||||||
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);
|
||||||
@@ -149,7 +131,17 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
|||||||
rectangle.heightProperty().bind(titleContainer.heightProperty().add(100));
|
rectangle.heightProperty().bind(titleContainer.heightProperty().add(100));
|
||||||
titleContainer.setClip(rectangle);
|
titleContainer.setClip(rectangle);
|
||||||
{
|
{
|
||||||
titleContainer.setCenter(createNavBar(skinnable));
|
navBarPane = new TransitionPane();
|
||||||
|
FXUtils.onChangeAndOperate(skinnable.stateProperty(), s -> {
|
||||||
|
if (s == null) return;
|
||||||
|
Node node = createNavBar(skinnable, s.isBackable(), skinnable.canCloseProperty().get(), skinnable.showCloseAsHomeProperty().get(), s.isRefreshable(), s.getTitle(), s.getTitleNode());
|
||||||
|
if (s.isAnimate()) {
|
||||||
|
navBarPane.setContent(node, ContainerAnimations.FADE.getAnimationProducer());
|
||||||
|
} else {
|
||||||
|
navBarPane.getChildren().setAll(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
titleContainer.setCenter(navBarPane);
|
||||||
|
|
||||||
HBox buttonsContainer = new HBox();
|
HBox buttonsContainer = new HBox();
|
||||||
buttonsContainer.setStyle("-fx-background-color: transparent;");
|
buttonsContainer.setStyle("-fx-background-color: transparent;");
|
||||||
@@ -177,7 +169,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
|||||||
getChildren().setAll(root);
|
getChildren().setAll(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Node createNavBar(Decorator skinnable) {
|
private Node createNavBar(Decorator skinnable, boolean canBack, boolean canClose, boolean showCloseAsHome, boolean canRefresh, String title, Node titleNode) {
|
||||||
BorderPane navBar = new BorderPane();
|
BorderPane navBar = new BorderPane();
|
||||||
{
|
{
|
||||||
HBox navLeft = new HBox();
|
HBox navLeft = new HBox();
|
||||||
@@ -189,7 +181,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
|||||||
backNavButton.getStyleClass().add("jfx-decorator-button");
|
backNavButton.getStyleClass().add("jfx-decorator-button");
|
||||||
backNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
backNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||||
backNavButton.onActionProperty().bind(skinnable.onBackNavButtonActionProperty());
|
backNavButton.onActionProperty().bind(skinnable.onBackNavButtonActionProperty());
|
||||||
backNavButton.visibleProperty().bind(skinnable.canBackProperty());
|
backNavButton.visibleProperty().set(canBack);
|
||||||
|
|
||||||
JFXButton closeNavButton = new JFXButton();
|
JFXButton closeNavButton = new JFXButton();
|
||||||
closeNavButton.setGraphic(SVG.close(Theme.foregroundFillBinding(), -1, -1));
|
closeNavButton.setGraphic(SVG.close(Theme.foregroundFillBinding(), -1, -1));
|
||||||
@@ -197,32 +189,31 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
|||||||
closeNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
closeNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||||
closeNavButton.onActionProperty().bind(skinnable.onCloseNavButtonActionProperty());
|
closeNavButton.onActionProperty().bind(skinnable.onCloseNavButtonActionProperty());
|
||||||
|
|
||||||
FXUtils.onChangeAndOperate(skinnable.canBackProperty(), (newValue) -> {
|
if (canBack) navLeft.getChildren().add(backNavButton);
|
||||||
navLeft.getChildren().remove(backNavButton);
|
if (canClose) navLeft.getChildren().add(closeNavButton);
|
||||||
if (newValue) navLeft.getChildren().add(0, backNavButton);
|
if (showCloseAsHome)
|
||||||
});
|
|
||||||
FXUtils.onChangeAndOperate(skinnable.canCloseProperty(), (newValue) -> {
|
|
||||||
navLeft.getChildren().remove(closeNavButton);
|
|
||||||
if (newValue) navLeft.getChildren().add(closeNavButton);
|
|
||||||
});
|
|
||||||
|
|
||||||
FXUtils.onChangeAndOperate(skinnable.showCloseAsHomeProperty(), (newValue) -> {
|
|
||||||
if (newValue)
|
|
||||||
closeNavButton.setGraphic(SVG.home(Theme.foregroundFillBinding(), -1, -1));
|
closeNavButton.setGraphic(SVG.home(Theme.foregroundFillBinding(), -1, -1));
|
||||||
else
|
else
|
||||||
closeNavButton.setGraphic(SVG.close(Theme.foregroundFillBinding(), -1, -1));
|
closeNavButton.setGraphic(SVG.close(Theme.foregroundFillBinding(), -1, -1));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
navBar.setLeft(navLeft);
|
navBar.setLeft(navLeft);
|
||||||
|
|
||||||
VBox navCenter = new VBox();
|
BorderPane center = new BorderPane();
|
||||||
navCenter.setAlignment(Pos.CENTER_LEFT);
|
if (title != null) {
|
||||||
Label titleLabel = new Label();
|
Label titleLabel = new Label();
|
||||||
titleLabel.getStyleClass().add("jfx-decorator-title");
|
titleLabel.getStyleClass().add("jfx-decorator-title");
|
||||||
titleLabel.textProperty().bind(skinnable.drawerTitleProperty());
|
titleLabel.setText(title);
|
||||||
navCenter.getChildren().setAll(titleLabel);
|
center.setLeft(titleLabel);
|
||||||
navBar.setCenter(navCenter);
|
BorderPane.setAlignment(titleLabel, Pos.CENTER_LEFT);
|
||||||
|
}
|
||||||
|
if (titleNode != null) {
|
||||||
|
center.setCenter(titleNode);
|
||||||
|
BorderPane.setAlignment(titleNode, Pos.CENTER_LEFT);
|
||||||
|
BorderPane.setMargin(titleNode, new Insets(0, 0, 0, 8));
|
||||||
|
}
|
||||||
|
navBar.setCenter(center);
|
||||||
|
|
||||||
|
if (canRefresh) {
|
||||||
HBox navRight = new HBox();
|
HBox navRight = new HBox();
|
||||||
navRight.setAlignment(Pos.CENTER_RIGHT);
|
navRight.setAlignment(Pos.CENTER_RIGHT);
|
||||||
JFXButton refreshNavButton = new JFXButton();
|
JFXButton refreshNavButton = new JFXButton();
|
||||||
@@ -230,7 +221,6 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
|||||||
refreshNavButton.getStyleClass().add("jfx-decorator-button");
|
refreshNavButton.getStyleClass().add("jfx-decorator-button");
|
||||||
refreshNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
refreshNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||||
refreshNavButton.onActionProperty().bind(skinnable.onRefreshNavButtonActionProperty());
|
refreshNavButton.onActionProperty().bind(skinnable.onRefreshNavButtonActionProperty());
|
||||||
refreshNavButton.visibleProperty().bind(skinnable.canRefreshProperty());
|
|
||||||
|
|
||||||
Rectangle separator = new Rectangle();
|
Rectangle separator = new Rectangle();
|
||||||
separator.visibleProperty().bind(refreshNavButton.visibleProperty());
|
separator.visibleProperty().bind(refreshNavButton.visibleProperty());
|
||||||
@@ -240,6 +230,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
|||||||
navRight.getChildren().setAll(refreshNavButton, separator);
|
navRight.getChildren().setAll(refreshNavButton, separator);
|
||||||
navBar.setRight(navRight);
|
navBar.setRight(navRight);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return navBar;
|
return navBar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* 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.ReadOnlyObjectProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
|
public abstract class DecoratorTransitionPage extends Control implements DecoratorPage {
|
||||||
|
protected final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("")));
|
||||||
|
private final BooleanProperty refreshable = new SimpleBooleanProperty(false);
|
||||||
|
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 boolean isRefreshable() {
|
||||||
|
return refreshable.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BooleanProperty refreshableProperty() {
|
||||||
|
return refreshable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRefreshable(boolean refreshable) {
|
||||||
|
this.refreshable.set(refreshable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||||
|
return state.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,30 @@ 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 + " - ";
|
||||||
|
|
||||||
|
String title;
|
||||||
if (page instanceof WizardPage)
|
if (page instanceof WizardPage)
|
||||||
title.set(prefix + ((WizardPage) page).getTitle());
|
title = prefix + ((WizardPage) page).getTitle();
|
||||||
|
else
|
||||||
|
title = "";
|
||||||
|
state.set(new State(title, null, true, refreshableProperty().get(), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 +100,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;
|
||||||
@@ -41,22 +36,30 @@ import javafx.scene.layout.VBox;
|
|||||||
import javafx.scene.shape.Rectangle;
|
import javafx.scene.shape.Rectangle;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import org.jackhuang.hmcl.Metadata;
|
import org.jackhuang.hmcl.Metadata;
|
||||||
|
import org.jackhuang.hmcl.game.Version;
|
||||||
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;
|
||||||
|
import org.jackhuang.hmcl.ui.versions.GameItem;
|
||||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||||
import org.jackhuang.hmcl.upgrade.RemoteVersion;
|
import org.jackhuang.hmcl.upgrade.RemoteVersion;
|
||||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||||
|
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.ui.FXUtils.SINE;
|
import static org.jackhuang.hmcl.ui.FXUtils.SINE;
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
public final class MainPage extends StackPane implements DecoratorPage {
|
public final class MainPage extends StackPane implements DecoratorPage {
|
||||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", "Hello Minecraft! Launcher " + Metadata.VERSION);
|
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle("Hello Minecraft! Launcher " + Metadata.VERSION));
|
||||||
|
|
||||||
private final PopupMenu menu = new PopupMenu();
|
private final PopupMenu menu = new PopupMenu();
|
||||||
private final JFXPopup popup = new JFXPopup(menu);
|
private final JFXPopup popup = new JFXPopup(menu);
|
||||||
@@ -64,7 +67,9 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
|||||||
private final StringProperty currentGame = new SimpleStringProperty(this, "currentGame");
|
private final StringProperty currentGame = new SimpleStringProperty(this, "currentGame");
|
||||||
private final BooleanProperty showUpdate = new SimpleBooleanProperty(this, "showUpdate");
|
private final BooleanProperty showUpdate = new SimpleBooleanProperty(this, "showUpdate");
|
||||||
private final StringProperty latestVersion = new SimpleStringProperty(this, "latestVersion");
|
private final StringProperty latestVersion = new SimpleStringProperty(this, "latestVersion");
|
||||||
private final ObservableList<Node> versions = FXCollections.observableArrayList();
|
private final ObservableList<Version> versions = FXCollections.observableArrayList();
|
||||||
|
private final ObservableList<Node> versionNodes;
|
||||||
|
private Profile profile;
|
||||||
|
|
||||||
private StackPane updatePane;
|
private StackPane updatePane;
|
||||||
private JFXButton menuButton;
|
private JFXButton menuButton;
|
||||||
@@ -112,6 +117,18 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
|||||||
StackPane launchPane = new StackPane();
|
StackPane launchPane = new StackPane();
|
||||||
launchPane.setMaxWidth(230);
|
launchPane.setMaxWidth(230);
|
||||||
launchPane.setMaxHeight(55);
|
launchPane.setMaxHeight(55);
|
||||||
|
launchPane.setOnScroll(event -> {
|
||||||
|
int index = IntStream.range(0, versions.size())
|
||||||
|
.filter(i -> versions.get(i).getId().equals(getCurrentGame()))
|
||||||
|
.findFirst().orElse(-1);
|
||||||
|
if (index < 0) return;
|
||||||
|
if (event.getDeltaY() > 0) {
|
||||||
|
index--;
|
||||||
|
} else {
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
profile.setSelectedVersion(versions.get((index + versions.size()) % versions.size()).getId());
|
||||||
|
});
|
||||||
StackPane.setAlignment(launchPane, Pos.BOTTOM_RIGHT);
|
StackPane.setAlignment(launchPane, Pos.BOTTOM_RIGHT);
|
||||||
{
|
{
|
||||||
JFXButton launchButton = new JFXButton();
|
JFXButton launchButton = new JFXButton();
|
||||||
@@ -131,7 +148,13 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
|||||||
launchLabel.setStyle("-fx-font-size: 16px;");
|
launchLabel.setStyle("-fx-font-size: 16px;");
|
||||||
Label currentLabel = new Label();
|
Label currentLabel = new Label();
|
||||||
currentLabel.setStyle("-fx-font-size: 12px;");
|
currentLabel.setStyle("-fx-font-size: 12px;");
|
||||||
currentLabel.textProperty().bind(currentGameProperty());
|
currentLabel.textProperty().bind(Bindings.createStringBinding(() -> {
|
||||||
|
if (getCurrentGame() == null) {
|
||||||
|
return i18n("version.empty");
|
||||||
|
} else {
|
||||||
|
return getCurrentGame();
|
||||||
|
}
|
||||||
|
}, currentGameProperty()));
|
||||||
graphic.getChildren().setAll(launchLabel, currentLabel);
|
graphic.getChildren().setAll(launchLabel, currentLabel);
|
||||||
|
|
||||||
launchButton.setGraphic(graphic);
|
launchButton.setGraphic(graphic);
|
||||||
@@ -168,7 +191,12 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
|||||||
menu.setMaxWidth(545);
|
menu.setMaxWidth(545);
|
||||||
menu.setAlwaysShowingVBar(true);
|
menu.setAlwaysShowingVBar(true);
|
||||||
menu.setOnMouseClicked(e -> popup.hide());
|
menu.setOnMouseClicked(e -> popup.hide());
|
||||||
Bindings.bindContent(menu.getContent(), versions);
|
versionNodes = MappedObservableList.create(versions, version -> {
|
||||||
|
Node node = PopupMenu.wrapPopupMenuItem(new GameItem(profile, version.getId()));
|
||||||
|
node.setOnMouseClicked(e -> profile.setSelectedVersion(version.getId()));
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
Bindings.bindContent(menu.getContent(), versionNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doAnimation(boolean show) {
|
private void doAnimation(boolean show) {
|
||||||
@@ -208,17 +236,9 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
|||||||
showUpdate.set(false);
|
showUpdate.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTitle() {
|
|
||||||
return title.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReadOnlyStringProperty titleProperty() {
|
public ReadOnlyObjectWrapper<State> stateProperty() {
|
||||||
return title.getReadOnlyProperty();
|
return state;
|
||||||
}
|
|
||||||
|
|
||||||
public void setTitle(String title) {
|
|
||||||
this.title.set(title);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCurrentGame() {
|
public String getCurrentGame() {
|
||||||
@@ -257,7 +277,9 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
|||||||
this.latestVersion.set(latestVersion);
|
this.latestVersion.set(latestVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObservableList<Node> getVersions() {
|
public void initVersions(Profile profile, List<Version> versions) {
|
||||||
return versions;
|
FXUtils.checkFxUserThread();
|
||||||
|
this.profile = profile;
|
||||||
|
this.versions.setAll(versions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
269
HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java
Normal file
269
HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
/*
|
||||||
|
* 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.geometry.Insets;
|
||||||
|
import javafx.scene.control.SkinBase;
|
||||||
|
import javafx.scene.layout.BorderPane;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
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.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.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 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(), mainPage::setCurrentGame);
|
||||||
|
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<Version> 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())))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
runInFX(() -> {
|
||||||
|
if (profile == Profiles.getSelectedProfile())
|
||||||
|
mainPage.initVersions(profile, children);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.mainPage = mainPage;
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
|
||||||
|
{
|
||||||
|
StackPane drawerContainer = new StackPane();
|
||||||
|
FXUtils.setLimitWidth(drawerContainer, 200);
|
||||||
|
drawerContainer.getStyleClass().add("gray-background");
|
||||||
|
drawerContainer.getChildren().setAll(sideBar);
|
||||||
|
FXUtils.setOverflowHidden(drawerContainer, 8);
|
||||||
|
|
||||||
|
StackPane wrapper = new StackPane(drawerContainer);
|
||||||
|
wrapper.setPadding(new Insets(4, 0, 4, 4));
|
||||||
|
root.setLeft(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
control.navigator.getStyleClass().add("jfx-decorator-content-container");
|
||||||
|
control.navigator.init(getSkinnable().getMainPage());
|
||||||
|
FXUtils.setOverflowHidden(control.navigator, 8);
|
||||||
|
StackPane wrapper = new StackPane(control.navigator);
|
||||||
|
wrapper.setPadding(new Insets(4));
|
||||||
|
root.setCenter(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
@@ -24,13 +24,15 @@ import javafx.beans.InvalidationListener;
|
|||||||
import javafx.beans.WeakInvalidationListener;
|
import javafx.beans.WeakInvalidationListener;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.When;
|
import javafx.beans.binding.When;
|
||||||
import javafx.beans.property.ReadOnlyStringProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||||
import javafx.scene.control.ToggleGroup;
|
import javafx.scene.control.ToggleGroup;
|
||||||
import javafx.scene.paint.Color;
|
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;
|
||||||
@@ -65,7 +67,7 @@ import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.reservedSelected
|
|||||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
|
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
|
||||||
|
|
||||||
public final class SettingsPage extends SettingsView implements DecoratorPage {
|
public final class SettingsPage extends SettingsView implements DecoratorPage {
|
||||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("settings.launcher"));
|
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("settings.launcher")));
|
||||||
|
|
||||||
private InvalidationListener updateListener;
|
private InvalidationListener updateListener;
|
||||||
|
|
||||||
@@ -198,17 +200,9 @@ public final class SettingsPage extends SettingsView implements DecoratorPage {
|
|||||||
// ====
|
// ====
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTitle() {
|
|
||||||
return title.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReadOnlyStringProperty titleProperty() {
|
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||||
return title.getReadOnlyProperty();
|
return state.getReadOnlyProperty();
|
||||||
}
|
|
||||||
|
|
||||||
public void setTitle(String title) {
|
|
||||||
this.title.set(title);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -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;
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
|||||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
|
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
|
||||||
|
|
||||||
public class ProfileList extends ListPage<ProfileListItem> implements DecoratorPage {
|
public class ProfileList extends ListPage<ProfileListItem> implements DecoratorPage {
|
||||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(i18n("profile.manage"));
|
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("profile.manage")));
|
||||||
private final ListProperty<Profile> profiles = new SimpleListProperty<>(FXCollections.observableArrayList());
|
private final ListProperty<Profile> profiles = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||||
private ObjectProperty<Profile> selectedProfile;
|
private ObjectProperty<Profile> selectedProfile;
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ public class ProfileList extends ListPage<ProfileListItem> implements DecoratorP
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReadOnlyStringProperty titleProperty() {
|
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||||
return title.getReadOnlyProperty();
|
return state.getReadOnlyProperty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import com.jfoenix.controls.JFXButton;
|
|||||||
import com.jfoenix.controls.JFXCheckBox;
|
import com.jfoenix.controls.JFXCheckBox;
|
||||||
import com.jfoenix.controls.JFXTextField;
|
import com.jfoenix.controls.JFXTextField;
|
||||||
import com.jfoenix.validation.base.ValidatorBase;
|
import com.jfoenix.validation.base.ValidatorBase;
|
||||||
import javafx.beans.property.ReadOnlyStringProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
@@ -41,7 +41,7 @@ import java.util.Optional;
|
|||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
public final class ProfilePage extends StackPane implements DecoratorPage {
|
public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||||
private final ReadOnlyStringWrapper title;
|
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
||||||
private final StringProperty location;
|
private final StringProperty location;
|
||||||
private final Profile profile;
|
private final Profile profile;
|
||||||
|
|
||||||
@@ -57,8 +57,7 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
|||||||
this.profile = profile;
|
this.profile = profile;
|
||||||
String profileDisplayName = Optional.ofNullable(profile).map(Profiles::getProfileDisplayName).orElse("");
|
String profileDisplayName = Optional.ofNullable(profile).map(Profiles::getProfileDisplayName).orElse("");
|
||||||
|
|
||||||
title = new ReadOnlyStringWrapper(this, "title",
|
state.set(State.fromTitle(profile == null ? i18n("profile.new") : i18n("profile") + " - " + profileDisplayName));
|
||||||
profile == null ? i18n("profile.new") : i18n("profile") + " - " + profileDisplayName);
|
|
||||||
location = new SimpleStringProperty(this, "location",
|
location = new SimpleStringProperty(this, "location",
|
||||||
Optional.ofNullable(profile).map(Profile::getGameDir).map(File::getAbsolutePath).orElse(".minecraft"));
|
Optional.ofNullable(profile).map(Profile::getGameDir).map(File::getAbsolutePath).orElse(".minecraft"));
|
||||||
|
|
||||||
@@ -111,17 +110,9 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
|||||||
fireEvent(new PageCloseEvent());
|
fireEvent(new PageCloseEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTitle() {
|
|
||||||
return title.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReadOnlyStringProperty titleProperty() {
|
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||||
return title.getReadOnlyProperty();
|
return state.getReadOnlyProperty();
|
||||||
}
|
|
||||||
|
|
||||||
public void setTitle(String title) {
|
|
||||||
this.title.set(title);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLocation() {
|
public String getLocation() {
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui.versions;
|
package org.jackhuang.hmcl.ui.versions;
|
||||||
|
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.scene.control.Skin;
|
import javafx.scene.control.Skin;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
@@ -43,7 +43,7 @@ import java.util.logging.Level;
|
|||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
public class DatapackListPage extends ListPageBase<DatapackListPageSkin.DatapackInfoObject> implements DecoratorPage {
|
public class DatapackListPage extends ListPageBase<DatapackListPageSkin.DatapackInfoObject> implements DecoratorPage {
|
||||||
private final StringProperty title = new SimpleStringProperty();
|
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
||||||
private final Path worldDir;
|
private final Path worldDir;
|
||||||
private final Datapack datapack;
|
private final Datapack datapack;
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ public class DatapackListPage extends ListPageBase<DatapackListPageSkin.Datapack
|
|||||||
public DatapackListPage(String worldName, Path worldDir) {
|
public DatapackListPage(String worldName, Path worldDir) {
|
||||||
this.worldDir = worldDir;
|
this.worldDir = worldDir;
|
||||||
|
|
||||||
title.set(i18n("datapack.title", worldName));
|
state.set(State.fromTitle(i18n("datapack.title", worldName)));
|
||||||
|
|
||||||
datapack = new Datapack(worldDir.resolve("datapacks"));
|
datapack = new Datapack(worldDir.resolve("datapacks"));
|
||||||
datapack.loadFromDir();
|
datapack.loadFromDir();
|
||||||
@@ -86,8 +86,8 @@ public class DatapackListPage extends ListPageBase<DatapackListPageSkin.Datapack
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StringProperty titleProperty() {
|
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||||
return title;
|
return state.getReadOnlyProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add() {
|
public void add() {
|
||||||
|
|||||||
@@ -18,10 +18,11 @@
|
|||||||
package org.jackhuang.hmcl.ui.versions;
|
package org.jackhuang.hmcl.ui.versions;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.ReadOnlyStringProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.ToggleGroup;
|
import javafx.scene.control.ToggleGroup;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
import org.jackhuang.hmcl.event.EventBus;
|
import org.jackhuang.hmcl.event.EventBus;
|
||||||
import org.jackhuang.hmcl.event.RefreshingVersionsEvent;
|
import org.jackhuang.hmcl.event.RefreshingVersionsEvent;
|
||||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||||
@@ -33,10 +34,9 @@ import org.jackhuang.hmcl.ui.construct.Navigator;
|
|||||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||||
import org.jackhuang.hmcl.ui.download.VanillaInstallWizardProvider;
|
import org.jackhuang.hmcl.ui.download.VanillaInstallWizardProvider;
|
||||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
|
||||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -46,7 +46,7 @@ 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 class GameList extends ListPageBase<GameListItem> implements DecoratorPage {
|
public class GameList extends ListPageBase<GameListItem> implements DecoratorPage {
|
||||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(I18n.i18n("version.manage"));
|
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("version.manage")));
|
||||||
|
|
||||||
private ToggleGroup toggleGroup;
|
private ToggleGroup toggleGroup;
|
||||||
|
|
||||||
@@ -123,24 +123,29 @@ public class GameList extends ListPageBase<GameListItem> implements DecoratorPag
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReadOnlyStringProperty titleProperty() {
|
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||||
return title.getReadOnlyProperty();
|
return state.getReadOnlyProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class GameListSkin extends ToolbarListPageSkin<GameList> {
|
private class GameListSkin extends ToolbarListPageSkin<GameList> {
|
||||||
|
|
||||||
public GameListSkin() {
|
public GameListSkin() {
|
||||||
super(GameList.this);
|
super(GameList.this);
|
||||||
|
|
||||||
|
HBox hbox = new HBox(
|
||||||
|
createToolbarButton(i18n("install.new_game"), SVG::plus, GameList.this::addNewGame),
|
||||||
|
createToolbarButton(i18n("install.modpack"), SVG::importIcon, GameList.this::importModpack),
|
||||||
|
createToolbarButton(i18n("button.refresh"), SVG::refresh, GameList.this::refresh),
|
||||||
|
createToolbarButton(i18n("settings.type.global.manage"), SVG::gear, GameList.this::modifyGlobalGameSettings)
|
||||||
|
);
|
||||||
|
hbox.setPickOnBounds(false);
|
||||||
|
|
||||||
|
state.set(new State(i18n("version.manage"), hbox, true, false, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<Node> initializeToolbar(GameList skinnable) {
|
protected List<Node> initializeToolbar(GameList skinnable) {
|
||||||
return Arrays.asList(
|
return Collections.emptyList();
|
||||||
createToolbarButton(i18n("install.new_game"), SVG::plus, skinnable::addNewGame),
|
|
||||||
createToolbarButton(i18n("install.modpack"), SVG::importIcon, skinnable::importModpack),
|
|
||||||
createToolbarButton(i18n("button.refresh"), SVG::refresh, skinnable::refresh),
|
|
||||||
createToolbarButton(i18n("settings.type.global.manage"), SVG::gear, skinnable::modifyGlobalGameSettings)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
|
|||||||
right.getChildren().add(btnManage);
|
right.getChildren().add(btnManage);
|
||||||
root.setRight(right);
|
root.setRight(right);
|
||||||
|
|
||||||
root.setStyle("-fx-background-color: white; -fx-padding: 8 8 8 0;");
|
root.setStyle("-fx-background-color: white; -fx-background-radius: 4; -fx-padding: 8 8 8 0;");
|
||||||
JFXDepthManager.setDepth(root, 1);
|
JFXDepthManager.setDepth(root, 1);
|
||||||
|
|
||||||
getChildren().setAll(root);
|
getChildren().setAll(root);
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui.versions;
|
package org.jackhuang.hmcl.ui.versions;
|
||||||
|
|
||||||
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;
|
||||||
@@ -32,15 +32,18 @@ import org.jackhuang.hmcl.task.Task;
|
|||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.ListPageBase;
|
import org.jackhuang.hmcl.ui.ListPageBase;
|
||||||
|
import org.jackhuang.hmcl.ui.construct.TabHeader;
|
||||||
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 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;
|
||||||
|
|
||||||
@@ -50,11 +53,12 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
|||||||
public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObject> {
|
public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObject> {
|
||||||
private final BooleanProperty modded = new SimpleBooleanProperty(this, "modded", false);
|
private final BooleanProperty modded = new SimpleBooleanProperty(this, "modded", false);
|
||||||
|
|
||||||
private JFXTabPane parentTab;
|
private TabHeader.Tab tab;
|
||||||
private ModManager modManager;
|
private ModManager modManager;
|
||||||
private LibraryAnalyzer libraryAnalyzer;
|
private LibraryAnalyzer libraryAnalyzer;
|
||||||
|
|
||||||
public ModListPage() {
|
public ModListPage(TabHeader.Tab tab) {
|
||||||
|
this.tab = tab;
|
||||||
|
|
||||||
FXUtils.applyDragListener(this, it -> Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it)), mods -> {
|
FXUtils.applyDragListener(this, it -> Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it)), mods -> {
|
||||||
mods.forEach(it -> {
|
mods.forEach(it -> {
|
||||||
@@ -77,28 +81,34 @@ 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 -> {
|
getProperties().put(ModListPage.class, FXUtils.onWeakChangeAndOperate(tab.selectedProperty(), newValue -> {
|
||||||
if (newValue != null && newValue.getUserData() == ModListPage.this)
|
if (newValue)
|
||||||
itemsProperty().setAll(list.stream().map(ModListPageSkin.ModInfoObject::new).collect(Collectors.toList()));
|
itemsProperty().setAll(list.stream().map(ModListPageSkin.ModInfoObject::new).collect(Collectors.toList()));
|
||||||
});
|
}));
|
||||||
}).start();
|
else
|
||||||
|
getProperties().remove(ModListPage.class);
|
||||||
|
}, Platform::runLater);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add() {
|
public void add() {
|
||||||
@@ -134,10 +144,6 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
|||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setParentTab(JFXTabPane parentTab) {
|
|
||||||
this.parentTab = parentTab;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeSelected(ObservableList<ModListPageSkin.ModInfoObject> selectedItems) {
|
public void removeSelected(ObservableList<ModListPageSkin.ModInfoObject> selectedItems) {
|
||||||
try {
|
try {
|
||||||
modManager.removeMods(selectedItems.stream()
|
modManager.removeMods(selectedItems.stream()
|
||||||
|
|||||||
@@ -18,111 +18,101 @@
|
|||||||
package org.jackhuang.hmcl.ui.versions;
|
package org.jackhuang.hmcl.ui.versions;
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
import com.jfoenix.controls.JFXButton;
|
||||||
|
import com.jfoenix.controls.JFXListView;
|
||||||
import com.jfoenix.controls.JFXPopup;
|
import com.jfoenix.controls.JFXPopup;
|
||||||
import com.jfoenix.controls.JFXTabPane;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.ReadOnlyStringProperty;
|
import javafx.beans.property.*;
|
||||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
import javafx.geometry.Pos;
|
||||||
import javafx.fxml.FXML;
|
import javafx.scene.control.Control;
|
||||||
import javafx.scene.control.Tab;
|
import javafx.scene.control.SelectionMode;
|
||||||
|
import javafx.scene.control.SkinBase;
|
||||||
|
import javafx.scene.layout.BorderPane;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.StackPane;
|
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.Profile;
|
||||||
|
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.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.SVG;
|
import org.jackhuang.hmcl.ui.SVG;
|
||||||
import org.jackhuang.hmcl.ui.construct.IconedMenuItem;
|
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
import org.jackhuang.hmcl.ui.construct.*;
|
||||||
import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
|
||||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
|
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
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 VersionPage extends StackPane implements DecoratorPage {
|
public class VersionPage extends Control implements DecoratorPage {
|
||||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", null);
|
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
||||||
|
private final BooleanProperty loading = new SimpleBooleanProperty();
|
||||||
@FXML
|
private final JFXListView<String> listView = new JFXListView<>();
|
||||||
private VersionSettingsPage versionSettings;
|
private final TabHeader.Tab versionSettingsTab = new TabHeader.Tab("versionSettingsTab");
|
||||||
@FXML
|
private final VersionSettingsPage versionSettingsPage = new VersionSettingsPage();
|
||||||
private Tab modTab;
|
private final TabHeader.Tab modListTab = new TabHeader.Tab("modListTab");
|
||||||
@FXML
|
private final ModListPage modListPage = new ModListPage(modListTab);
|
||||||
private ModListPage mod;
|
private final TabHeader.Tab installerListTab = new TabHeader.Tab("installerListTab");
|
||||||
@FXML
|
private final InstallerListPage installerListPage = new InstallerListPage();
|
||||||
private InstallerListPage installer;
|
private final TabHeader.Tab worldListTab = new TabHeader.Tab("worldList");
|
||||||
@FXML
|
private final WorldListPage worldListPage = new WorldListPage();
|
||||||
private WorldListPage world;
|
private final TransitionPane transitionPane = new TransitionPane();
|
||||||
@FXML
|
private final ObjectProperty<TabHeader.Tab> selectedTab = new SimpleObjectProperty<>();
|
||||||
private JFXButton btnBrowseMenu;
|
|
||||||
@FXML
|
|
||||||
private JFXButton btnDelete;
|
|
||||||
@FXML
|
|
||||||
private JFXButton btnManagementMenu;
|
|
||||||
@FXML
|
|
||||||
private JFXButton btnExport;
|
|
||||||
@FXML
|
|
||||||
private JFXButton btnTestGame;
|
|
||||||
@FXML
|
|
||||||
private StackPane rootPane;
|
|
||||||
@FXML
|
|
||||||
private StackPane contentPane;
|
|
||||||
@FXML
|
|
||||||
private JFXTabPane tabPane;
|
|
||||||
|
|
||||||
private final JFXPopup browsePopup;
|
|
||||||
private final JFXPopup managementPopup;
|
|
||||||
|
|
||||||
private Profile profile;
|
private Profile profile;
|
||||||
private String version;
|
private String version;
|
||||||
|
|
||||||
{
|
{
|
||||||
FXUtils.loadFXML(this, "/assets/fxml/version/version.fxml");
|
Profiles.registerVersionsListener(this::loadVersions);
|
||||||
|
|
||||||
PopupMenu browseList = new PopupMenu();
|
listView.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> {
|
||||||
browsePopup = new JFXPopup(browseList);
|
loadVersion(newValue, profile);
|
||||||
browseList.getContent().setAll(
|
});
|
||||||
new IconedMenuItem(null, i18n("folder.game"), FXUtils.withJFXPopupClosing(() -> onBrowse(""), browsePopup)),
|
|
||||||
new IconedMenuItem(null, i18n("folder.mod"), FXUtils.withJFXPopupClosing(() -> onBrowse("mods"), browsePopup)),
|
|
||||||
new IconedMenuItem(null, i18n("folder.config"), FXUtils.withJFXPopupClosing(() -> onBrowse("config"), browsePopup)),
|
|
||||||
new IconedMenuItem(null, i18n("folder.resourcepacks"), FXUtils.withJFXPopupClosing(() -> onBrowse("resourcepacks"), browsePopup)),
|
|
||||||
new IconedMenuItem(null, i18n("folder.screenshots"), FXUtils.withJFXPopupClosing(() -> onBrowse("screenshots"), browsePopup)),
|
|
||||||
new IconedMenuItem(null, i18n("folder.saves"), FXUtils.withJFXPopupClosing(() -> onBrowse("saves"), browsePopup))
|
|
||||||
);
|
|
||||||
|
|
||||||
PopupMenu managementList = new PopupMenu();
|
|
||||||
managementPopup = new JFXPopup(managementList);
|
|
||||||
managementList.getContent().setAll(
|
|
||||||
new IconedMenuItem(null, i18n("version.manage.redownload_assets_index"), FXUtils.withJFXPopupClosing(() -> Versions.updateGameAssets(profile, version), managementPopup)),
|
|
||||||
new IconedMenuItem(null, i18n("version.manage.remove_libraries"), FXUtils.withJFXPopupClosing(() -> FileUtils.deleteDirectoryQuietly(new File(profile.getRepository().getBaseDirectory(), "libraries")), managementPopup)),
|
|
||||||
new IconedMenuItem(null, i18n("version.manage.clean"), FXUtils.withJFXPopupClosing(() -> Versions.cleanVersion(profile, version), managementPopup)).addTooltip(i18n("version.manage.clean.tooltip"))
|
|
||||||
);
|
|
||||||
|
|
||||||
FXUtils.installFastTooltip(btnDelete, i18n("version.manage.remove"));
|
|
||||||
FXUtils.installFastTooltip(btnBrowseMenu, i18n("settings.game.exploration"));
|
|
||||||
FXUtils.installFastTooltip(btnManagementMenu, i18n("settings.game.management"));
|
|
||||||
FXUtils.installFastTooltip(btnExport, i18n("modpack.export"));
|
|
||||||
|
|
||||||
btnTestGame.setGraphic(SVG.launch(Theme.whiteFillBinding(), 20, 20));
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void load(String id, Profile profile) {
|
private void loadVersions(Profile profile) {
|
||||||
this.version = id;
|
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);
|
||||||
|
this.version = version;
|
||||||
this.profile = profile;
|
this.profile = profile;
|
||||||
|
|
||||||
title.set(i18n("version.manage.manage") + " - " + id);
|
versionSettingsPage.loadVersion(profile, version);
|
||||||
|
loading.set(true);
|
||||||
|
|
||||||
versionSettings.loadVersion(profile, id);
|
CompletableFuture.allOf(
|
||||||
mod.setParentTab(tabPane);
|
modListPage.loadVersion(profile, version),
|
||||||
modTab.setUserData(mod);
|
installerListPage.loadVersion(profile, version),
|
||||||
mod.loadVersion(profile, id);
|
worldListPage.loadVersion(profile, version))
|
||||||
installer.loadVersion(profile, id);
|
.whenCompleteAsync((result, exception) -> loading.set(false), Platform::runLater);
|
||||||
world.loadVersion(profile, id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onNavigated(Navigator.NavigationEvent event) {
|
private void onNavigated(Navigator.NavigationEvent event) {
|
||||||
@@ -137,38 +127,164 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
load(this.version, this.profile);
|
loadVersion(this.version, this.profile);
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private void onTestGame() {
|
|
||||||
Versions.testGame(profile, version);
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private void onBrowseMenu() {
|
|
||||||
browsePopup.show(btnBrowseMenu, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, btnBrowseMenu.getHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private void onManagementMenu() {
|
|
||||||
managementPopup.show(btnManagementMenu, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, btnManagementMenu.getHeight());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onBrowse(String sub) {
|
private void onBrowse(String sub) {
|
||||||
FXUtils.openFolder(new File(profile.getRepository().getRunDirectory(version), sub));
|
FXUtils.openFolder(new File(profile.getRepository().getRunDirectory(version), sub));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTitle() {
|
private void redownloadAssetIndex() {
|
||||||
return title.get();
|
Versions.updateGameAssets(profile, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearLibraries() {
|
||||||
|
FileUtils.deleteDirectoryQuietly(new File(profile.getRepository().getBaseDirectory(), "libraries"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearJunkFiles() {
|
||||||
|
Versions.cleanVersion(profile, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testGame() {
|
||||||
|
Versions.testGame(profile, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReadOnlyStringProperty titleProperty() {
|
protected Skin createDefaultSkin() {
|
||||||
return title.getReadOnlyProperty();
|
return new Skin(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTitle(String title) {
|
@Override
|
||||||
this.title.set(title);
|
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||||
|
return state.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Skin extends SkinBase<VersionPage> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for all SkinBase instances.
|
||||||
|
*
|
||||||
|
* @param control The control for which this Skin should attach to.
|
||||||
|
*/
|
||||||
|
protected Skin(VersionPage 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
TabHeader tabPane = new TabHeader();
|
||||||
|
tabPane.setPickOnBounds(false);
|
||||||
|
tabPane.getStyleClass().add("jfx-decorator-tab");
|
||||||
|
control.versionSettingsTab.setText(i18n("settings"));
|
||||||
|
control.modListTab.setText(i18n("mods"));
|
||||||
|
control.installerListTab.setText(i18n("settings.tabs.installers"));
|
||||||
|
control.worldListTab.setText(i18n("world"));
|
||||||
|
tabPane.getTabs().setAll(
|
||||||
|
control.versionSettingsTab,
|
||||||
|
control.modListTab,
|
||||||
|
control.installerListTab,
|
||||||
|
control.worldListTab);
|
||||||
|
control.selectedTab.bind(tabPane.getSelectionModel().selectedItemProperty());
|
||||||
|
FXUtils.onChangeAndOperate(tabPane.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||||
|
if (control.versionSettingsTab.equals(newValue)) {
|
||||||
|
control.transitionPane.setContent(control.versionSettingsPage, ContainerAnimations.FADE.getAnimationProducer());
|
||||||
|
} else if (control.modListTab.equals(newValue)) {
|
||||||
|
control.transitionPane.setContent(control.modListPage, ContainerAnimations.FADE.getAnimationProducer());
|
||||||
|
} else if (control.installerListTab.equals(newValue)) {
|
||||||
|
control.transitionPane.setContent(control.installerListPage, ContainerAnimations.FADE.getAnimationProducer());
|
||||||
|
} else if (control.worldListTab.equals(newValue)) {
|
||||||
|
control.transitionPane.setContent(control.worldListPage, ContainerAnimations.FADE.getAnimationProducer());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
HBox toolBar = new HBox();
|
||||||
|
toolBar.setAlignment(Pos.TOP_RIGHT);
|
||||||
|
toolBar.setPickOnBounds(false);
|
||||||
|
{
|
||||||
|
PopupMenu browseList = new PopupMenu();
|
||||||
|
JFXPopup browsePopup = new JFXPopup(browseList);
|
||||||
|
browseList.getContent().setAll(
|
||||||
|
new IconedMenuItem(null, i18n("folder.game"), FXUtils.withJFXPopupClosing(() -> control.onBrowse(""), browsePopup)),
|
||||||
|
new IconedMenuItem(null, i18n("folder.mod"), FXUtils.withJFXPopupClosing(() -> control.onBrowse("mods"), browsePopup)),
|
||||||
|
new IconedMenuItem(null, i18n("folder.config"), FXUtils.withJFXPopupClosing(() -> control.onBrowse("config"), browsePopup)),
|
||||||
|
new IconedMenuItem(null, i18n("folder.resourcepacks"), FXUtils.withJFXPopupClosing(() -> control.onBrowse("resourcepacks"), browsePopup)),
|
||||||
|
new IconedMenuItem(null, i18n("folder.screenshots"), FXUtils.withJFXPopupClosing(() -> control.onBrowse("screenshots"), browsePopup)),
|
||||||
|
new IconedMenuItem(null, i18n("folder.saves"), FXUtils.withJFXPopupClosing(() -> control.onBrowse("saves"), browsePopup))
|
||||||
|
);
|
||||||
|
|
||||||
|
PopupMenu managementList = new PopupMenu();
|
||||||
|
JFXPopup managementPopup = new JFXPopup(managementList);
|
||||||
|
managementList.getContent().setAll(
|
||||||
|
new IconedMenuItem(null, i18n("version.manage.redownload_assets_index"), FXUtils.withJFXPopupClosing(control::redownloadAssetIndex, managementPopup)),
|
||||||
|
new IconedMenuItem(null, i18n("version.manage.remove_libraries"), FXUtils.withJFXPopupClosing(control::clearLibraries, managementPopup)),
|
||||||
|
new IconedMenuItem(null, i18n("version.manage.clean"), FXUtils.withJFXPopupClosing(control::clearJunkFiles, managementPopup)).addTooltip(i18n("version.manage.clean.tooltip"))
|
||||||
|
);
|
||||||
|
|
||||||
|
JFXButton testGameButton = new JFXButton();
|
||||||
|
FXUtils.setLimitWidth(testGameButton, 40);
|
||||||
|
FXUtils.setLimitHeight(testGameButton, 40);
|
||||||
|
testGameButton.setGraphic(SVG.launch(Theme.whiteFillBinding(), 20, 20));
|
||||||
|
testGameButton.getStyleClass().add("jfx-decorator-button");
|
||||||
|
testGameButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||||
|
testGameButton.setOnAction(event -> control.testGame());
|
||||||
|
FXUtils.installFastTooltip(testGameButton, i18n("version.launch.test"));
|
||||||
|
|
||||||
|
JFXButton browseMenuButton = new JFXButton();
|
||||||
|
FXUtils.setLimitWidth(browseMenuButton, 40);
|
||||||
|
FXUtils.setLimitHeight(browseMenuButton, 40);
|
||||||
|
browseMenuButton.setGraphic(SVG.folderOpen(Theme.whiteFillBinding(), 20, 20));
|
||||||
|
browseMenuButton.getStyleClass().add("jfx-decorator-button");
|
||||||
|
browseMenuButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||||
|
browseMenuButton.setOnAction(event -> browsePopup.show(browseMenuButton, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, browseMenuButton.getHeight()));
|
||||||
|
FXUtils.installFastTooltip(browseMenuButton, i18n("settings.game.exploration"));
|
||||||
|
|
||||||
|
JFXButton managementMenuButton = new JFXButton();
|
||||||
|
FXUtils.setLimitWidth(managementMenuButton, 40);
|
||||||
|
FXUtils.setLimitHeight(managementMenuButton, 40);;
|
||||||
|
managementMenuButton.setGraphic(SVG.wrench(Theme.whiteFillBinding(), 20, 20));
|
||||||
|
managementMenuButton.getStyleClass().add("jfx-decorator-button");
|
||||||
|
managementMenuButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||||
|
managementMenuButton.setOnAction(event -> managementPopup.show(managementMenuButton, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, managementMenuButton.getHeight()));
|
||||||
|
FXUtils.installFastTooltip(managementMenuButton, i18n("settings.game.management"));
|
||||||
|
|
||||||
|
toolBar.getChildren().setAll(testGameButton, browseMenuButton, managementMenuButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
BorderPane titleBar = new BorderPane();
|
||||||
|
titleBar.setLeft(tabPane);
|
||||||
|
titleBar.setRight(toolBar);
|
||||||
|
control.state.set(new State(i18n("version.manage.manage"), titleBar, true, false, true));
|
||||||
|
|
||||||
|
root.setCenter(control.transitionPane);
|
||||||
|
|
||||||
|
spinnerPane.loadingProperty().bind(control.loading);
|
||||||
|
spinnerPane.setContent(root);
|
||||||
|
getChildren().setAll(spinnerPane);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ import com.jfoenix.controls.JFXToggleButton;
|
|||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.InvalidationListener;
|
import javafx.beans.InvalidationListener;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.ReadOnlyStringProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.ScrollPane;
|
import javafx.scene.control.ScrollPane;
|
||||||
@@ -63,7 +63,7 @@ import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
|||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
|
|
||||||
public final class VersionSettingsPage extends StackPane implements DecoratorPage {
|
public final class VersionSettingsPage extends StackPane implements DecoratorPage {
|
||||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper();
|
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(new State("", null, false, false, false));
|
||||||
|
|
||||||
private VersionSetting lastVersionSetting = null;
|
private VersionSetting lastVersionSetting = null;
|
||||||
private Profile profile;
|
private Profile profile;
|
||||||
@@ -163,7 +163,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
|||||||
rootPane.getChildren().remove(iconPickerItemWrapper);
|
rootPane.getChildren().remove(iconPickerItemWrapper);
|
||||||
rootPane.getChildren().remove(settingsTypePane);
|
rootPane.getChildren().remove(settingsTypePane);
|
||||||
chkEnableSpecificSettings.setSelected(true);
|
chkEnableSpecificSettings.setSelected(true);
|
||||||
title.set(Profiles.getProfileDisplayName(profile) + " - " + i18n("settings.type.global.manage"));
|
state.set(State.fromTitle(Profiles.getProfileDisplayName(profile) + " - " + i18n("settings.type.global.manage")));
|
||||||
}
|
}
|
||||||
|
|
||||||
VersionSetting versionSetting = profile.getVersionSetting(versionId);
|
VersionSetting versionSetting = profile.getVersionSetting(versionId);
|
||||||
@@ -328,7 +328,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReadOnlyStringProperty titleProperty() {
|
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||||
return title;
|
return state.getReadOnlyProperty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,20 @@
|
|||||||
.root {
|
.root {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scroll-bar {
|
||||||
|
-fx-skin: "org.jackhuang.hmcl.ui.construct.FloatScrollBarSkin";
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-bar .track {
|
||||||
|
-fx-fill: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-bar .thumb {
|
||||||
|
-fx-fill: rgba(255, 255, 255, 0.5);
|
||||||
|
-fx-arc-width: 5px;
|
||||||
|
-fx-arc-height: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.disabled Label {
|
.disabled Label {
|
||||||
-fx-text-fill: rgba(0, 0, 0, 0.5);
|
-fx-text-fill: rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
@@ -353,7 +367,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.jfx-tool-bar HBox {
|
.jfx-tool-bar HBox {
|
||||||
-fx-alignment: center;
|
-fx-alignment: center-left;
|
||||||
-fx-padding: 0.0 5.0;
|
-fx-padding: 0.0 5.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1024,6 +1038,11 @@
|
|||||||
-fx-font-size: 14;
|
-fx-font-size: 14;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.jfx-decorator-tab .tab-label {
|
||||||
|
-fx-text-fill: -fx-base-text-fill;
|
||||||
|
-fx-font-size: 14;
|
||||||
|
}
|
||||||
|
|
||||||
.resize-border {
|
.resize-border {
|
||||||
-fx-border-color: -fx-base-color;
|
-fx-border-color: -fx-base-color;
|
||||||
-fx-border-width: 0 2 2 2;
|
-fx-border-width: 0 2 2 2;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -58,6 +58,20 @@ public final class Lang {
|
|||||||
return Collections.unmodifiableList(Arrays.asList(elements));
|
return Collections.unmodifiableList(Arrays.asList(elements));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T extends Comparable<T>> T clamp(T min, T val, T max) {
|
||||||
|
if (val.compareTo(min) < 0) return min;
|
||||||
|
else if (val.compareTo(max) > 0) return max;
|
||||||
|
else return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double clamp(double min, double val, double max) {
|
||||||
|
return Math.max(min, Math.min(val, max));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int clamp(int min, int val, int max) {
|
||||||
|
return Math.max(min, Math.min(val, max));
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean test(ExceptionalRunnable<?> r) {
|
public static boolean test(ExceptionalRunnable<?> r) {
|
||||||
try {
|
try {
|
||||||
r.run();
|
r.run();
|
||||||
|
|||||||
Reference in New Issue
Block a user