From ad6d3a6bfce73e7980ec0cb9abb8ef7c30779d0c Mon Sep 17 00:00:00 2001 From: lokins Date: Sat, 31 Jan 2026 21:38:08 +0800 Subject: [PATCH 01/40] [Feature] JIJ --- .../hmcl/ui/versions/ModListPageSkin.java | 133 +++++++++++++++++- .../org/jackhuang/hmcl/mod/LocalModFile.java | 20 ++- .../hmcl/mod/modinfo/FabricModMetadata.java | 27 +++- .../hmcl/mod/modinfo/ForgeNewModMetadata.java | 18 ++- .../hmcl/mod/modinfo/ForgeOldModMetadata.java | 3 +- .../hmcl/mod/modinfo/LiteModMetadata.java | 3 +- .../hmcl/mod/modinfo/PackMcMeta.java | 3 +- .../hmcl/mod/modinfo/QuiltModMetadata.java | 19 ++- 8 files changed, 204 insertions(+), 22 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java index d3fea4f1bc6..59ca0104e94 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -31,11 +31,11 @@ import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import javafx.scene.input.Clipboard; +import javafx.scene.input.ClipboardContent; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; -import javafx.scene.layout.StackPane; +import javafx.scene.layout.*; import javafx.stage.Stage; import javafx.util.Duration; import org.jackhuang.hmcl.mod.LocalModFile; @@ -85,6 +85,7 @@ final class ModListPageSkin extends SkinBase { private final HBox searchBar; private final HBox toolbarNormal; private final HBox toolbarSelecting; + private HBox container = new HBox(); private final JFXListView listView; private final JFXTextField searchField; @@ -559,6 +560,7 @@ final class ModInfoListCell extends MDListCell { JFXButton restoreButton = new JFXButton(); JFXButton infoButton = new JFXButton(); JFXButton revealButton = new JFXButton(); + JFXButton bundledModsButton = new JFXButton(); BooleanProperty booleanProperty; Tooltip warningTooltip; @@ -568,9 +570,11 @@ final class ModInfoListCell extends MDListCell { this.getStyleClass().add("mod-info-list-cell"); - HBox container = new HBox(8); + container = new HBox(8); container.setPickOnBounds(false); container.setAlignment(Pos.CENTER_LEFT); + StackPane.setMargin(container, new Insets(8)); + getContainer().getChildren().setAll(container); HBox.setHgrow(content, Priority.ALWAYS); content.setMouseTransparent(true); setSelectable(); @@ -591,12 +595,122 @@ final class ModInfoListCell extends MDListCell { infoButton.getStyleClass().add("toggle-icon4"); infoButton.setGraphic(FXUtils.limitingSize(SVG.INFO.createIcon(24), 24, 24)); - container.getChildren().setAll(checkBox, imageView, content, restoreButton, revealButton, infoButton); + bundledModsButton.getStyleClass().addAll("jfx-button", "toggle-icon4"); + bundledModsButton.setGraphic(FXUtils.limitingSize(SVG.INFO.createIcon(24), 24, 24)); + bundledModsButton.setTooltip(new Tooltip("内置模组")); + bundledModsButton.setFocusTraversable(false); + + + container.getChildren().setAll(checkBox, imageView, content, revealButton, infoButton); StackPane.setMargin(container, new Insets(8)); getContainer().getChildren().setAll(container); } + private static void exportJijList(String modName, List bundledMods) { + if (bundledMods == null || bundledMods.isEmpty()) return; + javafx.stage.FileChooser fileChooser = new javafx.stage.FileChooser(); + fileChooser.setTitle("保存内置模组列表"); + fileChooser.getExtensionFilters().add(new javafx.stage.FileChooser.ExtensionFilter("文本文件 (*.txt)", "*.txt")); + fileChooser.setInitialFileName("JIJ.txt"); + + java.io.File file = fileChooser.showSaveDialog(Controllers.getStage()); + + if (file != null) { + // 构建树状内容 + StringBuilder sb = new StringBuilder(); + sb.append(modName).append(System.lineSeparator()); + for (String mod : bundledMods) { + sb.append("\t- ").append(mod).append(System.lineSeparator()); + } + + Task.runAsync(() -> { + try { + java.nio.file.Files.writeString(file.toPath(), sb.toString()); + LOG.info("导出成功: " + file.getAbsolutePath()); + } catch (java.io.IOException ex) { + LOG.warning("Failed to export bundled mods list", ex); + } + }).start(); + } + } + + private void showBundledPopup(String modName, List bundledMods) { + VBox root = new VBox(10); + root.setPadding(new Insets(15)); + root.setMaxHeight(400); + root.setStyle("-fx-background-color: -fx-background;"); + + HBox header = new HBox(10); + header.setAlignment(Pos.CENTER_LEFT); + + Label titleLabel = new Label("内置模组 (" + bundledMods.size() + ")"); + titleLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold;"); + + Region spacer = new Region(); + HBox.setHgrow(spacer, Priority.ALWAYS); + + JFXButton exportButton = new JFXButton(); + exportButton.setGraphic(FXUtils.limitingSize(SVG.DOWNLOAD.createIcon(18), 18, 18)); + exportButton.getStyleClass().add("toggle-icon4"); + FXUtils.installFastTooltip(exportButton, "导出JIJ信息"); + + exportButton.setOnAction(e -> exportJijList(modName, bundledMods)); + + header.getChildren().addAll(titleLabel, spacer, exportButton); + + JFXTextField searchField = new JFXTextField(); + searchField.setPromptText("搜索内置模组..."); + searchField.setFocusTraversable(false); + + FlowPane flowPane = new FlowPane(); + flowPane.setHgap(8); + flowPane.setVgap(8); + flowPane.setPrefWrapLength(450); + + Runnable refreshList = () -> { + flowPane.getChildren().clear(); + String query = searchField.getText().toLowerCase(); + + for (String path : bundledMods) { + if (path.toLowerCase().contains(query)) { + String name = path.contains("/") ? path.substring(path.lastIndexOf('/') + 1) : path; + + Label tag = new Label(name); + tag.setStyle("-fx-background-color: -fx-control-inner-background-alt; " + + "-fx-padding: 4 8; " + + "-fx-background-radius: 4; " + + "-fx-border-color: -fx-box-border; " + + "-fx-border-radius: 4;"); + tag.setMaxWidth(430); + tag.setTooltip(new Tooltip(path)); + + flowPane.getChildren().add(tag); + } + } + + if (flowPane.getChildren().isEmpty()) { + Label emptyLabel = new Label("无匹配结果"); + emptyLabel.setStyle("-fx-text-fill: -fx-text-base-color-disabled;"); + flowPane.getChildren().add(emptyLabel); + } + }; + + refreshList.run(); + searchField.textProperty().addListener((obs, oldVal, newVal) -> refreshList.run()); + + ScrollPane scrollPane = new ScrollPane(flowPane); + scrollPane.setFitToWidth(true); + scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED); + scrollPane.setStyle("-fx-background-color: transparent; -fx-background: transparent;"); + + root.getChildren().addAll(header, searchField, scrollPane); + + JFXPopup popup = new JFXPopup(root); + popup.show(bundledModsButton, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, bundledModsButton.getHeight() + 5); + } + @Override protected void updateControl(ModInfoObject dataItem, boolean empty) { pseudoClassStateChanged(WARNING, false); @@ -605,6 +719,8 @@ protected void updateControl(ModInfoObject dataItem, boolean empty) { warningTooltip = null; } + container.getChildren().remove(bundledModsButton); + if (empty) return; List warning = new ArrayList<>(); @@ -663,6 +779,13 @@ protected void updateControl(ModInfoObject dataItem, boolean empty) { } } + List bundledMods = modInfo.getBundledMods(); + String modName = modInfo.getName(); + if (bundledMods != null && !bundledMods.isEmpty()) { + bundledModsButton.setOnAction(e -> showBundledPopup(modName,bundledMods)); + container.getChildren().add(container.getChildren().indexOf(content) + 1, bundledModsButton); + } + String modVersion = modInfo.getVersion(); if (StringUtils.isNotBlank(modVersion) && !"${version}".equals(modVersion)) { content.addTag(modVersion); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java index aa12e7302b1..38c77e19723 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java @@ -23,11 +23,7 @@ import java.io.IOException; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; -import java.util.Optional; +import java.util.*; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -49,12 +45,13 @@ public final class LocalModFile implements Comparable { private final String fileName; private final String logoPath; private final BooleanProperty activeProperty; + private final List bundledMods; public LocalModFile(ModManager modManager, LocalMod mod, Path file, String name, Description description) { - this(modManager, mod, file, name, description, "", "", "", "", ""); + this(modManager, mod, file, name, description, "", "", "", "", "", Collections.emptyList()); } - public LocalModFile(ModManager modManager, LocalMod mod, Path file, String name, Description description, String authors, String version, String gameVersion, String url, String logoPath) { + public LocalModFile(ModManager modManager, LocalMod mod, Path file, String name, Description description, String authors, String version, String gameVersion, String url, String logoPath, List bundledMods) { this.modManager = modManager; this.mod = mod; this.file = file; @@ -65,6 +62,11 @@ public LocalModFile(ModManager modManager, LocalMod mod, Path file, String name, this.gameVersion = gameVersion; this.url = url; this.logoPath = logoPath; + this.bundledMods = bundledMods; + + if (bundledMods != null && !bundledMods.isEmpty()) { + System.out.println("Found bundled jars in " + file + ": " + bundledMods.size()); + } activeProperty = new SimpleBooleanProperty(this, "active", !modManager.isDisabled(file)) { @Override @@ -141,6 +143,10 @@ public String getLogoPath() { return logoPath; } + public List getBundledMods() { + return bundledMods; + } + public BooleanProperty activeProperty() { return activeProperty; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java index dd9b6c4d8aa..daca402b8cf 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java @@ -19,6 +19,7 @@ import com.google.gson.*; import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; import kala.compress.archivers.zip.ZipArchiveEntry; import org.jackhuang.hmcl.mod.LocalModFile; import org.jackhuang.hmcl.mod.ModLoaderType; @@ -44,12 +45,13 @@ public final class FabricModMetadata { private final String icon; private final List authors; private final Map contact; + private final List jars; public FabricModMetadata() { - this("", "", "", "", "", Collections.emptyList(), Collections.emptyMap()); + this("", "", "", "", "", Collections.emptyList(), Collections.emptyMap(), Collections.emptyList()); } - public FabricModMetadata(String id, String name, String version, String icon, String description, List authors, Map contact) { + public FabricModMetadata(String id, String name, String version, String icon, String description, List authors, Map contact,List jars) { this.id = id; this.name = name; this.version = version; @@ -57,6 +59,7 @@ public FabricModMetadata(String id, String name, String version, String icon, St this.description = description; this.authors = authors; this.contact = contact; + this.jars = jars; } public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException { @@ -65,8 +68,14 @@ public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFile throw new IOException("File " + modFile + " is not a Fabric mod."); FabricModMetadata metadata = JsonUtils.fromNonNullJsonFully(tree.getInputStream(mcmod), FabricModMetadata.class); String authors = metadata.authors == null ? "" : metadata.authors.stream().map(author -> author.name).collect(Collectors.joining(", ")); + if (metadata.jars != null && !metadata.jars.isEmpty()) { + System.out.println("Found bundled jars in " + modFile + ": " + metadata.jars.size()); + } + List bundledMods = metadata.jars != null ? + metadata.jars.stream().map(jar -> jar.file).toList() : + Collections.emptyList(); return new LocalModFile(modManager, modManager.getLocalMod(metadata.id, ModLoaderType.FABRIC), modFile, metadata.name, new LocalModFile.Description(metadata.description), - authors, metadata.version, "", metadata.contact != null ? metadata.contact.getOrDefault("homepage", "") : "", metadata.icon); + authors, metadata.version, "", metadata.contact != null ? metadata.contact.getOrDefault("homepage", "") : "", metadata.icon, bundledMods); } @JsonAdapter(FabricModAuthorSerializer.class) @@ -93,4 +102,16 @@ public JsonElement serialize(FabricModAuthor src, Type typeOfSrc, JsonSerializat return src == null ? JsonNull.INSTANCE : new JsonPrimitive(src.name); } } + public static final class FabricNestedJar { + @SerializedName("file") + private final String file; + + public FabricNestedJar() { + this(""); + } + + public FabricNestedJar(String file) { + this.file = file; + } + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java index 9a0f815de56..1f77ef540a6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java @@ -207,12 +207,28 @@ private static LocalModFile fromFile0( } } + List bundledMods = new ArrayList<>(); + ZipArchiveEntry jarInJarEntry = tree.getEntry("META-INF/jarjar/metadata.json"); + if (jarInJarEntry != null) { + try { + JarInJarMetadata jijMetadata = JsonUtils.fromJsonFully(tree.getInputStream(jarInJarEntry), JarInJarMetadata.class); + if (jijMetadata != null && jijMetadata.jars != null) { + jijMetadata.jars.stream() + .map(jar -> jar.path) + .forEach(bundledMods::add); + } + } catch (Exception e) { + LOG.warning("Failed to parse JarInJar metadata for " + modFile, e); + } + } + ModLoaderType type = analyzeLoader(toml, mod.getModId(), modLoaderType); return new LocalModFile(modManager, modManager.getLocalMod(mod.getModId(), type), modFile, mod.getDisplayName(), new LocalModFile.Description(mod.getDescription()), mod.getAuthors(), jarVersion == null ? mod.getVersion() : mod.getVersion().replace("${file.jarVersion}", jarVersion), "", mod.getDisplayURL(), - metadata.getLogoFile()); + metadata.getLogoFile(), + bundledMods); } private static LocalModFile fromEmbeddedMod(ModManager modManager, Path modFile, ZipFileTree tree, ModLoaderType modLoaderType) throws IOException { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java index 53c6b09252d..6830df1aa8a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeOldModMetadata.java @@ -33,6 +33,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.List; +import java.util.Collections; import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; @@ -159,6 +160,6 @@ else if (firstToken == JsonToken.BEGIN_OBJECT) { return new LocalModFile(modManager, modManager.getLocalMod(metadata.getModId(), ModLoaderType.FORGE), modFile, metadata.getName(), new LocalModFile.Description(metadata.getDescription()), authors, metadata.getVersion(), metadata.getGameVersion(), StringUtils.isBlank(metadata.getUrl()) ? metadata.getUpdateUrl() : metadata.url, - metadata.getLogoFile()); + metadata.getLogoFile(), Collections.emptyList()); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/LiteModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/LiteModMetadata.java index 8a59a711be4..5238959afc3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/LiteModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/LiteModMetadata.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.Collections; /** * @@ -117,7 +118,7 @@ public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFile if (metadata == null) throw new IOException("Mod " + modFile + " `litemod.json` is malformed."); return new LocalModFile(modManager, modManager.getLocalMod(metadata.getName(), ModLoaderType.LITE_LOADER), modFile, metadata.getName(), new LocalModFile.Description(metadata.getDescription()), metadata.getAuthor(), - metadata.getVersion(), metadata.getGameVersion(), metadata.getUpdateURI(), ""); + metadata.getVersion(), metadata.getGameVersion(), metadata.getUpdateURI(), "", Collections.emptyList()); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/PackMcMeta.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/PackMcMeta.java index 024a0102ee2..2bf99cef722 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/PackMcMeta.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/PackMcMeta.java @@ -36,6 +36,7 @@ import java.lang.reflect.Type; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -194,6 +195,6 @@ public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFile modFile, FileUtils.getNameWithoutExtension(modFile), metadata.pack.description, - "", "", "", "", ""); + "", "", "", "", "", Collections.emptyList()); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/QuiltModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/QuiltModMetadata.java index b9e7ebd56b0..6af5d8d5d8b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/QuiltModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/QuiltModMetadata.java @@ -12,6 +12,8 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.Collections; +import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -34,14 +36,21 @@ public Metadata(String name, String description, JsonObject contributors, String } } + private static final class NestedJar { + private final String file; + public NestedJar(String file) { this.file = file; } + } + private final String id; private final String version; private final Metadata metadata; + private List jars = List.of(); - public QuiltLoader(String id, String version, Metadata metadata) { + public QuiltLoader(String id, String version, Metadata metadata, List jars) { this.id = id; this.version = version; this.metadata = metadata; + this.jars = jars; } } @@ -64,6 +73,10 @@ public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFile throw new IOException("File " + modFile + " is not a supported Quilt mod."); } + List bundledMods = root.quilt_loader.jars != null ? + root.quilt_loader.jars.stream().map(jar -> jar.file).toList() : + Collections.emptyList(); + return new LocalModFile( modManager, modManager.getLocalMod(root.quilt_loader.id, ModLoaderType.QUILT), @@ -74,7 +87,7 @@ public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFile root.quilt_loader.version, "", Optional.ofNullable(root.quilt_loader.metadata.contact.get("homepage")).map(jsonElement -> jsonElement.getAsJsonPrimitive().getAsString()).orElse(""), - root.quilt_loader.metadata.icon - ); + root.quilt_loader.metadata.icon, + bundledMods); } } From 8f20d8abdb300a447d3bf7aac51cd66fe3807d9d Mon Sep 17 00:00:00 2001 From: lokins Date: Sun, 1 Feb 2026 01:37:10 +0800 Subject: [PATCH 02/40] update --- .../hmcl/ui/versions/BuiltInModListPage.java | 99 +++++ .../ui/versions/BuiltInModListPageSkin.java | 337 ++++++++++++++++++ .../hmcl/ui/versions/ModListPageSkin.java | 133 +------ .../hmcl/ui/versions/VersionPage.java | 7 +- .../org/jackhuang/hmcl/mod/LocalModFile.java | 4 + 5 files changed, 451 insertions(+), 129 deletions(-) create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPage.java create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPage.java new file mode 100644 index 00000000000..c1cfccd033c --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPage.java @@ -0,0 +1,99 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.versions; + +import javafx.scene.control.Skin; +import org.jackhuang.hmcl.mod.LocalModFile; +import org.jackhuang.hmcl.mod.ModManager; +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.ListPageBase; +import org.jackhuang.hmcl.ui.construct.PageAware; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.i18n.I18n; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +public class BuiltInModListPage extends ListPageBase implements VersionPage.VersionLoadable, PageAware { + + private ModManager modManager; + + @Override + protected Skin createDefaultSkin() { + return new BuiltInModListPageSkin(this); + } + + @Override + public void loadVersion(Profile profile, String id) { + getItems().clear(); + this.modManager = profile.getRepository().getModManager(id); + refresh(); + } + + public void refresh() { + if (modManager == null) return; + try { + if (modManager.getModsDirectory() != null) { + LOG.info("Refreshing built-in mod list for folder: " + modManager.getModsDirectory().toAbsolutePath()); + } + } catch (Exception e) { + LOG.warning("Failed to log mods directory", e); + } + + LOG.info("Refreshing built-in mod list page..."); + + getItems().clear(); + setLoading(true); + + CompletableFuture.supplyAsync(() -> { + try { + modManager.refreshMods(); + return modManager.getMods().stream() + .filter(mod -> { + try { + return mod != null && mod.hasBundledMods(); + } catch (Exception e) { + return false; + } + }) + .map(ModListPageSkin.ModInfoObject::new) + .collect(Collectors.toList()); + } catch (Exception e) { + LOG.warning("Failed to refresh built-in mods", e); + return null; + } + }, Schedulers.io()).whenCompleteAsync((list, ex) -> { + if (ex != null) { + LOG.warning("Async task failed in BuiltInModListPage", ex); + } else if (list != null) { + getItems().setAll(list); + LOG.info("Built-in mod list refreshed, found " + list.size() + " mods with JIJ."); + } + setLoading(false); + }, Schedulers.javafx()); + } +} \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java new file mode 100644 index 00000000000..48175cb2092 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -0,0 +1,337 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.versions; + +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXListView; +import com.jfoenix.controls.JFXPopup; +import com.jfoenix.controls.JFXTextField; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.SkinBase; +import javafx.scene.control.Tooltip; +import javafx.scene.image.ImageView; +import javafx.scene.layout.*; +import javafx.stage.FileChooser; +import org.jackhuang.hmcl.mod.LocalModFile; +import org.jackhuang.hmcl.mod.ModLoaderType; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.ui.Controllers; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.SVG; +import org.jackhuang.hmcl.ui.construct.MDListCell; +import org.jackhuang.hmcl.ui.construct.TwoLineListItem; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.setting.VersionIconType; +import org.jackhuang.hmcl.util.i18n.I18n; + +import java.io.File; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.nio.file.Files; +import java.util.List; +import java.util.StringJoiner; + +import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +public class BuiltInModListPageSkin extends SkinBase { + + private final JFXListView listView; + private final HBox toolbar; + + protected BuiltInModListPageSkin(BuiltInModListPage skinnable) { + super(skinnable); + + StackPane rootPane = new StackPane(); + rootPane.setPadding(new Insets(10)); + rootPane.getStyleClass().add("notice-pane"); + + VBox contentBox = new VBox(); + contentBox.setSpacing(10); + + listView = new JFXListView<>(); + toolbar = new HBox(); + toolbar.getChildren().addAll( + createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh), + createToolbarButton2("导出全部 JIJ 信息", SVG.DOWNLOAD, () -> exportAllJijList(listView.getItems())), + createToolbarButton2(i18n("search"), SVG.SEARCH, () -> { + // 搜索功能预留 + }) + ); + + listView.setItems(skinnable.getItems()); + + VBox.setVgrow(listView, Priority.ALWAYS); + + listView.setCellFactory(param -> new JijModListCell(listView)); + + contentBox.getChildren().addAll(toolbar, listView); + rootPane.getChildren().add(contentBox); + + getChildren().add(rootPane); + } + + private class JijModListCell extends MDListCell { + + private final ImageView imageView = new ImageView(); + private final TwoLineListItem content = new TwoLineListItem(); + private final HBox container = new HBox(8); + + public JijModListCell(JFXListView listView) { + super(listView); + this.getStyleClass().add("mod-info-list-cell"); + + container.setPickOnBounds(false); + container.setAlignment(Pos.CENTER_LEFT); + StackPane.setMargin(container, new Insets(8)); + + imageView.setFitWidth(24); + imageView.setFitHeight(24); + imageView.setPreserveRatio(true); + + HBox.setHgrow(content, Priority.ALWAYS); + content.setMouseTransparent(true); + + container.getChildren().addAll(imageView, content); + getContainer().getChildren().setAll(container); + + setSelectable(); + + this.setOnMouseClicked(e -> { + if (getItem() != null && getItem().getModInfo() != null) { + LocalModFile modFile = getItem().getModInfo(); + if (modFile.hasBundledMods()) { + showBundledPopup(this, modFile.getName(), modFile.getBundledMods()); + } + } + }); + } + + @Override + protected void updateControl(ModListPageSkin.ModInfoObject dataItem, boolean empty) { + if (empty || dataItem == null) return; + + LocalModFile modInfo = dataItem.getModInfo(); + ModTranslations.Mod modTranslations = dataItem.getModTranslations(); + ModLoaderType modLoaderType = modInfo.getModLoaderType(); + + dataItem.loadIcon(imageView, new WeakReference<>(this.itemProperty())); + + String displayName = modInfo.getName(); + if (modTranslations != null && I18n.isUseChinese()) { + String chineseName = modTranslations.getName(); + if (StringUtils.containsChinese(chineseName)) { + if (StringUtils.containsEmoji(chineseName)) { + StringBuilder builder = new StringBuilder(); + chineseName.codePoints().forEach(ch -> { + if (ch < 0x1F300 || ch > 0x1FAFF) builder.appendCodePoint(ch); + }); + chineseName = builder.toString().trim(); + } + if (StringUtils.isNotBlank(chineseName) && !displayName.equalsIgnoreCase(chineseName)) { + displayName = displayName + " (" + chineseName + ")"; + } + } + } + content.setTitle(displayName); + + StringJoiner joiner = new StringJoiner(" | "); + if (modLoaderType != ModLoaderType.UNKNOWN && StringUtils.isNotBlank(modInfo.getId())) + joiner.add(modInfo.getId()); + joiner.add(org.jackhuang.hmcl.util.io.FileUtils.getName(modInfo.getFile())); + content.setSubtitle(joiner.toString()); + + content.getTags().clear(); + if (modInfo.hasBundledMods()) { + content.addTag("内置: " + modInfo.getBundledMods().size()); + } + + String modVersion = modInfo.getVersion(); + if (StringUtils.isNotBlank(modVersion) && !"${version}".equals(modVersion)) { + content.addTag(modVersion); + } + } + } + + private void showBundledPopup(Node anchor, String modName, List bundledMods) { + VBox root = new VBox(10); + root.setPadding(new Insets(15)); + root.setMaxHeight(400); + root.setStyle("-fx-background-color: -fx-background;"); + + HBox header = new HBox(10); + header.setAlignment(Pos.CENTER_LEFT); + + Label titleLabel = new Label("内置模组 (" + bundledMods.size() + ")"); + titleLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold;"); + + Region spacer = new Region(); + HBox.setHgrow(spacer, Priority.ALWAYS); + + JFXButton exportButton = new JFXButton(); + exportButton.setGraphic(FXUtils.limitingSize(SVG.DOWNLOAD.createIcon(18), 18, 18)); + exportButton.getStyleClass().add("toggle-icon4"); + FXUtils.installFastTooltip(exportButton, "导出JIJ信息"); + + exportButton.setOnAction(e -> exportJijList(modName, bundledMods)); + + header.getChildren().addAll(titleLabel, spacer, exportButton); + + JFXTextField searchField = new JFXTextField(); + searchField.setPromptText("搜索内置模组..."); + searchField.setFocusTraversable(false); + + FlowPane flowPane = new FlowPane(); + flowPane.setHgap(8); + flowPane.setVgap(8); + flowPane.setPrefWrapLength(450); + + Runnable refreshList = () -> { + flowPane.getChildren().clear(); + String query = searchField.getText().toLowerCase(); + + for (String path : bundledMods) { + if (path.toLowerCase().contains(query)) { + String name = path.contains("/") ? path.substring(path.lastIndexOf('/') + 1) : path; + + Label tag = new Label(name); + tag.setStyle("-fx-background-color: -fx-control-inner-background-alt; " + + "-fx-padding: 4 8; " + + "-fx-background-radius: 4; " + + "-fx-border-color: -fx-box-border; " + + "-fx-border-radius: 4;"); + tag.setMaxWidth(430); + tag.setTooltip(new Tooltip(path)); + + flowPane.getChildren().add(tag); + } + } + + if (flowPane.getChildren().isEmpty()) { + Label emptyLabel = new Label("无匹配结果"); + emptyLabel.setStyle("-fx-text-fill: -fx-text-base-color-disabled;"); + flowPane.getChildren().add(emptyLabel); + } + }; + + refreshList.run(); + searchField.textProperty().addListener((obs, oldVal, newVal) -> refreshList.run()); + + ScrollPane scrollPane = new ScrollPane(flowPane); + scrollPane.setFitToWidth(true); + scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED); + scrollPane.setStyle("-fx-background-color: transparent; -fx-background: transparent;"); + + root.getChildren().addAll(header, searchField, scrollPane); + + JFXPopup popup = new JFXPopup(root); + popup.show(anchor, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, anchor.getLayoutBounds().getHeight() + 5); + } + + private static void exportJijList(String modName, List bundledMods) { + if (bundledMods == null || bundledMods.isEmpty()) return; + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("保存内置模组列表"); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("文本文件 (*.txt)", "*.txt")); + fileChooser.setInitialFileName("JIJ.txt"); + + File file = fileChooser.showSaveDialog(Controllers.getStage()); + + if (file != null) { + StringBuilder sb = new StringBuilder(); + sb.append(modName).append(System.lineSeparator()); + + for (String modPath : bundledMods) { + String fileName = modPath.contains("/") ? modPath.substring(modPath.lastIndexOf('/') + 1) : modPath; + sb.append("\t|->").append(fileName).append(System.lineSeparator()); + } + + Task.runAsync(() -> { + try { + Files.writeString(file.toPath(), sb.toString()); + LOG.info("导出成功: " + file.getAbsolutePath()); + } catch (IOException ex) { + LOG.warning("Failed to export bundled mods list", ex); + } + }).start(); + } + } + + private static void exportAllJijList(List allMods) { + if (allMods == null || allMods.isEmpty()) return; + + StringBuilder sb = new StringBuilder(); + boolean hasData = false; + + for (ModListPageSkin.ModInfoObject item : allMods) { + LocalModFile modInfo = item.getModInfo(); + if (modInfo == null) continue; + List bundledMods = modInfo.getBundledMods(); + + if (bundledMods != null && !bundledMods.isEmpty()) { + hasData = true; + + String displayName = modInfo.getName(); + if (item.getModTranslations() != null && I18n.isUseChinese()) { + String chineseName = item.getModTranslations().getName(); + if (StringUtils.isNotBlank(chineseName)) { + displayName = displayName + " (" + chineseName + ")"; + } + } + + sb.append(displayName).append(System.lineSeparator()); + + for (String modPath : bundledMods) { + String fileName = modPath.contains("/") ? modPath.substring(modPath.lastIndexOf('/') + 1) : modPath; + sb.append("\t|-> ").append(fileName).append(System.lineSeparator()); + } + + sb.append(System.lineSeparator()); + } + } + + if (!hasData) { + FXUtils.runInFX(() -> Controllers.confirm("无包含JIJ信息的模组,操作将取消", i18n("button.ok"), () -> {}, null)); + return; + } + + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("导出所有内置模组信息"); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("文本文件 (*.txt)", "*.txt")); + fileChooser.setInitialFileName("ALL_JIJ_INFO.txt"); + + File file = fileChooser.showSaveDialog(Controllers.getStage()); + + if (file != null) { + Task.runAsync(() -> { + try { + Files.writeString(file.toPath(), sb.toString()); + LOG.info("全部导出成功: " + file.getAbsolutePath()); + } catch (IOException ex) { + LOG.warning("Failed to export all bundled mods list", ex); + } + }).start(); + } + } +} \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java index 59ca0104e94..d3fea4f1bc6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -31,11 +31,11 @@ import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.input.Clipboard; -import javafx.scene.input.ClipboardContent; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; -import javafx.scene.layout.*; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.StackPane; import javafx.stage.Stage; import javafx.util.Duration; import org.jackhuang.hmcl.mod.LocalModFile; @@ -85,7 +85,6 @@ final class ModListPageSkin extends SkinBase { private final HBox searchBar; private final HBox toolbarNormal; private final HBox toolbarSelecting; - private HBox container = new HBox(); private final JFXListView listView; private final JFXTextField searchField; @@ -560,7 +559,6 @@ final class ModInfoListCell extends MDListCell { JFXButton restoreButton = new JFXButton(); JFXButton infoButton = new JFXButton(); JFXButton revealButton = new JFXButton(); - JFXButton bundledModsButton = new JFXButton(); BooleanProperty booleanProperty; Tooltip warningTooltip; @@ -570,11 +568,9 @@ final class ModInfoListCell extends MDListCell { this.getStyleClass().add("mod-info-list-cell"); - container = new HBox(8); + HBox container = new HBox(8); container.setPickOnBounds(false); container.setAlignment(Pos.CENTER_LEFT); - StackPane.setMargin(container, new Insets(8)); - getContainer().getChildren().setAll(container); HBox.setHgrow(content, Priority.ALWAYS); content.setMouseTransparent(true); setSelectable(); @@ -595,122 +591,12 @@ final class ModInfoListCell extends MDListCell { infoButton.getStyleClass().add("toggle-icon4"); infoButton.setGraphic(FXUtils.limitingSize(SVG.INFO.createIcon(24), 24, 24)); - bundledModsButton.getStyleClass().addAll("jfx-button", "toggle-icon4"); - bundledModsButton.setGraphic(FXUtils.limitingSize(SVG.INFO.createIcon(24), 24, 24)); - bundledModsButton.setTooltip(new Tooltip("内置模组")); - bundledModsButton.setFocusTraversable(false); - - - container.getChildren().setAll(checkBox, imageView, content, revealButton, infoButton); + container.getChildren().setAll(checkBox, imageView, content, restoreButton, revealButton, infoButton); StackPane.setMargin(container, new Insets(8)); getContainer().getChildren().setAll(container); } - private static void exportJijList(String modName, List bundledMods) { - if (bundledMods == null || bundledMods.isEmpty()) return; - javafx.stage.FileChooser fileChooser = new javafx.stage.FileChooser(); - fileChooser.setTitle("保存内置模组列表"); - fileChooser.getExtensionFilters().add(new javafx.stage.FileChooser.ExtensionFilter("文本文件 (*.txt)", "*.txt")); - fileChooser.setInitialFileName("JIJ.txt"); - - java.io.File file = fileChooser.showSaveDialog(Controllers.getStage()); - - if (file != null) { - // 构建树状内容 - StringBuilder sb = new StringBuilder(); - sb.append(modName).append(System.lineSeparator()); - for (String mod : bundledMods) { - sb.append("\t- ").append(mod).append(System.lineSeparator()); - } - - Task.runAsync(() -> { - try { - java.nio.file.Files.writeString(file.toPath(), sb.toString()); - LOG.info("导出成功: " + file.getAbsolutePath()); - } catch (java.io.IOException ex) { - LOG.warning("Failed to export bundled mods list", ex); - } - }).start(); - } - } - - private void showBundledPopup(String modName, List bundledMods) { - VBox root = new VBox(10); - root.setPadding(new Insets(15)); - root.setMaxHeight(400); - root.setStyle("-fx-background-color: -fx-background;"); - - HBox header = new HBox(10); - header.setAlignment(Pos.CENTER_LEFT); - - Label titleLabel = new Label("内置模组 (" + bundledMods.size() + ")"); - titleLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold;"); - - Region spacer = new Region(); - HBox.setHgrow(spacer, Priority.ALWAYS); - - JFXButton exportButton = new JFXButton(); - exportButton.setGraphic(FXUtils.limitingSize(SVG.DOWNLOAD.createIcon(18), 18, 18)); - exportButton.getStyleClass().add("toggle-icon4"); - FXUtils.installFastTooltip(exportButton, "导出JIJ信息"); - - exportButton.setOnAction(e -> exportJijList(modName, bundledMods)); - - header.getChildren().addAll(titleLabel, spacer, exportButton); - - JFXTextField searchField = new JFXTextField(); - searchField.setPromptText("搜索内置模组..."); - searchField.setFocusTraversable(false); - - FlowPane flowPane = new FlowPane(); - flowPane.setHgap(8); - flowPane.setVgap(8); - flowPane.setPrefWrapLength(450); - - Runnable refreshList = () -> { - flowPane.getChildren().clear(); - String query = searchField.getText().toLowerCase(); - - for (String path : bundledMods) { - if (path.toLowerCase().contains(query)) { - String name = path.contains("/") ? path.substring(path.lastIndexOf('/') + 1) : path; - - Label tag = new Label(name); - tag.setStyle("-fx-background-color: -fx-control-inner-background-alt; " + - "-fx-padding: 4 8; " + - "-fx-background-radius: 4; " + - "-fx-border-color: -fx-box-border; " + - "-fx-border-radius: 4;"); - tag.setMaxWidth(430); - tag.setTooltip(new Tooltip(path)); - - flowPane.getChildren().add(tag); - } - } - - if (flowPane.getChildren().isEmpty()) { - Label emptyLabel = new Label("无匹配结果"); - emptyLabel.setStyle("-fx-text-fill: -fx-text-base-color-disabled;"); - flowPane.getChildren().add(emptyLabel); - } - }; - - refreshList.run(); - searchField.textProperty().addListener((obs, oldVal, newVal) -> refreshList.run()); - - ScrollPane scrollPane = new ScrollPane(flowPane); - scrollPane.setFitToWidth(true); - scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); - scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED); - scrollPane.setStyle("-fx-background-color: transparent; -fx-background: transparent;"); - - root.getChildren().addAll(header, searchField, scrollPane); - - JFXPopup popup = new JFXPopup(root); - popup.show(bundledModsButton, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, bundledModsButton.getHeight() + 5); - } - @Override protected void updateControl(ModInfoObject dataItem, boolean empty) { pseudoClassStateChanged(WARNING, false); @@ -719,8 +605,6 @@ protected void updateControl(ModInfoObject dataItem, boolean empty) { warningTooltip = null; } - container.getChildren().remove(bundledModsButton); - if (empty) return; List warning = new ArrayList<>(); @@ -779,13 +663,6 @@ protected void updateControl(ModInfoObject dataItem, boolean empty) { } } - List bundledMods = modInfo.getBundledMods(); - String modName = modInfo.getName(); - if (bundledMods != null && !bundledMods.isEmpty()) { - bundledModsButton.setOnAction(e -> showBundledPopup(modName,bundledMods)); - container.getChildren().add(container.getChildren().indexOf(content) + 1, bundledModsButton); - } - String modVersion = modInfo.getVersion(); if (StringUtils.isNotBlank(modVersion) && !"${version}".equals(modVersion)) { content.addTag(modVersion); 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 7ea91bacb83..dd931cf6931 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 @@ -51,6 +51,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage private final TabHeader.Tab versionSettingsTab = new TabHeader.Tab<>("versionSettingsTab"); private final TabHeader.Tab installerListTab = new TabHeader.Tab<>("installerListTab"); private final TabHeader.Tab modListTab = new TabHeader.Tab<>("modListTab"); + private final TabHeader.Tab builtInModListTab = new TabHeader.Tab<>("builtInModListTab"); private final TabHeader.Tab worldListTab = new TabHeader.Tab<>("worldList"); private final TabHeader.Tab schematicsTab = new TabHeader.Tab<>("schematicsTab"); private final TabHeader.Tab resourcePackTab = new TabHeader.Tab<>("resourcePackTab"); @@ -65,11 +66,12 @@ public VersionPage() { versionSettingsTab.setNodeSupplier(loadVersionFor(() -> new VersionSettingsPage(false))); installerListTab.setNodeSupplier(loadVersionFor(InstallerListPage::new)); modListTab.setNodeSupplier(loadVersionFor(ModListPage::new)); + builtInModListTab.setNodeSupplier(loadVersionFor(BuiltInModListPage::new)); resourcePackTab.setNodeSupplier(loadVersionFor(ResourcepackListPage::new)); worldListTab.setNodeSupplier(loadVersionFor(WorldListPage::new)); schematicsTab.setNodeSupplier(loadVersionFor(SchematicsPage::new)); - tab = new TabHeader(transitionPane, versionSettingsTab, installerListTab, modListTab, resourcePackTab, worldListTab, schematicsTab); + tab = new TabHeader(transitionPane, versionSettingsTab, installerListTab, modListTab,builtInModListTab, resourcePackTab, worldListTab, schematicsTab); tab.select(versionSettingsTab); addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated); @@ -129,6 +131,8 @@ public void loadVersion(String version, Profile profile) { installerListTab.getNode().loadVersion(profile, version); if (modListTab.isInitialized()) modListTab.getNode().loadVersion(profile, version); + if (builtInModListTab.isInitialized()) + builtInModListTab.getNode().loadVersion(profile, version); if (resourcePackTab.isInitialized()) resourcePackTab.getNode().loadVersion(profile, version); if (worldListTab.isInitialized()) @@ -239,6 +243,7 @@ protected Skin(VersionPage control) { .addNavigationDrawerTab(control.tab, control.versionSettingsTab, i18n("settings.game"), SVG.SETTINGS, SVG.SETTINGS_FILL) .addNavigationDrawerTab(control.tab, control.installerListTab, i18n("settings.tabs.installers"), SVG.DEPLOYED_CODE, SVG.DEPLOYED_CODE_FILL) .addNavigationDrawerTab(control.tab, control.modListTab, i18n("mods.manage"), SVG.EXTENSION, SVG.EXTENSION_FILL) + .addNavigationDrawerTab(control.tab, control.builtInModListTab, "内置模组", SVG.EXTENSION, SVG.EXTENSION_FILL) .addNavigationDrawerTab(control.tab, control.resourcePackTab, i18n("resourcepack.manage"), SVG.TEXTURE) .addNavigationDrawerTab(control.tab, control.worldListTab, i18n("world.manage"), SVG.PUBLIC) .addNavigationDrawerTab(control.tab, control.schematicsTab, i18n("schematics.manage"), SVG.SCHEMA, SVG.SCHEMA_FILL); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java index 38c77e19723..ef11bbfe24c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java @@ -147,6 +147,10 @@ public List getBundledMods() { return bundledMods; } + public boolean hasBundledMods() { + return bundledMods != null && !bundledMods.isEmpty(); + } + public BooleanProperty activeProperty() { return activeProperty; } From aeb8a1b66f3a15e1f1e091185f3fbeaf6d1534cc Mon Sep 17 00:00:00 2001 From: lokins Date: Sun, 1 Feb 2026 01:42:05 +0800 Subject: [PATCH 03/40] update --- .../org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java index 48175cb2092..2dfbbcb83ef 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -334,4 +334,4 @@ private static void exportAllJijList(List allMods }).start(); } } -} \ No newline at end of file +} From e98ccb13cfeac94efe55dd5a9631f3b768a21820 Mon Sep 17 00:00:00 2001 From: lokins Date: Sun, 1 Feb 2026 01:43:46 +0800 Subject: [PATCH 04/40] update --- .../java/org/jackhuang/hmcl/ui/versions/BuiltInModListPage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPage.java index c1cfccd033c..6a12b8a14e8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPage.java @@ -96,4 +96,4 @@ public void refresh() { setLoading(false); }, Schedulers.javafx()); } -} \ No newline at end of file +} From 949e2bdda7a1c50dbe927f0b392ad693d6153266 Mon Sep 17 00:00:00 2001 From: lokins Date: Sun, 1 Feb 2026 01:50:08 +0800 Subject: [PATCH 05/40] update --- .../jackhuang/hmcl/ui/versions/BuiltInModListPage.java | 9 --------- .../hmcl/ui/versions/BuiltInModListPageSkin.java | 6 +++--- .../jackhuang/hmcl/mod/modinfo/FabricModMetadata.java | 1 + 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPage.java index 6a12b8a14e8..252ba0f51ba 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPage.java @@ -18,24 +18,15 @@ package org.jackhuang.hmcl.ui.versions; import javafx.scene.control.Skin; -import org.jackhuang.hmcl.mod.LocalModFile; import org.jackhuang.hmcl.mod.ModManager; 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.ListPageBase; import org.jackhuang.hmcl.ui.construct.PageAware; -import org.jackhuang.hmcl.util.StringUtils; -import org.jackhuang.hmcl.util.i18n.I18n; -import java.io.IOException; -import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; -import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.logging.Logger.LOG; public class BuiltInModListPage extends ListPageBase implements VersionPage.VersionLoadable, PageAware { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java index 2dfbbcb83ef..b766efd34f3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -40,7 +40,6 @@ import org.jackhuang.hmcl.ui.construct.MDListCell; import org.jackhuang.hmcl.ui.construct.TwoLineListItem; import org.jackhuang.hmcl.util.StringUtils; -import org.jackhuang.hmcl.setting.VersionIconType; import org.jackhuang.hmcl.util.i18n.I18n; import java.io.File; @@ -48,6 +47,7 @@ import java.lang.ref.WeakReference; import java.nio.file.Files; import java.util.List; +import java.util.Locale; import java.util.StringJoiner; import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; @@ -208,10 +208,10 @@ private void showBundledPopup(Node anchor, String modName, List bundledM Runnable refreshList = () -> { flowPane.getChildren().clear(); - String query = searchField.getText().toLowerCase(); + String query = searchField.getText().toLowerCase(Locale.ROOT); for (String path : bundledMods) { - if (path.toLowerCase().contains(query)) { + if (path.toLowerCase(Locale.ROOT).contains(query)) { String name = path.contains("/") ? path.substring(path.lastIndexOf('/') + 1) : path; Label tag = new Label(name); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java index daca402b8cf..2b3f6dcc780 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java @@ -102,6 +102,7 @@ public JsonElement serialize(FabricModAuthor src, Type typeOfSrc, JsonSerializat return src == null ? JsonNull.INSTANCE : new JsonPrimitive(src.name); } } + public static final class FabricNestedJar { @SerializedName("file") private final String file; From cdd8a36328f584892bbdf94c578fbcbcd55472a0 Mon Sep 17 00:00:00 2001 From: lokins Date: Sun, 1 Feb 2026 15:48:28 +0800 Subject: [PATCH 06/40] update --- .../ui/versions/BuiltInModListPageSkin.java | 139 +++++++++++++++--- 1 file changed, 115 insertions(+), 24 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java index b766efd34f3..ea00499dbf8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -21,6 +21,9 @@ import com.jfoenix.controls.JFXListView; import com.jfoenix.controls.JFXPopup; import com.jfoenix.controls.JFXTextField; +import javafx.animation.PauseTransition; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; @@ -31,13 +34,18 @@ import javafx.scene.image.ImageView; import javafx.scene.layout.*; import javafx.stage.FileChooser; +import javafx.util.Duration; import org.jackhuang.hmcl.mod.LocalModFile; import org.jackhuang.hmcl.mod.ModLoaderType; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; +import org.jackhuang.hmcl.ui.animation.ContainerAnimations; +import org.jackhuang.hmcl.ui.animation.TransitionPane; +import org.jackhuang.hmcl.ui.construct.ComponentList; import org.jackhuang.hmcl.ui.construct.MDListCell; +import org.jackhuang.hmcl.ui.construct.SpinnerPane; import org.jackhuang.hmcl.ui.construct.TwoLineListItem; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.i18n.I18n; @@ -49,46 +57,129 @@ import java.util.List; import java.util.Locale; import java.util.StringJoiner; +import java.util.function.Predicate; +import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.logging.Logger.LOG; public class BuiltInModListPageSkin extends SkinBase { + private final TransitionPane toolbarPane; + private final HBox searchBar; + private final HBox toolbarNormal; + private final JFXListView listView; - private final HBox toolbar; + private final JFXTextField searchField; + + private boolean isSearching = false; protected BuiltInModListPageSkin(BuiltInModListPage skinnable) { super(skinnable); - StackPane rootPane = new StackPane(); - rootPane.setPadding(new Insets(10)); - rootPane.getStyleClass().add("notice-pane"); - - VBox contentBox = new VBox(); - contentBox.setSpacing(10); + StackPane pane = new StackPane(); + pane.setPadding(new Insets(10)); + pane.getStyleClass().addAll("notice-pane"); + ComponentList root = new ComponentList(); + root.getStyleClass().add("no-padding"); listView = new JFXListView<>(); - toolbar = new HBox(); - toolbar.getChildren().addAll( - createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh), - createToolbarButton2("导出全部 JIJ 信息", SVG.DOWNLOAD, () -> exportAllJijList(listView.getItems())), - createToolbarButton2(i18n("search"), SVG.SEARCH, () -> { - // 搜索功能预留 - }) - ); - listView.setItems(skinnable.getItems()); + { + toolbarPane = new TransitionPane(); + + searchBar = new HBox(); + toolbarNormal = new HBox(); + + // Search Bar + 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 -> search()); + searchField.textProperty().addListener((observable, oldValue, newValue) -> { + pause.setRate(1); + pause.playFromStart(); + }); + + JFXButton closeSearchBar = createToolbarButton2(null, SVG.CLOSE, + () -> { + changeToolbar(toolbarNormal); + isSearching = false; + searchField.clear(); + Bindings.bindContent(listView.getItems(), getSkinnable().getItems()); + }); - VBox.setVgrow(listView, Priority.ALWAYS); + onEscPressed(searchField, closeSearchBar::fire); - listView.setCellFactory(param -> new JijModListCell(listView)); + searchBar.getChildren().setAll(searchField, closeSearchBar); - contentBox.getChildren().addAll(toolbar, listView); - rootPane.getChildren().add(contentBox); + // Toolbar Normal + toolbarNormal.getChildren().addAll( + createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh), + createToolbarButton2("导出全部 JIJ 信息", SVG.DOWNLOAD, () -> exportAllJijList(listView.getItems())), + createToolbarButton2(i18n("search"), SVG.SEARCH, () -> changeToolbar(searchBar)) + ); - getChildren().add(rootPane); + root.getContent().add(toolbarPane); + changeToolbar(toolbarNormal); + } + + { + SpinnerPane center = new SpinnerPane(); + ComponentList.setVgrow(center, Priority.ALWAYS); + center.getStyleClass().add("large-spinner-pane"); + center.loadingProperty().bind(skinnable.loadingProperty()); + + // 修复:不要直接设置 Items,否则 Bindings.bindContent 会导致 "Cannot bind object to itself" + // listView.setItems(skinnable.getItems()); + + listView.setCellFactory(param -> new JijModListCell(listView)); + Bindings.bindContent(listView.getItems(), skinnable.getItems()); + + center.setContent(listView); + root.getContent().add(center); + } + + pane.getChildren().add(root); + getChildren().add(pane); + } + + private void changeToolbar(HBox newToolbar) { + Node oldToolbar = toolbarPane.getCurrentNode(); + if (newToolbar != oldToolbar) { + toolbarPane.setContent(newToolbar, ContainerAnimations.FADE); + if (newToolbar == searchBar) { + Platform.runLater(searchField::requestFocus); + } + } + } + + private void search() { + isSearching = true; + Bindings.unbindContent(listView.getItems(), getSkinnable().getItems()); + + String queryString = searchField.getText(); + if (StringUtils.isBlank(queryString)) { + listView.getItems().setAll(getSkinnable().getItems()); + } else { + listView.getItems().clear(); + String lowerQueryString = queryString.toLowerCase(Locale.ROOT); + Predicate predicate = s -> s != null && s.toLowerCase(Locale.ROOT).contains(lowerQueryString); + + for (ModListPageSkin.ModInfoObject item : getSkinnable().getItems()) { + LocalModFile modInfo = item.getModInfo(); + if (predicate.test(modInfo.getFileName()) + || predicate.test(modInfo.getName()) + || predicate.test(modInfo.getId()) + || (item.getModTranslations() != null && predicate.test(item.getModTranslations().getDisplayName()))) { + listView.getItems().add(item); + } + } + } } private class JijModListCell extends MDListCell { @@ -228,7 +319,7 @@ private void showBundledPopup(Node anchor, String modName, List bundledM } if (flowPane.getChildren().isEmpty()) { - Label emptyLabel = new Label("无匹配结果"); + Label emptyLabel = new Label("无匹配���果"); emptyLabel.setStyle("-fx-text-fill: -fx-text-base-color-disabled;"); flowPane.getChildren().add(emptyLabel); } @@ -254,7 +345,7 @@ private static void exportJijList(String modName, List bundledMods) { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("保存内置模组列表"); fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("文本文件 (*.txt)", "*.txt")); - fileChooser.setInitialFileName("JIJ.txt"); + fileChooser.setInitialFileName(modName+"_JIJ_INFO.txt"); File file = fileChooser.showSaveDialog(Controllers.getStage()); @@ -334,4 +425,4 @@ private static void exportAllJijList(List allMods }).start(); } } -} +} \ No newline at end of file From 0a6f120625ef97c0cc4ef4e2d351fba59ac3eea6 Mon Sep 17 00:00:00 2001 From: lokins Date: Sun, 1 Feb 2026 16:03:14 +0800 Subject: [PATCH 07/40] update --- .../org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java index ea00499dbf8..a8c4db7197d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -186,12 +186,12 @@ private class JijModListCell extends MDListCell { private final ImageView imageView = new ImageView(); private final TwoLineListItem content = new TwoLineListItem(); - private final HBox container = new HBox(8); public JijModListCell(JFXListView listView) { super(listView); this.getStyleClass().add("mod-info-list-cell"); + HBox container = new HBox(8); container.setPickOnBounds(false); container.setAlignment(Pos.CENTER_LEFT); StackPane.setMargin(container, new Insets(8)); From 8ecce954acd00f7455b6c848042c92095f41d014 Mon Sep 17 00:00:00 2001 From: lokins Date: Sun, 1 Feb 2026 16:06:52 +0800 Subject: [PATCH 08/40] update --- .../hmcl/ui/versions/BuiltInModListPageSkin.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java index a8c4db7197d..5b089876fb2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -194,7 +194,7 @@ public JijModListCell(JFXListView listView) { HBox container = new HBox(8); container.setPickOnBounds(false); container.setAlignment(Pos.CENTER_LEFT); - StackPane.setMargin(container, new Insets(8)); + StackPane.setMargin(container, new Insets(8, 8, 8, 18)); imageView.setFitWidth(24); imageView.setFitHeight(24); @@ -268,7 +268,7 @@ private void showBundledPopup(Node anchor, String modName, List bundledM VBox root = new VBox(10); root.setPadding(new Insets(15)); root.setMaxHeight(400); - root.setStyle("-fx-background-color: -fx-background;"); + root.setStyle("-fx-background-color: -fx-control-inner-background; -fx-text-fill: -fx-text-inner-color;"); HBox header = new HBox(10); header.setAlignment(Pos.CENTER_LEFT); @@ -306,7 +306,8 @@ private void showBundledPopup(Node anchor, String modName, List bundledM String name = path.contains("/") ? path.substring(path.lastIndexOf('/') + 1) : path; Label tag = new Label(name); - tag.setStyle("-fx-background-color: -fx-control-inner-background-alt; " + + tag.setStyle("-fx-background-color: -fx-background; " + + "-fx-text-fill: -fx-text-base-color; " + "-fx-padding: 4 8; " + "-fx-background-radius: 4; " + "-fx-border-color: -fx-box-border; " + @@ -319,7 +320,7 @@ private void showBundledPopup(Node anchor, String modName, List bundledM } if (flowPane.getChildren().isEmpty()) { - Label emptyLabel = new Label("无匹配���果"); + Label emptyLabel = new Label("无匹配结果"); emptyLabel.setStyle("-fx-text-fill: -fx-text-base-color-disabled;"); flowPane.getChildren().add(emptyLabel); } @@ -345,7 +346,7 @@ private static void exportJijList(String modName, List bundledMods) { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("保存内置模组列表"); fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("文本文件 (*.txt)", "*.txt")); - fileChooser.setInitialFileName(modName+"_JIJ_INFO.txt"); + fileChooser.setInitialFileName(modName + "_JIJ_INFO.txt"); File file = fileChooser.showSaveDialog(Controllers.getStage()); From df4d36cb352fdf36e5dc2d1228e7ea849f9a6842 Mon Sep 17 00:00:00 2001 From: lokins Date: Sun, 1 Feb 2026 16:09:54 +0800 Subject: [PATCH 09/40] update --- .../org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java index 5b089876fb2..be861496598 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -426,4 +426,4 @@ private static void exportAllJijList(List allMods }).start(); } } -} \ No newline at end of file +} From 28c3d82406047dde907dd0125040d6d28a025508 Mon Sep 17 00:00:00 2001 From: lokins Date: Sun, 1 Feb 2026 16:56:00 +0800 Subject: [PATCH 10/40] move the BuiltinmodPage to ModListPage --- .../hmcl/ui/versions/VersionPage.java | 73 ++++++++++++++++++- 1 file changed, 69 insertions(+), 4 deletions(-) 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 dd931cf6931..e509a30bd11 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 @@ -241,10 +241,75 @@ protected Skin(VersionPage control) { { AdvancedListBox sideBar = new AdvancedListBox() .addNavigationDrawerTab(control.tab, control.versionSettingsTab, i18n("settings.game"), SVG.SETTINGS, SVG.SETTINGS_FILL) - .addNavigationDrawerTab(control.tab, control.installerListTab, i18n("settings.tabs.installers"), SVG.DEPLOYED_CODE, SVG.DEPLOYED_CODE_FILL) - .addNavigationDrawerTab(control.tab, control.modListTab, i18n("mods.manage"), SVG.EXTENSION, SVG.EXTENSION_FILL) - .addNavigationDrawerTab(control.tab, control.builtInModListTab, "内置模组", SVG.EXTENSION, SVG.EXTENSION_FILL) - .addNavigationDrawerTab(control.tab, control.resourcePackTab, i18n("resourcepack.manage"), SVG.TEXTURE) + .addNavigationDrawerTab(control.tab, control.installerListTab, i18n("settings.tabs.installers"), SVG.DEPLOYED_CODE, SVG.DEPLOYED_CODE_FILL); + + BooleanProperty isExpanded = new SimpleBooleanProperty(false); + + AdvancedListItem modListItem = new AdvancedListItem(); + modListItem.getStyleClass().add("navigation-drawer-item"); + modListItem.setTitle(i18n("mods.manage")); + modListItem.setActionButtonVisible(true); + + { + Node unselectedIcon = SVG.EXTENSION.createIcon(20); + Node selectedIcon = SVG.EXTENSION_FILL.createIcon(20); + TransitionPane leftGraphic = new TransitionPane(); + leftGraphic.setAlignment(Pos.CENTER); + FXUtils.setLimitWidth(leftGraphic, 30); + FXUtils.setLimitHeight(leftGraphic, 20); + leftGraphic.setPadding(Insets.EMPTY); + + modListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.modListTab)); + + leftGraphic.setContent(modListItem.isActive() ? selectedIcon : unselectedIcon, ContainerAnimations.NONE); + FXUtils.onChange(modListItem.activeProperty(), active -> + leftGraphic.setContent(active ? selectedIcon : unselectedIcon, ContainerAnimations.FADE)); + modListItem.setLeftGraphic(leftGraphic); + } + + StackPane arrowContainer = new StackPane(); + FXUtils.setLimitWidth(arrowContainer, 40); + arrowContainer.setCursor(Cursor.HAND); + + Node arrowIcon = SVG.KEYBOARD_ARROW_DOWN.createIcon(); + arrowIcon.rotateProperty().bind(Bindings.when(isExpanded).then(180).otherwise(0)); + arrowContainer.getChildren().add(arrowIcon); + + arrowContainer.setOnMouseClicked(e -> { + isExpanded.set(!isExpanded.get()); + e.consume(); + }); + modListItem.setRightGraphic(arrowContainer); + + modListItem.setOnAction(e -> control.tab.select(control.modListTab)); + sideBar.add(modListItem); + + AdvancedListItem jijListItem = new AdvancedListItem(); + jijListItem.getStyleClass().add("navigation-drawer-item"); + jijListItem.setActionButtonVisible(false); + jijListItem.setTitle("内置模组"); // 如果有语言文件 Key,建议替换为 i18n("mods.built_in") + + jijListItem.setPadding(new Insets(0, 0, 0, 15)); + + // 设置一个空白的左侧图标占位,确保文字对齐 + Region blankIcon = new Region(); + FXUtils.setLimitWidth(blankIcon, 30); + jijListItem.setLeftGraphic(blankIcon); + + jijListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.builtInModListTab)); + jijListItem.setOnAction(e -> control.tab.select(control.builtInModListTab)); + + jijListItem.visibleProperty().bind(isExpanded); + jijListItem.managedProperty().bind(isExpanded); + sideBar.add(jijListItem); + + FXUtils.onChange(control.tab.getSelectionModel().selectedItemProperty(), tab -> { + if (tab == control.builtInModListTab) { + isExpanded.set(true); + } + }); + + sideBar.addNavigationDrawerTab(control.tab, control.resourcePackTab, i18n("resourcepack.manage"), SVG.TEXTURE) .addNavigationDrawerTab(control.tab, control.worldListTab, i18n("world.manage"), SVG.PUBLIC) .addNavigationDrawerTab(control.tab, control.schematicsTab, i18n("schematics.manage"), SVG.SCHEMA, SVG.SCHEMA_FILL); VBox.setVgrow(sideBar, Priority.ALWAYS); From a5f884dc9c4691edcf8945afe7d6610fab5b28fe Mon Sep 17 00:00:00 2001 From: lokins Date: Sun, 1 Feb 2026 17:18:30 +0800 Subject: [PATCH 11/40] update --- .../main/java/org/jackhuang/hmcl/ui/SVG.java | 2 ++ .../ui/versions/BuiltInModListPageSkin.java | 4 +-- .../hmcl/ui/versions/VersionPage.java | 26 ++++++++++++++----- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java index 8c200f8bc56..69defd901a1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java @@ -61,6 +61,7 @@ public enum SVG { DOWNLOAD("M12 16 7 11 8.4 9.55 11 12.15V4H13V12.15L15.6 9.55 17 11 12 16ZM6 20Q5.175 20 4.5875 19.4125T4 18V15H6V18H18V15H20V18Q20 18.825 19.4125 19.4125T18 20H6Z"), DRESSER("M4 21V5Q4 4.175 4.5875 3.5875T6 3H18Q18.825 3 19.4125 3.5875T20 5V21H18V19H6V21H4ZM6 11H11V5H6V11ZM13 7H18V5H13V7ZM13 11H18V9H13V11ZM10 16H14V14H10V16ZM6 13V17H18V13H6ZM6 13V17 13Z"), EDIT("M5 19H6.425L16.2 9.225 14.775 7.8 5 17.575V19ZM3 21V16.75L16.2 3.575Q16.5 3.3 16.8625 3.15T17.625 3Q18.025 3 18.4 3.15T19.05 3.6L20.425 5Q20.725 5.275 20.8625 5.65T21 6.4Q21 6.8 20.8625 7.1625T20.425 7.825L7.25 21H3ZM19 6.4 17.6 5 19 6.4ZM15.475 8.525 14.775 7.8 16.2 9.225 15.475 8.525Z"), + FILE_EXPORT("M19 19H5V5h7V3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59L7.05 15.54 8.46 16.95 19 6.41V10h2V3z"), ERROR("M12 17Q12.425 17 12.7125 16.7125T13 16Q13 15.575 12.7125 15.2875T12 15Q11.575 15 11.2875 15.2875T11 16Q11 16.425 11.2875 16.7125T12 17ZM11 13H13V7H11V13ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"), EXPLORE("M12 12Zm0 8q-3.325 0-5.6625-2.3375T4 12Q4 8.675 6.3375 6.3375T12 4q3.325-0 5.6625 2.3375T20 12q0 3.325-2.3375 5.6625T12 20Zm0 2q2.075-0 3.9-.7875t3.175-2.1375q1.35-1.35 2.1375-3.175T22 12q-0-2.075-.7875-3.9T19.075 4.925q-1.35-1.35-3.175-2.1375T12 2q-2.075 0-3.9.7875T4.925 4.925Q3.575 6.275 2.7875 8.1T2 12q0 2.075.7875 3.9T4.925 19.075q1.35 1.35 3.175 2.1375T12 22Zm0-8.5q.625 0 1.0625-.4375T13.5 12t-.4375-1.0625T12 10.5t-1.0625.4375T10.5 12t.4375 1.0625T12 13.5Zm-4.5 3 2-7 7-2-2 7-7 2Z"), EXTENSION("M8.8 21H5Q4.175 21 3.5875 20.4125T3 19V15.2Q4.2 15.2 5.1 14.4375T6 12.5Q6 11.325 5.1 10.5625T3 9.8V6Q3 5.175 3.5875 4.5875T5 4H9Q9 2.95 9.725 2.225T11.5 1.5Q12.55 1.5 13.275 2.225T14 4H18Q18.825 4 19.4125 4.5875T20 6V10Q21.05 10 21.775 10.725T22.5 12.5Q22.5 13.55 21.775 14.275T20 15V19Q20 19.825 19.4125 20.4125T18 21H14.2Q14.2 19.75 13.4125 18.875T11.5 18Q10.375 18 9.5875 18.875T8.8 21ZM5 19H7.125Q7.725 17.35 9.05 16.675T11.5 16Q12.625 16 13.95 16.675T15.875 19H18V13H20Q20.2 13 20.35 12.85T20.5 12.5Q20.5 12.3 20.35 12.15T20 12H18V6H12V4Q12 3.8 11.85 3.65T11.5 3.5Q11.3 3.5 11.15 3.65T11 4V6H5V8.2Q6.35 8.7 7.175 9.875T8 12.5Q8 13.925 7.175 15.1T5 16.8V19ZM11.5 12.5Z"), @@ -116,6 +117,7 @@ public enum SVG { SELECT_ALL("M7 17V7H17V17H7ZM9 15H15V9H9V15ZM5 19V21Q4.175 21 3.5875 20.4125T3 19H5ZM3 17V15H5V17H3ZM3 13V11H5V13H3ZM3 9V7H5V9H3ZM5 5H3Q3 4.175 3.5875 3.5875T5 3V5ZM7 21V19H9V21H7ZM7 5V3H9V5H7ZM11 21V19H13V21H11ZM11 5V3H13V5H11ZM15 21V19H17V21H15ZM15 5V3H17V5H15ZM19 21V19H21Q21 19.825 20.4125 20.4125T19 21ZM19 17V15H21V17H19ZM19 13V11H21V13H19ZM19 9V7H21V9H19ZM19 5V3Q19.825 3 20.4125 3.5875T21 5H19Z"), SETTINGS("M19.43 12.98C19.47 12.66 19.5 12.34 19.5 12 19.5 11.66 19.47 11.34 19.43 11.02L21.54 9.37C21.73 9.22 21.78 8.95 21.66 8.73L19.66 5.27C19.57 5.11 19.4 5.02 19.22 5.02 19.16 5.02 19.1 5.03 19.05 5.05L16.56 6.05C16.04 5.65 15.48 5.32 14.87 5.07L14.49 2.42C14.46 2.18 14.25 2 14 2H10C9.75 2 9.54 2.18 9.51 2.42L9.13 5.07C8.52 5.32 7.96 5.66 7.44 6.05L4.95 5.05C4.89 5.03 4.83 5.02 4.77 5.02 4.6 5.02 4.43 5.11 4.34 5.27L2.34 8.73C2.21 8.95 2.27 9.22 2.46 9.37L4.57 11.02C4.53 11.34 4.5 11.67 4.5 12 4.5 12.33 4.53 12.66 4.57 12.98L2.46 14.63C2.27 14.78 2.22 15.05 2.34 15.27L4.34 18.73C4.43 18.89 4.6 18.98 4.78 18.98 4.84 18.98 4.9 18.97 4.95 18.95L7.44 17.95C7.96 18.35 8.52 18.68 9.13 18.93L9.51 21.58C9.54 21.82 9.75 22 10 22H14C14.25 22 14.46 21.82 14.49 21.58L14.87 18.93C15.48 18.68 16.04 18.34 16.56 17.95L19.05 18.95C19.11 18.97 19.17 18.98 19.23 18.98 19.4 18.98 19.57 18.89 19.66 18.73L21.66 15.27C21.78 15.05 21.73 14.78 21.54 14.63L19.43 12.98ZM17.45 11.27C17.49 11.58 17.5 11.79 17.5 12 17.5 12.21 17.48 12.43 17.45 12.73L17.31 13.86 18.2 14.56 19.28 15.4 18.58 16.61 17.31 16.1 16.27 15.68 15.37 16.36C14.94 16.68 14.53 16.92 14.12 17.09L13.06 17.52 12.9 18.65 12.7 20H11.3L11.11 18.65 10.95 17.52 9.89 17.09C9.46 16.91 9.06 16.68 8.66 16.38L7.75 15.68 6.69 16.11 5.42 16.62 4.72 15.41 5.8 14.57 6.69 13.87 6.55 12.74C6.52 12.43 6.5 12.2 6.5 12S6.52 11.57 6.55 11.27L6.69 10.14 5.8 9.44 4.72 8.6 5.42 7.39 6.69 7.9 7.73 8.32 8.63 7.64C9.06 7.32 9.47 7.08 9.88 6.91L10.94 6.48 11.1 5.35 11.3 4H12.69L12.88 5.35 13.04 6.48 14.1 6.91C14.53 7.09 14.93 7.32 15.33 7.62L16.24 8.32 17.3 7.89 18.57 7.38 19.27 8.59 18.2 9.44 17.31 10.14 17.45 11.27ZM12 8C9.79 8 8 9.79 8 12S9.79 16 12 16 16 14.21 16 12 14.21 8 12 8ZM12 14C10.9 14 10 13.1 10 12S10.9 10 12 10 14 10.9 14 12 13.1 14 12 14Z"), // Material Icons SETTINGS_FILL("M9.25 22l-.4-3.2q-.325-.125-.6125-.3t-.5625-.375L4.7 19.375l-2.75-4.75 2.575-1.95Q4.5 12.5 4.5 12.3375v-.675q0-.1625.025-.3375L1.95 9.375 4.7 4.625l2.975 1.25q.275-.2.575-.375t.6-.3L9.25 2h5.5l.4 3.2q.325.125.6125.3t.5625.375L19.3 4.625l2.75 4.75-2.575 1.95q.025.175.025.3375v.675q0 .1625-.05.3375l2.575 1.95-2.75 4.75-2.95-1.25q-.275.2-.575.375t-.6.3l-.4 3.2H9.25Zm2.8-6.5q1.45 0 2.475-1.025T15.55 12 14.525 9.525 12.05 8.5q-1.475 0-2.4875 1.025T8.55 12q0 1.45 1.0125 2.475T12.05 15.5Z"), // Material Icons + STACKS("M11.99 18.54l-7.37-5.73L3 14.07l9 7 9-7-1.63-1.27-7.38 5.74zM12 16l7.36-5.73L21 9l-9-7-9 7 1.63 1.27L12 16z"), STADIA_CONTROLLER("M4.725 20Q3.225 20 2.1625 18.925T1.05 16.325Q1.05 16.1 1.075 15.875T1.15 15.425L3.25 7.025Q3.6 5.675 4.675 4.8375T7.125 4H16.875Q18.25 4 19.325 4.8375T20.75 7.025L22.85 15.425Q22.9 15.65 22.9375 15.8875T22.975 16.35Q22.975 17.875 21.8875 18.9375T19.275 20Q18.225 20 17.325 19.45T15.975 17.95L15.275 16.5Q15.15 16.25 14.9 16.125T14.375 16H9.625Q9.35 16 9.1 16.125T8.725 16.5L8.025 17.95Q7.575 18.9 6.675 19.45T4.725 20ZM4.8 18Q5.275 18 5.6625 17.75T6.25 17.075L6.95 15.65Q7.325 14.875 8.05 14.4375T9.625 14H14.375Q15.225 14 15.95 14.45T17.075 15.65L17.775 17.075Q17.975 17.5 18.3625 17.75T19.225 18Q19.925 18 20.425 17.5375T20.95 16.375Q20.95 16.4 20.9 15.9L18.8 7.525Q18.625 6.85 18.1 6.425T16.875 6H7.125Q6.425 6 5.8875 6.425T5.2 7.525L3.1 15.9Q3.05 16.05 3.05 16.35 3.05 17.05 3.5625 17.525T4.8 18ZM13.5 11Q13.925 11 14.2125 10.7125T14.5 10Q14.5 9.575 14.2125 9.2875T13.5 9Q13.075 9 12.7875 9.2875T12.5 10Q12.5 10.425 12.7875 10.7125T13.5 11ZM15.5 9Q15.925 9 16.2125 8.7125T16.5 8Q16.5 7.575 16.2125 7.2875T15.5 7Q15.075 7 14.7875 7.2875T14.5 8Q14.5 8.425 14.7875 8.7125T15.5 9ZM15.5 13Q15.925 13 16.2125 12.7125T16.5 12Q16.5 11.575 16.2125 11.2875T15.5 11Q15.075 11 14.7875 11.2875T14.5 12Q14.5 12.425 14.7875 12.7125T15.5 13ZM17.5 11Q17.925 11 18.2125 10.7125T18.5 10Q18.5 9.575 18.2125 9.2875T17.5 9Q17.075 9 16.7875 9.2875T16.5 10Q16.5 10.425 16.7875 10.7125T17.5 11ZM8.5 12.5Q8.825 12.5 9.0375 12.2875T9.25 11.75V10.75H10.25Q10.575 10.75 10.7875 10.5375T11 10Q11 9.675 10.7875 9.4625T10.25 9.25H9.25V8.25Q9.25 7.925 9.0375 7.7125T8.5 7.5Q8.175 7.5 7.9625 7.7125T7.75 8.25V9.25H6.75Q6.425 9.25 6.2125 9.4625T6 10Q6 10.325 6.2125 10.5375T6.75 10.75H7.75V11.75Q7.75 12.075 7.9625 12.2875T8.5 12.5ZM12 12Z"), STADIA_CONTROLLER_FILL("M4.725 20q-1.5 0-2.5625-1.075T1.05 16.325q0-.225.025-.45t.075-.45l2.1-8.4q.35-1.35 1.425-2.1875T7.125 4h9.75q1.375 0 2.45.8375T20.75 7.025l2.1 8.4q.05.225.0875.4625t.0375.4625q0 1.525-1.0875 2.5875T19.275 20q-1.05 0-1.95-.55t-1.35-1.5l-.7-1.45q-.125-.25-.375-.375T14.375 16H9.625q-.275 0-.525.125t-.375.375l-.7 1.45q-.45.95-1.35 1.5T4.725 20ZM13.5 11q.425 0 .7125-.2875T14.5 10t-.2875-.7125T13.5 9t-.7125.2875T12.5 10t.2875.7125T13.5 11Zm2-2q.425 0 .7125-.2875T16.5 8q0-.425-.2875-.7125T15.5 7q-.425 0-.7125.2875T14.5 8t.2875.7125T15.5 9Zm0 4q.425 0 .7125-.2875T16.5 12q0-.425-.2875-.7125T15.5 11q-.425 0-.7125.2875T14.5 12t.2875.7125T15.5 13Zm2-2q.425 0 .7125-.2875T18.5 10q0-.425-.2875-.7125T17.5 9q-.425 0-.7125.2875T16.5 10q0 .425.2875.7125T17.5 11Zm-9 1.5q.325 0 .5375-.2125T9.25 11.75v-1h1q.325 0 .5375-.2125T11 10t-.2125-.5375T10.25 9.25h-1v-1q0-.325-.2125-.5375T8.5 7.5q-.325 0-.5375.2125T7.75 8.25v1h-1q-.325 0-.5375.2125T6 10q0 .325.2125.5375T6.75 10.75h1v1q0 .325.2125.5375T8.5 12.5Z"), STYLE("M3.975 19.8 3.125 19.45Q2.35 19.125 2.0875 18.325T2.175 16.75L3.975 12.85V19.8ZM7.975 22Q7.15 22 6.5625 21.4125T5.975 20V14L8.625 21.35Q8.7 21.525 8.775 21.6875T8.975 22H7.975ZM13.125 21.9Q12.325 22.2 11.575 21.825T10.525 20.65L6.075 8.45Q5.775 7.65 6.125 6.8875T7.275 5.85L14.825 3.1Q15.625 2.8 16.375 3.175T17.425 4.35L21.875 16.55Q22.175 17.35 21.825 18.1125T20.675 19.15L13.125 21.9ZM10.975 10Q11.4 10 11.6875 9.7125T11.975 9Q11.975 8.575 11.6875 8.2875T10.975 8Q10.55 8 10.2625 8.2875T9.975 9Q9.975 9.425 10.2625 9.7125T10.975 10ZM12.425 20 19.975 17.25 15.525 5 7.975 7.75 12.425 20ZM7.975 7.75 15.525 5 7.975 7.75Z"), diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java index be861496598..28b68eaad2c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -120,7 +120,7 @@ protected BuiltInModListPageSkin(BuiltInModListPage skinnable) { // Toolbar Normal toolbarNormal.getChildren().addAll( createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh), - createToolbarButton2("导出全部 JIJ 信息", SVG.DOWNLOAD, () -> exportAllJijList(listView.getItems())), + createToolbarButton2("导出全部 JIJ 信息", SVG.FILE_EXPORT, () -> exportAllJijList(listView.getItems())), createToolbarButton2(i18n("search"), SVG.SEARCH, () -> changeToolbar(searchBar)) ); @@ -280,7 +280,7 @@ private void showBundledPopup(Node anchor, String modName, List bundledM HBox.setHgrow(spacer, Priority.ALWAYS); JFXButton exportButton = new JFXButton(); - exportButton.setGraphic(FXUtils.limitingSize(SVG.DOWNLOAD.createIcon(18), 18, 18)); + exportButton.setGraphic(FXUtils.limitingSize(SVG.FILE_EXPORT.createIcon(18), 18, 18)); exportButton.getStyleClass().add("toggle-icon4"); FXUtils.installFastTooltip(exportButton, "导出JIJ信息"); 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 e509a30bd11..9ebb385088b 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 @@ -18,12 +18,19 @@ package org.jackhuang.hmcl.ui.versions; import com.jfoenix.controls.JFXPopup; +import javafx.animation.RotateTransition; import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.property.*; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Cursor; import javafx.scene.Node; import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; +import javafx.util.Duration; import org.jackhuang.hmcl.event.EventBus; import org.jackhuang.hmcl.event.EventPriority; import org.jackhuang.hmcl.event.RefreshedVersionsEvent; @@ -33,6 +40,7 @@ import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.WeakListenerHolder; +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; @@ -272,7 +280,16 @@ protected Skin(VersionPage control) { arrowContainer.setCursor(Cursor.HAND); Node arrowIcon = SVG.KEYBOARD_ARROW_DOWN.createIcon(); - arrowIcon.rotateProperty().bind(Bindings.when(isExpanded).then(180).otherwise(0)); + // 设置初始角度,不使用绑定 + arrowIcon.setRotate(isExpanded.get() ? 180 : 0); + + // 添加旋转动画监听 + FXUtils.onChange(isExpanded, expanded -> { + RotateTransition rt = new RotateTransition(Duration.millis(200), arrowIcon); + rt.setToAngle(expanded ? 180 : 0); + rt.play(); + }); + arrowContainer.getChildren().add(arrowIcon); arrowContainer.setOnMouseClicked(e -> { @@ -291,10 +308,7 @@ protected Skin(VersionPage control) { jijListItem.setPadding(new Insets(0, 0, 0, 15)); - // 设置一个空白的左侧图标占位,确保文字对齐 - Region blankIcon = new Region(); - FXUtils.setLimitWidth(blankIcon, 30); - jijListItem.setLeftGraphic(blankIcon); + jijListItem.setLeftGraphic(SVG.STACKS.createIcon(20)); jijListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.builtInModListTab)); jijListItem.setOnAction(e -> control.tab.select(control.builtInModListTab)); @@ -375,4 +389,4 @@ protected Skin(VersionPage control) { public interface VersionLoadable { void loadVersion(Profile profile, String version); } -} +} \ No newline at end of file From d916fd8220b6df2bee3d30f925803fdae9876ef0 Mon Sep 17 00:00:00 2001 From: lokins Date: Sun, 1 Feb 2026 17:21:55 +0800 Subject: [PATCH 12/40] update --- .../main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 9ebb385088b..b0d6ff9a0ea 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 @@ -27,7 +27,6 @@ import javafx.scene.Cursor; import javafx.scene.Node; import javafx.scene.layout.Priority; -import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.util.Duration; @@ -389,4 +388,4 @@ protected Skin(VersionPage control) { public interface VersionLoadable { void loadVersion(Profile profile, String version); } -} \ No newline at end of file +} From 93851df15a023928addad0ed7fb8b22387513069 Mon Sep 17 00:00:00 2001 From: lokins Date: Sun, 1 Feb 2026 17:50:13 +0800 Subject: [PATCH 13/40] update i18n --- .../ui/versions/BuiltInModListPageSkin.java | 25 ++++++++----------- .../hmcl/ui/versions/VersionPage.java | 4 +-- .../resources/assets/lang/I18N.properties | 7 ++++++ 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java index 28b68eaad2c..a884836d6ca 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -120,7 +120,7 @@ protected BuiltInModListPageSkin(BuiltInModListPage skinnable) { // Toolbar Normal toolbarNormal.getChildren().addAll( createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh), - createToolbarButton2("导出全部 JIJ 信息", SVG.FILE_EXPORT, () -> exportAllJijList(listView.getItems())), + createToolbarButton2(i18n("mods.built_in.exportAllJIJINFO"), SVG.FILE_EXPORT, () -> exportAllJijList(listView.getItems())), createToolbarButton2(i18n("search"), SVG.SEARCH, () -> changeToolbar(searchBar)) ); @@ -134,9 +134,6 @@ protected BuiltInModListPageSkin(BuiltInModListPage skinnable) { center.getStyleClass().add("large-spinner-pane"); center.loadingProperty().bind(skinnable.loadingProperty()); - // 修复:不要直接设置 Items,否则 Bindings.bindContent 会导致 "Cannot bind object to itself" - // listView.setItems(skinnable.getItems()); - listView.setCellFactory(param -> new JijModListCell(listView)); Bindings.bindContent(listView.getItems(), skinnable.getItems()); @@ -254,7 +251,7 @@ protected void updateControl(ModListPageSkin.ModInfoObject dataItem, boolean emp content.getTags().clear(); if (modInfo.hasBundledMods()) { - content.addTag("内置: " + modInfo.getBundledMods().size()); + content.addTag(i18n("mods.built_in") +" : "+modInfo.getBundledMods().size()); } String modVersion = modInfo.getVersion(); @@ -273,7 +270,7 @@ private void showBundledPopup(Node anchor, String modName, List bundledM HBox header = new HBox(10); header.setAlignment(Pos.CENTER_LEFT); - Label titleLabel = new Label("内置模组 (" + bundledMods.size() + ")"); + Label titleLabel = new Label(i18n("mods.built_in")+" (" + bundledMods.size() + ")"); titleLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold;"); Region spacer = new Region(); @@ -282,14 +279,14 @@ private void showBundledPopup(Node anchor, String modName, List bundledM JFXButton exportButton = new JFXButton(); exportButton.setGraphic(FXUtils.limitingSize(SVG.FILE_EXPORT.createIcon(18), 18, 18)); exportButton.getStyleClass().add("toggle-icon4"); - FXUtils.installFastTooltip(exportButton, "导出JIJ信息"); + FXUtils.installFastTooltip(exportButton, i18n("mods.built_in.exportJIJINFO")); exportButton.setOnAction(e -> exportJijList(modName, bundledMods)); header.getChildren().addAll(titleLabel, spacer, exportButton); JFXTextField searchField = new JFXTextField(); - searchField.setPromptText("搜索内置模组..."); + searchField.setPromptText(i18n("mods.built_in.search")); searchField.setFocusTraversable(false); FlowPane flowPane = new FlowPane(); @@ -320,7 +317,7 @@ private void showBundledPopup(Node anchor, String modName, List bundledM } if (flowPane.getChildren().isEmpty()) { - Label emptyLabel = new Label("无匹配结果"); + Label emptyLabel = new Label(i18n("mods.built_in.noresult")); emptyLabel.setStyle("-fx-text-fill: -fx-text-base-color-disabled;"); flowPane.getChildren().add(emptyLabel); } @@ -344,7 +341,7 @@ private void showBundledPopup(Node anchor, String modName, List bundledM private static void exportJijList(String modName, List bundledMods) { if (bundledMods == null || bundledMods.isEmpty()) return; FileChooser fileChooser = new FileChooser(); - fileChooser.setTitle("保存内置模组列表"); + fileChooser.setTitle(i18n("mods.built_in.exportJIJINFO")); fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("文本文件 (*.txt)", "*.txt")); fileChooser.setInitialFileName(modName + "_JIJ_INFO.txt"); @@ -362,7 +359,7 @@ private static void exportJijList(String modName, List bundledMods) { Task.runAsync(() -> { try { Files.writeString(file.toPath(), sb.toString()); - LOG.info("导出成功: " + file.getAbsolutePath()); + LOG.info("Save to: " + file.getAbsolutePath()); } catch (IOException ex) { LOG.warning("Failed to export bundled mods list", ex); } @@ -404,12 +401,12 @@ private static void exportAllJijList(List allMods } if (!hasData) { - FXUtils.runInFX(() -> Controllers.confirm("无包含JIJ信息的模组,操作将取消", i18n("button.ok"), () -> {}, null)); + FXUtils.runInFX(() -> Controllers.confirm(i18n("mods.built_in.cancleexport"), i18n("button.ok"), () -> {}, null)); return; } FileChooser fileChooser = new FileChooser(); - fileChooser.setTitle("导出所有内置模组信息"); + fileChooser.setTitle(i18n("mods.built_in.exportAllJIJINFO")); fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("文本文件 (*.txt)", "*.txt")); fileChooser.setInitialFileName("ALL_JIJ_INFO.txt"); @@ -419,7 +416,7 @@ private static void exportAllJijList(List allMods Task.runAsync(() -> { try { Files.writeString(file.toPath(), sb.toString()); - LOG.info("全部导出成功: " + file.getAbsolutePath()); + LOG.info("Save to: " + file.getAbsolutePath()); } catch (IOException ex) { LOG.warning("Failed to export all bundled mods list", ex); } 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 b0d6ff9a0ea..bb2e1e56e93 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 @@ -279,10 +279,8 @@ protected Skin(VersionPage control) { arrowContainer.setCursor(Cursor.HAND); Node arrowIcon = SVG.KEYBOARD_ARROW_DOWN.createIcon(); - // 设置初始角度,不使用绑定 arrowIcon.setRotate(isExpanded.get() ? 180 : 0); - // 添加旋转动画监听 FXUtils.onChange(isExpanded, expanded -> { RotateTransition rt = new RotateTransition(Duration.millis(200), arrowIcon); rt.setToAngle(expanded ? 180 : 0); @@ -303,7 +301,7 @@ protected Skin(VersionPage control) { AdvancedListItem jijListItem = new AdvancedListItem(); jijListItem.getStyleClass().add("navigation-drawer-item"); jijListItem.setActionButtonVisible(false); - jijListItem.setTitle("内置模组"); // 如果有语言文件 Key,建议替换为 i18n("mods.built_in") + jijListItem.setTitle(i18n("mods.built_in.mods")); jijListItem.setPadding(new Insets(0, 0, 0, 15)); diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 579fdfe5eaa..f13b4dff03c 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -1066,6 +1066,13 @@ mods=Mods mods.add=Add Mod mods.add.failed=Failed to add mod %s. mods.add.success=%s was successfully added. +mods.built_in=Built-in +mods.built_in.cancleexport=No mod contains JIJ information, the operation will be canceled. +mods.built_in.exportJIJINFO=Export The JIJ INFO +mods.built_in.exportAllJIJINFO=Export All JIJ INFO +mods.built_in.mods=Built-in Mods +mods.built_in.noresult=No matching results +mods.built_in.search=Search Built-in Mods mods.broken_dependency.title=Broken dependency mods.broken_dependency.desc=This dependency existed before, but it does not exist anymore. Try using another download source. mods.category=Category From 5ed53949b5e8a09526ac5d1852eaa3d938e39448 Mon Sep 17 00:00:00 2001 From: lokins Date: Sun, 1 Feb 2026 17:54:38 +0800 Subject: [PATCH 14/40] update --- .../jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java index a884836d6ca..0e9b3a5ba72 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -251,7 +251,7 @@ protected void updateControl(ModListPageSkin.ModInfoObject dataItem, boolean emp content.getTags().clear(); if (modInfo.hasBundledMods()) { - content.addTag(i18n("mods.built_in") +" : "+modInfo.getBundledMods().size()); + content.addTag(i18n("mods.built_in") + " : " + modInfo.getBundledMods().size()); } String modVersion = modInfo.getVersion(); @@ -270,7 +270,7 @@ private void showBundledPopup(Node anchor, String modName, List bundledM HBox header = new HBox(10); header.setAlignment(Pos.CENTER_LEFT); - Label titleLabel = new Label(i18n("mods.built_in")+" (" + bundledMods.size() + ")"); + Label titleLabel = new Label(i18n("mods.built_in") + " (" + bundledMods.size() + ")"); titleLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold;"); Region spacer = new Region(); From bbfcf619baad90d9df529e17ec91dcfdec754780 Mon Sep 17 00:00:00 2001 From: lokins Date: Sun, 1 Feb 2026 18:07:34 +0800 Subject: [PATCH 15/40] update logexporter:package All_JIJ_INFO.txt to zip --- .../org/jackhuang/hmcl/game/LogExporter.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java index 3ace4df057d..bf98933a4df 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java @@ -17,6 +17,8 @@ */ package org.jackhuang.hmcl.game; +import org.jackhuang.hmcl.mod.LocalModFile; +import org.jackhuang.hmcl.mod.ModManager; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.IOUtils; import org.jackhuang.hmcl.util.io.Zipper; @@ -74,6 +76,37 @@ public static CompletableFuture exportLogs( zipper.putTextFile(logs, "minecraft.log"); zipper.putTextFile(Logger.filterForbiddenToken(launchScript), OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "launch.bat" : "launch.sh"); + try { + ModManager modManager = gameRepository.getModManager(versionId); + modManager.refreshMods(); + + StringBuilder jijInfo = new StringBuilder(); + boolean hasJij = false; + + for (LocalModFile mod : modManager.getMods()) { + if (mod.hasBundledMods()) { + hasJij = true; + jijInfo.append(mod.getName()); + if (!mod.getName().equals(mod.getFileName())) { + jijInfo.append(" (").append(mod.getFileName()).append(")"); + } + jijInfo.append(System.lineSeparator()); + + for (String bundled : mod.getBundledMods()) { + String name = bundled.contains("/") ? bundled.substring(bundled.lastIndexOf('/') + 1) : bundled; + jijInfo.append("\t|-> ").append(name).append(System.lineSeparator()); + } + jijInfo.append(System.lineSeparator()); + } + } + + if (hasJij) { + zipper.putTextFile(jijInfo.toString(), "ALL_JIJ_INFO.txt"); + } + } catch (Exception e) { + LOG.warning("Failed to export All JIJ info to crash report package", e); + } + for (String id : versions) { Path versionJson = baseDirectory.resolve("versions").resolve(id).resolve(id + ".json"); if (Files.exists(versionJson)) { From 718abd2d0627294b3ecc96161043fe932ccd77ae Mon Sep 17 00:00:00 2001 From: lokins Date: Sun, 1 Feb 2026 18:20:14 +0800 Subject: [PATCH 16/40] update --- .../org/jackhuang/hmcl/game/LogExporter.java | 47 ++++++++++++++----- .../ui/versions/BuiltInModListPageSkin.java | 2 - .../org/jackhuang/hmcl/mod/LocalModFile.java | 3 -- .../hmcl/mod/modinfo/FabricModMetadata.java | 4 +- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java index bf98933a4df..0ee70779d4b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java @@ -80,31 +80,56 @@ public static CompletableFuture exportLogs( ModManager modManager = gameRepository.getModManager(versionId); modManager.refreshMods(); - StringBuilder jijInfo = new StringBuilder(); - boolean hasJij = false; + StringBuilder infoBuilder = new StringBuilder(); + + infoBuilder.append("=== Mod List ===").append(System.lineSeparator()); + + modManager.getMods().stream() + .filter(LocalModFile::isActive) + .sorted((m1, m2) -> String.CASE_INSENSITIVE_ORDER.compare(m1.getName(), m2.getName())) + .forEach(mod -> { + infoBuilder.append(mod.getName()); + if (StringUtils.isNotBlank(mod.getVersion()) && !"${version}".equals(mod.getVersion())) { + infoBuilder.append(" (").append(mod.getVersion()).append(")"); + } + if (!mod.getName().equals(mod.getFileName())) { + infoBuilder.append(" [").append(mod.getFileName()).append("]"); + } + infoBuilder.append(System.lineSeparator()); + }); + + infoBuilder.append(System.lineSeparator()); + infoBuilder.append("----------------------------").append(System.lineSeparator()); + infoBuilder.append(System.lineSeparator()); + + infoBuilder.append("=== JIJ Info List ===").append(System.lineSeparator()); + boolean hasJij = false; for (LocalModFile mod : modManager.getMods()) { - if (mod.hasBundledMods()) { + if (mod.isActive() && mod.hasBundledMods()) { hasJij = true; - jijInfo.append(mod.getName()); + infoBuilder.append(mod.getName()); if (!mod.getName().equals(mod.getFileName())) { - jijInfo.append(" (").append(mod.getFileName()).append(")"); + infoBuilder.append(" (").append(mod.getFileName()).append(")"); } - jijInfo.append(System.lineSeparator()); + infoBuilder.append(System.lineSeparator()); for (String bundled : mod.getBundledMods()) { String name = bundled.contains("/") ? bundled.substring(bundled.lastIndexOf('/') + 1) : bundled; - jijInfo.append("\t|-> ").append(name).append(System.lineSeparator()); + infoBuilder.append("\t|-> ").append(name).append(System.lineSeparator()); } - jijInfo.append(System.lineSeparator()); + infoBuilder.append(System.lineSeparator()); } } - if (hasJij) { - zipper.putTextFile(jijInfo.toString(), "ALL_JIJ_INFO.txt"); + if (!hasJij) { + infoBuilder.append("无 / None").append(System.lineSeparator()); } + + zipper.putTextFile(infoBuilder.toString(), "ALL_MOD_INFO.txt"); + } catch (Exception e) { - LOG.warning("Failed to export All JIJ info to crash report package", e); + LOG.warning("Failed to export mod info to crash report package", e); } for (String id : versions) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java index 0e9b3a5ba72..68130ac4d8d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -92,7 +92,6 @@ protected BuiltInModListPageSkin(BuiltInModListPage skinnable) { searchBar = new HBox(); toolbarNormal = new HBox(); - // Search Bar searchBar.setAlignment(Pos.CENTER); searchBar.setPadding(new Insets(0, 5, 0, 5)); searchField = new JFXTextField(); @@ -117,7 +116,6 @@ protected BuiltInModListPageSkin(BuiltInModListPage skinnable) { searchBar.getChildren().setAll(searchField, closeSearchBar); - // Toolbar Normal toolbarNormal.getChildren().addAll( createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh), createToolbarButton2(i18n("mods.built_in.exportAllJIJINFO"), SVG.FILE_EXPORT, () -> exportAllJijList(listView.getItems())), diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java index ef11bbfe24c..2c0e16f9a75 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java @@ -64,9 +64,6 @@ public LocalModFile(ModManager modManager, LocalMod mod, Path file, String name, this.logoPath = logoPath; this.bundledMods = bundledMods; - if (bundledMods != null && !bundledMods.isEmpty()) { - System.out.println("Found bundled jars in " + file + ": " + bundledMods.size()); - } activeProperty = new SimpleBooleanProperty(this, "active", !modManager.isDisabled(file)) { @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java index 2b3f6dcc780..2aec3433ea0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java @@ -68,9 +68,7 @@ public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFile throw new IOException("File " + modFile + " is not a Fabric mod."); FabricModMetadata metadata = JsonUtils.fromNonNullJsonFully(tree.getInputStream(mcmod), FabricModMetadata.class); String authors = metadata.authors == null ? "" : metadata.authors.stream().map(author -> author.name).collect(Collectors.joining(", ")); - if (metadata.jars != null && !metadata.jars.isEmpty()) { - System.out.println("Found bundled jars in " + modFile + ": " + metadata.jars.size()); - } + List bundledMods = metadata.jars != null ? metadata.jars.stream().map(jar -> jar.file).toList() : Collections.emptyList(); From eb7b0ee666b39b32dede014262c1353aa826c4b3 Mon Sep 17 00:00:00 2001 From: lokins Date: Sun, 1 Feb 2026 19:36:12 +0800 Subject: [PATCH 17/40] update --- HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java index 0ee70779d4b..bfb4171bbdc 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java @@ -126,7 +126,7 @@ public static CompletableFuture exportLogs( infoBuilder.append("无 / None").append(System.lineSeparator()); } - zipper.putTextFile(infoBuilder.toString(), "ALL_MOD_INFO.txt"); + zipper.putTextFile(infoBuilder.toString(), "ALL_MOD_JIJ_INFO.txt"); } catch (Exception e) { LOG.warning("Failed to export mod info to crash report package", e); From 0779f9f6b3460556d483cc1cec89b00575a7e992 Mon Sep 17 00:00:00 2001 From: lokins Date: Sun, 1 Feb 2026 19:52:00 +0800 Subject: [PATCH 18/40] update --- HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java index bfb4171bbdc..d543b31e584 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java @@ -84,10 +84,16 @@ public static CompletableFuture exportLogs( infoBuilder.append("=== Mod List ===").append(System.lineSeparator()); + Path modsDir = runDirectory.resolve("mods"); + + infoBuilder.append("Filesystem structure of: ").append(modsDir).append(System.lineSeparator()); + infoBuilder.append("|-> mods").append(System.lineSeparator()); + modManager.getMods().stream() .filter(LocalModFile::isActive) .sorted((m1, m2) -> String.CASE_INSENSITIVE_ORDER.compare(m1.getName(), m2.getName())) .forEach(mod -> { + infoBuilder.append("| |-> "); infoBuilder.append(mod.getName()); if (StringUtils.isNotBlank(mod.getVersion()) && !"${version}".equals(mod.getVersion())) { infoBuilder.append(" (").append(mod.getVersion()).append(")"); From 7ac48c69141f08a4c06a3537c05831082a25ab23 Mon Sep 17 00:00:00 2001 From: NoClassDefFoundError Date: Sun, 1 Feb 2026 20:27:37 +0800 Subject: [PATCH 19/40] Update Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com> --- .../org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java index 68130ac4d8d..96947676a01 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -249,7 +249,7 @@ protected void updateControl(ModListPageSkin.ModInfoObject dataItem, boolean emp content.getTags().clear(); if (modInfo.hasBundledMods()) { - content.addTag(i18n("mods.built_in") + " : " + modInfo.getBundledMods().size()); + content.addTag(i18n("mods.built_in") + ": " + modInfo.getBundledMods().size()); } String modVersion = modInfo.getVersion(); From bc411852e52d85421be1e505ad7aa84eef37574e Mon Sep 17 00:00:00 2001 From: lokins Date: Sun, 1 Feb 2026 22:10:05 +0800 Subject: [PATCH 20/40] update i18n --- HMCL/src/main/resources/assets/lang/I18N.properties | 12 ++++++------ .../main/resources/assets/lang/I18N_zh.properties | 7 +++++++ .../main/resources/assets/lang/I18N_zh_CN.properties | 7 +++++++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index f13b4dff03c..bbc633aabe9 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -1066,13 +1066,13 @@ mods=Mods mods.add=Add Mod mods.add.failed=Failed to add mod %s. mods.add.success=%s was successfully added. -mods.built_in=Built-in -mods.built_in.cancleexport=No mod contains JIJ information, the operation will be canceled. -mods.built_in.exportJIJINFO=Export The JIJ INFO -mods.built_in.exportAllJIJINFO=Export All JIJ INFO -mods.built_in.mods=Built-in Mods +mods.built_in=Nested +mods.built_in.cancleexport=No mod contains JIJ information. The operation will be canceled. +mods.built_in.exportJIJINFO=Export JIJ info +mods.built_in.exportAllJIJINFO=Export all JIJ info +mods.built_in.mods=Nested mods mods.built_in.noresult=No matching results -mods.built_in.search=Search Built-in Mods +mods.built_in.search=Search nested mods mods.broken_dependency.title=Broken dependency mods.broken_dependency.desc=This dependency existed before, but it does not exist anymore. Try using another download source. mods.category=Category diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index c1166f1b4d4..20ae19dea60 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -855,6 +855,13 @@ mods=模組 mods.add=新增模組 mods.add.failed=新增模組「%s」失敗。 mods.add.success=成功新增模組「%s」。 +mods.built_in=嵌套模組數 +mods.built_in.cancleexport=無 JIJ 資訊包含。操作將取消。 +mods.built_in.exportJIJINFO=匯出 JIJ 資訊 +mods.built_in.exportAllJIJINFO=匯出所有 JIJ 資訊 +mods.built_in.mods=嵌套模組管理 +mods.built_in.noresult=沒有匹配結果 +mods.built_in.search=搜尋嵌套模組 mods.broken_dependency.title=損壞的相依模組 mods.broken_dependency.desc=該相依模組曾經存在於模組倉庫中,但現在已被刪除,請嘗試其他下載源。 mods.category=類別 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index d47299447f4..ebf38095ff2 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -860,6 +860,13 @@ mods=模组 mods.add=添加模组 mods.add.failed=添加模组“%s”失败。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 mods.add.success=成功添加模组 %s。 +mods.built_in=嵌套模组数 +mods.built_in.cancleexport=无 JIJ 信息包含。操作将取消。 +mods.built_in.exportJIJINFO=导出 JIJ 信息 +mods.built_in.exportAllJIJINFO=导出所有 JIJ 信息 +mods.built_in.mods=嵌套模组管理 +mods.built_in.noresult=没有匹配结果 +mods.built_in.search=搜索嵌套模组 mods.broken_dependency.title=损坏的前置模组 mods.broken_dependency.desc=该前置模组曾经在该模组仓库上存在过,但现在被删除了。换个下载源试试吧。 mods.category=类别 From 62a999b458ba9968420203ea4c9db255269c0e55 Mon Sep 17 00:00:00 2001 From: lokins Date: Sun, 1 Feb 2026 22:38:45 +0800 Subject: [PATCH 21/40] update --- .../main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 bb2e1e56e93..042301529cc 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 @@ -276,9 +276,10 @@ protected Skin(VersionPage control) { StackPane arrowContainer = new StackPane(); FXUtils.setLimitWidth(arrowContainer, 40); + FXUtils.setLimitHeight(arrowContainer, 20); arrowContainer.setCursor(Cursor.HAND); - Node arrowIcon = SVG.KEYBOARD_ARROW_DOWN.createIcon(); + Node arrowIcon = SVG.KEYBOARD_ARROW_DOWN.createIcon(20); arrowIcon.setRotate(isExpanded.get() ? 180 : 0); FXUtils.onChange(isExpanded, expanded -> { From 607a119ddf62c5f91abbb4702cdfbbf96acefcce Mon Sep 17 00:00:00 2001 From: lokins Date: Sun, 1 Feb 2026 23:14:53 +0800 Subject: [PATCH 22/40] update --- .../org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java index 96947676a01..5c17e80c66f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -263,7 +263,7 @@ private void showBundledPopup(Node anchor, String modName, List bundledM VBox root = new VBox(10); root.setPadding(new Insets(15)); root.setMaxHeight(400); - root.setStyle("-fx-background-color: -fx-control-inner-background; -fx-text-fill: -fx-text-inner-color;"); + root.getStyleClass().add("card-pane"); HBox header = new HBox(10); header.setAlignment(Pos.CENTER_LEFT); @@ -302,7 +302,6 @@ private void showBundledPopup(Node anchor, String modName, List bundledM Label tag = new Label(name); tag.setStyle("-fx-background-color: -fx-background; " + - "-fx-text-fill: -fx-text-base-color; " + "-fx-padding: 4 8; " + "-fx-background-radius: 4; " + "-fx-border-color: -fx-box-border; " + From 35317ff3d4840e0f483788689aef6ba4ac915489 Mon Sep 17 00:00:00 2001 From: lokins Date: Sun, 1 Feb 2026 23:52:16 +0800 Subject: [PATCH 23/40] update --- .../ui/versions/BuiltInModListPageSkin.java | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java index 5c17e80c66f..66274c5d877 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -181,6 +181,8 @@ private class JijModListCell extends MDListCell { private final ImageView imageView = new ImageView(); private final TwoLineListItem content = new TwoLineListItem(); + private JFXPopup activePopup; + private boolean ignoreNextClick = false; public JijModListCell(JFXListView listView) { super(listView); @@ -203,11 +205,32 @@ public JijModListCell(JFXListView listView) { setSelectable(); + this.setOnMousePressed(e -> { + if (activePopup != null && activePopup.isShowing()) { + ignoreNextClick = true; + } else { + ignoreNextClick = false; + } + }); + this.setOnMouseClicked(e -> { if (getItem() != null && getItem().getModInfo() != null) { LocalModFile modFile = getItem().getModInfo(); if (modFile.hasBundledMods()) { - showBundledPopup(this, modFile.getName(), modFile.getBundledMods()); + + if (activePopup != null && activePopup.isShowing()) { + activePopup.hide(); + activePopup = null; + return; + } + + activePopup = showBundledPopup(this, modFile.getName(), modFile.getBundledMods()); + + activePopup.setOnHidden(event -> { + if (activePopup == event.getSource()) { + activePopup = null; + } + }); } } }); @@ -259,7 +282,7 @@ protected void updateControl(ModListPageSkin.ModInfoObject dataItem, boolean emp } } - private void showBundledPopup(Node anchor, String modName, List bundledMods) { + private JFXPopup showBundledPopup(Node anchor, String modName, List bundledMods) { VBox root = new VBox(10); root.setPadding(new Insets(15)); root.setMaxHeight(400); @@ -333,6 +356,7 @@ private void showBundledPopup(Node anchor, String modName, List bundledM JFXPopup popup = new JFXPopup(root); popup.show(anchor, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, anchor.getLayoutBounds().getHeight() + 5); + return popup; } private static void exportJijList(String modName, List bundledMods) { From 9b3ef4724f9455b51548a1d2be7949ca0f4bbb75 Mon Sep 17 00:00:00 2001 From: lokins Date: Mon, 2 Feb 2026 20:46:53 +0800 Subject: [PATCH 24/40] update i18n --- .../hmcl/ui/versions/BuiltInModListPageSkin.java | 8 ++++---- HMCL/src/main/resources/assets/lang/I18N.properties | 4 ++-- HMCL/src/main/resources/assets/lang/I18N_zh.properties | 4 ++-- HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java index 66274c5d877..aec2d3ac40c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -118,7 +118,7 @@ protected BuiltInModListPageSkin(BuiltInModListPage skinnable) { toolbarNormal.getChildren().addAll( createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh), - createToolbarButton2(i18n("mods.built_in.exportAllJIJINFO"), SVG.FILE_EXPORT, () -> exportAllJijList(listView.getItems())), + createToolbarButton2(i18n("mods.built_in.export.jij_info.all"), SVG.FILE_EXPORT, () -> exportAllJijList(listView.getItems())), createToolbarButton2(i18n("search"), SVG.SEARCH, () -> changeToolbar(searchBar)) ); @@ -300,7 +300,7 @@ private JFXPopup showBundledPopup(Node anchor, String modName, List bund JFXButton exportButton = new JFXButton(); exportButton.setGraphic(FXUtils.limitingSize(SVG.FILE_EXPORT.createIcon(18), 18, 18)); exportButton.getStyleClass().add("toggle-icon4"); - FXUtils.installFastTooltip(exportButton, i18n("mods.built_in.exportJIJINFO")); + FXUtils.installFastTooltip(exportButton, i18n("mods.built_in.export.jij_info")); exportButton.setOnAction(e -> exportJijList(modName, bundledMods)); @@ -362,7 +362,7 @@ private JFXPopup showBundledPopup(Node anchor, String modName, List bund private static void exportJijList(String modName, List bundledMods) { if (bundledMods == null || bundledMods.isEmpty()) return; FileChooser fileChooser = new FileChooser(); - fileChooser.setTitle(i18n("mods.built_in.exportJIJINFO")); + fileChooser.setTitle(i18n("mods.built_in.export.jij_info")); fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("文本文件 (*.txt)", "*.txt")); fileChooser.setInitialFileName(modName + "_JIJ_INFO.txt"); @@ -427,7 +427,7 @@ private static void exportAllJijList(List allMods } FileChooser fileChooser = new FileChooser(); - fileChooser.setTitle(i18n("mods.built_in.exportAllJIJINFO")); + fileChooser.setTitle(i18n("mods.built_in.export.jij_info.all")); fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("文本文件 (*.txt)", "*.txt")); fileChooser.setInitialFileName("ALL_JIJ_INFO.txt"); diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index bbc633aabe9..caf3937cf56 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -1068,8 +1068,8 @@ mods.add.failed=Failed to add mod %s. mods.add.success=%s was successfully added. mods.built_in=Nested mods.built_in.cancleexport=No mod contains JIJ information. The operation will be canceled. -mods.built_in.exportJIJINFO=Export JIJ info -mods.built_in.exportAllJIJINFO=Export all JIJ info +mods.built_in.export.jij_info=Export JIJ info +mods.built_in.export.jij_info.all=Export all JIJ info mods.built_in.mods=Nested mods mods.built_in.noresult=No matching results mods.built_in.search=Search nested mods diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 20ae19dea60..bd535d7c265 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -857,8 +857,8 @@ mods.add.failed=新增模組「%s」失敗。 mods.add.success=成功新增模組「%s」。 mods.built_in=嵌套模組數 mods.built_in.cancleexport=無 JIJ 資訊包含。操作將取消。 -mods.built_in.exportJIJINFO=匯出 JIJ 資訊 -mods.built_in.exportAllJIJINFO=匯出所有 JIJ 資訊 +mods.built_in.export.jij_info=匯出 JIJ 資訊 +mods.built_in.export.jij_info.all=匯出所有 JIJ 資訊 mods.built_in.mods=嵌套模組管理 mods.built_in.noresult=沒有匹配結果 mods.built_in.search=搜尋嵌套模組 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index ebf38095ff2..070c4a81c31 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -862,8 +862,8 @@ mods.add.failed=添加模组“%s”失败。\n如遇到问题,你可以点击 mods.add.success=成功添加模组 %s。 mods.built_in=嵌套模组数 mods.built_in.cancleexport=无 JIJ 信息包含。操作将取消。 -mods.built_in.exportJIJINFO=导出 JIJ 信息 -mods.built_in.exportAllJIJINFO=导出所有 JIJ 信息 +mods.built_in.export.jij_info=导出 JIJ 信息 +mods.built_in.export.jij_info.all=导出所有 JIJ 信息 mods.built_in.mods=嵌套模组管理 mods.built_in.noresult=没有匹配结果 mods.built_in.search=搜索嵌套模组 From 53ef1d6abc7be58b551197d8c0ef79dedb629dce Mon Sep 17 00:00:00 2001 From: lokins Date: Mon, 2 Feb 2026 21:14:23 +0800 Subject: [PATCH 25/40] update --- .../org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java | 1 + 1 file changed, 1 insertion(+) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java index aec2d3ac40c..31c729cf272 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -229,6 +229,7 @@ public JijModListCell(JFXListView listView) { activePopup.setOnHidden(event -> { if (activePopup == event.getSource()) { activePopup = null; + listView.getSelectionModel().clearSelection(); } }); } From aea9cbba3a95a07d8daadb8dae097148b8e254a2 Mon Sep 17 00:00:00 2001 From: lokins Date: Mon, 2 Feb 2026 21:15:53 +0800 Subject: [PATCH 26/40] update --- HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java index d543b31e584..bd9fed9657b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java @@ -129,7 +129,7 @@ public static CompletableFuture exportLogs( } if (!hasJij) { - infoBuilder.append("无 / None").append(System.lineSeparator()); + infoBuilder.append("No JIJ INFO contain").append(System.lineSeparator()); } zipper.putTextFile(infoBuilder.toString(), "ALL_MOD_JIJ_INFO.txt"); From f327bc7204a7e6dd9792a895fb556dc8e8520a31 Mon Sep 17 00:00:00 2001 From: lokins Date: Mon, 2 Feb 2026 21:31:28 +0800 Subject: [PATCH 27/40] update --- .../jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java | 4 ++-- HMCL/src/main/resources/assets/lang/I18N.properties | 1 + HMCL/src/main/resources/assets/lang/I18N_zh.properties | 1 + HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties | 1 + 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java index 31c729cf272..4c2b2a99a3c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -364,7 +364,7 @@ private static void exportJijList(String modName, List bundledMods) { if (bundledMods == null || bundledMods.isEmpty()) return; FileChooser fileChooser = new FileChooser(); fileChooser.setTitle(i18n("mods.built_in.export.jij_info")); - fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("文本文件 (*.txt)", "*.txt")); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("mods.built_in.export.filetype"), "*.txt")); fileChooser.setInitialFileName(modName + "_JIJ_INFO.txt"); File file = fileChooser.showSaveDialog(Controllers.getStage()); @@ -429,7 +429,7 @@ private static void exportAllJijList(List allMods FileChooser fileChooser = new FileChooser(); fileChooser.setTitle(i18n("mods.built_in.export.jij_info.all")); - fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("文本文件 (*.txt)", "*.txt")); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("mods.built_in.export.filetype"), "*.txt")); fileChooser.setInitialFileName("ALL_JIJ_INFO.txt"); File file = fileChooser.showSaveDialog(Controllers.getStage()); diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index caf3937cf56..b63004c4dbf 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -1070,6 +1070,7 @@ mods.built_in=Nested mods.built_in.cancleexport=No mod contains JIJ information. The operation will be canceled. mods.built_in.export.jij_info=Export JIJ info mods.built_in.export.jij_info.all=Export all JIJ info +mods.built_in.export.filetype=Text File (*.txt) mods.built_in.mods=Nested mods mods.built_in.noresult=No matching results mods.built_in.search=Search nested mods diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index bd535d7c265..9753bc2aa5f 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -859,6 +859,7 @@ mods.built_in=嵌套模組數 mods.built_in.cancleexport=無 JIJ 資訊包含。操作將取消。 mods.built_in.export.jij_info=匯出 JIJ 資訊 mods.built_in.export.jij_info.all=匯出所有 JIJ 資訊 +mods.built_in.export.filetype=文本檔案 (*.txt) mods.built_in.mods=嵌套模組管理 mods.built_in.noresult=沒有匹配結果 mods.built_in.search=搜尋嵌套模組 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 070c4a81c31..06b7e76a8b0 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -864,6 +864,7 @@ mods.built_in=嵌套模组数 mods.built_in.cancleexport=无 JIJ 信息包含。操作将取消。 mods.built_in.export.jij_info=导出 JIJ 信息 mods.built_in.export.jij_info.all=导出所有 JIJ 信息 +mods.built_in.export.filetype=文本文件 (*.txt) mods.built_in.mods=嵌套模组管理 mods.built_in.noresult=没有匹配结果 mods.built_in.search=搜索嵌套模组 From 57d465b75a6fed7354ae38ba9d060897c8090d74 Mon Sep 17 00:00:00 2001 From: lokins Date: Mon, 2 Feb 2026 22:23:58 +0800 Subject: [PATCH 28/40] update --- HMCL/src/main/resources/assets/lang/I18N.properties | 8 ++++---- HMCL/src/main/resources/assets/lang/I18N_zh.properties | 8 ++++---- HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index b63004c4dbf..ec950aebe1b 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -1067,10 +1067,10 @@ mods.add=Add Mod mods.add.failed=Failed to add mod %s. mods.add.success=%s was successfully added. mods.built_in=Nested -mods.built_in.cancleexport=No mod contains JIJ information. The operation will be canceled. -mods.built_in.export.jij_info=Export JIJ info -mods.built_in.export.jij_info.all=Export all JIJ info -mods.built_in.export.filetype=Text File (*.txt) +mods.built_in.cancleexport=No mod contains JiJ information. The operation will be canceled. +mods.built_in.export.jij_info=Export JiJ info +mods.built_in.export.jij_info.all=Export all JiJ info +mods.built_in.export.filetype=Text documents (*.txt) mods.built_in.mods=Nested mods mods.built_in.noresult=No matching results mods.built_in.search=Search nested mods diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 9753bc2aa5f..b13976f5250 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -856,10 +856,10 @@ mods.add=新增模組 mods.add.failed=新增模組「%s」失敗。 mods.add.success=成功新增模組「%s」。 mods.built_in=嵌套模組數 -mods.built_in.cancleexport=無 JIJ 資訊包含。操作將取消。 -mods.built_in.export.jij_info=匯出 JIJ 資訊 -mods.built_in.export.jij_info.all=匯出所有 JIJ 資訊 -mods.built_in.export.filetype=文本檔案 (*.txt) +mods.built_in.cancleexport=無 JiJ 資訊包含。操作將取消。 +mods.built_in.export.jij_info=匯出 JiJ 資訊 +mods.built_in.export.jij_info.all=匯出所有 JiJ 資訊 +mods.built_in.export.filetype=文字文件 (*.txt) mods.built_in.mods=嵌套模組管理 mods.built_in.noresult=沒有匹配結果 mods.built_in.search=搜尋嵌套模組 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 06b7e76a8b0..4a7b0de43c5 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -861,10 +861,10 @@ mods.add=添加模组 mods.add.failed=添加模组“%s”失败。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 mods.add.success=成功添加模组 %s。 mods.built_in=嵌套模组数 -mods.built_in.cancleexport=无 JIJ 信息包含。操作将取消。 -mods.built_in.export.jij_info=导出 JIJ 信息 -mods.built_in.export.jij_info.all=导出所有 JIJ 信息 -mods.built_in.export.filetype=文本文件 (*.txt) +mods.built_in.cancleexport=无 JiJ 信息包含。操作将取消。 +mods.built_in.export.jij_info=导出 JiJ 信息 +mods.built_in.export.jij_info.all=导出所有 JiJ 信息 +mods.built_in.export.filetype=文本文档 (*.txt) mods.built_in.mods=嵌套模组管理 mods.built_in.noresult=没有匹配结果 mods.built_in.search=搜索嵌套模组 From a0eeb66bafc9c17d620971ea032162e9c65a4658 Mon Sep 17 00:00:00 2001 From: lokins Date: Tue, 3 Feb 2026 21:07:11 +0800 Subject: [PATCH 29/40] update i18n --- .../jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java | 4 ++-- HMCL/src/main/resources/assets/lang/I18N.properties | 2 +- HMCL/src/main/resources/assets/lang/I18N_zh.properties | 2 +- HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java index 4c2b2a99a3c..0a249e01571 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -364,7 +364,7 @@ private static void exportJijList(String modName, List bundledMods) { if (bundledMods == null || bundledMods.isEmpty()) return; FileChooser fileChooser = new FileChooser(); fileChooser.setTitle(i18n("mods.built_in.export.jij_info")); - fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("mods.built_in.export.filetype"), "*.txt")); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.file"), "*.txt")); fileChooser.setInitialFileName(modName + "_JIJ_INFO.txt"); File file = fileChooser.showSaveDialog(Controllers.getStage()); @@ -429,7 +429,7 @@ private static void exportAllJijList(List allMods FileChooser fileChooser = new FileChooser(); fileChooser.setTitle(i18n("mods.built_in.export.jij_info.all")); - fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("mods.built_in.export.filetype"), "*.txt")); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.file"), "*.txt")); fileChooser.setInitialFileName("ALL_JIJ_INFO.txt"); File file = fileChooser.showSaveDialog(Controllers.getStage()); diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index ec950aebe1b..0569e1e26c7 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -390,6 +390,7 @@ exception.ssl_handshake=Failed to establish SSL connection because the SSL certi exception.dns.pollution=Failed to establish an SSL connection. DNS resolution may be incorrect. Please try changing your DNS server or using a proxy service. extension.bat=Windows Batch File +extension.file=Text documents extension.mod=Mod File extension.png=Image File extension.ps1=Windows PowerShell Script @@ -1070,7 +1071,6 @@ mods.built_in=Nested mods.built_in.cancleexport=No mod contains JiJ information. The operation will be canceled. mods.built_in.export.jij_info=Export JiJ info mods.built_in.export.jij_info.all=Export all JiJ info -mods.built_in.export.filetype=Text documents (*.txt) mods.built_in.mods=Nested mods mods.built_in.noresult=No matching results mods.built_in.search=Search nested mods diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index b13976f5250..27c6b6eba2e 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -380,6 +380,7 @@ exception.ssl_handshake=無法建立 SSL 連線。目前 Java 缺少相關的 SS exception.dns.pollution=無法建立 SSL 連線。可能是 DNS 解析有誤。請嘗試更換 DNS 伺服器或使用代理服務。 extension.bat=Windows 批次檔 +extension.file=文字文件 extension.mod=模組檔案 extension.png=圖片檔案 extension.ps1=PowerShell 指令碼 @@ -859,7 +860,6 @@ mods.built_in=嵌套模組數 mods.built_in.cancleexport=無 JiJ 資訊包含。操作將取消。 mods.built_in.export.jij_info=匯出 JiJ 資訊 mods.built_in.export.jij_info.all=匯出所有 JiJ 資訊 -mods.built_in.export.filetype=文字文件 (*.txt) mods.built_in.mods=嵌套模組管理 mods.built_in.noresult=沒有匹配結果 mods.built_in.search=搜尋嵌套模組 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 4a7b0de43c5..5d8cac32b05 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -383,6 +383,7 @@ exception.ssl_handshake=无法建立 SSL 连接。当前 Java 缺少相关的 SS exception.dns.pollution=无法建立 SSL 连接。可能是 DNS 解析有误。请尝试更换 DNS 服务器或使用代理服务。\n你可以点击右上角帮助按钮进行求助。 extension.bat=Windows 脚本 +extension.file=文本文档 extension.mod=模组文件 extension.png=图片文件 extension.ps1=PowerShell 脚本 @@ -864,7 +865,6 @@ mods.built_in=嵌套模组数 mods.built_in.cancleexport=无 JiJ 信息包含。操作将取消。 mods.built_in.export.jij_info=导出 JiJ 信息 mods.built_in.export.jij_info.all=导出所有 JiJ 信息 -mods.built_in.export.filetype=文本文档 (*.txt) mods.built_in.mods=嵌套模组管理 mods.built_in.noresult=没有匹配结果 mods.built_in.search=搜索嵌套模组 From 772728ffe3bdd83a40aae8ac7e69cb599ef1c2db Mon Sep 17 00:00:00 2001 From: lokins Date: Sat, 7 Feb 2026 17:47:44 +0800 Subject: [PATCH 30/40] update --- .../main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java | 2 -- 1 file changed, 2 deletions(-) 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 042301529cc..3ae133feccb 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 @@ -255,7 +255,6 @@ protected Skin(VersionPage control) { AdvancedListItem modListItem = new AdvancedListItem(); modListItem.getStyleClass().add("navigation-drawer-item"); modListItem.setTitle(i18n("mods.manage")); - modListItem.setActionButtonVisible(true); { Node unselectedIcon = SVG.EXTENSION.createIcon(20); @@ -301,7 +300,6 @@ protected Skin(VersionPage control) { AdvancedListItem jijListItem = new AdvancedListItem(); jijListItem.getStyleClass().add("navigation-drawer-item"); - jijListItem.setActionButtonVisible(false); jijListItem.setTitle(i18n("mods.built_in.mods")); jijListItem.setPadding(new Insets(0, 0, 0, 15)); From 5ccf3f49f7a2af166e824ef5f51357fabc0db571 Mon Sep 17 00:00:00 2001 From: lokins Date: Sat, 7 Feb 2026 17:48:37 +0800 Subject: [PATCH 31/40] update --- HMCL/src/main/resources/assets/lang/I18N.properties | 1 - HMCL/src/main/resources/assets/lang/I18N_zh.properties | 1 - HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties | 1 - 3 files changed, 3 deletions(-) diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index f8398708a6e..c03e46def92 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -391,7 +391,6 @@ exception.dns.pollution=Failed to establish an SSL connection. DNS resolution ma extension.bat=Windows Batch File extension.file=Text documents -extension.mod=Mod File extension.png=Image File extension.ps1=Windows PowerShell Script extension.sh=Shell Script diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 2e4f1667a81..ef44ce9de38 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -381,7 +381,6 @@ exception.dns.pollution=無法建立 SSL 連線。可能是 DNS 解析有誤。 extension.bat=Windows 批次檔 extension.file=文字文件 -extension.mod=模組檔案 extension.png=圖片檔案 extension.ps1=PowerShell 指令碼 extension.sh=Bash 指令碼 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 87cafa2dfba..1622beab00b 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -384,7 +384,6 @@ exception.dns.pollution=无法建立 SSL 连接。可能是 DNS 解析有误。 extension.bat=Windows 脚本 extension.file=文本文档 -extension.mod=模组文件 extension.png=图片文件 extension.ps1=PowerShell 脚本 extension.sh=Bash 脚本 From 0cc4938cef3555b618ae81bb5a028d583a34d6ed Mon Sep 17 00:00:00 2001 From: lokins Date: Mon, 9 Feb 2026 20:11:29 +0800 Subject: [PATCH 32/40] update --- HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java | 2 +- .../jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java | 2 +- .../main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java | 2 +- HMCL/src/main/resources/assets/lang/I18N.properties | 2 +- HMCL/src/main/resources/assets/lang/I18N_zh.properties | 2 +- HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties | 2 +- .../src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java | 4 +++- .../org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java | 2 +- 8 files changed, 10 insertions(+), 8 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java index bd9fed9657b..3a5b30032e2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java @@ -129,7 +129,7 @@ public static CompletableFuture exportLogs( } if (!hasJij) { - infoBuilder.append("No JIJ INFO contain").append(System.lineSeparator()); + infoBuilder.append("No JIJ INFO found").append(System.lineSeparator()); } zipper.putTextFile(infoBuilder.toString(), "ALL_MOD_JIJ_INFO.txt"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java index 0a249e01571..0d1864404a5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -423,7 +423,7 @@ private static void exportAllJijList(List allMods } if (!hasData) { - FXUtils.runInFX(() -> Controllers.confirm(i18n("mods.built_in.cancleexport"), i18n("button.ok"), () -> {}, null)); + FXUtils.runInFX(() -> Controllers.dialog(i18n("mods.built_in.cancelexport"))); return; } 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 3ae133feccb..6d75ad91bfa 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 @@ -78,7 +78,7 @@ public VersionPage() { worldListTab.setNodeSupplier(loadVersionFor(WorldListPage::new)); schematicsTab.setNodeSupplier(loadVersionFor(SchematicsPage::new)); - tab = new TabHeader(transitionPane, versionSettingsTab, installerListTab, modListTab,builtInModListTab, resourcePackTab, worldListTab, schematicsTab); + tab = new TabHeader(transitionPane, versionSettingsTab, installerListTab, modListTab, builtInModListTab, resourcePackTab, worldListTab, schematicsTab); tab.select(versionSettingsTab); addEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated); diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index c03e46def92..8cfd5c3032d 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -1074,7 +1074,7 @@ mods.add=Add mods.add.failed=Failed to add mod %s. mods.add.success=%s was successfully added. mods.built_in=Nested -mods.built_in.cancleexport=No mod contains JiJ information. The operation will be canceled. +mods.built_in.cancelexport=No mod contains JiJ information. The operation will be canceled. mods.built_in.export.jij_info=Export JiJ info mods.built_in.export.jij_info.all=Export all JiJ info mods.built_in.mods=Nested mods diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index ef44ce9de38..04cdb819d6e 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -863,7 +863,7 @@ mods.add=新增模組 mods.add.failed=新增模組「%s」失敗。 mods.add.success=成功新增模組「%s」。 mods.built_in=嵌套模組數 -mods.built_in.cancleexport=無 JiJ 資訊包含。操作將取消。 +mods.built_in.cancelexport=無 JiJ 資訊包含。操作將取消。 mods.built_in.export.jij_info=匯出 JiJ 資訊 mods.built_in.export.jij_info.all=匯出所有 JiJ 資訊 mods.built_in.mods=嵌套模組管理 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 1622beab00b..28a9d49327a 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -868,7 +868,7 @@ mods.add=添加模组 mods.add.failed=添加模组“%s”失败。\n如遇到问题,你可以点击右上角帮助按钮进行求助。 mods.add.success=成功添加模组 %s。 mods.built_in=嵌套模组数 -mods.built_in.cancleexport=无 JiJ 信息包含。操作将取消。 +mods.built_in.cancelexport=无 JiJ 信息包含。操作将取消。 mods.built_in.export.jij_info=导出 JiJ 信息 mods.built_in.export.jij_info.all=导出所有 JiJ 信息 mods.built_in.mods=嵌套模组管理 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java index 2c0e16f9a75..580d3ffad04 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java @@ -62,7 +62,9 @@ public LocalModFile(ModManager modManager, LocalMod mod, Path file, String name, this.gameVersion = gameVersion; this.url = url; this.logoPath = logoPath; - this.bundledMods = bundledMods; + this.bundledMods = bundledMods == null + ? Collections.emptyList() + : Collections.unmodifiableList(new ArrayList<>(bundledMods)); activeProperty = new SimpleBooleanProperty(this, "active", !modManager.isDisabled(file)) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java index 2aec3433ea0..c604b9c5026 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/FabricModMetadata.java @@ -51,7 +51,7 @@ public FabricModMetadata() { this("", "", "", "", "", Collections.emptyList(), Collections.emptyMap(), Collections.emptyList()); } - public FabricModMetadata(String id, String name, String version, String icon, String description, List authors, Map contact,List jars) { + public FabricModMetadata(String id, String name, String version, String icon, String description, List authors, Map contact, List jars) { this.id = id; this.name = name; this.version = version; From 54f5373843e2bbc365cd8f17768c1662b6a1af9e Mon Sep 17 00:00:00 2001 From: lokins Date: Wed, 18 Feb 2026 00:10:02 +0800 Subject: [PATCH 33/40] update --- HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java | 2 +- .../jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java | 2 +- .../org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java index 3a5b30032e2..d4f8cc0bf4c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java @@ -116,7 +116,7 @@ public static CompletableFuture exportLogs( hasJij = true; infoBuilder.append(mod.getName()); if (!mod.getName().equals(mod.getFileName())) { - infoBuilder.append(" (").append(mod.getFileName()).append(")"); + infoBuilder.append("[").append(mod.getFileName()).append("]"); } infoBuilder.append(System.lineSeparator()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java index 0d1864404a5..8373a5ee049 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -375,7 +375,7 @@ private static void exportJijList(String modName, List bundledMods) { for (String modPath : bundledMods) { String fileName = modPath.contains("/") ? modPath.substring(modPath.lastIndexOf('/') + 1) : modPath; - sb.append("\t|->").append(fileName).append(System.lineSeparator()); + sb.append("\t|-> ").append(fileName).append(System.lineSeparator()); } Task.runAsync(() -> { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java index 1f77ef540a6..0a27aa7bf80 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java @@ -208,10 +208,10 @@ private static LocalModFile fromFile0( } List bundledMods = new ArrayList<>(); - ZipArchiveEntry jarInJarEntry = tree.getEntry("META-INF/jarjar/metadata.json"); - if (jarInJarEntry != null) { + ZipArchiveEntry jijEntry = tree.getEntry("META-INF/jarjar/metadata.json"); + if (jijEntry != null) { try { - JarInJarMetadata jijMetadata = JsonUtils.fromJsonFully(tree.getInputStream(jarInJarEntry), JarInJarMetadata.class); + JarInJarMetadata jijMetadata = JsonUtils.fromJsonFully(tree.getInputStream(jijEntry), JarInJarMetadata.class); if (jijMetadata != null && jijMetadata.jars != null) { jijMetadata.jars.stream() .map(jar -> jar.path) From 46fabf7a616bc0e2093dae03211519d1b52e0799 Mon Sep 17 00:00:00 2001 From: lokins Date: Wed, 25 Feb 2026 20:47:44 +0800 Subject: [PATCH 34/40] update --- .../ui/versions/BuiltInModListPageSkin.java | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java index 8373a5ee049..1a0c792d867 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -31,7 +31,6 @@ import javafx.scene.control.ScrollPane; import javafx.scene.control.SkinBase; import javafx.scene.control.Tooltip; -import javafx.scene.image.ImageView; import javafx.scene.layout.*; import javafx.stage.FileChooser; import javafx.util.Duration; @@ -43,10 +42,7 @@ import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.animation.TransitionPane; -import org.jackhuang.hmcl.ui.construct.ComponentList; -import org.jackhuang.hmcl.ui.construct.MDListCell; -import org.jackhuang.hmcl.ui.construct.SpinnerPane; -import org.jackhuang.hmcl.ui.construct.TwoLineListItem; +import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.i18n.I18n; @@ -178,8 +174,7 @@ private void search() { } private class JijModListCell extends MDListCell { - - private final ImageView imageView = new ImageView(); + private final ImageContainer imageContainer = new ImageContainer(24); private final TwoLineListItem content = new TwoLineListItem(); private JFXPopup activePopup; private boolean ignoreNextClick = false; @@ -193,25 +188,15 @@ public JijModListCell(JFXListView listView) { container.setAlignment(Pos.CENTER_LEFT); StackPane.setMargin(container, new Insets(8, 8, 8, 18)); - imageView.setFitWidth(24); - imageView.setFitHeight(24); - imageView.setPreserveRatio(true); - HBox.setHgrow(content, Priority.ALWAYS); content.setMouseTransparent(true); - container.getChildren().addAll(imageView, content); + container.getChildren().addAll(imageContainer, content); getContainer().getChildren().setAll(container); setSelectable(); - this.setOnMousePressed(e -> { - if (activePopup != null && activePopup.isShowing()) { - ignoreNextClick = true; - } else { - ignoreNextClick = false; - } - }); + this.setOnMousePressed(e -> ignoreNextClick = activePopup != null && activePopup.isShowing()); this.setOnMouseClicked(e -> { if (getItem() != null && getItem().getModInfo() != null) { @@ -245,7 +230,7 @@ protected void updateControl(ModListPageSkin.ModInfoObject dataItem, boolean emp ModTranslations.Mod modTranslations = dataItem.getModTranslations(); ModLoaderType modLoaderType = modInfo.getModLoaderType(); - dataItem.loadIcon(imageView, new WeakReference<>(this.itemProperty())); + dataItem.loadIcon(imageContainer, new WeakReference<>(this.itemProperty())); String displayName = modInfo.getName(); if (modTranslations != null && I18n.isUseChinese()) { @@ -299,7 +284,7 @@ private JFXPopup showBundledPopup(Node anchor, String modName, List bund HBox.setHgrow(spacer, Priority.ALWAYS); JFXButton exportButton = new JFXButton(); - exportButton.setGraphic(FXUtils.limitingSize(SVG.FILE_EXPORT.createIcon(18), 18, 18)); + exportButton.setGraphic(SVG.FILE_EXPORT.createIcon(18)); exportButton.getStyleClass().add("toggle-icon4"); FXUtils.installFastTooltip(exportButton, i18n("mods.built_in.export.jij_info")); From 1692a586b6d2ab6d5f7b47678e5acc485ce2536d Mon Sep 17 00:00:00 2001 From: lokins Date: Wed, 25 Feb 2026 21:59:55 +0800 Subject: [PATCH 35/40] update --- .../ui/versions/BuiltInModListPageSkin.java | 84 +++++++++++++++++-- 1 file changed, 77 insertions(+), 7 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java index 1a0c792d867..d6bab58c43c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -19,20 +19,24 @@ import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXListView; -import com.jfoenix.controls.JFXPopup; import com.jfoenix.controls.JFXTextField; import javafx.animation.PauseTransition; import javafx.application.Platform; import javafx.beans.binding.Bindings; +import javafx.geometry.Bounds; import javafx.geometry.Insets; import javafx.geometry.Pos; +import javafx.geometry.Rectangle2D; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.control.SkinBase; import javafx.scene.control.Tooltip; +import javafx.scene.effect.DropShadow; import javafx.scene.layout.*; +import javafx.scene.paint.Color; import javafx.stage.FileChooser; +import javafx.stage.Popup; import javafx.util.Duration; import org.jackhuang.hmcl.mod.LocalModFile; import org.jackhuang.hmcl.mod.ModLoaderType; @@ -62,6 +66,7 @@ public class BuiltInModListPageSkin extends SkinBase { + private final StackPane pane; private final TransitionPane toolbarPane; private final HBox searchBar; private final HBox toolbarNormal; @@ -74,7 +79,7 @@ public class BuiltInModListPageSkin extends SkinBase { protected BuiltInModListPageSkin(BuiltInModListPage skinnable) { super(skinnable); - StackPane pane = new StackPane(); + pane = new StackPane(); pane.setPadding(new Insets(10)); pane.getStyleClass().addAll("notice-pane"); @@ -176,7 +181,7 @@ private void search() { private class JijModListCell extends MDListCell { private final ImageContainer imageContainer = new ImageContainer(24); private final TwoLineListItem content = new TwoLineListItem(); - private JFXPopup activePopup; + private Popup activePopup; private boolean ignoreNextClick = false; public JijModListCell(JFXListView listView) { @@ -268,11 +273,19 @@ protected void updateControl(ModListPageSkin.ModInfoObject dataItem, boolean emp } } - private JFXPopup showBundledPopup(Node anchor, String modName, List bundledMods) { + private Popup showBundledPopup(Node anchor, String modName, List bundledMods) { VBox root = new VBox(10); root.setPadding(new Insets(15)); root.setMaxHeight(400); root.getStyleClass().add("card-pane"); + root.setStyle(root.getStyle() + """ + -fx-background-color: -fx-background; + -fx-background-radius: 8; + -fx-border-color: -fx-box-border; + -fx-border-radius: 8; + """); + + root.setEffect(new DropShadow(18, Color.rgb(0, 0, 0, 0.30))); HBox header = new HBox(10); header.setAlignment(Pos.CENTER_LEFT); @@ -287,7 +300,6 @@ private JFXPopup showBundledPopup(Node anchor, String modName, List bund exportButton.setGraphic(SVG.FILE_EXPORT.createIcon(18)); exportButton.getStyleClass().add("toggle-icon4"); FXUtils.installFastTooltip(exportButton, i18n("mods.built_in.export.jij_info")); - exportButton.setOnAction(e -> exportJijList(modName, bundledMods)); header.getChildren().addAll(titleLabel, spacer, exportButton); @@ -340,8 +352,66 @@ private JFXPopup showBundledPopup(Node anchor, String modName, List bund root.getChildren().addAll(header, searchField, scrollPane); - JFXPopup popup = new JFXPopup(root); - popup.show(anchor, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, anchor.getLayoutBounds().getHeight() + 5); + StackPane wrapper = new StackPane(root); + wrapper.setPadding(new Insets(12)); + wrapper.setStyle("-fx-background-color: transparent;"); + + Popup popup = new Popup(); + popup.getContent().add(wrapper); + popup.setAutoHide(true); + popup.setHideOnEscape(true); + + Platform.runLater(() -> { + Bounds aScreen = anchor.localToScreen(anchor.getBoundsInLocal()); + Bounds pScreen = pane.localToScreen(pane.getBoundsInLocal()); + if (aScreen == null || pScreen == null) return; + + Rectangle2D content = new Rectangle2D( + pScreen.getMinX(), + pScreen.getMinY(), + pScreen.getWidth(), + pScreen.getHeight() + ); + + final double padding = 8; + final double gap = 5; + + wrapper.applyCss(); + wrapper.layout(); + double popupW = wrapper.prefWidth(-1); + double popupH = wrapper.prefHeight(-1); + + double maxAllowedH = Math.max(120, content.getHeight() - padding * 2); + double wrapperExtraH = wrapper.getPadding().getTop() + wrapper.getPadding().getBottom(); + double maxRootH = Math.max(120, maxAllowedH - wrapperExtraH); + + if (root.prefHeight(-1) > maxRootH) { + root.setMaxHeight(maxRootH); + wrapper.applyCss(); + wrapper.layout(); + popupW = wrapper.prefWidth(-1); + popupH = wrapper.prefHeight(-1); + } + + double targetX = aScreen.getMaxX() - popupW; + + double downY = aScreen.getMaxY() + gap; + double upY = aScreen.getMinY() - gap - popupH; + + double targetY = downY; + if (targetY + popupH > content.getMaxY() - padding) targetY = upY; + + double minX = content.getMinX() + padding; + double maxX = content.getMaxX() - padding - popupW; + targetX = Math.max(minX, Math.min(targetX, maxX)); + + double minY = content.getMinY() + padding; + double maxY = content.getMaxY() - padding - popupH; + targetY = Math.max(minY, Math.min(targetY, maxY)); + + popup.show(Controllers.getStage(), targetX, targetY); + }); + return popup; } From 61b528ef415808b732c640003c6efe70d52a53b9 Mon Sep 17 00:00:00 2001 From: lokins Date: Wed, 25 Feb 2026 22:03:57 +0800 Subject: [PATCH 36/40] update --- .../org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java index d6bab58c43c..afe652da6e8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPageSkin.java @@ -283,7 +283,7 @@ private Popup showBundledPopup(Node anchor, String modName, List bundled -fx-background-radius: 8; -fx-border-color: -fx-box-border; -fx-border-radius: 8; - """); + """); root.setEffect(new DropShadow(18, Color.rgb(0, 0, 0, 0.30))); From 162cd8fff6bfeb0500ec2acb20605a56505670fe Mon Sep 17 00:00:00 2001 From: lokins Date: Sat, 21 Mar 2026 17:56:39 +0800 Subject: [PATCH 37/40] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20BuiltInModListPage.r?= =?UTF-8?q?efresh()=20=E9=87=8D=E5=A4=8D=E8=B0=83=E7=94=A8=20modManager.re?= =?UTF-8?q?freshMods()=20=EF=BC=8C=20QuiltModMetadata=20=E7=A9=BA=E5=AE=89?= =?UTF-8?q?=E5=85=A8=EF=BC=8C=E5=86=97=E4=BD=99=E6=97=A5=E5=BF=97=E3=80=82?= =?UTF-8?q?=E8=B0=83=E6=95=B4LogExporter=20=E6=A0=BC=E5=BC=8F=E6=B3=A8?= =?UTF-8?q?=E6=98=8E=E4=BB=85=E5=90=AB=E5=B7=B2=E5=90=AF=E7=94=A8=E6=A8=A1?= =?UTF-8?q?=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/game/LogExporter.java | 2 +- .../hmcl/ui/versions/BuiltInModListPage.java | 38 ++++++++----------- .../hmcl/mod/modinfo/QuiltModMetadata.java | 14 ++++++- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java index d4f8cc0bf4c..fc8505dcf60 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java @@ -108,7 +108,7 @@ public static CompletableFuture exportLogs( infoBuilder.append("----------------------------").append(System.lineSeparator()); infoBuilder.append(System.lineSeparator()); - infoBuilder.append("=== JIJ Info List ===").append(System.lineSeparator()); + infoBuilder.append("=== JIJ Info List (active mods only) ===").append(System.lineSeparator()); boolean hasJij = false; for (LocalModFile mod : modManager.getMods()) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPage.java index 252ba0f51ba..380c5c34b4c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/BuiltInModListPage.java @@ -24,6 +24,7 @@ import org.jackhuang.hmcl.ui.ListPageBase; import org.jackhuang.hmcl.ui.construct.PageAware; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -42,47 +43,40 @@ protected Skin createDefaultSkin() { public void loadVersion(Profile profile, String id) { getItems().clear(); this.modManager = profile.getRepository().getModManager(id); - refresh(); + loadMods(false); } + /** + * Called by the refresh button in the skin — forces a full rescan of the mods directory. + */ public void refresh() { - if (modManager == null) return; - try { - if (modManager.getModsDirectory() != null) { - LOG.info("Refreshing built-in mod list for folder: " + modManager.getModsDirectory().toAbsolutePath()); - } - } catch (Exception e) { - LOG.warning("Failed to log mods directory", e); - } + loadMods(true); + } - LOG.info("Refreshing built-in mod list page..."); + private void loadMods(boolean forceRefresh) { + if (modManager == null) return; getItems().clear(); setLoading(true); CompletableFuture.supplyAsync(() -> { try { - modManager.refreshMods(); + if (forceRefresh) { + modManager.refreshMods(); + } return modManager.getMods().stream() - .filter(mod -> { - try { - return mod != null && mod.hasBundledMods(); - } catch (Exception e) { - return false; - } - }) + .filter(mod -> mod != null && mod.hasBundledMods()) .map(ModListPageSkin.ModInfoObject::new) .collect(Collectors.toList()); } catch (Exception e) { - LOG.warning("Failed to refresh built-in mods", e); - return null; + LOG.warning("Failed to load built-in mods", e); + return List.of(); } }, Schedulers.io()).whenCompleteAsync((list, ex) -> { if (ex != null) { LOG.warning("Async task failed in BuiltInModListPage", ex); - } else if (list != null) { + } else { getItems().setAll(list); - LOG.info("Built-in mod list refreshed, found " + list.size() + " mods with JIJ."); } setLoading(false); }, Schedulers.javafx()); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/QuiltModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/QuiltModMetadata.java index 6af5d8d5d8b..f1052f9210f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/QuiltModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/QuiltModMetadata.java @@ -77,16 +77,26 @@ public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFile root.quilt_loader.jars.stream().map(jar -> jar.file).toList() : Collections.emptyList(); + String authors = root.quilt_loader.metadata.contributors != null + ? root.quilt_loader.metadata.contributors.entrySet().stream() + .map(entry -> String.format("%s (%s)", entry.getKey(), entry.getValue().getAsJsonPrimitive().getAsString())) + .collect(Collectors.joining(", ")) + : ""; + String homepage = root.quilt_loader.metadata.contact != null + ? Optional.ofNullable(root.quilt_loader.metadata.contact.get("homepage")) + .map(jsonElement -> jsonElement.getAsJsonPrimitive().getAsString()).orElse("") + : ""; + return new LocalModFile( modManager, modManager.getLocalMod(root.quilt_loader.id, ModLoaderType.QUILT), modFile, root.quilt_loader.metadata.name, new LocalModFile.Description(root.quilt_loader.metadata.description), - root.quilt_loader.metadata.contributors.entrySet().stream().map(entry -> String.format("%s (%s)", entry.getKey(), entry.getValue().getAsJsonPrimitive().getAsString())).collect(Collectors.joining(", ")), + authors, root.quilt_loader.version, "", - Optional.ofNullable(root.quilt_loader.metadata.contact.get("homepage")).map(jsonElement -> jsonElement.getAsJsonPrimitive().getAsString()).orElse(""), + homepage, root.quilt_loader.metadata.icon, bundledMods); } From 1c517759c0d971df6b34a294aff37b79fc921080 Mon Sep 17 00:00:00 2001 From: lokins Date: Sat, 21 Mar 2026 21:51:29 +0800 Subject: [PATCH 38/40] =?UTF-8?q?=E4=B8=BA=E2=80=9C=E5=B5=8C=E5=A5=97?= =?UTF-8?q?=E6=A8=A1=E7=BB=84=E7=AE=A1=E7=90=86=E9=A1=B5=E9=9D=A2=E2=80=9D?= =?UTF-8?q?=E7=9A=84=E5=B1=95=E5=BC=80=E5=A2=9E=E5=8A=A0=E5=8A=A8=E7=94=BB?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/VersionPage.java | 41 +++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) 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 40ebfa8fe62..97f4c1d044f 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 @@ -18,7 +18,7 @@ package org.jackhuang.hmcl.ui.versions; import com.jfoenix.controls.JFXPopup; -import javafx.animation.RotateTransition; +import javafx.animation.*; import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.property.*; @@ -41,7 +41,9 @@ import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.WeakListenerHolder; +import org.jackhuang.hmcl.ui.animation.AnimationUtils; import org.jackhuang.hmcl.ui.animation.ContainerAnimations; +import org.jackhuang.hmcl.ui.animation.Motion; import org.jackhuang.hmcl.ui.animation.TransitionPane; import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage; @@ -333,10 +335,43 @@ protected Skin(VersionPage control) { jijListItem.activeProperty().bind(control.tab.getSelectionModel().selectedItemProperty().isEqualTo(control.builtInModListTab)); jijListItem.setOnAction(e -> control.tab.select(control.builtInModListTab)); - jijListItem.visibleProperty().bind(isExpanded); - jijListItem.managedProperty().bind(isExpanded); + javafx.scene.shape.Rectangle jijClip = new javafx.scene.shape.Rectangle(); + jijClip.widthProperty().bind(jijListItem.widthProperty()); + jijClip.setHeight(0); + jijListItem.setClip(jijClip); + jijListItem.setMinHeight(0); + jijListItem.setMaxHeight(0); + jijListItem.setManaged(false); sideBar.add(jijListItem); + FXUtils.onChange(isExpanded, expanded -> { + if (AnimationUtils.isAnimationEnabled()) { + if (expanded) { + jijListItem.setManaged(true); + } + Platform.runLater(() -> { + double h = expanded ? jijListItem.prefHeight(-1) : 0; + Interpolator interpolator = Motion.EASE_IN_OUT_CUBIC_EMPHASIZED; + Timeline timeline = new Timeline( + new KeyFrame(Motion.LONG2, + new KeyValue(jijListItem.minHeightProperty(), h, interpolator), + new KeyValue(jijListItem.maxHeightProperty(), h, interpolator), + new KeyValue(jijClip.heightProperty(), h, interpolator)) + ); + if (!expanded) { + timeline.setOnFinished(e -> jijListItem.setManaged(false)); + } + timeline.play(); + }); + } else { + double h = expanded ? jijListItem.prefHeight(-1) : 0; + jijListItem.setMinHeight(h); + jijListItem.setMaxHeight(h); + jijClip.setHeight(h); + jijListItem.setManaged(expanded); + } + }); + FXUtils.onChange(control.tab.getSelectionModel().selectedItemProperty(), tab -> { if (tab == control.builtInModListTab) { isExpanded.set(true); From a409f22bd4bb3edc47a414d7b2206d640b5819d7 Mon Sep 17 00:00:00 2001 From: lokins Date: Sat, 21 Mar 2026 23:50:53 +0800 Subject: [PATCH 39/40] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8A=A8=E7=94=BB?= =?UTF-8?q?=E7=AB=9E=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jackhuang/hmcl/ui/versions/VersionPage.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) 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 97f4c1d044f..91fba7238a2 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 @@ -344,8 +344,14 @@ protected Skin(VersionPage control) { jijListItem.setManaged(false); sideBar.add(jijListItem); + final Timeline[] currentAnimation = {null}; FXUtils.onChange(isExpanded, expanded -> { if (AnimationUtils.isAnimationEnabled()) { + // Stop any in-progress animation to prevent race conditions + if (currentAnimation[0] != null) { + currentAnimation[0].stop(); + currentAnimation[0] = null; + } if (expanded) { jijListItem.setManaged(true); } @@ -359,8 +365,14 @@ protected Skin(VersionPage control) { new KeyValue(jijClip.heightProperty(), h, interpolator)) ); if (!expanded) { - timeline.setOnFinished(e -> jijListItem.setManaged(false)); + timeline.setOnFinished(e -> { + jijListItem.setManaged(false); + currentAnimation[0] = null; + }); + } else { + timeline.setOnFinished(e -> currentAnimation[0] = null); } + currentAnimation[0] = timeline; timeline.play(); }); } else { From e0ffe3950f611818d8aa022143ccf5c7cda3b025 Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 25 Mar 2026 21:41:02 +0800 Subject: [PATCH 40/40] Use List.of() and List.copyOf() in LocalModFile --- .../src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java index 22005af8666..09bb19bbb2d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LocalModFile.java @@ -64,9 +64,8 @@ public LocalModFile(ModManager modManager, LocalMod mod, Path file, String name, this.url = url; this.logoPath = logoPath; this.bundledMods = bundledMods == null - ? Collections.emptyList() - : Collections.unmodifiableList(new ArrayList<>(bundledMods)); - + ? List.of() + : List.copyOf(bundledMods); activeProperty = new SimpleBooleanProperty(this, "active", !modManager.isDisabled(file)) { @Override