diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPage.java index effce1734..556b2023d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPage.java @@ -17,7 +17,9 @@ */ package org.jackhuang.hmcl.ui; +import javafx.beans.property.BooleanProperty; import javafx.beans.property.ListProperty; +import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleListProperty; import javafx.collections.FXCollections; import javafx.scene.Node; @@ -26,6 +28,7 @@ import javafx.scene.control.Skin; public abstract class ListPage extends Control { private final ListProperty items = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final BooleanProperty loading = new SimpleBooleanProperty(false); public abstract void add(); @@ -33,6 +36,10 @@ public abstract class ListPage extends Control { return items; } + public BooleanProperty loadingProperty() { + return loading; + } + @Override protected Skin createDefaultSkin() { return new ListPageSkin(this); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java index dfed6b147..cf530fb05 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java @@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXScrollPane; +import com.jfoenix.controls.JFXSpinner; import javafx.beans.binding.Bindings; import javafx.geometry.Insets; import javafx.geometry.Pos; @@ -33,43 +34,57 @@ public class ListPageSkin extends SkinBase { public ListPageSkin(ListPage skinnable) { super(skinnable); - StackPane root = new StackPane(); + StackPane rootPane = new StackPane(); - ScrollPane scrollPane = new ScrollPane(); + JFXSpinner spinner = new JFXSpinner(); + spinner.setRadius(16); + spinner.getStyleClass().setAll("materialDesign-purple", "first-spinner"); + + StackPane contentPane = new StackPane(); { - scrollPane.setFitToWidth(true); + ScrollPane scrollPane = new ScrollPane(); + { + scrollPane.setFitToWidth(true); - VBox accountList = new VBox(); - accountList.maxWidthProperty().bind(scrollPane.widthProperty()); - accountList.setSpacing(10); - accountList.setStyle("-fx-padding: 10 10 10 10;"); + VBox accountList = new VBox(); + accountList.maxWidthProperty().bind(scrollPane.widthProperty()); + accountList.setSpacing(10); + accountList.setStyle("-fx-padding: 10 10 10 10;"); - Bindings.bindContent(accountList.getChildren(), skinnable.itemsProperty()); + Bindings.bindContent(accountList.getChildren(), skinnable.itemsProperty()); - scrollPane.setContent(accountList); - JFXScrollPane.smoothScrolling(scrollPane); + scrollPane.setContent(accountList); + JFXScrollPane.smoothScrolling(scrollPane); + } + + VBox vBox = new VBox(); + { + vBox.setAlignment(Pos.BOTTOM_RIGHT); + vBox.setPickOnBounds(false); + vBox.setPadding(new Insets(15)); + vBox.setSpacing(15); + + JFXButton btnAdd = new JFXButton(); + FXUtils.setLimitWidth(btnAdd, 40); + FXUtils.setLimitHeight(btnAdd, 40); + btnAdd.getStyleClass().setAll("jfx-button-raised-round"); + btnAdd.setButtonType(JFXButton.ButtonType.RAISED); + btnAdd.setGraphic(SVG.plus(Theme.whiteFillBinding(), -1, -1)); + btnAdd.setOnMouseClicked(e -> skinnable.add()); + + vBox.getChildren().setAll(btnAdd); + } + + contentPane.getChildren().setAll(scrollPane, vBox); } - VBox vBox = new VBox(); - { - vBox.setAlignment(Pos.BOTTOM_RIGHT); - vBox.setPickOnBounds(false); - vBox.setPadding(new Insets(15)); - vBox.setSpacing(15); + rootPane.getChildren().setAll(contentPane); - JFXButton btnAdd = new JFXButton(); - FXUtils.setLimitWidth(btnAdd, 40); - FXUtils.setLimitHeight(btnAdd, 40); - btnAdd.getStyleClass().setAll("jfx-button-raised-round"); - btnAdd.setButtonType(JFXButton.ButtonType.RAISED); - btnAdd.setGraphic(SVG.plus(Theme.whiteFillBinding(), -1, -1)); - btnAdd.setOnMouseClicked(e -> skinnable.add()); + skinnable.loadingProperty().addListener((a, b, newValue) -> { + if (newValue) rootPane.getChildren().setAll(spinner); + else rootPane.getChildren().setAll(contentPane); + }); - vBox.getChildren().setAll(btnAdd); - } - - root.getChildren().setAll(scrollPane, vBox); - - getChildren().setAll(root); + getChildren().setAll(rootPane); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java similarity index 76% rename from HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerController.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java index 941096802..85fc47e2c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java @@ -17,9 +17,6 @@ */ package org.jackhuang.hmcl.ui.versions; -import javafx.fxml.FXML; -import javafx.scene.control.ScrollPane; -import javafx.scene.layout.VBox; import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.download.MaintainTask; import org.jackhuang.hmcl.download.game.VersionJsonSaveTask; @@ -30,8 +27,8 @@ import org.jackhuang.hmcl.setting.Profile; 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.InstallerItem; +import org.jackhuang.hmcl.ui.ListPage; import org.jackhuang.hmcl.ui.download.InstallerWizardProvider; import java.util.LinkedList; @@ -41,26 +38,16 @@ import java.util.function.Function; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public class InstallerController { +public class InstallerListPage extends ListPage { private Profile profile; private String versionId; private Version version; - @FXML - private ScrollPane scrollPane; - @FXML private VBox contentPane; - - @FXML - private void initialize() { - FXUtils.smoothScrolling(scrollPane); - } public void loadVersion(Profile profile, String versionId) { this.profile = profile; this.versionId = versionId; this.version = profile.getRepository().getResolvedVersion(versionId); - contentPane.getChildren().clear(); - LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version); Function> removeAction = library -> x -> { @@ -73,13 +60,13 @@ public class InstallerController { .start(); }; - analyzer.getForge().ifPresent(library -> contentPane.getChildren().add(new InstallerItem("Forge", library.getVersion(), removeAction.apply(library)))); - analyzer.getLiteLoader().ifPresent(library -> contentPane.getChildren().add(new InstallerItem("LiteLoader", library.getVersion(), removeAction.apply(library)))); - analyzer.getOptiFine().ifPresent(library -> contentPane.getChildren().add(new InstallerItem("OptiFine", library.getVersion(), removeAction.apply(library)))); + analyzer.getForge().ifPresent(library -> itemsProperty().add(new InstallerItem("Forge", library.getVersion(), removeAction.apply(library)))); + analyzer.getLiteLoader().ifPresent(library -> itemsProperty().add(new InstallerItem("LiteLoader", library.getVersion(), removeAction.apply(library)))); + analyzer.getOptiFine().ifPresent(library -> itemsProperty().add(new InstallerItem("OptiFine", library.getVersion(), removeAction.apply(library)))); } - @FXML - private void onAdd() { + @Override + public void add() { Optional gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)); if (!gameVersion.isPresent()) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java similarity index 83% rename from HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModController.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java index 57e172a8d..799573aba 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java @@ -18,13 +18,8 @@ package org.jackhuang.hmcl.ui.versions; import com.jfoenix.concurrency.JFXUtilities; -import com.jfoenix.controls.JFXSpinner; import com.jfoenix.controls.JFXTabPane; -import javafx.fxml.FXML; -import javafx.scene.control.ScrollPane; import javafx.scene.input.TransferMode; -import javafx.scene.layout.StackPane; -import javafx.scene.layout.VBox; import javafx.stage.FileChooser; import org.jackhuang.hmcl.mod.ModInfo; import org.jackhuang.hmcl.mod.ModManager; @@ -32,6 +27,7 @@ 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.ListPage; import org.jackhuang.hmcl.ui.ModItem; import org.jackhuang.hmcl.util.FileUtils; import org.jackhuang.hmcl.util.Logging; @@ -47,32 +43,20 @@ import java.util.stream.Collectors; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public final class ModController { - @FXML - private ScrollPane scrollPane; - - @FXML private StackPane rootPane; - - @FXML private VBox modPane; - - @FXML private StackPane contentPane; - @FXML private JFXSpinner spinner; +public final class ModListPage extends ListPage { private JFXTabPane parentTab; private ModManager modManager; private String versionId; - @FXML - private void initialize() { - FXUtils.smoothScrolling(scrollPane); - - rootPane.setOnDragOver(event -> { - if (event.getGestureSource() != rootPane && event.getDragboard().hasFiles()) + public ModListPage() { + setOnDragOver(event -> { + if (event.getGestureSource() != this && event.getDragboard().hasFiles()) event.acceptTransferModes(TransferMode.COPY_OR_MOVE); event.consume(); }); - rootPane.setOnDragDropped(event -> { + setOnDragDropped(event -> { List files = event.getDragboard().getFiles(); if (files != null) { Collection mods = files.stream() @@ -98,11 +82,8 @@ public final class ModController { this.modManager = modManager; this.versionId = versionId; Task.of(variables -> { - synchronized (ModController.this) { - JFXUtilities.runInFX(() -> { - rootPane.getChildren().remove(contentPane); - spinner.setVisible(true); - }); + synchronized (ModListPage.this) { + JFXUtilities.runInFX(() -> loadingProperty().set(true)); modManager.refreshMods(versionId); @@ -130,18 +111,17 @@ public final class ModController { variables.set("list", list); } }).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> { - rootPane.getChildren().add(contentPane); - spinner.setVisible(false); + loadingProperty().set(false); if (isDependentsSucceeded) FXUtils.onWeakChangeAndOperate(parentTab.getSelectionModel().selectedItemProperty(), newValue -> { - if (newValue != null && newValue.getUserData() == ModController.this) - modPane.getChildren().setAll(variables.>get("list")); + if (newValue != null && newValue.getUserData() == ModListPage.this) + itemsProperty().setAll(variables.>get("list")); }); }).start(); } - @FXML - private void onAdd() { + @Override + public void add() { FileChooser chooser = new FileChooser(); chooser.setTitle(i18n("mods.choose_mod")); chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n("extension.mod"), "*.jar", "*.zip", "*.litemod")); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java index 39dca326b..51aa9182a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java @@ -44,9 +44,9 @@ public final class VersionPage extends StackPane implements DecoratorPage { @FXML private Tab modTab; @FXML - private ModController modController; + private ModListPage mod; @FXML - private InstallerController installerController; + private InstallerListPage installer; @FXML private JFXListView browseList; @FXML @@ -93,10 +93,10 @@ public final class VersionPage extends StackPane implements DecoratorPage { title.set(i18n("settings.game") + " - " + id); versionSettings.loadVersionSetting(profile, id); - modController.setParentTab(tabPane); - modTab.setUserData(modController); - modController.loadMods(profile.getModManager(), id); - installerController.loadVersion(profile, id); + mod.setParentTab(tabPane); + modTab.setUserData(mod); + mod.loadMods(profile.getModManager(), id); + installer.loadVersion(profile, id); } @FXML diff --git a/HMCL/src/main/resources/assets/fxml/version/installer.fxml b/HMCL/src/main/resources/assets/fxml/version/installer.fxml deleted file mode 100644 index e29ff41fa..000000000 --- a/HMCL/src/main/resources/assets/fxml/version/installer.fxml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/HMCL/src/main/resources/assets/fxml/version/mod.fxml b/HMCL/src/main/resources/assets/fxml/version/mod.fxml deleted file mode 100644 index aaa8d0828..000000000 --- a/HMCL/src/main/resources/assets/fxml/version/mod.fxml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/HMCL/src/main/resources/assets/fxml/version/version.fxml b/HMCL/src/main/resources/assets/fxml/version/version.fxml index bddb50096..13ab04457 100644 --- a/HMCL/src/main/resources/assets/fxml/version/version.fxml +++ b/HMCL/src/main/resources/assets/fxml/version/version.fxml @@ -5,6 +5,8 @@ + + - + - + diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java new file mode 100644 index 000000000..bc8eedc11 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -0,0 +1,61 @@ +package org.jackhuang.hmcl.game; + +import org.jackhuang.hmcl.util.CompressingUtils; +import org.jackhuang.hmcl.util.FileUtils; +import org.jackhuang.hmcl.util.Unzipper; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; + +public class World { + + private final Path file; + private final String name; + + public World(Path file) throws IOException { + this.file = file; + + if (Files.isDirectory(file)) + name = loadFromDirectory(); + else if (Files.isRegularFile(file)) + name = loadFromZip(); + else + throw new IOException("Path " + file + " cannot be recognized as a Minecraft world"); + } + + private String loadFromDirectory() { + return FileUtils.getName(file); + } + + private String loadFromZip() throws IOException { + try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(file)) { + Path root = Files.list(fs.getPath("/")).filter(Files::isDirectory).findAny() + .orElseThrow(() -> new IOException("Not a valid world zip file")); + + Path levelDat = root.resolve("level.dat"); + if (!Files.exists(levelDat)) + throw new FileNotFoundException("Not a valid world zip file since level.dat cannot be found."); + + return FileUtils.getName(root); + } + } + + public void install(Path savesDir) throws IOException { + Path worldDir = savesDir.resolve(name); + if (Files.isDirectory(worldDir)) { + throw new FileAlreadyExistsException("World already exists"); + } + + if (Files.isRegularFile(file)) { + new Unzipper(file, savesDir) + .setSubDirectory("/" + name + "/") + .unzip(); + } else if (Files.isDirectory(file)) { + FileUtils.copyDirectory(file, worldDir); + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/FileUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/FileUtils.java index 27dcba1fb..22c01febf 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/FileUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/FileUtils.java @@ -22,8 +22,8 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.Method; import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; import java.util.LinkedList; import java.util.List; import java.util.Objects; @@ -54,6 +54,10 @@ public final class FileUtils { return StringUtils.addPrefix(StringUtils.removeSuffix(path, "/", "\\"), "/"); } + public static String getName(Path path) { + return StringUtils.removeSuffix(path.getFileName().toString(), "/", "\\"); + } + public static String readText(File file) throws IOException { return readText(file, UTF_8); } @@ -97,6 +101,26 @@ public final class FileUtils { return Lang.test(() -> deleteDirectory(directory)); } + public static void copyDirectory(Path src, Path dest) throws IOException { + Files.walkFileTree(src, new SimpleFileVisitor(){ + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Path destFile = dest.resolve(src.relativize(file)); + Files.copy(file, destFile); + + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + Path destDir = dest.resolve(src.relativize(dir)); + Files.createDirectory(destDir); + + return FileVisitResult.CONTINUE; + } + }); + } + public static boolean moveToTrash(File file) { try { java.awt.Desktop desktop = java.awt.Desktop.getDesktop();