Refactor ModListPage and InstallerListPage

This commit is contained in:
huanghongxun 2018-09-08 00:36:57 +08:00
parent d8a34aac1c
commit daa1a38a63
10 changed files with 168 additions and 141 deletions

View File

@ -17,7 +17,9 @@
*/ */
package org.jackhuang.hmcl.ui; package org.jackhuang.hmcl.ui;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ListProperty; import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleListProperty; import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.scene.Node; import javafx.scene.Node;
@ -26,6 +28,7 @@ import javafx.scene.control.Skin;
public abstract class ListPage<T extends Node> extends Control { public abstract class ListPage<T extends Node> extends Control {
private final ListProperty<T> items = new SimpleListProperty<>(FXCollections.observableArrayList()); private final ListProperty<T> items = new SimpleListProperty<>(FXCollections.observableArrayList());
private final BooleanProperty loading = new SimpleBooleanProperty(false);
public abstract void add(); public abstract void add();
@ -33,6 +36,10 @@ public abstract class ListPage<T extends Node> extends Control {
return items; return items;
} }
public BooleanProperty loadingProperty() {
return loading;
}
@Override @Override
protected Skin<?> createDefaultSkin() { protected Skin<?> createDefaultSkin() {
return new ListPageSkin(this); return new ListPageSkin(this);

View File

@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXScrollPane; import com.jfoenix.controls.JFXScrollPane;
import com.jfoenix.controls.JFXSpinner;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos; import javafx.geometry.Pos;
@ -33,43 +34,57 @@ public class ListPageSkin extends SkinBase<ListPage> {
public ListPageSkin(ListPage<?> skinnable) { public ListPageSkin(ListPage<?> skinnable) {
super(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(); VBox accountList = new VBox();
accountList.maxWidthProperty().bind(scrollPane.widthProperty()); accountList.maxWidthProperty().bind(scrollPane.widthProperty());
accountList.setSpacing(10); accountList.setSpacing(10);
accountList.setStyle("-fx-padding: 10 10 10 10;"); accountList.setStyle("-fx-padding: 10 10 10 10;");
Bindings.bindContent(accountList.getChildren(), skinnable.itemsProperty()); Bindings.bindContent(accountList.getChildren(), skinnable.itemsProperty());
scrollPane.setContent(accountList); scrollPane.setContent(accountList);
JFXScrollPane.smoothScrolling(scrollPane); 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(); rootPane.getChildren().setAll(contentPane);
{
vBox.setAlignment(Pos.BOTTOM_RIGHT);
vBox.setPickOnBounds(false);
vBox.setPadding(new Insets(15));
vBox.setSpacing(15);
JFXButton btnAdd = new JFXButton(); skinnable.loadingProperty().addListener((a, b, newValue) -> {
FXUtils.setLimitWidth(btnAdd, 40); if (newValue) rootPane.getChildren().setAll(spinner);
FXUtils.setLimitHeight(btnAdd, 40); else rootPane.getChildren().setAll(contentPane);
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); getChildren().setAll(rootPane);
}
root.getChildren().setAll(scrollPane, vBox);
getChildren().setAll(root);
} }
} }

View File

@ -17,9 +17,6 @@
*/ */
package org.jackhuang.hmcl.ui.versions; 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.LibraryAnalyzer;
import org.jackhuang.hmcl.download.MaintainTask; import org.jackhuang.hmcl.download.MaintainTask;
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask; 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.Schedulers;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.InstallerItem; import org.jackhuang.hmcl.ui.InstallerItem;
import org.jackhuang.hmcl.ui.ListPage;
import org.jackhuang.hmcl.ui.download.InstallerWizardProvider; import org.jackhuang.hmcl.ui.download.InstallerWizardProvider;
import java.util.LinkedList; import java.util.LinkedList;
@ -41,26 +38,16 @@ import java.util.function.Function;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class InstallerController { public class InstallerListPage extends ListPage<InstallerItem> {
private Profile profile; private Profile profile;
private String versionId; private String versionId;
private Version version; 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) { public void loadVersion(Profile profile, String versionId) {
this.profile = profile; this.profile = profile;
this.versionId = versionId; this.versionId = versionId;
this.version = profile.getRepository().getResolvedVersion(versionId); this.version = profile.getRepository().getResolvedVersion(versionId);
contentPane.getChildren().clear();
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version); LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version);
Function<Library, Consumer<InstallerItem>> removeAction = library -> x -> { Function<Library, Consumer<InstallerItem>> removeAction = library -> x -> {
@ -73,13 +60,13 @@ public class InstallerController {
.start(); .start();
}; };
analyzer.getForge().ifPresent(library -> contentPane.getChildren().add(new InstallerItem("Forge", library.getVersion(), removeAction.apply(library)))); analyzer.getForge().ifPresent(library -> itemsProperty().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.getLiteLoader().ifPresent(library -> itemsProperty().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.getOptiFine().ifPresent(library -> itemsProperty().add(new InstallerItem("OptiFine", library.getVersion(), removeAction.apply(library))));
} }
@FXML @Override
private void onAdd() { public void add() {
Optional<String> gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)); Optional<String> gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version));
if (!gameVersion.isPresent()) if (!gameVersion.isPresent())

View File

@ -18,13 +18,8 @@
package org.jackhuang.hmcl.ui.versions; package org.jackhuang.hmcl.ui.versions;
import com.jfoenix.concurrency.JFXUtilities; import com.jfoenix.concurrency.JFXUtilities;
import com.jfoenix.controls.JFXSpinner;
import com.jfoenix.controls.JFXTabPane; import com.jfoenix.controls.JFXTabPane;
import javafx.fxml.FXML;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.TransferMode; import javafx.scene.input.TransferMode;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import org.jackhuang.hmcl.mod.ModInfo; import org.jackhuang.hmcl.mod.ModInfo;
import org.jackhuang.hmcl.mod.ModManager; 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.task.Task;
import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.ListPage;
import org.jackhuang.hmcl.ui.ModItem; import org.jackhuang.hmcl.ui.ModItem;
import org.jackhuang.hmcl.util.FileUtils; import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.Logging;
@ -47,32 +43,20 @@ import java.util.stream.Collectors;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class ModController { public final class ModListPage extends ListPage<ModItem> {
@FXML
private ScrollPane scrollPane;
@FXML private StackPane rootPane;
@FXML private VBox modPane;
@FXML private StackPane contentPane;
@FXML private JFXSpinner spinner;
private JFXTabPane parentTab; private JFXTabPane parentTab;
private ModManager modManager; private ModManager modManager;
private String versionId; private String versionId;
@FXML public ModListPage() {
private void initialize() { setOnDragOver(event -> {
FXUtils.smoothScrolling(scrollPane); if (event.getGestureSource() != this && event.getDragboard().hasFiles())
rootPane.setOnDragOver(event -> {
if (event.getGestureSource() != rootPane && event.getDragboard().hasFiles())
event.acceptTransferModes(TransferMode.COPY_OR_MOVE); event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
event.consume(); event.consume();
}); });
rootPane.setOnDragDropped(event -> { setOnDragDropped(event -> {
List<File> files = event.getDragboard().getFiles(); List<File> files = event.getDragboard().getFiles();
if (files != null) { if (files != null) {
Collection<File> mods = files.stream() Collection<File> mods = files.stream()
@ -98,11 +82,8 @@ public final class ModController {
this.modManager = modManager; this.modManager = modManager;
this.versionId = versionId; this.versionId = versionId;
Task.of(variables -> { Task.of(variables -> {
synchronized (ModController.this) { synchronized (ModListPage.this) {
JFXUtilities.runInFX(() -> { JFXUtilities.runInFX(() -> loadingProperty().set(true));
rootPane.getChildren().remove(contentPane);
spinner.setVisible(true);
});
modManager.refreshMods(versionId); modManager.refreshMods(versionId);
@ -130,18 +111,17 @@ public final class ModController {
variables.set("list", list); variables.set("list", list);
} }
}).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> { }).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
rootPane.getChildren().add(contentPane); loadingProperty().set(false);
spinner.setVisible(false);
if (isDependentsSucceeded) if (isDependentsSucceeded)
FXUtils.onWeakChangeAndOperate(parentTab.getSelectionModel().selectedItemProperty(), newValue -> { FXUtils.onWeakChangeAndOperate(parentTab.getSelectionModel().selectedItemProperty(), newValue -> {
if (newValue != null && newValue.getUserData() == ModController.this) if (newValue != null && newValue.getUserData() == ModListPage.this)
modPane.getChildren().setAll(variables.<List<ModItem>>get("list")); itemsProperty().setAll(variables.<List<ModItem>>get("list"));
}); });
}).start(); }).start();
} }
@FXML @Override
private void onAdd() { public void add() {
FileChooser chooser = new FileChooser(); FileChooser chooser = new FileChooser();
chooser.setTitle(i18n("mods.choose_mod")); chooser.setTitle(i18n("mods.choose_mod"));
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n("extension.mod"), "*.jar", "*.zip", "*.litemod")); chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n("extension.mod"), "*.jar", "*.zip", "*.litemod"));

View File

@ -44,9 +44,9 @@ public final class VersionPage extends StackPane implements DecoratorPage {
@FXML @FXML
private Tab modTab; private Tab modTab;
@FXML @FXML
private ModController modController; private ModListPage mod;
@FXML @FXML
private InstallerController installerController; private InstallerListPage installer;
@FXML @FXML
private JFXListView<?> browseList; private JFXListView<?> browseList;
@FXML @FXML
@ -93,10 +93,10 @@ public final class VersionPage extends StackPane implements DecoratorPage {
title.set(i18n("settings.game") + " - " + id); title.set(i18n("settings.game") + " - " + id);
versionSettings.loadVersionSetting(profile, id); versionSettings.loadVersionSetting(profile, id);
modController.setParentTab(tabPane); mod.setParentTab(tabPane);
modTab.setUserData(modController); modTab.setUserData(mod);
modController.loadMods(profile.getModManager(), id); mod.loadMods(profile.getModManager(), id);
installerController.loadVersion(profile, id); installer.loadVersion(profile, id);
} }
@FXML @FXML

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.JFXButton?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<StackPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.jackhuang.hmcl.ui.versions.InstallerController">
<ScrollPane fx:id="scrollPane" fitToWidth="true" fitToHeight="true">
<VBox fx:id="contentPane" spacing="10" style="-fx-padding: 20;">
</VBox>
</ScrollPane>
<VBox style="-fx-padding: 15;" spacing="15" pickOnBounds="false" alignment="BOTTOM_RIGHT">
<JFXButton prefWidth="40" prefHeight="40" buttonType="RAISED" onMouseClicked="#onAdd" styleClass="jfx-button-raised-round">
<graphic>
<fx:include source="/assets/svg/plus.fxml" />
</graphic>
</JFXButton>
</VBox>
</StackPane>

View File

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.JFXButton?>
<?import com.jfoenix.controls.JFXSpinner?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<StackPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:id="rootPane"
fx:controller="org.jackhuang.hmcl.ui.versions.ModController">
<JFXSpinner fx:id="spinner" style="-fx-radius:16" styleClass="materialDesign-purple, first-spinner" />
<StackPane fx:id="contentPane">
<ScrollPane fx:id="scrollPane" fitToWidth="true" fitToHeight="true">
<VBox fx:id="modPane" spacing="10" style="-fx-padding: 20 20 70 20;">
</VBox>
</ScrollPane>
<VBox style="-fx-padding: 15;" spacing="15" pickOnBounds="false" alignment="BOTTOM_RIGHT">
<JFXButton prefWidth="40" prefHeight="40" buttonType="RAISED" onMouseClicked="#onAdd" styleClass="jfx-button-raised-round">
<graphic>
<fx:include source="/assets/svg/plus.fxml"/>
</graphic>
</JFXButton>
</VBox>
</StackPane>
</StackPane>

View File

@ -5,6 +5,8 @@
<?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?> <?import javafx.scene.layout.StackPane?>
<?import org.jackhuang.hmcl.ui.versions.VersionSettingsPage?> <?import org.jackhuang.hmcl.ui.versions.VersionSettingsPage?>
<?import org.jackhuang.hmcl.ui.versions.ModListPage?>
<?import org.jackhuang.hmcl.ui.versions.InstallerListPage?>
<fx:root xmlns="http://javafx.com/javafx" <fx:root xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
fx:id="rootPane" fx:id="rootPane"
@ -16,10 +18,10 @@
<VersionSettingsPage fx:id="versionSettings" /> <VersionSettingsPage fx:id="versionSettings" />
</Tab> </Tab>
<Tab fx:id="modTab" text="%mods"> <Tab fx:id="modTab" text="%mods">
<fx:include source="mod.fxml" fx:id="mod"/> <ModListPage fx:id="mod" />
</Tab> </Tab>
<Tab text="%settings.tabs.installers"> <Tab text="%settings.tabs.installers">
<fx:include source="installer.fxml" fx:id="installer"/> <InstallerListPage fx:id="installer" />
</Tab> </Tab>
</JFXTabPane> </JFXTabPane>

View File

@ -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);
}
}
}

View File

@ -22,8 +22,8 @@ import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.file.Files; import java.nio.file.*;
import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -54,6 +54,10 @@ public final class FileUtils {
return StringUtils.addPrefix(StringUtils.removeSuffix(path, "/", "\\"), "/"); 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 { public static String readText(File file) throws IOException {
return readText(file, UTF_8); return readText(file, UTF_8);
} }
@ -97,6 +101,26 @@ public final class FileUtils {
return Lang.test(() -> deleteDirectory(directory)); return Lang.test(() -> deleteDirectory(directory));
} }
public static void copyDirectory(Path src, Path dest) throws IOException {
Files.walkFileTree(src, new SimpleFileVisitor<Path>(){
@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) { public static boolean moveToTrash(File file) {
try { try {
java.awt.Desktop desktop = java.awt.Desktop.getDesktop(); java.awt.Desktop desktop = java.awt.Desktop.getDesktop();