Merge branch 'gui' into javafx
This commit is contained in:
@@ -23,58 +23,36 @@ import javafx.scene.layout.Region;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
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.Profiles;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
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.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.construct.InputDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
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.decorator.DecoratorController;
|
||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
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.main.RootPage;
|
||||
import org.jackhuang.hmcl.ui.versions.VersionPage;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.FutureCallback;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
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.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.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
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 Controllers {
|
||||
|
||||
private static Scene scene;
|
||||
private static Stage stage;
|
||||
private static MainPage mainPage = null;
|
||||
private static SettingsPage settingsPage = null;
|
||||
private static VersionPage versionPage = null;
|
||||
private static GameList gameListPage = null;
|
||||
private static AccountList accountListPage = null;
|
||||
private static ProfileList profileListPage = null;
|
||||
private static AuthlibInjectorServersPage serversPage = null;
|
||||
private static LeftPaneController leftPaneController;
|
||||
private static RootPage rootPage;
|
||||
private static DecoratorController decorator;
|
||||
|
||||
public static Scene getScene() {
|
||||
@@ -85,47 +63,6 @@ public final class Controllers {
|
||||
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
|
||||
public static VersionPage getVersionPage() {
|
||||
if (versionPage == null)
|
||||
@@ -133,6 +70,13 @@ public final class Controllers {
|
||||
return versionPage;
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static RootPage getRootPage() {
|
||||
if (rootPage == null)
|
||||
rootPage = new RootPage();
|
||||
return rootPage;
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static AuthlibInjectorServersPage getServersPage() {
|
||||
if (serversPage == null)
|
||||
@@ -145,51 +89,6 @@ public final class Controllers {
|
||||
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) {
|
||||
Logging.LOG.info("Start initializing application");
|
||||
@@ -198,9 +97,7 @@ public final class Controllers {
|
||||
|
||||
stage.setOnCloseRequest(e -> Launcher.stopApplication());
|
||||
|
||||
decorator = new DecoratorController(stage, getMainPage());
|
||||
leftPaneController = new LeftPaneController();
|
||||
decorator.getDecorator().drawerProperty().setAll(leftPaneController);
|
||||
decorator = new DecoratorController(stage, getRootPage());
|
||||
|
||||
if (config().getCommonDirType() == EnumCommonDirectory.CUSTOM &&
|
||||
!FileUtils.canCreateDirectory(config().getCommonDirectory())) {
|
||||
@@ -261,7 +158,7 @@ public final class Controllers {
|
||||
}
|
||||
|
||||
public static void navigate(Node node) {
|
||||
decorator.getNavigator().navigate(node);
|
||||
decorator.getNavigator().navigate(node, ContainerAnimations.FADE.getAnimationProducer());
|
||||
}
|
||||
|
||||
public static boolean isStopped() {
|
||||
@@ -269,15 +166,11 @@ public final class Controllers {
|
||||
}
|
||||
|
||||
public static void shutdown() {
|
||||
mainPage = null;
|
||||
settingsPage = null;
|
||||
rootPage = null;
|
||||
versionPage = null;
|
||||
serversPage = null;
|
||||
decorator = null;
|
||||
stage = 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.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.ResourceNotFoundError;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
@@ -100,8 +101,10 @@ public final class FXUtils {
|
||||
value.addListener((a, b, c) -> consumer.accept(c));
|
||||
}
|
||||
|
||||
public static <T> void onWeakChange(ObservableValue<T> value, Consumer<T> consumer) {
|
||||
value.addListener(new WeakChangeListener<>((a, b, c) -> consumer.accept(c)));
|
||||
public static <T> WeakChangeListener<T> onWeakChange(ObservableValue<T> value, Consumer<T> consumer) {
|
||||
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) {
|
||||
@@ -109,9 +112,9 @@ public final class FXUtils {
|
||||
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());
|
||||
onWeakChange(value, consumer);
|
||||
return onWeakChange(value, consumer);
|
||||
}
|
||||
|
||||
public static void runLaterIf(BooleanSupplier condition, Runnable runnable) {
|
||||
@@ -201,19 +204,19 @@ public final class FXUtils {
|
||||
return field.getProperties().containsKey("FXUtils.validation");
|
||||
}
|
||||
|
||||
public static void setOverflowHidden(Region region, boolean hidden) {
|
||||
if (hidden) {
|
||||
Rectangle rectangle = new Rectangle();
|
||||
rectangle.widthProperty().bind(region.widthProperty());
|
||||
rectangle.heightProperty().bind(region.heightProperty());
|
||||
region.setClip(rectangle);
|
||||
} else {
|
||||
region.setClip(null);
|
||||
}
|
||||
public static Rectangle setOverflowHidden(Region region) {
|
||||
Rectangle rectangle = new Rectangle();
|
||||
rectangle.widthProperty().bind(region.widthProperty());
|
||||
rectangle.heightProperty().bind(region.heightProperty());
|
||||
region.setClip(rectangle);
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
public static boolean getOverflowHidden(Region region) {
|
||||
return region.getClip() != null;
|
||||
public static Rectangle setOverflowHidden(Region region, double arc) {
|
||||
Rectangle rectangle = setOverflowHidden(region);
|
||||
rectangle.setArcWidth(arc);
|
||||
rectangle.setArcHeight(arc);
|
||||
return rectangle;
|
||||
}
|
||||
|
||||
public static void setLimitWidth(Region region, double width) {
|
||||
@@ -452,7 +455,7 @@ public final class FXUtils {
|
||||
derivatives[i] += derivatives[i - 1];
|
||||
double dy = derivatives[derivatives.length - 1];
|
||||
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)
|
||||
timeline.stop();
|
||||
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) {
|
||||
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();
|
||||
|
||||
{
|
||||
List<Node> toolbarButtons = initializeToolbar(skinnable);
|
||||
if (!toolbarButtons.isEmpty()) {
|
||||
HBox toolbar = new HBox();
|
||||
toolbar.getStyleClass().add("jfx-tool-bar-second");
|
||||
JFXDepthManager.setDepth(toolbar, 1);
|
||||
toolbar.setPickOnBounds(false);
|
||||
toolbar.getChildren().setAll(initializeToolbar(skinnable));
|
||||
toolbar.getChildren().setAll(toolbarButtons);
|
||||
root.setTop(toolbar);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,23 +15,6 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* 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;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
|
||||
@@ -70,9 +70,9 @@ public class AccountAdvancedListItem extends AdvancedListItem {
|
||||
ObservableList<Account> accounts = Accounts.getAccounts();
|
||||
int currentIndex = accounts.indexOf(account.get());
|
||||
if (event.getDeltaY() > 0) { // up
|
||||
currentIndex += 1;
|
||||
currentIndex--;
|
||||
} else { // down
|
||||
currentIndex -= 1;
|
||||
currentIndex++;
|
||||
}
|
||||
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.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
|
||||
|
||||
public class AccountList extends ListPage<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 ObjectProperty<Account> selectedAccount;
|
||||
|
||||
@@ -51,7 +52,7 @@ public class AccountList extends ListPage<AccountListItem> implements DecoratorP
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyStringProperty titleProperty() {
|
||||
return title.getReadOnlyProperty();
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state.getReadOnlyProperty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ package org.jackhuang.hmcl.ui.account;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialogLayout;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
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.Task;
|
||||
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.DialogCloseEvent;
|
||||
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 {
|
||||
|
||||
@FXML private StackPane addServerContainer;
|
||||
@FXML private TransitionPane root;
|
||||
@FXML private Label lblServerUrl;
|
||||
@FXML private Label lblServerName;
|
||||
@FXML private Label lblCreationWarning;
|
||||
@@ -55,8 +54,6 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa
|
||||
@FXML private SpinnerPane nextPane;
|
||||
@FXML private JFXButton btnAddNext;
|
||||
|
||||
private TransitionHandler transitionHandler;
|
||||
|
||||
private AuthlibInjectorServer serverBeingAdded;
|
||||
|
||||
public AddAuthlibInjectorServerPane(String url) {
|
||||
@@ -67,8 +64,7 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa
|
||||
|
||||
public AddAuthlibInjectorServerPane() {
|
||||
loadFXML(this, "/assets/fxml/authlib-injector-server-add.fxml");
|
||||
transitionHandler = new TransitionHandler(addServerContainer);
|
||||
transitionHandler.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer());
|
||||
root.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer());
|
||||
|
||||
btnAddNext.disableProperty().bind(txtServerUrl.textProperty().isEmpty());
|
||||
nextPane.hideSpinner();
|
||||
@@ -116,7 +112,7 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa
|
||||
|
||||
lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverBeingAdded.getUrl()).getProtocol()));
|
||||
|
||||
transitionHandler.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer());
|
||||
root.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer());
|
||||
} else {
|
||||
LOG.log(Level.WARNING, "Failed to resolve auth server: " + url, exception);
|
||||
lblCreationWarning.setText(resolveFetchExceptionMessage(exception));
|
||||
@@ -127,7 +123,7 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa
|
||||
|
||||
@FXML
|
||||
private void onAddPrev() {
|
||||
transitionHandler.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer());
|
||||
root.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer());
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
@@ -50,16 +49,8 @@ public class AuthlibInjectorServersPage extends ListPage<AuthlibInjectorServerIt
|
||||
Controllers.dialog(new AddAuthlibInjectorServerPane());
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyStringProperty titleProperty() {
|
||||
return title.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
public ReadOnlyObjectWrapper<State> stateProperty() {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.animation;
|
||||
|
||||
import javafx.animation.KeyFrame;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -25,4 +26,6 @@ public interface AnimationProducer {
|
||||
void init(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.KeyValue;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -41,6 +43,7 @@ public enum ContainerAnimations {
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
}, c -> Collections.emptyList()),
|
||||
|
||||
/**
|
||||
* A fade between the old and new view
|
||||
*/
|
||||
@@ -62,6 +65,36 @@ public enum ContainerAnimations {
|
||||
new KeyFrame(c.getDuration(),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0, 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
|
||||
*/
|
||||
@@ -143,6 +176,7 @@ public enum ContainerAnimations {
|
||||
new KeyValue(c.getPreviousNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH))));
|
||||
|
||||
private final AnimationProducer animationProducer;
|
||||
private ContainerAnimations opposite;
|
||||
|
||||
ContainerAnimations(Consumer<AnimationHandler> init, Function<AnimationHandler, List<KeyFrame>> animationProducer) {
|
||||
this.animationProducer = new AnimationProducer() {
|
||||
@@ -155,10 +189,30 @@ public enum ContainerAnimations {
|
||||
public List<KeyFrame> animate(AnimationHandler handler) {
|
||||
return animationProducer.apply(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable AnimationProducer opposite() {
|
||||
return opposite != null ? opposite.getAnimationProducer() : null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public AnimationProducer getAnimationProducer() {
|
||||
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.application.Platform;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
public final class TransitionHandler implements AnimationHandler {
|
||||
private final StackPane view;
|
||||
public class TransitionPane extends StackPane implements AnimationHandler {
|
||||
private Timeline animation;
|
||||
private Duration duration;
|
||||
private Node previousNode, currentNode;
|
||||
|
||||
/**
|
||||
* @param view A stack pane that contains another control that is {@link Parent}
|
||||
*/
|
||||
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);
|
||||
{
|
||||
currentNode = getChildren().stream().findFirst().orElse(null);
|
||||
FXUtils.setOverflowHidden(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,7 +47,7 @@ public final class TransitionHandler implements AnimationHandler {
|
||||
|
||||
@Override
|
||||
public StackPane getCurrentRoot() {
|
||||
return view;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -86,8 +75,8 @@ public final class TransitionHandler implements AnimationHandler {
|
||||
Timeline nowAnimation = new Timeline();
|
||||
nowAnimation.getKeyFrames().addAll(transition.animate(this));
|
||||
nowAnimation.getKeyFrames().add(new KeyFrame(duration, e -> {
|
||||
view.setMouseTransparent(false);
|
||||
view.getChildren().remove(previousNode);
|
||||
setMouseTransparent(false);
|
||||
getChildren().remove(previousNode);
|
||||
}));
|
||||
nowAnimation.play();
|
||||
animation = nowAnimation;
|
||||
@@ -95,7 +84,7 @@ public final class TransitionHandler implements AnimationHandler {
|
||||
}
|
||||
|
||||
private void updateContent(Node newView) {
|
||||
if (view.getWidth() > 0 && view.getHeight() > 0) {
|
||||
if (getWidth() > 0 && getHeight() > 0) {
|
||||
previousNode = currentNode;
|
||||
if (previousNode == null)
|
||||
previousNode = EMPTY_PANE;
|
||||
@@ -105,11 +94,11 @@ public final class TransitionHandler implements AnimationHandler {
|
||||
if (previousNode == newView)
|
||||
previousNode = EMPTY_PANE;
|
||||
|
||||
view.setMouseTransparent(true);
|
||||
setMouseTransparent(true);
|
||||
|
||||
currentNode = newView;
|
||||
|
||||
view.getChildren().setAll(previousNode, currentNode);
|
||||
getChildren().setAll(previousNode, currentNode);
|
||||
}
|
||||
|
||||
private final StackPane EMPTY_PANE = new StackPane();
|
||||
@@ -126,7 +126,7 @@ class ComponentListCell extends StackPane {
|
||||
VBox container = new VBox();
|
||||
container.setPadding(new Insets(8, 0, 0, 0));
|
||||
FXUtils.setLimitHeight(container, 0);
|
||||
FXUtils.setOverflowHidden(container, true);
|
||||
FXUtils.setOverflowHidden(container);
|
||||
container.getChildren().setAll(content);
|
||||
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.scene.Node;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
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.TransitionHandler;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Stack;
|
||||
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 final Stack<Node> stack = new Stack<>();
|
||||
private final TransitionHandler animationHandler = new TransitionHandler(this);
|
||||
private boolean initialized = false;
|
||||
|
||||
public void init(Node init) {
|
||||
@@ -50,7 +49,7 @@ public class Navigator extends StackPane {
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
public void navigate(Node node) {
|
||||
public void navigate(Node node, AnimationProducer animationProducer) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
if (!initialized)
|
||||
@@ -68,10 +67,10 @@ public class Navigator extends StackPane {
|
||||
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);
|
||||
fireEvent(navigated);
|
||||
node.fireEvent(navigated);
|
||||
|
||||
EventHandler<PageCloseEvent> handler = event -> close(node);
|
||||
@@ -110,10 +109,14 @@ public class Navigator extends StackPane {
|
||||
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);
|
||||
fireEvent(navigated);
|
||||
node.fireEvent(navigated);
|
||||
|
||||
Optional.ofNullable(from.getProperties().get(PROPERTY_DIALOG_CLOSE_HANDLER))
|
||||
@@ -128,12 +131,16 @@ public class Navigator extends StackPane {
|
||||
return stack.size() > 1;
|
||||
}
|
||||
|
||||
private void setContent(Node content) {
|
||||
animationHandler.setContent(content, ContainerAnimations.FADE.getAnimationProducer());
|
||||
public int size() {
|
||||
return stack.size();
|
||||
}
|
||||
|
||||
public void setContent(Node content, AnimationProducer animationProducer) {
|
||||
super.setContent(content, animationProducer);
|
||||
|
||||
if (content instanceof Region) {
|
||||
((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> NAVIGATING = new EventType<>("NAVIGATING");
|
||||
|
||||
private final Navigator source;
|
||||
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);
|
||||
|
||||
this.source = source;
|
||||
this.node = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Navigator getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public Node getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
@@ -18,38 +18,28 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXSpinner;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.DefaultProperty;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
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.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.TransitionHandler;
|
||||
|
||||
@DefaultProperty("content")
|
||||
public class SpinnerPane extends StackPane {
|
||||
private final TransitionHandler transitionHandler = new TransitionHandler(this);
|
||||
private final JFXSpinner spinner = new JFXSpinner();
|
||||
private final StackPane contentPane = new StackPane();
|
||||
public class SpinnerPane extends Control {
|
||||
private final ObjectProperty<Node> content = new SimpleObjectProperty<>(this, "content");
|
||||
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));
|
||||
}
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty(this, "loading");
|
||||
|
||||
public void showSpinner() {
|
||||
setLoading(true);
|
||||
@@ -82,4 +72,72 @@ public class SpinnerPane extends StackPane {
|
||||
public void setLoading(boolean 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> container = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
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 ObjectProperty<Runnable> onCloseButtonAction = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<EventHandler<ActionEvent>> onCloseNavButtonAction = new SimpleObjectProperty<>();
|
||||
@@ -90,16 +90,16 @@ public class Decorator extends Control {
|
||||
this.content.set(content);
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
public DecoratorPage.State getState() {
|
||||
return state.get();
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
public ObjectProperty<DecoratorPage.State> stateProperty() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
public void setState(DecoratorPage.State state) {
|
||||
this.state.set(state);
|
||||
}
|
||||
|
||||
public String getDrawerTitle() {
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
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.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||
@@ -70,14 +71,11 @@ public class DecoratorController {
|
||||
private final Decorator decorator;
|
||||
private final ImageView welcomeView;
|
||||
private final Navigator navigator;
|
||||
private final Node mainPage;
|
||||
|
||||
private JFXDialog dialog;
|
||||
private StackContainerPane dialogPane;
|
||||
|
||||
public DecoratorController(Stage stage, Node mainPage) {
|
||||
this.mainPage = mainPage;
|
||||
|
||||
decorator = new Decorator(stage);
|
||||
decorator.setOnCloseButtonAction(Launcher::stopApplication);
|
||||
|
||||
@@ -220,8 +218,8 @@ public class DecoratorController {
|
||||
if (navigator.getCurrentPage() instanceof DecoratorPage) {
|
||||
DecoratorPage page = (DecoratorPage) navigator.getCurrentPage();
|
||||
|
||||
if (page.canForceToClose()) {
|
||||
page.onForceToClose();
|
||||
if (page.isPageCloseable()) {
|
||||
page.closePage();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -232,7 +230,7 @@ public class DecoratorController {
|
||||
if (navigator.getCurrentPage() instanceof DecoratorPage) {
|
||||
DecoratorPage page = (DecoratorPage) navigator.getCurrentPage();
|
||||
|
||||
if (page.onClose())
|
||||
if (page.back())
|
||||
navigator.close();
|
||||
} else {
|
||||
navigator.close();
|
||||
@@ -243,39 +241,45 @@ public class DecoratorController {
|
||||
if (navigator.getCurrentPage() instanceof Refreshable) {
|
||||
Refreshable refreshable = (Refreshable) navigator.getCurrentPage();
|
||||
|
||||
if (refreshable.canRefreshProperty().get())
|
||||
if (refreshable.refreshableProperty().get())
|
||||
refreshable.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private void onNavigating(Navigator.NavigationEvent event) {
|
||||
if (event.getSource() != this.navigator) return;
|
||||
Node from = event.getNode();
|
||||
|
||||
if (from instanceof DecoratorPage)
|
||||
((DecoratorPage) from).onClose();
|
||||
((DecoratorPage) from).back();
|
||||
}
|
||||
|
||||
private void onNavigated(Navigator.NavigationEvent event) {
|
||||
if (event.getSource() != this.navigator) return;
|
||||
Node to = event.getNode();
|
||||
|
||||
if (to instanceof Refreshable) {
|
||||
decorator.canRefreshProperty().bind(((Refreshable) to).canRefreshProperty());
|
||||
decorator.canRefreshProperty().bind(((Refreshable) to).refreshableProperty());
|
||||
} else {
|
||||
decorator.canRefreshProperty().unbind();
|
||||
decorator.canRefreshProperty().set(false);
|
||||
}
|
||||
|
||||
decorator.canCloseProperty().set(navigator.size() > 2);
|
||||
|
||||
if (to instanceof DecoratorPage) {
|
||||
decorator.drawerTitleProperty().bind(((DecoratorPage) to).titleProperty());
|
||||
decorator.showCloseAsHomeProperty().set(!((DecoratorPage) to).canForceToClose());
|
||||
decorator.showCloseAsHomeProperty().set(!((DecoratorPage) to).isPageCloseable());
|
||||
} else {
|
||||
decorator.drawerTitleProperty().unbind();
|
||||
decorator.drawerTitleProperty().set("");
|
||||
decorator.showCloseAsHomeProperty().set(true);
|
||||
}
|
||||
|
||||
decorator.canBackProperty().set(navigator.canGoBack());
|
||||
decorator.canCloseProperty().set(navigator.canGoBack());
|
||||
// state property should be updated at last.
|
||||
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) {
|
||||
Region region = (Region) to;
|
||||
@@ -359,7 +363,7 @@ public class DecoratorController {
|
||||
public void startWizard(WizardProvider wizardProvider, String category) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
getNavigator().navigate(new DecoratorWizardDisplayer(wizardProvider, category));
|
||||
getNavigator().navigate(new DecoratorWizardDisplayer(wizardProvider, category), ContainerAnimations.FADE.getAnimationProducer());
|
||||
}
|
||||
|
||||
// ==== 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;
|
||||
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
||||
|
||||
public interface DecoratorPage {
|
||||
ReadOnlyStringProperty titleProperty();
|
||||
public interface DecoratorPage extends Refreshable {
|
||||
ReadOnlyObjectProperty<State> stateProperty();
|
||||
|
||||
default boolean canForceToClose() {
|
||||
default boolean isPageCloseable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean onClose() {
|
||||
default boolean back() {
|
||||
return true;
|
||||
}
|
||||
|
||||
default void onForceToClose() {
|
||||
@Override
|
||||
default void refresh() {
|
||||
}
|
||||
|
||||
default void closePage() {
|
||||
}
|
||||
|
||||
default void onDecoratorPageNavigating(Navigator.NavigationEvent event) {
|
||||
((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.SkinBase;
|
||||
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.shape.Rectangle;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
@@ -45,6 +50,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
private final BorderPane titleContainer;
|
||||
private final StackPane contentPlaceHolder;
|
||||
private final Stage primaryStage;
|
||||
private final TransitionPane navBarPane;
|
||||
|
||||
private double xOffset, yOffset, newX, newY, initX, initY;
|
||||
private boolean allowMove, isDragging;
|
||||
@@ -72,65 +78,41 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
root.setMaxWidth(Region.USE_PREF_SIZE);
|
||||
root.setMinWidth(Region.USE_PREF_SIZE);
|
||||
|
||||
StackPane drawerWrapper = new StackPane();
|
||||
skinnable.setDrawerWrapper(drawerWrapper);
|
||||
drawerWrapper.getStyleClass().add("jfx-decorator-drawer");
|
||||
drawerWrapper.backgroundProperty().bind(skinnable.backgroundProperty());
|
||||
FXUtils.setOverflowHidden(drawerWrapper, true);
|
||||
// center node with a container node in bottom layer and a "welcome" layer at the top layer.
|
||||
StackPane container = new StackPane();
|
||||
skinnable.setDrawerWrapper(container);
|
||||
container.getStyleClass().add("jfx-decorator-drawer");
|
||||
container.backgroundProperty().bind(skinnable.backgroundProperty());
|
||||
FXUtils.setOverflowHidden(container);
|
||||
// bottom layer
|
||||
{
|
||||
BorderPane drawer = new BorderPane();
|
||||
contentPlaceHolder = new StackPane();
|
||||
contentPlaceHolder.getStyleClass().add("jfx-decorator-content-container");
|
||||
Bindings.bindContent(contentPlaceHolder.getChildren(), skinnable.contentProperty());
|
||||
|
||||
{
|
||||
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.getStyleClass().add("jfx-decorator-content-container");
|
||||
FXUtils.setOverflowHidden(contentPlaceHolder, true);
|
||||
Bindings.bindContent(contentPlaceHolder.getChildren(), skinnable.contentProperty());
|
||||
|
||||
drawer.setCenter(contentPlaceHolder);
|
||||
}
|
||||
|
||||
drawerWrapper.getChildren().add(drawer);
|
||||
container.getChildren().add(contentPlaceHolder);
|
||||
}
|
||||
|
||||
// top layer for welcome and hint
|
||||
{
|
||||
StackPane container = new StackPane();
|
||||
Bindings.bindContent(container.getChildren(), skinnable.containerProperty());
|
||||
StackPane floatLayer = new StackPane();
|
||||
Bindings.bindContent(floatLayer.getChildren(), skinnable.containerProperty());
|
||||
ListChangeListener<Node> listener = c -> {
|
||||
if (skinnable.getContainer().isEmpty()) {
|
||||
container.setMouseTransparent(true);
|
||||
container.setVisible(false);
|
||||
floatLayer.setMouseTransparent(true);
|
||||
floatLayer.setVisible(false);
|
||||
} else {
|
||||
container.setMouseTransparent(false);
|
||||
container.setVisible(true);
|
||||
floatLayer.setMouseTransparent(false);
|
||||
floatLayer.setVisible(true);
|
||||
}
|
||||
};
|
||||
skinnable.containerProperty().addListener(listener);
|
||||
listener.onChanged(null);
|
||||
|
||||
drawerWrapper.getChildren().add(container);
|
||||
container.getChildren().add(floatLayer);
|
||||
}
|
||||
|
||||
root.setCenter(drawerWrapper);
|
||||
root.setCenter(container);
|
||||
|
||||
titleContainer = new BorderPane();
|
||||
titleContainer.setOnMouseReleased(this::onMouseReleased);
|
||||
@@ -149,7 +131,17 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
rectangle.heightProperty().bind(titleContainer.heightProperty().add(100));
|
||||
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();
|
||||
buttonsContainer.setStyle("-fx-background-color: transparent;");
|
||||
@@ -177,7 +169,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
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();
|
||||
{
|
||||
HBox navLeft = new HBox();
|
||||
@@ -189,7 +181,7 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
backNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
backNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||
backNavButton.onActionProperty().bind(skinnable.onBackNavButtonActionProperty());
|
||||
backNavButton.visibleProperty().bind(skinnable.canBackProperty());
|
||||
backNavButton.visibleProperty().set(canBack);
|
||||
|
||||
JFXButton closeNavButton = new JFXButton();
|
||||
closeNavButton.setGraphic(SVG.close(Theme.foregroundFillBinding(), -1, -1));
|
||||
@@ -197,48 +189,47 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
closeNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||
closeNavButton.onActionProperty().bind(skinnable.onCloseNavButtonActionProperty());
|
||||
|
||||
FXUtils.onChangeAndOperate(skinnable.canBackProperty(), (newValue) -> {
|
||||
navLeft.getChildren().remove(backNavButton);
|
||||
if (newValue) navLeft.getChildren().add(0, backNavButton);
|
||||
});
|
||||
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));
|
||||
else
|
||||
closeNavButton.setGraphic(SVG.close(Theme.foregroundFillBinding(), -1, -1));
|
||||
});
|
||||
if (canBack) navLeft.getChildren().add(backNavButton);
|
||||
if (canClose) navLeft.getChildren().add(closeNavButton);
|
||||
if (showCloseAsHome)
|
||||
closeNavButton.setGraphic(SVG.home(Theme.foregroundFillBinding(), -1, -1));
|
||||
else
|
||||
closeNavButton.setGraphic(SVG.close(Theme.foregroundFillBinding(), -1, -1));
|
||||
}
|
||||
navBar.setLeft(navLeft);
|
||||
|
||||
VBox navCenter = new VBox();
|
||||
navCenter.setAlignment(Pos.CENTER_LEFT);
|
||||
Label titleLabel = new Label();
|
||||
titleLabel.getStyleClass().add("jfx-decorator-title");
|
||||
titleLabel.textProperty().bind(skinnable.drawerTitleProperty());
|
||||
navCenter.getChildren().setAll(titleLabel);
|
||||
navBar.setCenter(navCenter);
|
||||
BorderPane center = new BorderPane();
|
||||
if (title != null) {
|
||||
Label titleLabel = new Label();
|
||||
titleLabel.getStyleClass().add("jfx-decorator-title");
|
||||
titleLabel.setText(title);
|
||||
center.setLeft(titleLabel);
|
||||
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);
|
||||
|
||||
HBox navRight = new HBox();
|
||||
navRight.setAlignment(Pos.CENTER_RIGHT);
|
||||
JFXButton refreshNavButton = new JFXButton();
|
||||
refreshNavButton.setGraphic(SVG.refresh(Theme.foregroundFillBinding(), -1, -1));
|
||||
refreshNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
refreshNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||
refreshNavButton.onActionProperty().bind(skinnable.onRefreshNavButtonActionProperty());
|
||||
refreshNavButton.visibleProperty().bind(skinnable.canRefreshProperty());
|
||||
if (canRefresh) {
|
||||
HBox navRight = new HBox();
|
||||
navRight.setAlignment(Pos.CENTER_RIGHT);
|
||||
JFXButton refreshNavButton = new JFXButton();
|
||||
refreshNavButton.setGraphic(SVG.refresh(Theme.foregroundFillBinding(), -1, -1));
|
||||
refreshNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
refreshNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||
refreshNavButton.onActionProperty().bind(skinnable.onRefreshNavButtonActionProperty());
|
||||
|
||||
Rectangle separator = new Rectangle();
|
||||
separator.visibleProperty().bind(refreshNavButton.visibleProperty());
|
||||
separator.heightProperty().bind(navBar.heightProperty());
|
||||
separator.setFill(Color.GRAY);
|
||||
Rectangle separator = new Rectangle();
|
||||
separator.visibleProperty().bind(refreshNavButton.visibleProperty());
|
||||
separator.heightProperty().bind(navBar.heightProperty());
|
||||
separator.setFill(Color.GRAY);
|
||||
|
||||
navRight.getChildren().setAll(refreshNavButton, separator);
|
||||
navBar.setRight(navRight);
|
||||
navRight.getChildren().setAll(refreshNavButton, separator);
|
||||
navBar.setRight(navRight);
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
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.layout.StackPane;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.wizard.*;
|
||||
@@ -31,18 +26,12 @@ import org.jackhuang.hmcl.ui.wizard.*;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
public class DecoratorWizardDisplayer extends StackPane implements TaskExecutorDialogWizardDisplayer, Refreshable, DecoratorPage {
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
private final BooleanProperty canRefresh = new SimpleBooleanProperty();
|
||||
|
||||
private final TransitionHandler transitionHandler = new TransitionHandler(this);
|
||||
public class DecoratorWizardDisplayer extends DecoratorTransitionPage implements TaskExecutorDialogWizardDisplayer {
|
||||
private final WizardController wizardController = new WizardController(this);
|
||||
private final Queue<Object> cancelQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private final String category;
|
||||
|
||||
private Node nowPage;
|
||||
|
||||
public DecoratorWizardDisplayer(WizardProvider provider) {
|
||||
this(provider, null);
|
||||
}
|
||||
@@ -56,16 +45,6 @@ public class DecoratorWizardDisplayer extends StackPane implements TaskExecutorD
|
||||
addEventHandler(Navigator.NavigationEvent.NAVIGATING, this::onDecoratorPageNavigating);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BooleanProperty canRefreshProperty() {
|
||||
return canRefresh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WizardController getWizardController() {
|
||||
return wizardController;
|
||||
@@ -88,30 +67,30 @@ public class DecoratorWizardDisplayer extends StackPane implements TaskExecutorD
|
||||
|
||||
@Override
|
||||
public void navigateTo(Node page, Navigation.NavigationDirection nav) {
|
||||
nowPage = page;
|
||||
|
||||
transitionHandler.setContent(page, nav.getAnimation().getAnimationProducer());
|
||||
|
||||
canRefresh.set(page instanceof Refreshable);
|
||||
navigate(page, nav.getAnimation().getAnimationProducer());
|
||||
|
||||
String prefix = category == null ? "" : category + " - ";
|
||||
|
||||
String title;
|
||||
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
|
||||
public boolean canForceToClose() {
|
||||
public boolean isPageCloseable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onForceToClose() {
|
||||
public void closePage() {
|
||||
wizardController.onCancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onClose() {
|
||||
public boolean back() {
|
||||
if (wizardController.canPrev()) {
|
||||
wizardController.onPrev(true);
|
||||
return false;
|
||||
@@ -121,6 +100,20 @@ public class DecoratorWizardDisplayer extends StackPane implements TaskExecutorD
|
||||
|
||||
@Override
|
||||
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.ui.FXUtils;
|
||||
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.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
||||
@@ -72,7 +72,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
@FXML
|
||||
private StackPane emptyPane;
|
||||
@FXML
|
||||
private StackPane root;
|
||||
private TransitionPane root;
|
||||
@FXML
|
||||
private JFXCheckBox chkRelease;
|
||||
@FXML
|
||||
@@ -84,7 +84,6 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
@FXML
|
||||
private VBox centrePane;
|
||||
|
||||
private final TransitionHandler transitionHandler;
|
||||
private final VersionList<?> versionList;
|
||||
private TaskExecutor executor;
|
||||
|
||||
@@ -97,8 +96,6 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/download/versions.fxml");
|
||||
|
||||
transitionHandler = new TransitionHandler(root);
|
||||
|
||||
if (versionList.hasType()) {
|
||||
centrePane.getChildren().setAll(checkPane, list);
|
||||
} else
|
||||
@@ -187,14 +184,14 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
transitionHandler.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer());
|
||||
root.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer());
|
||||
executor = versionList.refreshAsync(gameVersion).whenComplete(exception -> {
|
||||
if (exception == null) {
|
||||
List<RemoteVersion> items = loadVersions();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
if (versionList.getVersions(gameVersion).isEmpty()) {
|
||||
transitionHandler.setContent(emptyPane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
root.setContent(emptyPane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
} else {
|
||||
if (items.isEmpty()) {
|
||||
chkRelease.setSelected(true);
|
||||
@@ -203,13 +200,13 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
} else {
|
||||
list.getItems().setAll(items);
|
||||
}
|
||||
transitionHandler.setContent(centrePane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
root.setContent(centrePane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
LOG.log(Level.WARNING, "Failed to fetch versions list", exception);
|
||||
Platform.runLater(() -> {
|
||||
transitionHandler.setContent(failedPane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
root.setContent(failedPane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
});
|
||||
}
|
||||
}).executor().start();
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* 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.main;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
@@ -23,12 +23,7 @@ import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
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.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
@@ -41,22 +36,30 @@ import javafx.scene.layout.VBox;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
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.upgrade.RemoteVersion;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
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.util.i18n.I18n.i18n;
|
||||
|
||||
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 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 BooleanProperty showUpdate = new SimpleBooleanProperty(this, "showUpdate");
|
||||
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 JFXButton menuButton;
|
||||
@@ -112,6 +117,18 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
StackPane launchPane = new StackPane();
|
||||
launchPane.setMaxWidth(230);
|
||||
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);
|
||||
{
|
||||
JFXButton launchButton = new JFXButton();
|
||||
@@ -131,7 +148,13 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
launchLabel.setStyle("-fx-font-size: 16px;");
|
||||
Label currentLabel = new Label();
|
||||
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);
|
||||
|
||||
launchButton.setGraphic(graphic);
|
||||
@@ -168,7 +191,12 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
menu.setMaxWidth(545);
|
||||
menu.setAlwaysShowingVBar(true);
|
||||
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) {
|
||||
@@ -208,17 +236,9 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
showUpdate.set(false);
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyStringProperty titleProperty() {
|
||||
return title.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
public ReadOnlyObjectWrapper<State> stateProperty() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public String getCurrentGame() {
|
||||
@@ -257,7 +277,9 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
this.latestVersion.set(latestVersion);
|
||||
}
|
||||
|
||||
public ObservableList<Node> getVersions() {
|
||||
return versions;
|
||||
public void initVersions(Profile profile, List<Version> 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
|
||||
* 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.effects.JFXDepthManager;
|
||||
@@ -24,13 +24,15 @@ import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.When;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
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.Navigator;
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
@@ -198,17 +200,9 @@ public final class SettingsPage extends SettingsView implements DecoratorPage {
|
||||
// ====
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyStringProperty titleProperty() {
|
||||
return title.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -15,7 +15,7 @@
|
||||
* 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.main;
|
||||
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.geometry.HPos;
|
||||
@@ -31,6 +31,8 @@ import javafx.scene.text.TextAlignment;
|
||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
||||
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.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;
|
||||
|
||||
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 ObjectProperty<Profile> selectedProfile;
|
||||
|
||||
@@ -52,7 +52,7 @@ public class ProfileList extends ListPage<ProfileListItem> implements DecoratorP
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyStringProperty titleProperty() {
|
||||
return title.getReadOnlyProperty();
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state.getReadOnlyProperty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
@@ -41,7 +41,7 @@ import java.util.Optional;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
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 Profile profile;
|
||||
|
||||
@@ -57,8 +57,7 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||
this.profile = profile;
|
||||
String profileDisplayName = Optional.ofNullable(profile).map(Profiles::getProfileDisplayName).orElse("");
|
||||
|
||||
title = new ReadOnlyStringWrapper(this, "title",
|
||||
profile == null ? i18n("profile.new") : i18n("profile") + " - " + profileDisplayName);
|
||||
state.set(State.fromTitle(profile == null ? i18n("profile.new") : i18n("profile") + " - " + profileDisplayName));
|
||||
location = new SimpleStringProperty(this, "location",
|
||||
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());
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyStringProperty titleProperty() {
|
||||
return title.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.stage.FileChooser;
|
||||
@@ -43,7 +43,7 @@ import java.util.logging.Level;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
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 Datapack datapack;
|
||||
|
||||
@@ -52,7 +52,7 @@ public class DatapackListPage extends ListPageBase<DatapackListPageSkin.Datapack
|
||||
public DatapackListPage(String worldName, Path 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.loadFromDir();
|
||||
@@ -86,8 +86,8 @@ public class DatapackListPage extends ListPageBase<DatapackListPageSkin.Datapack
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public void add() {
|
||||
|
||||
@@ -18,10 +18,11 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshingVersionsEvent;
|
||||
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.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.download.VanillaInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
@@ -123,24 +123,29 @@ public class GameList extends ListPageBase<GameListItem> implements DecoratorPag
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyStringProperty titleProperty() {
|
||||
return title.getReadOnlyProperty();
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
private class GameListSkin extends ToolbarListPageSkin<GameList> {
|
||||
|
||||
public GameListSkin() {
|
||||
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
|
||||
protected List<Node> initializeToolbar(GameList skinnable) {
|
||||
return Arrays.asList(
|
||||
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)
|
||||
);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
|
||||
right.getChildren().add(btnManage);
|
||||
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);
|
||||
|
||||
getChildren().setAll(root);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Skin;
|
||||
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.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
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.*;
|
||||
import org.jackhuang.hmcl.ui.download.InstallerWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.download.UpdateInstallerWizardProvider;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
@@ -43,6 +39,7 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
@@ -67,17 +64,17 @@ public class InstallerListPage extends ListPageBase<InstallerItem> {
|
||||
return new InstallerListPageSkin();
|
||||
}
|
||||
|
||||
public void loadVersion(Profile profile, String versionId) {
|
||||
public CompletableFuture<?> loadVersion(Profile profile, String versionId) {
|
||||
this.profile = profile;
|
||||
this.versionId = versionId;
|
||||
this.version = profile.getRepository().getVersion(versionId);
|
||||
this.gameVersion = null;
|
||||
|
||||
Task.supplyAsync(() -> {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse(null);
|
||||
|
||||
return LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(versionId));
|
||||
}).thenAcceptAsync(Schedulers.javafx(), analyzer -> {
|
||||
}).thenAcceptAsync(analyzer -> {
|
||||
Function<String, Consumer<InstallerItem>> removeAction = libraryId -> x -> {
|
||||
profile.getDependency().removeLibraryAsync(version, libraryId)
|
||||
.thenComposeAsync(profile.getRepository()::save)
|
||||
@@ -100,7 +97,7 @@ public class InstallerListPage extends ListPageBase<InstallerItem> {
|
||||
else
|
||||
itemsProperty().add(new InstallerItem(title, libraryVersion, null, action));
|
||||
}
|
||||
}).start();
|
||||
}, Platform::runLater);
|
||||
}
|
||||
|
||||
public void installOnline() {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXTabPane;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
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.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.ListPageBase;
|
||||
import org.jackhuang.hmcl.ui.construct.TabHeader;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Level;
|
||||
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> {
|
||||
private final BooleanProperty modded = new SimpleBooleanProperty(this, "modded", false);
|
||||
|
||||
private JFXTabPane parentTab;
|
||||
private TabHeader.Tab tab;
|
||||
private ModManager modManager;
|
||||
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 -> {
|
||||
mods.forEach(it -> {
|
||||
@@ -77,28 +81,34 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
loadMods(modManager);
|
||||
}
|
||||
|
||||
public void loadVersion(Profile profile, String id) {
|
||||
public CompletableFuture<?> loadVersion(Profile profile, String id) {
|
||||
libraryAnalyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(id));
|
||||
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;
|
||||
Task.supplyAsync(() -> {
|
||||
synchronized (ModListPage.this) {
|
||||
runInFX(() -> loadingProperty().set(true));
|
||||
modManager.refreshMods();
|
||||
return new LinkedList<>(modManager.getMods());
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
synchronized (ModListPage.this) {
|
||||
runInFX(() -> loadingProperty().set(true));
|
||||
modManager.refreshMods();
|
||||
return new LinkedList<>(modManager.getMods());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}).whenComplete(Schedulers.javafx(), (list, exception) -> {
|
||||
}).whenCompleteAsync((list, exception) -> {
|
||||
loadingProperty().set(false);
|
||||
if (exception == null)
|
||||
FXUtils.onWeakChangeAndOperate(parentTab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
if (newValue != null && newValue.getUserData() == ModListPage.this)
|
||||
getProperties().put(ModListPage.class, FXUtils.onWeakChangeAndOperate(tab.selectedProperty(), newValue -> {
|
||||
if (newValue)
|
||||
itemsProperty().setAll(list.stream().map(ModListPageSkin.ModInfoObject::new).collect(Collectors.toList()));
|
||||
});
|
||||
}).start();
|
||||
}));
|
||||
else
|
||||
getProperties().remove(ModListPage.class);
|
||||
}, Platform::runLater);
|
||||
}
|
||||
|
||||
public void add() {
|
||||
@@ -134,10 +144,6 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void setParentTab(JFXTabPane parentTab) {
|
||||
this.parentTab = parentTab;
|
||||
}
|
||||
|
||||
public void removeSelected(ObservableList<ModListPageSkin.ModInfoObject> selectedItems) {
|
||||
try {
|
||||
modManager.removeMods(selectedItems.stream()
|
||||
|
||||
@@ -18,111 +18,101 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import com.jfoenix.controls.JFXTabPane;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Control;
|
||||
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.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedMenuItem;
|
||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
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.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", null);
|
||||
|
||||
@FXML
|
||||
private VersionSettingsPage versionSettings;
|
||||
@FXML
|
||||
private Tab modTab;
|
||||
@FXML
|
||||
private ModListPage mod;
|
||||
@FXML
|
||||
private InstallerListPage installer;
|
||||
@FXML
|
||||
private WorldListPage world;
|
||||
@FXML
|
||||
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;
|
||||
public class VersionPage extends Control implements DecoratorPage {
|
||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty();
|
||||
private final JFXListView<String> listView = new JFXListView<>();
|
||||
private final TabHeader.Tab versionSettingsTab = new TabHeader.Tab("versionSettingsTab");
|
||||
private final VersionSettingsPage versionSettingsPage = new VersionSettingsPage();
|
||||
private final TabHeader.Tab modListTab = new TabHeader.Tab("modListTab");
|
||||
private final ModListPage modListPage = new ModListPage(modListTab);
|
||||
private final TabHeader.Tab installerListTab = new TabHeader.Tab("installerListTab");
|
||||
private final InstallerListPage installerListPage = new InstallerListPage();
|
||||
private final TabHeader.Tab worldListTab = new TabHeader.Tab("worldList");
|
||||
private final WorldListPage worldListPage = new WorldListPage();
|
||||
private final TransitionPane transitionPane = new TransitionPane();
|
||||
private final ObjectProperty<TabHeader.Tab> selectedTab = new SimpleObjectProperty<>();
|
||||
|
||||
private Profile profile;
|
||||
private String version;
|
||||
|
||||
{
|
||||
FXUtils.loadFXML(this, "/assets/fxml/version/version.fxml");
|
||||
Profiles.registerVersionsListener(this::loadVersions);
|
||||
|
||||
PopupMenu browseList = new PopupMenu();
|
||||
browsePopup = new JFXPopup(browseList);
|
||||
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))
|
||||
);
|
||||
listView.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> {
|
||||
loadVersion(newValue, profile);
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public void load(String id, Profile profile) {
|
||||
this.version = id;
|
||||
private void loadVersions(Profile profile) {
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
List<String> children = repository.getVersions().parallelStream()
|
||||
.filter(version -> !version.isHidden())
|
||||
.sorted(Comparator.comparing((Version version) -> version.getReleaseTime() == null ? new Date(0L) : version.getReleaseTime())
|
||||
.thenComparing(a -> VersionNumber.asVersion(a.getId())))
|
||||
.map(Version::getId)
|
||||
.collect(Collectors.toList());
|
||||
runInFX(() -> {
|
||||
if (profile == Profiles.getSelectedProfile()) {
|
||||
this.profile = profile;
|
||||
loading.set(false);
|
||||
listView.getItems().setAll(children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void loadVersion(String version, Profile profile) {
|
||||
listView.getSelectionModel().select(version);
|
||||
this.version = version;
|
||||
this.profile = profile;
|
||||
|
||||
title.set(i18n("version.manage.manage") + " - " + id);
|
||||
versionSettingsPage.loadVersion(profile, version);
|
||||
loading.set(true);
|
||||
|
||||
versionSettings.loadVersion(profile, id);
|
||||
mod.setParentTab(tabPane);
|
||||
modTab.setUserData(mod);
|
||||
mod.loadVersion(profile, id);
|
||||
installer.loadVersion(profile, id);
|
||||
world.loadVersion(profile, id);
|
||||
CompletableFuture.allOf(
|
||||
modListPage.loadVersion(profile, version),
|
||||
installerListPage.loadVersion(profile, version),
|
||||
worldListPage.loadVersion(profile, version))
|
||||
.whenCompleteAsync((result, exception) -> loading.set(false), Platform::runLater);
|
||||
}
|
||||
|
||||
private void onNavigated(Navigator.NavigationEvent event) {
|
||||
@@ -137,38 +127,164 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
return;
|
||||
}
|
||||
|
||||
load(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());
|
||||
loadVersion(this.version, this.profile);
|
||||
}
|
||||
|
||||
private void onBrowse(String sub) {
|
||||
FXUtils.openFolder(new File(profile.getRepository().getRunDirectory(version), sub));
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
private void redownloadAssetIndex() {
|
||||
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
|
||||
public ReadOnlyStringProperty titleProperty() {
|
||||
return title.getReadOnlyProperty();
|
||||
protected Skin createDefaultSkin() {
|
||||
return new Skin(this);
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
@Override
|
||||
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.beans.InvalidationListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
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;
|
||||
|
||||
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 Profile profile;
|
||||
@@ -163,7 +163,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
rootPane.getChildren().remove(iconPickerItemWrapper);
|
||||
rootPane.getChildren().remove(settingsTypePane);
|
||||
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);
|
||||
@@ -328,7 +328,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyStringProperty titleProperty() {
|
||||
return title;
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state.getReadOnlyProperty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ public class Versions {
|
||||
|
||||
private static boolean checkForLaunching(Profile profile, String id) {
|
||||
if (Accounts.getSelectedAccount() == null)
|
||||
Controllers.getLeftPaneController().checkAccount();
|
||||
Controllers.getRootPage().checkAccount();
|
||||
else if (id == null || !profile.getRepository().isLoaded() || !profile.getRepository().hasVersion(id))
|
||||
Controllers.dialog(i18n("version.empty.launch"));
|
||||
else
|
||||
@@ -136,7 +136,7 @@ public class Versions {
|
||||
}
|
||||
|
||||
public static void modifyGameSettings(Profile profile, String version) {
|
||||
Controllers.getVersionPage().load(version, profile);
|
||||
Controllers.getVersionPage().loadVersion(version, profile);
|
||||
Controllers.navigate(Controllers.getVersionPage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.scene.Node;
|
||||
@@ -39,6 +40,7 @@ import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -71,29 +73,29 @@ public class WorldListPage extends ListPageBase<WorldListItem> {
|
||||
return new WorldListPageSkin();
|
||||
}
|
||||
|
||||
public void loadVersion(Profile profile, String id) {
|
||||
public CompletableFuture<?> loadVersion(Profile profile, String id) {
|
||||
this.profile = profile;
|
||||
this.id = id;
|
||||
this.savesDir = profile.getRepository().getRunDirectory(id).toPath().resolve("saves");
|
||||
refresh();
|
||||
return refresh();
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
public CompletableFuture<?> refresh() {
|
||||
if (profile == null || id == null)
|
||||
return;
|
||||
return CompletableFuture.completedFuture(null);
|
||||
|
||||
setLoading(true);
|
||||
Task
|
||||
return CompletableFuture
|
||||
.runAsync(() -> gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse(null))
|
||||
.thenSupplyAsync(() -> World.getWorlds(savesDir).parallel().collect(Collectors.toList()))
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
.thenApplyAsync(unused -> World.getWorlds(savesDir).parallel().collect(Collectors.toList()))
|
||||
.whenCompleteAsync((result, exception) -> {
|
||||
worlds = result;
|
||||
setLoading(false);
|
||||
if (exception == null)
|
||||
itemsProperty().setAll(result.stream()
|
||||
.filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
|
||||
.map(WorldListItem::new).collect(Collectors.toList()));
|
||||
}).start();
|
||||
}, Platform::runLater);
|
||||
}
|
||||
|
||||
public void add() {
|
||||
|
||||
@@ -24,7 +24,7 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
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 java.util.Queue;
|
||||
@@ -38,10 +38,8 @@ public class DefaultWizardDisplayer extends StackPane implements AbstractWizardD
|
||||
|
||||
private Node nowPage;
|
||||
|
||||
private TransitionHandler transitionHandler;
|
||||
|
||||
@FXML
|
||||
private StackPane root;
|
||||
private TransitionPane root;
|
||||
@FXML
|
||||
private JFXButton backButton;
|
||||
@FXML
|
||||
@@ -86,7 +84,7 @@ public class DefaultWizardDisplayer extends StackPane implements AbstractWizardD
|
||||
@Override
|
||||
public void navigateTo(Node page, Navigation.NavigationDirection nav) {
|
||||
backButton.setDisable(!wizardController.canPrev());
|
||||
transitionHandler.setContent(page, nav.getAnimation().getAnimationProducer());
|
||||
root.setContent(page, nav.getAnimation().getAnimationProducer());
|
||||
String title = StringUtils.isBlank(prefix) ? "" : prefix + " - ";
|
||||
if (page instanceof WizardPage)
|
||||
titleLabel.setText(title + ((WizardPage) page).getTitle());
|
||||
@@ -96,7 +94,6 @@ public class DefaultWizardDisplayer extends StackPane implements AbstractWizardD
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
transitionHandler = new TransitionHandler(root);
|
||||
wizardController.onStart();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import javafx.beans.property.SimpleBooleanProperty;
|
||||
public interface Refreshable {
|
||||
void refresh();
|
||||
|
||||
default BooleanProperty canRefreshProperty() {
|
||||
default BooleanProperty refreshableProperty() {
|
||||
return new SimpleBooleanProperty(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,20 @@
|
||||
.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 {
|
||||
-fx-text-fill: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
@@ -353,7 +367,7 @@
|
||||
}
|
||||
|
||||
.jfx-tool-bar HBox {
|
||||
-fx-alignment: center;
|
||||
-fx-alignment: center-left;
|
||||
-fx-padding: 0.0 5.0;
|
||||
}
|
||||
|
||||
@@ -1024,6 +1038,11 @@
|
||||
-fx-font-size: 14;
|
||||
}
|
||||
|
||||
.jfx-decorator-tab .tab-label {
|
||||
-fx-text-fill: -fx-base-text-fill;
|
||||
-fx-font-size: 14;
|
||||
}
|
||||
|
||||
.resize-border {
|
||||
-fx-border-color: -fx-base-color;
|
||||
-fx-border-width: 0 2 2 2;
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
<?import com.jfoenix.controls.*?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.SpinnerPane?>
|
||||
|
||||
<?import org.jackhuang.hmcl.ui.animation.TransitionPane?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
<StackPane fx:id="addServerContainer">
|
||||
<TransitionPane fx:id="root">
|
||||
<JFXDialogLayout fx:id="addServerPane">
|
||||
<heading>
|
||||
<Label text="%account.injector.add" />
|
||||
@@ -51,5 +52,5 @@
|
||||
<JFXButton onMouseClicked="#onAddFinish" text="%wizard.finish" styleClass="dialog-accept" />
|
||||
</actions>
|
||||
</JFXDialogLayout>
|
||||
</StackPane>
|
||||
</TransitionPane>
|
||||
</fx:root>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<?import com.jfoenix.controls.*?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import org.jackhuang.hmcl.ui.animation.TransitionPane?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="BorderPane"
|
||||
@@ -13,7 +14,7 @@
|
||||
</StackPane>
|
||||
</top>
|
||||
<center>
|
||||
<StackPane fx:id="root">
|
||||
<TransitionPane fx:id="root">
|
||||
<JFXSpinner fx:id="spinner" styleClass="first-spinner" />
|
||||
<VBox fx:id="centrePane">
|
||||
<HBox fx:id="checkPane" spacing="10" style="-fx-padding: 10;">
|
||||
@@ -30,6 +31,6 @@
|
||||
<StackPane fx:id="emptyPane" styleClass="notice-pane">
|
||||
<Label onMouseClicked="#onBack" text="%download.failed.empty" />
|
||||
</StackPane>
|
||||
</StackPane>
|
||||
</TransitionPane>
|
||||
</center>
|
||||
</fx:root>
|
||||
|
||||
@@ -10,9 +10,7 @@
|
||||
<?import org.jackhuang.hmcl.ui.versions.WorldListPage?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:id="rootPane"
|
||||
type="StackPane">
|
||||
<JFXRippler />
|
||||
<StackPane fx:id="contentPane">
|
||||
<JFXTabPane fx:id="tabPane">
|
||||
<Tab text="%settings">
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import org.jackhuang.hmcl.ui.animation.TransitionPane?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
type="StackPane"
|
||||
style="-fx-background-color: gray;"
|
||||
@@ -43,6 +44,6 @@
|
||||
</rightItems>
|
||||
</JFXToolbar>
|
||||
|
||||
<StackPane fx:id="root"/>
|
||||
<TransitionPane fx:id="root"/>
|
||||
</VBox>
|
||||
</fx:root>
|
||||
|
||||
@@ -58,6 +58,20 @@ public final class Lang {
|
||||
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) {
|
||||
try {
|
||||
r.run();
|
||||
|
||||
Reference in New Issue
Block a user