feat(ui): decorator page animation..

This commit is contained in:
huanghongxun
2021-10-06 17:05:51 +08:00
parent 135f15dd9f
commit 1b8cf6b1e5
13 changed files with 224 additions and 133 deletions

View File

@@ -38,7 +38,6 @@ import org.jackhuang.hmcl.setting.Profiles;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.account.AccountListPage;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
import org.jackhuang.hmcl.ui.decorator.DecoratorController;
@@ -286,7 +285,7 @@ public final class Controllers {
}
public static void navigate(Node node) {
decorator.getNavigator().navigate(node, ContainerAnimations.FADE.getAnimationProducer());
decorator.navigate(node);
}
public static void showToast(String content) {

View File

@@ -27,9 +27,8 @@ import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Skin;
import javafx.scene.control.SkinBase;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
@@ -37,10 +36,10 @@ import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.ListPageBase;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
import org.jackhuang.hmcl.ui.construct.ClassTitle;
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
@@ -51,15 +50,16 @@ import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
public class AccountListPage extends ListPageBase<AccountListItem> implements DecoratorPage {
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("account.manage"), -1));
public class AccountListPage extends DecoratorAnimatedPage implements DecoratorPage {
private final ObservableList<AccountListItem> items;
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("account.manage")));
private final ListProperty<Account> accounts = new SimpleListProperty<>(this, "accounts", FXCollections.observableArrayList());
private final ListProperty<AuthlibInjectorServer> authServers = new SimpleListProperty<>(this, "authServers", FXCollections.observableArrayList());
private final ObjectProperty<Account> selectedAccount;
public AccountListPage() {
setItems(MappedObservableList.create(accounts, AccountListItem::new));
selectedAccount = createSelectedItemPropertyFor(getItems(), Account.class);
items = MappedObservableList.create(accounts, AccountListItem::new);
selectedAccount = createSelectedItemPropertyFor(items, Account.class);
}
public ObjectProperty<Account> selectedAccountProperty() {
@@ -84,100 +84,87 @@ public class AccountListPage extends ListPageBase<AccountListItem> implements De
return new AccountListPageSkin(this);
}
private static class AccountListPageSkin extends SkinBase<AccountListPage> {
private static class AccountListPageSkin extends DecoratorAnimatedPageSkin<AccountListPage> {
private final ObservableList<AdvancedListItem> authServerItems;
public AccountListPageSkin(AccountListPage skinnable) {
super(skinnable);
BorderPane root = new BorderPane();
{
BorderPane left = new BorderPane();
FXUtils.setLimitWidth(left, 200);
VBox boxMethods = new VBox();
{
VBox boxItemList = new VBox();
boxItemList.getStyleClass().add("advanced-list-box-content");
boxMethods.getStyleClass().add("advanced-list-box-content");
boxMethods.getChildren().add(new ClassTitle(i18n("account.create")));
FXUtils.setLimitWidth(boxMethods, 200);
boxItemList.getChildren().add(new ClassTitle(i18n("account.create")));
AdvancedListItem offlineItem = new AdvancedListItem();
offlineItem.getStyleClass().add("navigation-drawer-item");
offlineItem.setActionButtonVisible(false);
offlineItem.setTitle(i18n("account.methods.offline"));
offlineItem.setLeftGraphic(wrap(SVG::account));
offlineItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_OFFLINE)));
boxMethods.getChildren().add(offlineItem);
{
VBox boxMethods = new VBox();
FXUtils.setLimitWidth(boxMethods, 200);
AdvancedListItem mojangItem = new AdvancedListItem();
mojangItem.getStyleClass().add("navigation-drawer-item");
mojangItem.setActionButtonVisible(false);
mojangItem.setTitle(i18n("account.methods.yggdrasil"));
mojangItem.setLeftGraphic(wrap(SVG::mojang));
mojangItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_MOJANG)));
boxMethods.getChildren().add(mojangItem);
AdvancedListItem offlineItem = new AdvancedListItem();
offlineItem.getStyleClass().add("navigation-drawer-item");
offlineItem.setActionButtonVisible(false);
offlineItem.setTitle(i18n("account.methods.offline"));
offlineItem.setLeftGraphic(wrap(SVG::account));
offlineItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_OFFLINE)));
boxMethods.getChildren().add(offlineItem);
AdvancedListItem microsoftItem = new AdvancedListItem();
microsoftItem.getStyleClass().add("navigation-drawer-item");
microsoftItem.setActionButtonVisible(false);
microsoftItem.setTitle(i18n("account.methods.microsoft"));
microsoftItem.setLeftGraphic(wrap(SVG::microsoft));
microsoftItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_MICROSOFT)));
boxMethods.getChildren().add(microsoftItem);
AdvancedListItem mojangItem = new AdvancedListItem();
mojangItem.getStyleClass().add("navigation-drawer-item");
mojangItem.setActionButtonVisible(false);
mojangItem.setTitle(i18n("account.methods.yggdrasil"));
mojangItem.setLeftGraphic(wrap(SVG::mojang));
mojangItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_MOJANG)));
boxMethods.getChildren().add(mojangItem);
VBox boxAuthServers = new VBox();
authServerItems = MappedObservableList.create(skinnable.authServersProperty(), server -> {
AdvancedListItem item = new AdvancedListItem();
item.getStyleClass().add("navigation-drawer-item");
item.setLeftGraphic(wrap(SVG::server));
item.setOnAction(e -> Controllers.dialog(new CreateAccountPane(server)));
AdvancedListItem microsoftItem = new AdvancedListItem();
microsoftItem.getStyleClass().add("navigation-drawer-item");
microsoftItem.setActionButtonVisible(false);
microsoftItem.setTitle(i18n("account.methods.microsoft"));
microsoftItem.setLeftGraphic(wrap(SVG::microsoft));
microsoftItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_MICROSOFT)));
boxMethods.getChildren().add(microsoftItem);
VBox boxAuthServers = new VBox();
authServerItems = MappedObservableList.create(skinnable.authServersProperty(), server -> {
AdvancedListItem item = new AdvancedListItem();
item.getStyleClass().add("navigation-drawer-item");
item.setLeftGraphic(wrap(SVG::server));
item.setOnAction(e -> Controllers.dialog(new CreateAccountPane(server)));
JFXButton btnRemove = new JFXButton();
btnRemove.setOnAction(e -> {
skinnable.authServersProperty().remove(server);
e.consume();
});
btnRemove.getStyleClass().add("toggle-icon4");
btnRemove.setGraphic(SVG.close(Theme.blackFillBinding(), 14, 14));
item.setRightGraphic(btnRemove);
ObservableValue<String> title = BindingMapping.of(server, AuthlibInjectorServer::getName);
item.titleProperty().bind(title);
item.subtitleProperty().set(URI.create(server.getUrl()).getHost());
Tooltip tooltip = new Tooltip();
tooltip.textProperty().bind(Bindings.format("%s (%s)", title, server.getUrl()));
FXUtils.installFastTooltip(item, tooltip);
return item;
JFXButton btnRemove = new JFXButton();
btnRemove.setOnAction(e -> {
skinnable.authServersProperty().remove(server);
e.consume();
});
Bindings.bindContent(boxAuthServers.getChildren(), authServerItems);
boxMethods.getChildren().add(boxAuthServers);
btnRemove.getStyleClass().add("toggle-icon4");
btnRemove.setGraphic(SVG.close(Theme.blackFillBinding(), 14, 14));
item.setRightGraphic(btnRemove);
boxItemList.getChildren().add(new ScrollPane(boxMethods));
}
ObservableValue<String> title = BindingMapping.of(server, AuthlibInjectorServer::getName);
item.titleProperty().bind(title);
item.subtitleProperty().set(URI.create(server.getUrl()).getHost());
Tooltip tooltip = new Tooltip();
tooltip.textProperty().bind(Bindings.format("%s (%s)", title, server.getUrl()));
FXUtils.installFastTooltip(item, tooltip);
left.setCenter(boxItemList);
return item;
});
Bindings.bindContent(boxAuthServers.getChildren(), authServerItems);
boxMethods.getChildren().add(boxAuthServers);
}
AdvancedListItem addAuthServerItem = new AdvancedListItem();
{
AdvancedListItem addAuthServerItem = new AdvancedListItem();
addAuthServerItem.getStyleClass().add("navigation-drawer-item");
addAuthServerItem.setTitle(i18n("account.injector.add"));
addAuthServerItem.setSubtitle(i18n("account.methods.authlib_injector"));
addAuthServerItem.setActionButtonVisible(false);
addAuthServerItem.setLeftGraphic(wrap(SVG::plusCircleOutline));
addAuthServerItem.setOnAction(e -> Controllers.dialog(new AddAuthlibInjectorServerPane()));
BorderPane.setMargin(addAuthServerItem, new Insets(0, 0, 12, 0));
left.setBottom(addAuthServerItem);
VBox.setMargin(addAuthServerItem, new Insets(0, 0, 12, 0));
}
root.setLeft(left);
ScrollPane scrollPane = new ScrollPane(boxMethods);
VBox.setVgrow(scrollPane, Priority.ALWAYS);
setLeft(scrollPane, addAuthServerItem);
}
ScrollPane scrollPane = new ScrollPane();
@@ -189,15 +176,13 @@ public class AccountListPage extends ListPageBase<AccountListItem> implements De
list.setSpacing(10);
list.getStyleClass().add("card-list");
Bindings.bindContent(list.getChildren(), skinnable.itemsProperty());
Bindings.bindContent(list.getChildren(), skinnable.items);
scrollPane.setContent(list);
JFXScrollPane.smoothScrolling(scrollPane);
root.setCenter(scrollPane);
setCenter(scrollPane);
}
getChildren().setAll(root);
}
}
}

View File

@@ -95,5 +95,8 @@ public class TransitionPane extends StackPane implements AnimationHandler {
getChildren().setAll(previousNode, currentNode);
}
private final StackPane EMPTY_PANE = new StackPane();
private final EmptyPane EMPTY_PANE = new EmptyPane();
public static class EmptyPane extends StackPane {
}
}

View File

@@ -31,6 +31,10 @@ public class DecoratorAnimatedPage extends Control {
protected final VBox left = new VBox();
protected final StackPane center = new StackPane();
{
getStyleClass().add("gray-background");
}
protected void setLeft(Node... children) {
left.getChildren().setAll(children);
}
@@ -39,6 +43,14 @@ public class DecoratorAnimatedPage extends Control {
center.getChildren().setAll(children);
}
public VBox getLeft() {
return left;
}
public StackPane getCenter() {
return center;
}
@Override
protected Skin<?> createDefaultSkin() {
return new DecoratorAnimatedPageSkin<>(this);

View File

@@ -0,0 +1,90 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.decorator;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.scene.Node;
import javafx.util.Duration;
import org.jackhuang.hmcl.ui.animation.AnimationHandler;
import org.jackhuang.hmcl.ui.animation.AnimationProducer;
import org.jackhuang.hmcl.ui.animation.TransitionPane;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class DecoratorAnimationProducer implements AnimationProducer {
@Override
public void init(AnimationHandler handler) {
}
@Override
public List<KeyFrame> animate(AnimationHandler handler) {
Node prev = handler.getPreviousNode();
Node next = handler.getCurrentNode();
if (prev instanceof TransitionPane.EmptyPane) {
return Collections.emptyList();
}
Duration halfDuration = handler.getDuration().divide(2);
List<KeyFrame> keyFrames = new ArrayList<>();
keyFrames.add(new KeyFrame(Duration.ZERO,
new KeyValue(prev.opacityProperty(), 1, Interpolator.EASE_BOTH)));
keyFrames.add(new KeyFrame(halfDuration,
new KeyValue(prev.opacityProperty(), 0, Interpolator.EASE_BOTH)));
if (prev instanceof DecoratorAnimatedPage) {
Node left = ((DecoratorAnimatedPage) prev).getLeft();
Node center = ((DecoratorAnimatedPage) prev).getCenter();
keyFrames.add(new KeyFrame(Duration.ZERO,
new KeyValue(left.translateXProperty(), 0, Interpolator.EASE_BOTH),
new KeyValue(center.translateXProperty(), 0, Interpolator.EASE_BOTH)));
keyFrames.add(new KeyFrame(halfDuration,
new KeyValue(left.translateXProperty(), -30, Interpolator.EASE_BOTH),
new KeyValue(center.translateXProperty(), 30, Interpolator.EASE_BOTH)));
}
keyFrames.add(new KeyFrame(halfDuration,
new KeyValue(next.opacityProperty(), 0, Interpolator.EASE_BOTH)));
keyFrames.add(new KeyFrame(handler.getDuration(),
new KeyValue(next.opacityProperty(), 1, Interpolator.EASE_BOTH)));
if (next instanceof DecoratorAnimatedPage) {
Node left = ((DecoratorAnimatedPage) next).getLeft();
Node center = ((DecoratorAnimatedPage) next).getCenter();
keyFrames.add(new KeyFrame(halfDuration,
new KeyValue(left.translateXProperty(), -30, Interpolator.EASE_BOTH),
new KeyValue(center.translateXProperty(), 30, Interpolator.EASE_BOTH)));
keyFrames.add(new KeyFrame(handler.getDuration(),
new KeyValue(left.translateXProperty(), 0, Interpolator.EASE_BOTH),
new KeyValue(center.translateXProperty(), 0, Interpolator.EASE_BOTH)));
}
return keyFrames;
}
@Override
public @Nullable AnimationProducer opposite() {
return null;
}
}

View File

@@ -224,8 +224,10 @@ public class DecoratorController {
// ==== Navigation ====
public Navigator getNavigator() {
return navigator;
private static final DecoratorAnimationProducer animation = new DecoratorAnimationProducer();
public void navigate(Node node) {
navigator.navigate(node, animation);
}
private void close() {
@@ -390,7 +392,7 @@ public class DecoratorController {
public void startWizard(WizardProvider wizardProvider, String category) {
FXUtils.checkFxUserThread();
getNavigator().navigate(new DecoratorWizardDisplayer(wizardProvider, category), ContainerAnimations.FADE.getAnimationProducer());
navigator.navigate(new DecoratorWizardDisplayer(wizardProvider, category), ContainerAnimations.FADE.getAnimationProducer());
}
// ==== Authlib Injector DnD ====

View File

@@ -19,7 +19,6 @@ package org.jackhuang.hmcl.ui.decorator;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.svg.SVGGlyph;
import javafx.animation.Animation;
import javafx.beans.binding.Bindings;
import javafx.collections.ListChangeListener;
import javafx.css.PseudoClass;
@@ -197,21 +196,8 @@ public class DecoratorSkin extends SkinBase<Decorator> {
navBarPane.getChildren().setAll(node);
}
leftPane.prefWidthProperty().unbind();
if (s.getLeftPaneWidth() >= 0) {
FXUtils.playAnimation(leftPane, "animation",
s.isAnimate() ? Duration.millis(160) : null, leftPane.prefWidthProperty(), null, s.getLeftPaneWidth(), FXUtils.SINE);
} else {
Animation animation = FXUtils.playAnimation(leftPane, "animation1",
s.isAnimate() ? Duration.millis(160) : null, leftPane.prefWidthProperty(), null, container.getWidth(), FXUtils.SINE);
if (animation != null) {
animation.setOnFinished(action -> {
if (animation.getStatus() != Animation.Status.STOPPED) {
leftPane.prefWidthProperty().bind(container.widthProperty());
}
});
}
}
FXUtils.playAnimation(leftPane, "animation",
s.isAnimate() ? Duration.millis(160) : null, leftPane.prefWidthProperty(), null, s.getLeftPaneWidth(), FXUtils.SINE);
});
titleBar.setCenter(navBarPane);
titleBar.setRight(buttonsContainerPlaceHolder);

View File

@@ -58,6 +58,7 @@ import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CancellationException;
import java.util.function.Supplier;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
@@ -78,9 +79,9 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
private WeakListenerHolder listenerHolder;
public DownloadPage() {
newGameTab.setNodeSupplier(() -> new VersionsPage(versionPageNavigator, i18n("install.installer.choose", i18n("install.installer.game")), "", DownloadProviders.getDownloadProvider(),
"game", versionPageNavigator::onGameSelected));
modpackTab.setNodeSupplier(() -> {
newGameTab.setNodeSupplier(loadVersionFor(() -> new VersionsPage(versionPageNavigator, i18n("install.installer.choose", i18n("install.installer.game")), "", DownloadProviders.getDownloadProvider(),
"game", versionPageNavigator::onGameSelected)));
modpackTab.setNodeSupplier(loadVersionFor(() -> {
DownloadListPage page = new DownloadListPage(CurseForgeRemoteModRepository.MODPACKS, Versions::downloadModpackImpl);
JFXButton installLocalModpackButton = new JFXButton(i18n("install.modpack"));
@@ -90,22 +91,17 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
page.getActions().add(installLocalModpackButton);
return page;
});
modTab.setNodeSupplier(() -> new ModDownloadListPage((profile, version, file) -> download(profile, version, file, "mods"), true));
resourcePackTab.setNodeSupplier(() -> new DownloadListPage(CurseForgeRemoteModRepository.RESOURCE_PACKS, (profile, version, file) -> download(profile, version, file, "resourcepacks")));
}));
modTab.setNodeSupplier(loadVersionFor(() -> new ModDownloadListPage((profile, version, file) -> download(profile, version, file, "mods"), true)));
resourcePackTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(CurseForgeRemoteModRepository.RESOURCE_PACKS, (profile, version, file) -> download(profile, version, file, "resourcepacks"))));
// customizationTab.setNodeSupplier(() -> new ModDownloadListPage(CurseModManager.CUSTOMIZATIONS, this::download));
worldTab.setNodeSupplier(() -> new DownloadListPage(CurseForgeRemoteModRepository.WORLDS));
worldTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(CurseForgeRemoteModRepository.WORLDS)));
tab = new TabHeader(newGameTab, modpackTab, modTab, resourcePackTab, worldTab);
Profiles.registerVersionsListener(this::loadVersions);
tab.select(newGameTab);
FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> {
if (newValue.initializeIfNeeded()) {
if (newValue.getNode() instanceof VersionPage.VersionLoadable) {
((VersionPage.VersionLoadable) newValue.getNode()).loadVersion(Profiles.getSelectedProfile(), null);
}
}
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer());
});
@@ -154,6 +150,16 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
setCenter(transitionPane);
}
private <T extends Node> Supplier<T> loadVersionFor(Supplier<T> nodeSupplier) {
return () -> {
T node = nodeSupplier.get();
if (node instanceof VersionPage.VersionLoadable) {
((VersionPage.VersionLoadable) node).loadVersion(Profiles.getSelectedProfile(), null);
}
return node;
};
}
private void download(Profile profile, @Nullable String version, RemoteMod.Version file, String subdirectoryName) {
if (version == null) version = profile.getSelectedVersion();

View File

@@ -36,7 +36,7 @@ import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class LauncherSettingsPage extends DecoratorAnimatedPage implements DecoratorPage, PageAware {
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("settings"), -1));
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("settings")));
private final TabHeader tab;
private final TabHeader.Tab<VersionSettingsPage> gameTab = new TabHeader.Tab<>("versionSettingsPage");
private final TabHeader.Tab<SettingsPage> settingsTab = new TabHeader.Tab<>("settingsPage");

View File

@@ -50,7 +50,7 @@ import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorPage, PageAware {
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("multiplayer"), -1));
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("multiplayer")));
private final ObjectProperty<MultiplayerManager.State> multiplayerState = new SimpleObjectProperty<>(MultiplayerManager.State.DISCONNECTED);
private final ReadOnlyStringWrapper token = new ReadOnlyStringWrapper();

View File

@@ -47,7 +47,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
public class GameListPage extends DecoratorAnimatedPage implements DecoratorPage {
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("version.manage"), -1));
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("version.manage")));
private final ListProperty<Profile> profiles = new SimpleListProperty<>(FXCollections.observableArrayList());
@SuppressWarnings("FieldCanBeLocal")
private final ObservableList<ProfileListItem> profileListItems;

View File

@@ -52,7 +52,7 @@ import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class ModUpdatesPage extends BorderPane implements DecoratorPage {
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("mods.check_updates"), -1));
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("mods.check_updates")));
private final ModManager modManager;
private final ObservableList<ModUpdateObject> objects;
@@ -60,6 +60,8 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
public ModUpdatesPage(ModManager modManager, List<LocalModFile.ModUpdate> updates) {
this.modManager = modManager;
getStyleClass().add("gray-background");
JFXTreeTableColumn<ModUpdateObject, Boolean> enabledColumn = new JFXTreeTableColumn<>();
enabledColumn.setCellFactory(column -> new JFXCheckBoxTreeTableCell<>());
setupCellValueFactory(enabledColumn, ModUpdateObject::enabledProperty);

View File

@@ -29,6 +29,7 @@ import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
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;
@@ -41,6 +42,7 @@ import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.util.Optional;
import java.util.function.Supplier;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -58,29 +60,33 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
private String preferredVersionName = null;
{
versionSettingsTab.setNodeSupplier(() -> new VersionSettingsPage(false));
modListTab.setNodeSupplier(ModListPage::new);
installerListTab.setNodeSupplier(InstallerListPage::new);
worldListTab.setNodeSupplier(WorldListPage::new);
versionSettingsTab.setNodeSupplier(loadVersionFor(() -> new VersionSettingsPage(false)));
modListTab.setNodeSupplier(loadVersionFor(ModListPage::new));
installerListTab.setNodeSupplier(loadVersionFor(InstallerListPage::new));
worldListTab.setNodeSupplier(loadVersionFor(WorldListPage::new));
tab = new TabHeader(versionSettingsTab, modListTab, installerListTab, worldListTab);
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
tab.getSelectionModel().select(versionSettingsTab);
tab.select(versionSettingsTab);
FXUtils.onChangeAndOperate(tab.getSelectionModel().selectedItemProperty(), newValue -> {
if (newValue.initializeIfNeeded()) {
if (this.version.get() != null) {
if (newValue.getNode() instanceof VersionLoadable) {
((VersionLoadable) newValue.getNode()).loadVersion(version.get().getProfile(), version.get().getVersion());
}
}
}
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE.getAnimationProducer());
});
}
private <T extends Node> Supplier<T> loadVersionFor(Supplier<T> nodeSupplier) {
return () -> {
T node = nodeSupplier.get();
if (this.version.get() != null) {
if (node instanceof VersionPage.VersionLoadable) {
((VersionPage.VersionLoadable) node).loadVersion(Profiles.getSelectedProfile(), null);
}
}
return node;
};
}
public void setVersion(String version, Profile profile) {
this.version.set(new Profile.ProfileVersion(profile, version));
}
@@ -207,7 +213,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
versionSettingsItem.setLeftGraphic(wrap(SVG::gearOutline));
versionSettingsItem.setActionButtonVisible(false);
versionSettingsItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.versionSettingsTab));
versionSettingsItem.setOnAction(e -> control.tab.getSelectionModel().select(control.versionSettingsTab));
versionSettingsItem.setOnAction(e -> control.tab.select(control.versionSettingsTab));
AdvancedListItem modListItem = new AdvancedListItem();
modListItem.getStyleClass().add("navigation-drawer-item");
@@ -215,7 +221,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
modListItem.setLeftGraphic(wrap(SVG::puzzle));
modListItem.setActionButtonVisible(false);
modListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.modListTab));
modListItem.setOnAction(e -> control.tab.getSelectionModel().select(control.modListTab));
modListItem.setOnAction(e -> control.tab.select(control.modListTab));
AdvancedListItem installerListItem = new AdvancedListItem();
installerListItem.getStyleClass().add("navigation-drawer-item");
@@ -223,7 +229,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
installerListItem.setLeftGraphic(wrap(SVG::cube));
installerListItem.setActionButtonVisible(false);
installerListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.installerListTab));
installerListItem.setOnAction(e -> control.tab.getSelectionModel().select(control.installerListTab));
installerListItem.setOnAction(e -> control.tab.select(control.installerListTab));
AdvancedListItem worldListItem = new AdvancedListItem();
worldListItem.getStyleClass().add("navigation-drawer-item");
@@ -231,7 +237,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
worldListItem.setLeftGraphic(wrap(SVG::earth));
worldListItem.setActionButtonVisible(false);
worldListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.worldListTab));
worldListItem.setOnAction(e -> control.tab.getSelectionModel().select(control.worldListTab));
worldListItem.setOnAction(e -> control.tab.select(control.worldListTab));
AdvancedListBox sideBar = new AdvancedListBox()
.add(versionSettingsItem)