From 6a982b2a5cc8dc0510cf9285dbc9245bd1ce05cf Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Mon, 16 Mar 2020 22:49:15 +0800 Subject: [PATCH] add: allow removing libraries in vanilla installation --- .../ui/download/AdditionalInstallersPage.java | 42 ++-- .../hmcl/ui/download/InstallersPage.java | 216 ++++++++++++------ .../UpdateInstallerWizardProvider.java | 12 +- .../hmcl/ui/versions/InstallerListPage.java | 1 + .../assets/fxml/download/installers.fxml | 93 -------- .../assets/fxml/version/installer-item.fxml | 22 -- .../download/DefaultDependencyManager.java | 3 +- 7 files changed, 193 insertions(+), 196 deletions(-) delete mode 100644 HMCL/src/main/resources/assets/fxml/download/installers.fxml delete mode 100644 HMCL/src/main/resources/assets/fxml/version/installer-item.fxml diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java index ab2d74e9c..7f92631a6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java @@ -20,7 +20,6 @@ package org.jackhuang.hmcl.ui.download; import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; -import javafx.scene.control.Label; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.download.RemoteVersion; @@ -51,9 +50,20 @@ class AdditionalInstallersPage extends InstallersPage { txtName.setText(version.getId()); txtName.setEditable(false); - btnInstall.disableProperty().bind(Bindings.createBooleanBinding( - () -> !compatible.get() || !txtName.validate(), + installable.bind(Bindings.createBooleanBinding( + () -> compatible.get() && txtName.validate(), txtName.textProperty(), compatible)); + + InstallerPageItem[] libraries = new InstallerPageItem[]{game, fabric, forge, liteLoader, optiFine}; + + for (InstallerPageItem library : libraries) { + String libraryId = library.id; + if (libraryId.equals("game")) continue; + library.removeAction.set(e -> { + controller.getSettings().put(libraryId, new UpdateInstallerWizardProvider.RemoveVersionAction(libraryId)); + reload(); + }); + } } @Override @@ -67,11 +77,13 @@ class AdditionalInstallersPage extends InstallersPage { } private String getVersion(String id) { - return Optional.ofNullable(controller.getSettings().get(id)).map(it -> (RemoteVersion) it).map(RemoteVersion::getSelfVersion).orElse(null); + return Optional.ofNullable(controller.getSettings().get(id)) + .flatMap(it -> Lang.tryCast(it, RemoteVersion.class)) + .map(RemoteVersion::getSelfVersion).orElse(null); } @Override - public void onNavigate(Map settings) { + protected void reload() { LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version.resolvePreservingPatches(repository)); String game = analyzer.getVersion(MINECRAFT).orElse(null); String fabric = analyzer.getVersion(FABRIC).orElse(null); @@ -79,26 +91,28 @@ class AdditionalInstallersPage extends InstallersPage { String liteLoader = analyzer.getVersion(LITELOADER).orElse(null); String optiFine = analyzer.getVersion(OPTIFINE).orElse(null); - Label[] labels = new Label[]{lblGame, lblFabric, lblForge, lblLiteLoader, lblOptiFine}; - String[] libraryIds = new String[]{"game", "fabric", "forge", "liteloader", "optifine"}; + InstallerPageItem[] libraries = new InstallerPageItem[]{this.game, this.fabric, this.forge, this.liteLoader, this.optiFine}; String[] versions = new String[]{game, fabric, forge, liteLoader, optiFine}; String currentGameVersion = Lang.nonNull(getVersion("game"), game); boolean compatible = true; - for (int i = 0; i < libraryIds.length; ++i) { - String libraryId = libraryIds[i]; + for (int i = 0; i < libraries.length; ++i) { + String libraryId = libraries[i].id; String libraryVersion = Lang.nonNull(getVersion(libraryId), versions[i]); - boolean alreadyInstalled = versions[i] != null; + boolean alreadyInstalled = versions[i] != null && !(controller.getSettings().get(libraryId) instanceof UpdateInstallerWizardProvider.RemoveVersionAction); if (!"game".equals(libraryId) && currentGameVersion != null && !currentGameVersion.equals(game) && getVersion(libraryId) == null && alreadyInstalled) { // For third-party libraries, if game version is being changed, and the library is not being reinstalled, // warns the user that we should update the library. - labels[i].setText(i18n("install.installer.change_version", i18n("install.installer." + libraryId), libraryVersion)); + libraries[i].label.set(i18n("install.installer.change_version", i18n("install.installer." + libraryId), libraryVersion)); + libraries[i].removable.set(true); compatible = false; - } else if (alreadyInstalled || controller.getSettings().containsKey(libraryId)) { - labels[i].setText(i18n("install.installer.version", i18n("install.installer." + libraryId)) + ": " + libraryVersion); + } else if (alreadyInstalled || getVersion(libraryId) != null) { + libraries[i].label.set(i18n("install.installer.version", i18n("install.installer." + libraryId)) + ": " + libraryVersion); + libraries[i].removable.set(true); } else { - labels[i].setText(i18n("install.installer.not_installed", i18n("install.installer." + libraryId))); + libraries[i].label.set(i18n("install.installer.not_installed", i18n("install.installer." + libraryId))); + libraries[i].removable.set(false); } } this.compatible.set(compatible); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java index f3f490486..132677497 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java @@ -21,15 +21,27 @@ import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXTextField; import com.jfoenix.effects.JFXDepthManager; import javafx.beans.binding.Bindings; +import javafx.beans.property.*; +import javafx.event.EventHandler; import javafx.fxml.FXML; -import javafx.scene.Node; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Cursor; +import javafx.scene.control.Control; import javafx.scene.control.Label; -import javafx.scene.layout.StackPane; +import javafx.scene.control.Skin; +import javafx.scene.control.SkinBase; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.download.RemoteVersion; import org.jackhuang.hmcl.game.GameRepository; -import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.setting.Theme; +import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.construct.Validator; import org.jackhuang.hmcl.ui.wizard.WizardController; import org.jackhuang.hmcl.ui.wizard.WizardPage; @@ -40,74 +52,40 @@ import java.util.Map; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public class InstallersPage extends StackPane implements WizardPage { +public class InstallersPage extends Control implements WizardPage { protected final WizardController controller; - @FXML - protected VBox list; - - @FXML - protected Node btnGame; - - @FXML - protected Node btnFabric; - - @FXML - protected Node btnForge; - - @FXML - protected Node btnLiteLoader; - - @FXML - protected Node btnOptiFine; - - @FXML - protected Label lblGame; - - @FXML - protected Label lblFabric; - - @FXML - protected Label lblForge; - - @FXML - protected Label lblLiteLoader; - - @FXML - protected Label lblOptiFine; - - @FXML - protected JFXTextField txtName; - - @FXML - protected JFXButton btnInstall; + protected InstallerPageItem game = new InstallerPageItem("game"); + protected InstallerPageItem fabric = new InstallerPageItem("fabric"); + protected InstallerPageItem forge = new InstallerPageItem("forge"); + protected InstallerPageItem liteLoader = new InstallerPageItem("liteloader"); + protected InstallerPageItem optiFine = new InstallerPageItem("optifine"); + protected JFXTextField txtName = new JFXTextField(); + protected BooleanProperty installable = new SimpleBooleanProperty(); public InstallersPage(WizardController controller, GameRepository repository, String gameVersion, DownloadProvider downloadProvider) { this.controller = controller; - FXUtils.loadFXML(this, "/assets/fxml/download/installers.fxml"); - Validator hasVersion = new Validator(s -> !repository.hasVersion(s) && StringUtils.isNotBlank(s)); hasVersion.setMessage(i18n("install.new_game.already_exists")); Validator nameValidator = new Validator(OperatingSystem::isNameValid); nameValidator.setMessage(i18n("install.new_game.malformed")); txtName.getValidators().addAll(hasVersion, nameValidator); - btnInstall.disableProperty().bind(Bindings.createBooleanBinding(() -> !txtName.validate(), + installable.bind(Bindings.createBooleanBinding(() -> txtName.validate(), txtName.textProperty())); txtName.setText(gameVersion); - Node[] buttons = new Node[]{btnGame, btnFabric, btnForge, btnLiteLoader, btnOptiFine}; - String[] libraryIds = new String[]{"game", "fabric", "forge", "liteloader", "optifine"}; + InstallerPageItem[] libraries = new InstallerPageItem[]{game, fabric, forge, liteLoader, optiFine}; - for (Node node : list.getChildren()) { - JFXDepthManager.setDepth(node, 1); - } - - for (int i = 0; i < libraryIds.length; ++i) { - String libraryId = libraryIds[i]; + for (InstallerPageItem library : libraries) { + String libraryId = library.id; if (libraryId.equals("game")) continue; - buttons[i].setOnMouseClicked(e -> + library.action.set(e -> controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer." + libraryId)), gameVersion, downloadProvider, libraryId, () -> controller.onPrev(false)))); + library.removeAction.set(e -> { + controller.getSettings().remove(libraryId); + reload(); + }); } } @@ -120,18 +98,24 @@ public class InstallersPage extends StackPane implements WizardPage { return ((RemoteVersion) controller.getSettings().get(id)).getSelfVersion(); } + protected void reload() { + InstallerPageItem[] libraries = new InstallerPageItem[]{game, fabric, forge, liteLoader, optiFine}; + + for (InstallerPageItem library : libraries) { + String libraryId = library.id; + if (controller.getSettings().containsKey(libraryId)) { + library.label.set(i18n("install.installer.version", i18n("install.installer." + libraryId)) + ": " + getVersion(libraryId)); + library.removable.set(true); + } else { + library.label.setValue(i18n("install.installer.not_installed", i18n("install.installer." + libraryId))); + library.removable.set(false); + } + } + } + @Override public void onNavigate(Map settings) { - Label[] labels = new Label[]{lblGame, lblFabric, lblForge, lblLiteLoader, lblOptiFine}; - String[] libraryIds = new String[]{"game", "fabric", "forge", "liteloader", "optifine"}; - - for (int i = 0; i < libraryIds.length; ++i) { - String libraryId = libraryIds[i]; - if (controller.getSettings().containsKey(libraryId)) - labels[i].setText(i18n("install.installer.version", i18n("install.installer." + libraryId)) + ": " + getVersion(libraryId)); - else - labels[i].setText(i18n("install.installer.not_installed", i18n("install.installer." + libraryId))); - } + reload(); } @Override @@ -143,4 +127,108 @@ public class InstallersPage extends StackPane implements WizardPage { controller.getSettings().put("name", txtName.getText()); controller.onFinish(); } + + @Override + protected Skin createDefaultSkin() { + return new InstallersPageSkin(this); + } + + protected static class InstallerPageItem { + String id; + StringProperty label = new SimpleStringProperty(); + BooleanProperty removable = new SimpleBooleanProperty(); + ObjectProperty> removeAction = new SimpleObjectProperty<>(); + ObjectProperty> action = new SimpleObjectProperty<>(); + + public InstallerPageItem(String id) { + this.id = id; + } + } + + protected static class InstallersPageSkin extends SkinBase { + + protected static class InstallersPageItemSkin extends BorderPane { + final ImageView imageView; + final Label label; + + InstallersPageItemSkin(String imageUrl, InstallerPageItem item, boolean clickable) { + setPadding(new Insets(8)); + getStyleClass().add("card"); + + setLeft(imageView = new ImageView(new Image(imageUrl, 32, 32, true, true))); + setCenter(label = new Label()); + label.textProperty().bind(item.label); + BorderPane.setMargin(label, new Insets(0, 0, 0, 8)); + BorderPane.setAlignment(label, Pos.CENTER_LEFT); + + if (clickable) { + HBox right = new HBox(); + right.setAlignment(Pos.CENTER_RIGHT); + setRight(right); + JFXButton closeButton = new JFXButton(); + closeButton.setGraphic(SVG.close(Theme.blackFillBinding(), -1, -1)); + right.getChildren().add(closeButton); + closeButton.getStyleClass().add("toggle-icon4"); + closeButton.visibleProperty().bind(item.removable); + closeButton.onMouseClickedProperty().bind(item.removeAction); + onMouseClickedProperty().bind(item.action); + JFXButton arrowButton = new JFXButton(); + arrowButton.setGraphic(SVG.arrowRight(Theme.blackFillBinding(), -1, -1)); + arrowButton.onMouseClickedProperty().bind(item.action); + arrowButton.getStyleClass().add("toggle-icon4"); + right.getChildren().add(arrowButton); + setCursor(Cursor.HAND); + } + } + } + + /** + * Constructor for all SkinBase instances. + * + * @param control The control for which this Skin should attach to. + */ + protected InstallersPageSkin(InstallersPage control) { + super(control); + + BorderPane root = new BorderPane(); + root.setPadding(new Insets(16)); + + VBox list = new VBox(8); + root.setCenter(list); + { + HBox versionNamePane = new HBox(8); + versionNamePane.setAlignment(Pos.CENTER_LEFT); + versionNamePane.getStyleClass().add("card"); + versionNamePane.setPadding(new Insets(20, 8, 20, 16)); + + versionNamePane.getChildren().add(new Label(i18n("archive.name"))); + + control.txtName.setMaxWidth(300); + versionNamePane.getChildren().add(control.txtName); + list.getChildren().add(versionNamePane); + } + + InstallersPageItemSkin game = new InstallersPageItemSkin("/assets/img/grass.png", control.game, false); + InstallersPageItemSkin fabric = new InstallersPageItemSkin("/assets/img/fabric.png", control.fabric, true); + InstallersPageItemSkin forge = new InstallersPageItemSkin("/assets/img/forge.png", control.forge, true); + InstallersPageItemSkin liteLoader = new InstallersPageItemSkin("/assets/img/chicken.png", control.liteLoader, true); + InstallersPageItemSkin optiFine = new InstallersPageItemSkin("/assets/img/command.png", control.optiFine, true); + list.getChildren().addAll(game, fabric, forge, liteLoader, optiFine); + list.getChildren().forEach(node -> JFXDepthManager.setDepth(node, 1)); + + { + JFXButton installButton = new JFXButton(i18n("button.install")); + installButton.disableProperty().bind(control.installable.not()); + installButton.getStyleClass().add("jfx-button-raised"); + installButton.setButtonType(JFXButton.ButtonType.RAISED); + installButton.setPrefWidth(100); + installButton.setPrefHeight(40); + installButton.setOnMouseClicked(e -> control.onInstall()); + BorderPane.setAlignment(installButton, Pos.CENTER_RIGHT); + root.setBottom(installButton); + } + + getChildren().setAll(root); + } + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java index e901da89a..b91820452 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java @@ -84,6 +84,8 @@ public final class UpdateInstallerWizardProvider implements WizardProvider { if ("game".equals(remoteVersion.getLibraryId())) { stages.add("hmcl.install.assets"); } + } else if (value instanceof RemoveVersionAction) { + ret = ret.thenComposeAsync(version -> dependencyManager.removeLibraryAsync(version, ((RemoveVersionAction) value).libraryId)); } } @@ -102,7 +104,7 @@ public final class UpdateInstallerWizardProvider implements WizardProvider { String newGameVersion = ((RemoteVersion) settings.get(libraryId)).getSelfVersion(); controller.onNext(new AdditionalInstallersPage(newGameVersion, version, controller, profile.getRepository(), provider)); } else { - Controllers.confirmDialog(i18n("install.change_version.confirm", i18n("install.installer." + libraryId), oldLibraryVersion, ((RemoteVersion) settings.get(libraryId)).getSelfVersion()), + Controllers.confirm(i18n("install.change_version.confirm", i18n("install.installer." + libraryId), oldLibraryVersion, ((RemoteVersion) settings.get(libraryId)).getSelfVersion()), i18n("install.change_version"), controller::onFinish, controller::onCancel); } }); @@ -167,4 +169,12 @@ public final class UpdateInstallerWizardProvider implements WizardProvider { Controllers.dialog(StringUtils.getStackTrace(exception), i18n("install.failed"), MessageDialogPane.MessageType.ERROR, next); } } + + public static class RemoveVersionAction { + private final String libraryId; + + public RemoveVersionAction(String libraryId) { + this.libraryId = libraryId; + } + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java index 7e157becb..336f3c912 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java @@ -122,6 +122,7 @@ public class InstallerListPage extends ListPageBase { private void doInstallOffline(File file) { Task task = profile.getDependency().installLibraryAsync(version, file.toPath()) + .thenComposeAsync(profile.getRepository()::save) .thenComposeAsync(profile.getRepository().refreshVersionsAsync()); task.setName(i18n("install.installer.install_offline")); TaskExecutor executor = task.executor(new TaskListener() { diff --git a/HMCL/src/main/resources/assets/fxml/download/installers.fxml b/HMCL/src/main/resources/assets/fxml/download/installers.fxml deleted file mode 100644 index f18b275af..000000000 --- a/HMCL/src/main/resources/assets/fxml/download/installers.fxml +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - -
- - - - - - - - - -
-
-
- - - - - - -
-
- - - -
- - - - - - -
-
- - - -
- - - - - - -
-
- - - -
- - - - - - -
-
- - - -
-
-
- - - - - -
-
diff --git a/HMCL/src/main/resources/assets/fxml/version/installer-item.fxml b/HMCL/src/main/resources/assets/fxml/version/installer-item.fxml deleted file mode 100644 index 087cb31fa..000000000 --- a/HMCL/src/main/resources/assets/fxml/version/installer-item.fxml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - -
- - -
- - - - - - - -
diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java index de3fc72a3..1c58e545e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultDependencyManager.java @@ -132,8 +132,7 @@ public class DefaultDependencyManager extends AbstractDependencyManager { throw new UnsupportedLibraryInstallerException(); }) - .thenApplyAsync(oldVersion::addPatch) - .thenComposeAsync(repository::save); + .thenApplyAsync(oldVersion::addPatch); } public static class UnsupportedLibraryInstallerException extends Exception {