mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-23 11:14:44 -04:00
Merge 2f71a1692eb01204695dee78b24e113dad62c378 into bd9ae189f83e33a6977bbe056774c851e96fe0a7
This commit is contained in:
commit
ba7a3d53c1
@ -212,6 +212,10 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
tab.select(modpackTab);
|
||||
}
|
||||
|
||||
public void showResourcepackDownloads() {
|
||||
tab.select(resourcePackTab);
|
||||
}
|
||||
|
||||
public DownloadListPage showModDownloads() {
|
||||
tab.select(modTab);
|
||||
return modTab.getNode();
|
||||
|
@ -0,0 +1,231 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.resourcepack.ResourcepackFile;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class ResourcepackListPage extends ListPageBase<ResourcepackListPage.ResourcepackItem> implements VersionPage.VersionLoadable {
|
||||
private Path resourcepackDirectory;
|
||||
|
||||
public ResourcepackListPage() {
|
||||
FXUtils.applyDragListener(this, file -> file.isFile() && file.getName().endsWith(".zip"), files -> addFiles(files.stream().map(File::toPath).collect(Collectors.toList())));
|
||||
}
|
||||
|
||||
private static Node createIcon(Path img) {
|
||||
ImageView imageView = new ImageView();
|
||||
imageView.setFitWidth(32);
|
||||
imageView.setFitHeight(32);
|
||||
|
||||
if (Files.exists(img)) {
|
||||
try (InputStream is = Files.newInputStream(img)) {
|
||||
Image image = new Image(is);
|
||||
imageView.setImage(image);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
if (imageView.getImage() == null) {
|
||||
imageView.setImage(FXUtils.newBuiltinImage("/assets/img/unknown_pack.png"));
|
||||
}
|
||||
|
||||
return imageView;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new ResourcepackListPageSkin(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadVersion(Profile profile, String version) {
|
||||
this.resourcepackDirectory = profile.getRepository().getResourcepacksDirectory(version);
|
||||
|
||||
try {
|
||||
if (!Files.exists(resourcepackDirectory)) {
|
||||
Files.createDirectories(resourcepackDirectory);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to create resourcepack directory", e);
|
||||
}
|
||||
refresh();
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
Task.runAsync(Schedulers.javafx(), this::load).whenComplete(Schedulers.javafx(), (result, exception) -> setLoading(false)).start();
|
||||
setLoading(true);
|
||||
}
|
||||
|
||||
public void addFiles(List<Path> files) {
|
||||
if (resourcepackDirectory == null) return;
|
||||
|
||||
try {
|
||||
for (Path file : files) {
|
||||
Path target = resourcepackDirectory.resolve(file.getFileName());
|
||||
if (!Files.exists(target)) {
|
||||
Files.copy(file, target);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Controllers.dialog(i18n("resourcepack.add.failed"), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
|
||||
LOG.warning("Failed to add resourcepacks", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void onAddFiles() {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle(i18n("resourcepack.add"));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("resourcepack"), "*.zip"));
|
||||
List<File> files = fileChooser.showOpenMultipleDialog(Controllers.getStage());
|
||||
if (files != null && !files.isEmpty()) {
|
||||
addFiles(files.stream().map(File::toPath).collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
|
||||
private void load() {
|
||||
itemsProperty().clear();
|
||||
if (resourcepackDirectory == null || !Files.exists(resourcepackDirectory)) return;
|
||||
|
||||
try (Stream<Path> stream = Files.list(resourcepackDirectory)) {
|
||||
stream.forEach(path -> {
|
||||
try {
|
||||
itemsProperty().add(new ResourcepackItem(ResourcepackFile.parse(path)));
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load resourcepacks " + path.getFileName(), e);
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to list resourcepacks directory", e);
|
||||
}
|
||||
|
||||
itemsProperty().sort(Comparator.comparing(item -> item.getFile().getName()));
|
||||
}
|
||||
|
||||
private void onDownload() {
|
||||
runInFX(() -> {
|
||||
Controllers.getDownloadPage().showResourcepackDownloads();
|
||||
Controllers.navigate(Controllers.getDownloadPage());
|
||||
});
|
||||
}
|
||||
|
||||
private static class ResourcepackListPageSkin extends ToolbarListPageSkin<ResourcepackListPage> {
|
||||
protected ResourcepackListPageSkin(ResourcepackListPage control) {
|
||||
super(control);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Node> initializeToolbar(ResourcepackListPage skinnable) {
|
||||
return Arrays.asList(createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh), createToolbarButton2(i18n("resourcepack.add"), SVG.ADD, skinnable::onAddFiles), createToolbarButton2(i18n("resourcepack.download"), SVG.DOWNLOAD, skinnable::onDownload));
|
||||
}
|
||||
}
|
||||
|
||||
public class ResourcepackItem extends Control {
|
||||
private final ResourcepackFile file;
|
||||
// final JFXCheckBox checkBox = new JFXCheckBox();
|
||||
|
||||
public ResourcepackItem(ResourcepackFile file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new ResourcepackItemSkin(this);
|
||||
}
|
||||
|
||||
public void onDelete() {
|
||||
try {
|
||||
if (file.getFile().isDirectory()) {
|
||||
FileUtils.deleteDirectory(file.getFile());
|
||||
} else {
|
||||
Files.delete(file.getFile().toPath());
|
||||
}
|
||||
ResourcepackListPage.this.refresh();
|
||||
} catch (IOException e) {
|
||||
Controllers.dialog(i18n("resourcepack.delete.failed", e.getMessage()), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
|
||||
LOG.warning("Failed to delete resourcepack", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void onReveal() {
|
||||
FXUtils.showFileInExplorer(file.getFile().toPath());
|
||||
}
|
||||
|
||||
public ResourcepackFile getFile() {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
private class ResourcepackItemSkin extends SkinBase<ResourcepackItem> {
|
||||
public ResourcepackItemSkin(ResourcepackItem item) {
|
||||
super(item);
|
||||
BorderPane root = new BorderPane();
|
||||
root.getStyleClass().add("md-list-cell");
|
||||
root.setPadding(new Insets(8));
|
||||
|
||||
HBox left = new HBox(8);
|
||||
left.setAlignment(Pos.CENTER);
|
||||
left.getChildren().addAll(createIcon(item.getFile().getIcon()));
|
||||
// left.getChildren().addAll(item.checkBox, createIcon(item.getFile().getIcon()));
|
||||
left.setPadding(new Insets(0, 8, 0, 0));
|
||||
// FXUtils.setLimitWidth(left, 64);
|
||||
FXUtils.setLimitWidth(left, 48);
|
||||
root.setLeft(left);
|
||||
|
||||
TwoLineListItem center = new TwoLineListItem();
|
||||
// center.setPadding(new Insets(0, 0, 0, 8));
|
||||
center.setTitle(item.getFile().getName());
|
||||
center.setSubtitle(item.getFile().getDescription());
|
||||
root.setCenter(center);
|
||||
|
||||
HBox right = new HBox(8);
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
JFXButton btnReveal = new JFXButton();
|
||||
FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_file_manager"));
|
||||
btnReveal.getStyleClass().add("toggle-icon4");
|
||||
btnReveal.setGraphic(SVG.FOLDER_OPEN.createIcon(Theme.blackFill(), -1));
|
||||
btnReveal.setOnAction(event -> item.onReveal());
|
||||
|
||||
JFXButton btnDelete = new JFXButton();
|
||||
btnDelete.getStyleClass().add("toggle-icon4");
|
||||
btnDelete.setGraphic(SVG.DELETE_FOREVER.createIcon(Theme.blackFill(), -1));
|
||||
btnDelete.setOnAction(event -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), item::onDelete, null));
|
||||
right.getChildren().setAll(btnReveal, btnDelete);
|
||||
root.setRight(right);
|
||||
|
||||
this.getChildren().add(new RipplerContainer(root));
|
||||
}
|
||||
}
|
||||
}
|
@ -59,6 +59,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
private final TabHeader.Tab<ModListPage> modListTab = new TabHeader.Tab<>("modListTab");
|
||||
private final TabHeader.Tab<WorldListPage> worldListTab = new TabHeader.Tab<>("worldList");
|
||||
private final TabHeader.Tab<SchematicsPage> schematicsTab = new TabHeader.Tab<>("schematicsTab");
|
||||
private final TabHeader.Tab<ResourcepackListPage> resourcePackTab = new TabHeader.Tab<>("resourcePackTab");
|
||||
private final TransitionPane transitionPane = new TransitionPane();
|
||||
private final BooleanProperty currentVersionUpgradable = new SimpleBooleanProperty();
|
||||
private final ObjectProperty<Profile.ProfileVersion> version = new SimpleObjectProperty<>();
|
||||
@ -72,8 +73,9 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
modListTab.setNodeSupplier(loadVersionFor(ModListPage::new));
|
||||
worldListTab.setNodeSupplier(loadVersionFor(WorldListPage::new));
|
||||
schematicsTab.setNodeSupplier(loadVersionFor(SchematicsPage::new));
|
||||
resourcePackTab.setNodeSupplier(loadVersionFor(ResourcepackListPage::new));
|
||||
|
||||
tab = new TabHeader(versionSettingsTab, installerListTab, modListTab, worldListTab, schematicsTab);
|
||||
tab = new TabHeader(versionSettingsTab, installerListTab, modListTab, worldListTab, schematicsTab, resourcePackTab);
|
||||
|
||||
addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated);
|
||||
|
||||
@ -138,6 +140,8 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
worldListTab.getNode().loadVersion(profile, version);
|
||||
if (schematicsTab.isInitialized())
|
||||
schematicsTab.getNode().loadVersion(profile, version);
|
||||
if (resourcePackTab.isInitialized())
|
||||
resourcePackTab.getNode().loadVersion(profile, version);
|
||||
currentVersionUpgradable.set(profile.getRepository().isModpack(version));
|
||||
}
|
||||
|
||||
@ -282,11 +286,20 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
schematicsListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.schematicsTab));
|
||||
schematicsListItem.setOnAction(e -> control.tab.select(control.schematicsTab));
|
||||
|
||||
AdvancedListItem resourcePackListItem = new AdvancedListItem();
|
||||
resourcePackListItem.getStyleClass().add("navigation-drawer-item");
|
||||
resourcePackListItem.setTitle(i18n("resourcepack.manage"));
|
||||
resourcePackListItem.setLeftGraphic(wrap(SVG.TEXTURE));
|
||||
resourcePackListItem.setActionButtonVisible(false);
|
||||
resourcePackListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.resourcePackTab));
|
||||
resourcePackListItem.setOnAction(e -> control.tab.select(control.resourcePackTab));
|
||||
|
||||
AdvancedListBox sideBar = new AdvancedListBox()
|
||||
.add(versionSettingsItem)
|
||||
.add(installerListItem)
|
||||
.add(modListItem)
|
||||
.add(worldListItem)
|
||||
.add(resourcePackListItem)
|
||||
.add(schematicsListItem);
|
||||
VBox.setVgrow(sideBar, Priority.ALWAYS);
|
||||
|
||||
|
BIN
HMCL/src/main/resources/assets/img/unknown_pack.png
Normal file
BIN
HMCL/src/main/resources/assets/img/unknown_pack.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@ -1200,6 +1200,11 @@ repositories.chooser=HMCL requires JavaFX to work.\n\
|
||||
repositories.chooser.title=Choose download source for JavaFX
|
||||
|
||||
resourcepack=Resource Packs
|
||||
resourcepack.add=Add Resource Pack
|
||||
resourcepack.manage=Resource Packs
|
||||
resourcepack.download=Download Resource Packs
|
||||
resourcepack.add.failed=Failed to add resource pack
|
||||
resourcepack.delete.failed=Failed to delete resource pack
|
||||
resourcepack.download.title=Download Resource Pack - %1s
|
||||
|
||||
reveal.in_file_manager=Reveal in File Manager
|
||||
|
@ -993,6 +993,11 @@ repositories.chooser=缺少 JavaFX 執行環境。HMCL 需要 JavaFX 才能正
|
||||
repositories.chooser.title=選取 JavaFX 下載源
|
||||
|
||||
resourcepack=資源包
|
||||
resourcepack.add=新增資源包
|
||||
resourcepack.manage=資源包管理
|
||||
resourcepack.download=下載資源包
|
||||
resourcepack.add.failed=新增資源包失敗
|
||||
resourcepack.delete.failed=刪除資源包失敗
|
||||
resourcepack.download.title=資源包下載 - %1s
|
||||
|
||||
reveal.in_file_manager=在檔案管理員中查看
|
||||
|
@ -1003,6 +1003,11 @@ repositories.chooser=缺少 JavaFX 运行环境。HMCL 需要 JavaFX 才能正
|
||||
repositories.chooser.title=选择 JavaFX 下载源
|
||||
|
||||
resourcepack=资源包
|
||||
resourcepack.add=添加资源包
|
||||
resourcepack.manage=资源包管理
|
||||
resourcepack.download=下载资源包
|
||||
resourcepack.add.failed=添加资源包失败
|
||||
resourcepack.delete.failed=删除资源包失败
|
||||
resourcepack.download.title=资源包下载 - %1s
|
||||
|
||||
reveal.in_file_manager=在文件管理器中查看
|
||||
|
@ -559,4 +559,8 @@ public class DefaultGameRepository implements GameRepository {
|
||||
.append("baseDirectory", baseDirectory)
|
||||
.toString();
|
||||
}
|
||||
|
||||
public Path getResourcepacksDirectory(String id) {
|
||||
return getRunDirectory(id).toPath().resolve("resourcepacks");
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,37 @@
|
||||
package org.jackhuang.hmcl.resourcepack;
|
||||
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public interface ResourcepackFile {
|
||||
String getDescription();
|
||||
|
||||
String getName();
|
||||
|
||||
File getFile();
|
||||
|
||||
Path getIcon();
|
||||
|
||||
default String parseDescriptionFromJson(String json) {
|
||||
try {
|
||||
return JsonParser.parseString(json).getAsJsonObject().getAsJsonObject("pack").get("description").getAsString();
|
||||
} catch (Exception ignored) {
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static ResourcepackFile parse(Path path) throws IOException {
|
||||
if (Files.isRegularFile(path) && path.toString().toLowerCase().endsWith(".zip")) {
|
||||
return new ResourcepackZipFile(path.toFile());
|
||||
} else if (Files.isDirectory(path) && Files.exists(path.resolve("pack.mcmeta"))) {
|
||||
return new ResourcepackFolder(path.toFile());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package org.jackhuang.hmcl.resourcepack;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class ResourcepackFolder implements ResourcepackFile {
|
||||
private final File folder;
|
||||
|
||||
public ResourcepackFolder(File folder) {
|
||||
this.folder = folder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return folder.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFile() {
|
||||
return folder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
try {
|
||||
return parseDescriptionFromJson(Files.readString(folder.toPath().resolve("pack.mcmeta")));
|
||||
} catch (Exception ignored) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getIcon() {
|
||||
return folder.toPath().resolve("pack.png");
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package org.jackhuang.hmcl.resourcepack;
|
||||
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class ResourcepackZipFile implements ResourcepackFile {
|
||||
private final FileSystem zipfs;
|
||||
private final File resourcepackfile;
|
||||
|
||||
public ResourcepackZipFile(File resourcepackfile) throws IOException {
|
||||
this.resourcepackfile = resourcepackfile;
|
||||
this.zipfs = CompressingUtils.createReadOnlyZipFileSystem(resourcepackfile.toPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return resourcepackfile.getName().replace(".zip", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFile() {
|
||||
return resourcepackfile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
|
||||
try {
|
||||
return parseDescriptionFromJson(Files.readString(zipfs.getPath("pack.mcmeta")));
|
||||
} catch (Exception ignored) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getIcon() {
|
||||
return zipfs.getPath("pack.png");
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user