refactor: remove multi-instance list pages and unused wizard pages
Mirror Repository / Mirror to CNB (push) Skipped
Mirror Repository / Mirror to Gitee (push) Skipped
Check Codes / build (push) Failing after 3m52s
Check Codes / build (pull_request) Failing after 2m45s
Java CI / build (push) Canceled after 1h0m4s
Java CI / build (pull_request) Canceled after 56m22s
Mirror Repository / Mirror to CNB (push) Skipped
Mirror Repository / Mirror to Gitee (push) Skipped
Check Codes / build (push) Failing after 3m52s
Check Codes / build (pull_request) Failing after 2m45s
Java CI / build (push) Canceled after 1h0m4s
Java CI / build (pull_request) Canceled after 56m22s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2c8446fa33
commit
3a6e80da0e
9 files changed
+1
-1017
No files matched your search
@@ -39,7 +39,6 @@ import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.decorator.Decorator;
|
||||
import org.jackhuang.hmcl.ui.main.LauncherSettingsPage;
|
||||
import org.jackhuang.hmcl.ui.main.RootPage;
|
||||
import org.jackhuang.hmcl.ui.instances.GameListPage;
|
||||
import org.jackhuang.hmcl.ui.instances.GameInstancePage;
|
||||
import org.jackhuang.hmcl.ui.instances.Instances;
|
||||
import org.jackhuang.hmcl.upgrade.IntegrityChecker;
|
||||
@@ -128,28 +127,6 @@ public final class Controllers {
|
||||
return gameInstancePage;
|
||||
}
|
||||
|
||||
/// The cached game-list page.
|
||||
private static @Nullable GameListPage gameListPage;
|
||||
|
||||
/// Creates and attaches the game-list page for a layout pulse unless already cached.
|
||||
@FXThread
|
||||
public static void prepareGameListPage() {
|
||||
if (gameListPage == null) {
|
||||
LOG.info("Preparing game list page");
|
||||
gameListPage = getDecorator().prewarmPage(new GameListPage());
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the cached game-list page, creating it if necessary.
|
||||
@FXThread
|
||||
public static GameListPage getGameListPage() {
|
||||
if (gameListPage == null) {
|
||||
gameListPage = new GameListPage();
|
||||
}
|
||||
return gameListPage;
|
||||
}
|
||||
|
||||
|
||||
/// The cached root page.
|
||||
private static @Nullable RootPage rootPage;
|
||||
|
||||
@@ -694,7 +671,6 @@ public final class Controllers {
|
||||
onApplicationStop();
|
||||
rootPage = null;
|
||||
gameInstancePage = null;
|
||||
gameListPage = null;
|
||||
accountListPage = null;
|
||||
settingsPage = null;
|
||||
decorator = null;
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2021 The HMCL Authors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
|
||||
package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.ComponentRemoteVersion;
|
||||
import org.jackhuang.hmcl.game.GameComponentType;
|
||||
import org.jackhuang.hmcl.game.GameInstanceID;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.InstallerItem;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.RequiredValidator;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import static javafx.beans.binding.Bindings.createBooleanBinding;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class InstallersPage extends AbstractInstallersPage {
|
||||
|
||||
private boolean isNameModifiedByUser = false;
|
||||
|
||||
public InstallersPage(WizardController controller, HMCLGameRepository repository, GameVersionNumber gameVersion, DownloadProvider downloadProvider) {
|
||||
super(controller, gameVersion, downloadProvider);
|
||||
|
||||
txtName.getValidators().addAll(
|
||||
new RequiredValidator(),
|
||||
new Validator(i18n("install.new_game.already_exists"), str -> !repository.instanceIdConflicts(str)),
|
||||
new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidInstanceId));
|
||||
installable.bind(createBooleanBinding(txtName::validate, txtName.textProperty()));
|
||||
|
||||
txtName.textProperty().addListener((obs, oldText, newText) -> isNameModifiedByUser = true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return ((ComponentRemoteVersion) controller.getSettings().get("game")).getGameVersion().toString();
|
||||
}
|
||||
|
||||
private String getVersion(String id) {
|
||||
return I18n.getDisplayVersion((ComponentRemoteVersion) controller.getSettings().get(id));
|
||||
}
|
||||
|
||||
protected void reload() {
|
||||
for (InstallerItem component : group.getComponents()) {
|
||||
String libraryId = component.getComponentType().getPatchId();
|
||||
if (controller.getSettings().containsKey(libraryId)) {
|
||||
component.versionProperty().set(new InstallerItem.InstalledState(getVersion(libraryId), false, false));
|
||||
} else {
|
||||
component.versionProperty().set(null);
|
||||
}
|
||||
}
|
||||
if (!isNameModifiedByUser) {
|
||||
setTxtNameWithLoaders();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(SettingsMap settings) {
|
||||
}
|
||||
|
||||
private static boolean checkName(String name) {
|
||||
for (int i = 0; i < name.length(); i++) {
|
||||
char c = name.charAt(i);
|
||||
if (!(c >= '0' && c <= '9')
|
||||
&& !(c >= 'a' && c <= 'z')
|
||||
&& !(c >= 'A' && c <= 'Z')
|
||||
&& c != '-' && c != '_' && c != '.'
|
||||
)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void onInstall() {
|
||||
GameInstanceID instanceId = new GameInstanceID(txtName.getText());
|
||||
|
||||
if (!checkName(instanceId.id())) {
|
||||
Controllers.dialog(new MessageDialogPane.Builder(
|
||||
i18n("install.name.invalid"),
|
||||
i18n("message.warning"),
|
||||
MessageDialogPane.MessageType.QUESTION)
|
||||
.yesOrNo(() -> {
|
||||
controller.getSettings().put(INSTANCE_ID, instanceId);
|
||||
controller.onFinish();
|
||||
}, () -> {
|
||||
// The user selects Cancel and does nothing.
|
||||
})
|
||||
.build());
|
||||
} else {
|
||||
controller.getSettings().put(INSTANCE_ID, instanceId);
|
||||
controller.onFinish();
|
||||
}
|
||||
}
|
||||
|
||||
private void setTxtNameWithLoaders() {
|
||||
StringBuilder nameBuilder = new StringBuilder(getTitle());
|
||||
|
||||
for (InstallerItem component : group.getComponents()) {
|
||||
if (component.getComponentType() == GameComponentType.GAME
|
||||
|| !controller.getSettings().containsKey(component.getComponentType().getPatchId()))
|
||||
continue;
|
||||
|
||||
String loaderName = switch (component.getComponentType()) {
|
||||
case FORGE -> "Forge";
|
||||
case NEO_FORGE -> "NeoForge";
|
||||
case CLEANROOM -> "Cleanroom";
|
||||
case LEGACY_FABRIC -> "LegacyFabric";
|
||||
case FABRIC -> "Fabric";
|
||||
case LITELOADER -> "LiteLoader";
|
||||
case QUILT -> "Quilt";
|
||||
case OPTIFINE -> "OptiFine";
|
||||
default -> null;
|
||||
};
|
||||
|
||||
if (loaderName != null)
|
||||
nameBuilder.append('-').append(loaderName);
|
||||
}
|
||||
|
||||
txtName.setText(nameBuilder.toString());
|
||||
isNameModifiedByUser = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean showExtendPane() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetDefaultName() {
|
||||
isNameModifiedByUser = false;
|
||||
setTxtNameWithLoaders();
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 The HMCL Authors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
|
||||
package org.jackhuang.hmcl.ui.instances;
|
||||
|
||||
import javafx.beans.property.*;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.modpack.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.setting.GameDirectory;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.threadPool;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class GameItem {
|
||||
private static final ThreadPoolExecutor POOL_VERSION_RESOLVE = threadPool("VersionResolve", true, 1, 10, TimeUnit.SECONDS);
|
||||
|
||||
protected final HMCLGameInstance gameInstance;
|
||||
|
||||
private boolean initialized = false;
|
||||
private StringProperty title;
|
||||
private StringProperty tag;
|
||||
private StringProperty subtitle;
|
||||
private ObjectProperty<Image> image;
|
||||
|
||||
public GameItem(HMCLGameInstance gameInstance) {
|
||||
this.gameInstance = gameInstance;
|
||||
}
|
||||
|
||||
public GameDirectory getGameDirectory() {
|
||||
return gameInstance.getRepository().getGameDirectory();
|
||||
}
|
||||
|
||||
public HMCLGameRepository getRepository() {
|
||||
return gameInstance.getRepository();
|
||||
}
|
||||
|
||||
public GameInstanceID getInstanceId() {
|
||||
return gameInstance.getId();
|
||||
}
|
||||
|
||||
public HMCLGameInstance getGameInstance() {
|
||||
return gameInstance;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return gameInstance.getId().toString();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
if (initialized)
|
||||
return;
|
||||
|
||||
initialized = true;
|
||||
title = new SimpleStringProperty();
|
||||
tag = new SimpleStringProperty();
|
||||
subtitle = new SimpleStringProperty();
|
||||
image = new SimpleObjectProperty<>();
|
||||
|
||||
record Result(@Nullable String gameVersion, @Nullable String tag) {
|
||||
}
|
||||
|
||||
CompletableFuture.supplyAsync(() -> {
|
||||
// Version detection performs blocking JAR reads and may hash the entire file.
|
||||
GameVersionNumber version = gameInstance.getVersion();
|
||||
@Nullable String gameVersion = version == GameVersionNumber.unknown() ? null : version.toString();
|
||||
@Nullable String modPackVersion = null;
|
||||
try {
|
||||
@Nullable ModpackConfiguration<?> config = gameInstance.readModpackConfiguration();
|
||||
modPackVersion = config != null ? config.getVersion() : null;
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to read modpack configuration from " + getId(), e);
|
||||
}
|
||||
return new Result(gameVersion, modPackVersion);
|
||||
}, POOL_VERSION_RESOLVE).whenCompleteAsync((result, exception) -> {
|
||||
if (exception == null) {
|
||||
if (result.tag != null) {
|
||||
tag.set(result.tag);
|
||||
}
|
||||
|
||||
StringBuilder libraries = new StringBuilder(Objects.requireNonNullElse(result.gameVersion, i18n("message.unknown")));
|
||||
GameComponentAnalyzer analyzer = gameInstance.getAnalyzer();
|
||||
for (GameComponentAnalyzer.Mark mark : analyzer) {
|
||||
if (mark.componentType() == GameComponentType.GAME) continue;
|
||||
|
||||
if (I18n.hasKey("install.installer." + mark.componentType().getPatchId())) {
|
||||
libraries.append(", ").append(i18n("install.installer." + mark.componentType().getPatchId()));
|
||||
if (mark.version() != null)
|
||||
libraries.append(": ").append(mark.version().replaceAll("(?i)" + mark.componentType().getPatchId(), ""));
|
||||
}
|
||||
}
|
||||
|
||||
subtitle.set(libraries.toString());
|
||||
} else {
|
||||
LOG.warning("Failed to read version info from " + getId(), exception);
|
||||
}
|
||||
}, Schedulers.javafx());
|
||||
|
||||
title.set(getId());
|
||||
image.set(gameInstance.getIconImage());
|
||||
}
|
||||
|
||||
public ReadOnlyStringProperty titleProperty() {
|
||||
init();
|
||||
return title;
|
||||
}
|
||||
|
||||
public ReadOnlyStringProperty tagProperty() {
|
||||
init();
|
||||
return tag;
|
||||
}
|
||||
|
||||
public ReadOnlyStringProperty subtitleProperty() {
|
||||
init();
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
public ReadOnlyObjectProperty<Image> imageProperty() {
|
||||
init();
|
||||
return image;
|
||||
}
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 The HMCL Authors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
|
||||
package org.jackhuang.hmcl.ui.instances;
|
||||
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.determineOptimalPopupPosition;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class GameListCell extends ListCell<GameListItem> {
|
||||
|
||||
private final RipplerContainer graphic;
|
||||
|
||||
private final ImageContainer imageView;
|
||||
private final TwoLineListItem content;
|
||||
|
||||
private final JFXRadioButton chkSelected;
|
||||
private final JFXButton btnUpgrade;
|
||||
/// Opens the settings for this cell's game instance.
|
||||
private final JFXButton btnEditInstance;
|
||||
private final JFXButton btnManage;
|
||||
|
||||
private final HBox right;
|
||||
|
||||
private final StringProperty tag = new SimpleStringProperty();
|
||||
|
||||
/// Creates a game instance cell constrained to the supplied list's viewport width.
|
||||
public GameListCell(ListView<GameListItem> listView) {
|
||||
FXUtils.limitCellWidth(listView, this);
|
||||
|
||||
TwoLineListItem root = this.content = new TwoLineListItem();
|
||||
root.getStyleClass().add("md-list-cell");
|
||||
root.setPadding(new Insets(8, 8, 8, 0));
|
||||
|
||||
RipplerContainer container = new RipplerContainer(root);
|
||||
this.graphic = container;
|
||||
|
||||
{
|
||||
this.chkSelected = new JFXRadioButton() {
|
||||
/// Selects this cell's game instance when the radio button is enabled and unselected.
|
||||
@Override
|
||||
public void fire() {
|
||||
if (!isDisable() && !isSelected()) {
|
||||
fireEvent(new ActionEvent());
|
||||
@Nullable GameListItem item = GameListCell.this.getItem();
|
||||
if (item != null) {
|
||||
item.getRepository().setSelectedInstance(item.getGameInstance());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
this.imageView = new ImageContainer(32);
|
||||
imageView.setMouseTransparent(true);
|
||||
HBox leading = new HBox(8, chkSelected, imageView);
|
||||
leading.setAlignment(Pos.CENTER_LEFT);
|
||||
leading.setPickOnBounds(false);
|
||||
TwoLineListItem.setMargin(leading, new Insets(0, 8, 0, 0));
|
||||
content.setLeading(leading);
|
||||
|
||||
FXUtils.onChangeAndOperate(tag, tag -> {
|
||||
if (StringUtils.isNotBlank(tag))
|
||||
content.getTags().setAll(new Tag(tag));
|
||||
else
|
||||
content.getTags().clear();
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
this.right = new HBox();
|
||||
content.setTrailing(right);
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
right.setPickOnBounds(false);
|
||||
|
||||
this.btnUpgrade = FXUtils.newToggleButton4(SVG.UPDATE);
|
||||
btnUpgrade.setOnAction(e -> {
|
||||
@Nullable GameListItem item = this.getItem();
|
||||
if (item != null)
|
||||
item.update();
|
||||
});
|
||||
FXUtils.installFastTooltip(btnUpgrade, i18n("instance.update"));
|
||||
right.getChildren().add(btnUpgrade);
|
||||
|
||||
this.btnEditInstance = FXUtils.newToggleButton4(SVG.SETTINGS);
|
||||
btnEditInstance.setOnAction(e -> {
|
||||
@Nullable GameListItem item = this.getItem();
|
||||
if (item != null)
|
||||
item.modifyGameSettings();
|
||||
});
|
||||
FXUtils.installFastTooltip(btnEditInstance, i18n("instance.manage.manage"));
|
||||
right.getChildren().add(btnEditInstance);
|
||||
|
||||
this.btnManage = FXUtils.newToggleButton4(SVG.MORE_VERT);
|
||||
btnManage.setOnAction(e -> {
|
||||
@Nullable GameListItem item = this.getItem();
|
||||
if (item == null)
|
||||
return;
|
||||
|
||||
JFXPopup popup = getPopup(item);
|
||||
JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(root, popup);
|
||||
popup.show(root, vPosition, JFXPopup.PopupHPosition.RIGHT, 0, vPosition == JFXPopup.PopupVPosition.TOP ? root.getHeight() : -root.getHeight());
|
||||
});
|
||||
FXUtils.installFastTooltip(btnManage, i18n("settings.game.management"));
|
||||
right.getChildren().add(btnManage);
|
||||
}
|
||||
|
||||
root.setCursor(Cursor.HAND);
|
||||
container.setOnMouseClicked(e -> {
|
||||
@Nullable GameListItem item = getItem();
|
||||
if (item == null)
|
||||
return;
|
||||
|
||||
if (e.getButton() == MouseButton.PRIMARY) {
|
||||
if (e.getClickCount() == 1) {
|
||||
item.getRepository().setSelectedInstance(item.getGameInstance());
|
||||
}
|
||||
} else if (e.getButton() == MouseButton.SECONDARY) {
|
||||
JFXPopup popup = getPopup(item);
|
||||
JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(root, popup);
|
||||
popup.show(root, vPosition, JFXPopup.PopupHPosition.LEFT, e.getX(), vPosition == JFXPopup.PopupVPosition.TOP ? e.getY() : e.getY() - root.getHeight());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateItem(GameListItem item, boolean empty) {
|
||||
GameListItem oldItem = getItem();
|
||||
boolean oldEmpty = isEmpty();
|
||||
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (oldItem == item && oldEmpty == empty) return;
|
||||
|
||||
this.graphic.resetFeedback();
|
||||
|
||||
this.imageView.imageProperty().unbind();
|
||||
this.content.titleProperty().unbind();
|
||||
this.content.subtitleProperty().unbind();
|
||||
this.tag.unbind();
|
||||
this.right.getChildren().clear();
|
||||
this.chkSelected.selectedProperty().unbind();
|
||||
|
||||
if (empty || item == null) {
|
||||
setGraphic(null);
|
||||
} else {
|
||||
setGraphic(this.graphic);
|
||||
|
||||
this.chkSelected.selectedProperty().bind(item.selectedProperty());
|
||||
this.imageView.imageProperty().bind(item.imageProperty());
|
||||
this.content.titleProperty().bind(item.titleProperty());
|
||||
this.content.subtitleProperty().bind(item.subtitleProperty());
|
||||
this.tag.bind(item.tagProperty());
|
||||
if (item.canUpdate())
|
||||
this.right.getChildren().add(btnUpgrade);
|
||||
this.right.getChildren().addAll(btnEditInstance, btnManage);
|
||||
}
|
||||
}
|
||||
|
||||
private static JFXPopup getPopup(GameListItem item) {
|
||||
PopupMenu menu = new PopupMenu();
|
||||
JFXPopup popup = new JFXPopup(menu);
|
||||
|
||||
menu.getContent().setAll(
|
||||
new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n("instance.launch.test"), item::testGame, popup),
|
||||
new IconedMenuItem(SVG.SCRIPT, i18n("instance.launch_script"), item::generateLaunchScript, popup),
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(SVG.EDIT, i18n("instance.manage.rename"), item::rename, popup),
|
||||
new IconedMenuItem(SVG.FOLDER_COPY, i18n("instance.manage.duplicate"), item::duplicate, popup),
|
||||
new IconedMenuItem(SVG.DELETE, i18n("instance.manage.remove"), item::remove, popup),
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(SVG.FOLDER_OPEN, i18n("folder.game"), item::browse, popup));
|
||||
return popup;
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2020 The HMCL Authors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
|
||||
package org.jackhuang.hmcl.ui.instances;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import org.jackhuang.hmcl.game.GameInstanceID;
|
||||
import org.jackhuang.hmcl.game.HMCLGameInstance;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.setting.GameDirectoryManager;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class GameListItem extends GameItem {
|
||||
private final boolean isModpack;
|
||||
private final BooleanProperty selected = new SimpleBooleanProperty(this, "selected");
|
||||
|
||||
public GameListItem(HMCLGameInstance gameInstance) {
|
||||
super(gameInstance);
|
||||
HMCLGameRepository repository = gameInstance.getRepository();
|
||||
GameInstanceID instanceId = gameInstance.getId();
|
||||
this.isModpack = gameInstance.isModpack();
|
||||
selected.bind(Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
if (repository.getGameDirectory() != GameDirectoryManager.getSelectedGameDirectory()) return false;
|
||||
@Nullable HMCLGameInstance selectedInstance = repository.getSelectedInstance();
|
||||
return selectedInstance != null && selectedInstance.getId().equals(instanceId);
|
||||
},
|
||||
GameDirectoryManager.selectedGameDirectoryProperty(),
|
||||
GameDirectoryManager.selectedInstanceProperty()));
|
||||
}
|
||||
|
||||
public ReadOnlyBooleanProperty selectedProperty() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
public void rename() {
|
||||
Instances.renameInstance(gameInstance);
|
||||
}
|
||||
|
||||
public void duplicate() {
|
||||
Instances.duplicateInstance(gameInstance);
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
Instances.deleteInstance(gameInstance);
|
||||
}
|
||||
|
||||
public void browse() {
|
||||
Instances.openFolder(gameInstance);
|
||||
}
|
||||
|
||||
public void testGame() {
|
||||
Instances.testGame(gameInstance);
|
||||
}
|
||||
|
||||
public void launch() {
|
||||
Instances.launch(gameInstance);
|
||||
}
|
||||
|
||||
public void modifyGameSettings() {
|
||||
Instances.modifyGameSettings(gameInstance);
|
||||
}
|
||||
|
||||
public void generateLaunchScript() {
|
||||
Instances.generateLaunchScript(gameInstance);
|
||||
}
|
||||
|
||||
public boolean canUpdate() {
|
||||
return isModpack;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
Instances.updateInstance(gameInstance);
|
||||
}
|
||||
}
|
||||
@@ -1,248 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2020 The HMCL Authors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
|
||||
package org.jackhuang.hmcl.ui.instances;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.animation.PauseTransition;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.FilteredList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.setting.GameDirectoryManager;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
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.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.util.FXThread;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.*;
|
||||
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class GameListPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("instance.manage")));
|
||||
|
||||
/// Creates the fixed-directory instance list.
|
||||
public GameListPage() {
|
||||
getSidebar()
|
||||
.addNavigationDrawerItem(i18n("settings.type.global.manage"), SVG.SETTINGS, this::modifyGlobalGameSettings);
|
||||
|
||||
setContent(new GameList());
|
||||
|
||||
FXUtils.applyDragListener(this, file -> ModpackHelper.isFileModpackByExtension(file) || "json".equalsIgnoreCase(FileUtils.getNameWithoutExtension(file)), files -> {
|
||||
Path file = files.get(0);
|
||||
|
||||
if (ModpackHelper.isFileModpackByExtension(file)) {
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(GameDirectoryManager.getSelectedRepository(), file), i18n("install.modpack"));
|
||||
} else if ("json".equalsIgnoreCase(FileUtils.getExtension(file))) {
|
||||
Instances.installFromJson(GameDirectoryManager.getSelectedRepository(), file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void modifyGlobalGameSettings() {
|
||||
Instances.modifyGlobalSettings(GameDirectoryManager.getSelectedRepository());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
private static class GameList extends ListPageBase<GameListItem> {
|
||||
private final WeakListenerHolder listenerHolder = new WeakListenerHolder();
|
||||
|
||||
private final ObservableList<GameListItem> sourceList = FXCollections.observableArrayList();
|
||||
private final FilteredList<GameListItem> filteredList = new FilteredList<>(sourceList);
|
||||
|
||||
public GameList() {
|
||||
setItems(filteredList);
|
||||
|
||||
GameDirectoryManager.registerVersionsListener(this::loadVersions);
|
||||
|
||||
setOnFailedAction(e -> Instances.installServerModpack());
|
||||
}
|
||||
|
||||
@FXThread
|
||||
private void loadVersions(HMCLGameRepository repository) {
|
||||
listenerHolder.clear();
|
||||
setLoading(true);
|
||||
setFailedReason(null);
|
||||
|
||||
List<GameListItem> instanceItems = repository.getDisplayInstances()
|
||||
.map(GameListItem::new)
|
||||
.toList();
|
||||
|
||||
sourceList.setAll(instanceItems);
|
||||
|
||||
if (instanceItems.isEmpty()) {
|
||||
setFailedReason(i18n("instance.empty.hint"));
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
private Predicate<GameListItem> createPredicate(String searchText) {
|
||||
if (searchText == null || searchText.isEmpty()) {
|
||||
return item -> true;
|
||||
}
|
||||
|
||||
if (searchText.startsWith("regex:")) {
|
||||
String regex = searchText.substring("regex:".length());
|
||||
try {
|
||||
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
|
||||
return item -> pattern.matcher(item.getId()).find();
|
||||
} catch (PatternSyntaxException e) {
|
||||
return item -> false;
|
||||
}
|
||||
} else {
|
||||
return item -> item.getId().toLowerCase(Locale.ROOT).contains(searchText.toLowerCase(Locale.ROOT));
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshList() {
|
||||
GameDirectoryManager.getSelectedRepository().refreshAsync().start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new GameListSkin(this);
|
||||
}
|
||||
|
||||
private static class GameListSkin extends SkinBase<GameList> {
|
||||
private final TransitionPane toolbarPane;
|
||||
private final HBox searchBar;
|
||||
private final HBox toolbarNormal;
|
||||
|
||||
/// Whether the search mechanism is currently active.
|
||||
private final BooleanProperty isSearching = new SimpleBooleanProperty(false);
|
||||
|
||||
private final JFXTextField searchField;
|
||||
|
||||
/// Creates the instance list with search controls and viewport-constrained rows.
|
||||
public GameListSkin(GameList skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
StackPane pane = new StackPane();
|
||||
pane.setPadding(new Insets(10));
|
||||
pane.getStyleClass().addAll("notice-pane");
|
||||
|
||||
ComponentList root = new ComponentList();
|
||||
root.getStyleClass().add("no-padding");
|
||||
JFXListView<GameListItem> listView = new JFXListView<>();
|
||||
listView.getStyleClass().add("no-horizontal-scrollbar");
|
||||
|
||||
{
|
||||
toolbarPane = new TransitionPane();
|
||||
|
||||
searchBar = new HBox();
|
||||
toolbarNormal = new HBox();
|
||||
|
||||
searchBar.setAlignment(Pos.CENTER);
|
||||
searchBar.setPadding(new Insets(0, 5, 0, 5));
|
||||
searchField = new JFXTextField();
|
||||
searchField.setPromptText(i18n("search"));
|
||||
HBox.setHgrow(searchField, Priority.ALWAYS);
|
||||
PauseTransition pause = new PauseTransition(Duration.millis(100));
|
||||
pause.setOnFinished(e -> skinnable.filteredList.setPredicate(skinnable.createPredicate(searchField.getText())));
|
||||
searchField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (isSearching.get() || !StringUtils.isBlank(newValue)) {
|
||||
pause.setRate(1);
|
||||
pause.playFromStart();
|
||||
}
|
||||
});
|
||||
|
||||
JFXButton closeSearchBar = createToolbarButton2(null, SVG.CLOSE, () -> {
|
||||
changeToolbar(toolbarNormal);
|
||||
searchField.clear();
|
||||
|
||||
pause.stop();
|
||||
skinnable.filteredList.setPredicate(null);
|
||||
isSearching.set(false);
|
||||
});
|
||||
|
||||
onEscPressed(searchField, closeSearchBar::fire);
|
||||
|
||||
searchBar.getChildren().setAll(searchField, closeSearchBar);
|
||||
|
||||
toolbarNormal.getChildren().setAll(createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refreshList), createToolbarButton2(i18n("install.modpack"), SVG.PACKAGE2, Instances::installServerModpack), createToolbarButton2(i18n("search"), SVG.SEARCH, () -> changeToolbar(searchBar)));
|
||||
|
||||
toolbarPane.setContent(toolbarNormal, ContainerAnimations.FADE);
|
||||
|
||||
FXUtils.setOverflowHidden(toolbarPane, 8);
|
||||
|
||||
root.getContent().add(toolbarPane);
|
||||
}
|
||||
|
||||
{
|
||||
SpinnerPane center = new SpinnerPane();
|
||||
ComponentList.setVgrow(center, Priority.ALWAYS);
|
||||
center.loadingProperty().bind(skinnable.loadingProperty());
|
||||
center.failedReasonProperty().bind(skinnable.failedReasonProperty());
|
||||
|
||||
listView.setCellFactory(GameListCell::new);
|
||||
listView.setItems(skinnable.getItems());
|
||||
|
||||
ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE);
|
||||
|
||||
StackPane placeholderContainer = new StackPane();
|
||||
placeholderContainer.getStyleClass().add("notice-pane");
|
||||
Label placeholderLabel = new Label();
|
||||
placeholderLabel.textProperty().bind(
|
||||
Bindings.when(isSearching).then(i18n("search.no_results_found")).otherwise("")
|
||||
);
|
||||
placeholderContainer.getChildren().add(placeholderLabel);
|
||||
listView.setPlaceholder(placeholderContainer);
|
||||
|
||||
center.setContent(listView);
|
||||
root.getContent().add(center);
|
||||
}
|
||||
|
||||
pane.getChildren().setAll(root);
|
||||
getChildren().setAll(pane);
|
||||
}
|
||||
|
||||
private void changeToolbar(HBox newToolbar) {
|
||||
Node oldToolbar = toolbarPane.getCurrentNode();
|
||||
if (newToolbar != oldToolbar) {
|
||||
toolbarPane.setContent(newToolbar, ContainerAnimations.FADE);
|
||||
if (newToolbar == searchBar) {
|
||||
runInFX(searchField::requestFocus);
|
||||
|
||||
isSearching.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2026 The HMCL Authors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
|
||||
package org.jackhuang.hmcl.ui.instances;
|
||||
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.WindowEvent;
|
||||
import org.jackhuang.hmcl.game.HMCLGameInstance;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.ImageContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.Tag;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/// Displays game instances in a popup selection list.
|
||||
///
|
||||
/// @author Glavo
|
||||
public final class GameListPopupMenu extends StackPane {
|
||||
|
||||
private static final String KEY = GameListPopupMenu.class.getName() + ".popup";
|
||||
|
||||
public static boolean hideShowing(Node owner) {
|
||||
JFXPopup popup = (JFXPopup) owner.getProperties().get(KEY);
|
||||
if (popup != null && popup.isShowing()) {
|
||||
popup.hide();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Shows an instance selection popup relative to its owner.
|
||||
///
|
||||
/// @param owner the node used to position the popup
|
||||
/// @param vAlign the popup's vertical alignment relative to `owner`
|
||||
/// @param hAlign the popup's horizontal alignment relative to `owner`
|
||||
/// @param initOffsetX the horizontal offset from the aligned position
|
||||
/// @param initOffsetY the vertical offset from the aligned position
|
||||
/// @param instances the instances to copy into the popup, in display order
|
||||
public static void show(Node owner, JFXPopup.PopupVPosition vAlign, JFXPopup.PopupHPosition hAlign,
|
||||
double initOffsetX, double initOffsetY,
|
||||
List<HMCLGameInstance> instances) {
|
||||
if (!hideShowing(owner)) {
|
||||
showAndGetPopup(owner, vAlign, hAlign, initOffsetX, initOffsetY, instances);
|
||||
}
|
||||
}
|
||||
|
||||
/// Shows and returns an instance selection popup relative to its owner.
|
||||
///
|
||||
/// @param owner the node used to position the popup
|
||||
/// @param vAlign the popup's vertical alignment relative to `owner`
|
||||
/// @param hAlign the popup's horizontal alignment relative to `owner`
|
||||
/// @param initOffsetX the horizontal offset from the aligned position
|
||||
/// @param initOffsetY the vertical offset from the aligned position
|
||||
/// @param instances the instances to copy into the popup, in display order
|
||||
/// @return the shown popup
|
||||
public static JFXPopup showAndGetPopup(Node owner, JFXPopup.PopupVPosition vAlign, JFXPopup.PopupHPosition hAlign,
|
||||
double initOffsetX, double initOffsetY,
|
||||
List<HMCLGameInstance> instances) {
|
||||
GameListPopupMenu menu = new GameListPopupMenu();
|
||||
menu.getItems().setAll(instances.stream()
|
||||
.map(GameItem::new)
|
||||
.toList());
|
||||
JFXPopup popup = new JFXPopup(menu);
|
||||
owner.getProperties().put(KEY, popup);
|
||||
popup.addEventFilter(WindowEvent.WINDOW_HIDDEN, event -> owner.getProperties().remove(KEY, popup));
|
||||
popup.show(owner, vAlign, hAlign, initOffsetX, initOffsetY, true);
|
||||
|
||||
return popup;
|
||||
}
|
||||
|
||||
private final JFXListView<GameItem> listView = new JFXListView<>();
|
||||
private final BooleanBinding isEmpty = Bindings.isEmpty(listView.getItems());
|
||||
|
||||
public GameListPopupMenu() {
|
||||
this.setMaxHeight(365);
|
||||
this.getStyleClass().add("popup-menu-content");
|
||||
|
||||
listView.setCellFactory(Cell::new);
|
||||
|
||||
listView.setFixedCellSize(50);
|
||||
listView.setPrefWidth(300);
|
||||
|
||||
listView.prefHeightProperty().bind(Bindings.size(getItems()).multiply(50).add(2));
|
||||
|
||||
Label placeholder = new Label(i18n("instance.empty"));
|
||||
placeholder.setStyle("-fx-padding: 10px; -fx-text-fill: -monet-on-surface-variant; -fx-font-style: italic;");
|
||||
|
||||
FXUtils.onChangeAndOperate(isEmpty, empty -> getChildren().setAll(empty ? placeholder : listView));
|
||||
}
|
||||
|
||||
public ObservableList<GameItem> getItems() {
|
||||
return listView.getItems();
|
||||
}
|
||||
|
||||
private static final class Cell extends ListCell<GameItem> {
|
||||
|
||||
private final Region graphic;
|
||||
private final RipplerContainer ripplerContainer;
|
||||
|
||||
private final ImageContainer imageView;
|
||||
private final TwoLineListItem content;
|
||||
|
||||
private final StringProperty tag = new SimpleStringProperty();
|
||||
|
||||
/// Creates a popup row with a leading icon and the shared advanced-item text styling.
|
||||
public Cell(ListView<GameItem> listView) {
|
||||
this.setPadding(Insets.EMPTY);
|
||||
|
||||
this.imageView = new ImageContainer(32);
|
||||
this.imageView.setMouseTransparent(true);
|
||||
|
||||
this.content = new TwoLineListItem();
|
||||
content.getStyleClass().add("container");
|
||||
content.setLeading(imageView);
|
||||
FXUtils.onChangeAndOperate(tag, tag -> {
|
||||
if (StringUtils.isNotBlank(tag))
|
||||
content.getTags().setAll(new Tag(tag));
|
||||
else
|
||||
content.getTags().clear();
|
||||
});
|
||||
|
||||
this.ripplerContainer = new RipplerContainer(content);
|
||||
|
||||
StackPane rootPane = new StackPane();
|
||||
rootPane.getStyleClass().add("advanced-list-item");
|
||||
rootPane.getChildren().setAll(ripplerContainer);
|
||||
rootPane.maxWidthProperty().bind(listView.widthProperty().subtract(5));
|
||||
|
||||
FXUtils.onClicked(rootPane, () -> {
|
||||
@Nullable GameItem item = getItem();
|
||||
if (item != null) {
|
||||
item.getRepository().setSelectedInstance(item.getGameInstance());
|
||||
if (getScene().getWindow() instanceof JFXPopup popup)
|
||||
popup.hide();
|
||||
}
|
||||
});
|
||||
|
||||
this.graphic = rootPane;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(GameItem item, boolean empty) {
|
||||
GameItem oldItem = getItem();
|
||||
boolean oldEmpty = isEmpty();
|
||||
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (oldItem == item && oldEmpty == empty) return;
|
||||
|
||||
this.ripplerContainer.resetFeedback();
|
||||
this.imageView.imageProperty().unbind();
|
||||
this.content.titleProperty().unbind();
|
||||
this.content.subtitleProperty().unbind();
|
||||
this.tag.unbind();
|
||||
|
||||
if (empty || item == null) {
|
||||
setGraphic(null);
|
||||
} else {
|
||||
setGraphic(this.graphic);
|
||||
|
||||
this.imageView.imageProperty().bind(item.imageProperty());
|
||||
this.content.titleProperty().bind(item.titleProperty());
|
||||
this.content.subtitleProperty().bind(item.subtitleProperty());
|
||||
this.tag.bind(item.tagProperty());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,6 @@ import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedMenuItem;
|
||||
import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.instances.GameListPopupMenu;
|
||||
import org.jackhuang.hmcl.ui.instances.Instances;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
@@ -21,7 +21,6 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.instances.GameAdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.instances.GameListPopupMenu;
|
||||
import org.jackhuang.hmcl.ui.instances.Instances;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
@@ -53,17 +52,11 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
gameInstanceItem.setOnAction(e -> {
|
||||
@Nullable HMCLGameInstance instance = GameDirectoryManager.getSelectedRepository().getSelectedInstance();
|
||||
if (instance == null) {
|
||||
Controllers.navigate(Controllers.getGameListPage());
|
||||
Instances.installServerModpack();
|
||||
} else {
|
||||
Instances.modifyGameSettings(instance);
|
||||
}
|
||||
});
|
||||
FXUtils.onScroll(gameInstanceItem, getMainPage().getInstances(), list -> {
|
||||
@Nullable HMCLGameInstance currentGame = getMainPage().getCurrentGame();
|
||||
@Nullable GameInstanceID currentId = currentGame != null ? currentGame.getId() : null;
|
||||
return Lang.indexWhere(list, instance -> instance.getId().equals(currentId));
|
||||
}, instance -> instance.getRepository().setSelectedInstance(instance));
|
||||
|
||||
if (AnimationUtils.isAnimationEnabled()) {
|
||||
gameInstanceItem.setPrepareAction(Controllers::prepareGameInstancePage);
|
||||
}
|
||||
@@ -116,14 +109,4 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
}
|
||||
return mainPage;
|
||||
}
|
||||
|
||||
/// Opens the instance selection menu beside the sidebar entry.
|
||||
private void showGameListPopupMenu(Region gameListItem) {
|
||||
GameListPopupMenu.show(gameListItem,
|
||||
JFXPopup.PopupVPosition.TOP,
|
||||
JFXPopup.PopupHPosition.LEFT,
|
||||
gameListItem.getWidth(),
|
||||
0,
|
||||
getMainPage().getInstances());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user