From afed2b20950e4a711e791e1577836bc35a2e90ab Mon Sep 17 00:00:00 2001 From: ToobLac Date: Tue, 14 Jul 2026 21:42:14 +0800 Subject: [PATCH 1/5] update --- .../hmcl/game/HMCLGameRepository.java | 43 ++++----- .../jackhuang/hmcl/setting/GameSettings.java | 4 +- ...ionIconType.java => InstanceIconType.java} | 20 ++--- .../setting/LegacyGameSettingsMigrator.java | 48 +++++----- .../org/jackhuang/hmcl/ui/InstallerItem.java | 26 +++--- .../hmcl/ui/download/VersionsPage.java | 32 +++---- .../hmcl/ui/game/GameSettingsPage.java | 6 +- .../ui/versions/GameAdvancedListItem.java | 4 +- ...conDialog.java => InstanceIconDialog.java} | 87 ++++++++++++++----- .../hmcl/ui/versions/ModListPageSkin.java | 8 +- 10 files changed, 156 insertions(+), 122 deletions(-) rename HMCL/src/main/java/org/jackhuang/hmcl/setting/{VersionIconType.java => InstanceIconType.java} (77%) rename HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/{VersionIconDialog.java => InstanceIconDialog.java} (55%) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java index 11ae454b96a..7ff3d3d80fd 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java @@ -34,18 +34,7 @@ import org.jackhuang.hmcl.modpack.Modpack; import org.jackhuang.hmcl.modpack.ModpackConfiguration; import org.jackhuang.hmcl.modpack.ModpackProvider; -import org.jackhuang.hmcl.setting.LauncherSettings; -import org.jackhuang.hmcl.setting.SettingsManager; -import org.jackhuang.hmcl.setting.DefaultIsolationType; -import org.jackhuang.hmcl.setting.DownloadProviders; -import org.jackhuang.hmcl.setting.GameSettings; -import org.jackhuang.hmcl.setting.GameWindowType; -import org.jackhuang.hmcl.setting.LegacyGameSettingsMigrator; -import org.jackhuang.hmcl.setting.GameDirectory; -import org.jackhuang.hmcl.setting.ProxyType; -import org.jackhuang.hmcl.setting.SettingFileUtils; -import org.jackhuang.hmcl.setting.GameSettingsPresetID; -import org.jackhuang.hmcl.setting.VersionIconType; +import org.jackhuang.hmcl.setting.*; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.util.FileSaver; import org.jackhuang.hmcl.util.Lang; @@ -603,12 +592,12 @@ public void deleteIconFile(String id) { public Image getVersionIconImage(@Nullable String id) { if (id == null || !isLoaded()) - return VersionIconType.DEFAULT.getIcon(); + return InstanceIconType.DEFAULT.getIcon(); GameSettings.Instance setting = getInstanceGameSettings(id); - VersionIconType iconType = setting != null ? Lang.requireNonNullElse(setting.iconProperty().getValue(), VersionIconType.DEFAULT) : VersionIconType.DEFAULT; + InstanceIconType iconType = setting != null ? Lang.requireNonNullElse(setting.iconProperty().getValue(), InstanceIconType.DEFAULT) : InstanceIconType.DEFAULT; - if (iconType == VersionIconType.DEFAULT) { + if (iconType == InstanceIconType.DEFAULT) { Version version = getVersion(id).resolve(this); Optional iconFile = getVersionIconFile(id); if (iconFile.isPresent()) { @@ -622,35 +611,35 @@ public Image getVersionIconImage(@Nullable String id) { if (LibraryAnalyzer.isModded(this, version)) { LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version, null); if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.FABRIC)) - return VersionIconType.FABRIC.getIcon(); + return InstanceIconType.FABRIC.getIcon(); else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.QUILT)) - return VersionIconType.QUILT.getIcon(); + return InstanceIconType.QUILT.getIcon(); else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.LEGACY_FABRIC)) - return VersionIconType.LEGACY_FABRIC.getIcon(); + return InstanceIconType.LEGACY_FABRIC.getIcon(); else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.NEO_FORGE)) - return VersionIconType.NEO_FORGE.getIcon(); + return InstanceIconType.NEO_FORGE.getIcon(); else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.FORGE)) - return VersionIconType.FORGE.getIcon(); + return InstanceIconType.FORGE.getIcon(); else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.CLEANROOM)) - return VersionIconType.CLEANROOM.getIcon(); + return InstanceIconType.CLEANROOM.getIcon(); else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.LITELOADER)) - return VersionIconType.CHICKEN.getIcon(); + return InstanceIconType.CHICKEN.getIcon(); else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.OPTIFINE)) - return VersionIconType.OPTIFINE.getIcon(); + return InstanceIconType.OPTIFINE.getIcon(); } String gameVersion = getGameVersion(version).orElse(null); if (gameVersion != null) { GameVersionNumber versionNumber = GameVersionNumber.asGameVersion(gameVersion); if (versionNumber.isAprilFools()) { - return VersionIconType.APRIL_FOOLS.getIcon(); + return InstanceIconType.APRIL_FOOLS.getIcon(); } else if (versionNumber instanceof GameVersionNumber.LegacySnapshot) { - return VersionIconType.COMMAND.getIcon(); + return InstanceIconType.COMMAND.getIcon(); } else if (versionNumber instanceof GameVersionNumber.Old) { - return VersionIconType.CRAFT_TABLE.getIcon(); + return InstanceIconType.CRAFT_TABLE.getIcon(); } } - return VersionIconType.GRASS.getIcon(); + return InstanceIconType.GRASS.getIcon(); } else { return iconType.getIcon(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/GameSettings.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/GameSettings.java index 75d2d19d33a..d83fd740e37 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/GameSettings.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/GameSettings.java @@ -152,10 +152,10 @@ public void setBackupOnNextSave(boolean backupOnNextSave) { /// The icon of the instance. @SerializedName("icon") - private final SettingProperty icon = newSettingProperty("icon", VersionIconType.DEFAULT); + private final SettingProperty icon = newSettingProperty("icon", InstanceIconType.DEFAULT); /// Returns the instance icon property. - public SettingProperty iconProperty() { + public SettingProperty iconProperty() { return icon; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/InstanceIconType.java similarity index 77% rename from HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java rename to HMCL/src/main/java/org/jackhuang/hmcl/setting/InstanceIconType.java index ed8a24cd9d5..02cd14c64e8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/InstanceIconType.java @@ -21,7 +21,7 @@ import org.jackhuang.hmcl.addon.mod.ModLoaderType; import org.jackhuang.hmcl.ui.FXUtils; -public enum VersionIconType { +public enum InstanceIconType { DEFAULT("/assets/img/grass.png"), GRASS("/assets/img/grass.png"), @@ -42,21 +42,21 @@ public enum VersionIconType { // Please append new items at last - public static VersionIconType getIconType(ModLoaderType modLoaderType) { + public static InstanceIconType getIconType(ModLoaderType modLoaderType) { return switch (modLoaderType) { - case FORGE -> VersionIconType.FORGE; - case NEO_FORGE -> VersionIconType.NEO_FORGE; - case FABRIC -> VersionIconType.FABRIC; - case QUILT -> VersionIconType.QUILT; - case LITE_LOADER -> VersionIconType.CHICKEN; - case CLEANROOM -> VersionIconType.CLEANROOM; - default -> VersionIconType.COMMAND; + case FORGE -> InstanceIconType.FORGE; + case NEO_FORGE -> InstanceIconType.NEO_FORGE; + case FABRIC -> InstanceIconType.FABRIC; + case QUILT -> InstanceIconType.QUILT; + case LITE_LOADER -> InstanceIconType.CHICKEN; + case CLEANROOM -> InstanceIconType.CLEANROOM; + default -> InstanceIconType.COMMAND; }; } private final String resourceUrl; - VersionIconType(String resourceUrl) { + InstanceIconType(String resourceUrl) { this.resourceUrl = resourceUrl; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/LegacyGameSettingsMigrator.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/LegacyGameSettingsMigrator.java index 16dea2450f9..8bc0e237658 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/LegacyGameSettingsMigrator.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/LegacyGameSettingsMigrator.java @@ -72,23 +72,23 @@ private enum GameDirectoryType { CUSTOM } - /// Legacy `VersionIconType` ordinal order used by old local settings. - private static final VersionIconType @Unmodifiable [] LEGACY_VERSION_ICON_TYPES = { - VersionIconType.DEFAULT, - VersionIconType.GRASS, - VersionIconType.CHEST, - VersionIconType.CHICKEN, - VersionIconType.COMMAND, - VersionIconType.OPTIFINE, - VersionIconType.CRAFT_TABLE, - VersionIconType.FABRIC, - VersionIconType.FORGE, - VersionIconType.NEO_FORGE, - VersionIconType.FURNACE, - VersionIconType.QUILT, - VersionIconType.APRIL_FOOLS, - VersionIconType.CLEANROOM, - VersionIconType.LEGACY_FABRIC + /// Legacy `InstanceIconType` ordinal order used by old local settings. + private static final InstanceIconType @Unmodifiable [] LEGACY_INSTANCE_ICON_TYPES = { + InstanceIconType.DEFAULT, + InstanceIconType.GRASS, + InstanceIconType.CHEST, + InstanceIconType.CHICKEN, + InstanceIconType.COMMAND, + InstanceIconType.OPTIFINE, + InstanceIconType.CRAFT_TABLE, + InstanceIconType.FABRIC, + InstanceIconType.FORGE, + InstanceIconType.NEO_FORGE, + InstanceIconType.FURNACE, + InstanceIconType.QUILT, + InstanceIconType.APRIL_FOOLS, + InstanceIconType.CLEANROOM, + InstanceIconType.LEGACY_FABRIC }; /// Legacy `JavaVersionType` ordinal order used by old game settings. @@ -488,22 +488,22 @@ private static GraphicsAPI parseGraphicsBackend(JsonObject source, Renderer rend } /// Parses the legacy icon selection with frozen ordinal order. - private static VersionIconType parseLegacyVersionIconType(@Nullable JsonObject source) { + private static InstanceIconType parseLegacyVersionIconType(@Nullable JsonObject source) { JsonPrimitive primitive = JsonUtils.getPrimitive(source, "versionIcon"); if (primitive == null) { - return VersionIconType.DEFAULT; + return InstanceIconType.DEFAULT; } try { if (primitive.isNumber()) { int index = primitive.getAsInt(); - return index >= 0 && index < LEGACY_VERSION_ICON_TYPES.length - ? LEGACY_VERSION_ICON_TYPES[index] - : VersionIconType.DEFAULT; + return index >= 0 && index < LEGACY_INSTANCE_ICON_TYPES.length + ? LEGACY_INSTANCE_ICON_TYPES[index] + : InstanceIconType.DEFAULT; } String value = primitive.getAsString(); - for (VersionIconType iconType : LEGACY_VERSION_ICON_TYPES) { + for (InstanceIconType iconType : LEGACY_INSTANCE_ICON_TYPES) { if (iconType.name().equalsIgnoreCase(value)) { return iconType; } @@ -511,7 +511,7 @@ private static VersionIconType parseLegacyVersionIconType(@Nullable JsonObject s } catch (RuntimeException ignored) { } - return VersionIconType.DEFAULT; + return InstanceIconType.DEFAULT; } /// Reads an enum property from either ordinal or name. diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java index 014f24d2f46..d4864d72ba1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java @@ -35,7 +35,7 @@ import javafx.scene.input.MouseButton; import javafx.scene.layout.*; import org.jackhuang.hmcl.download.LibraryAnalyzer; -import org.jackhuang.hmcl.setting.VersionIconType; +import org.jackhuang.hmcl.setting.InstanceIconType; import org.jackhuang.hmcl.ui.construct.ImageContainer; import org.jackhuang.hmcl.ui.construct.RipplerContainer; import org.jackhuang.hmcl.util.i18n.I18n; @@ -53,9 +53,9 @@ * @author huangyuhui */ public class InstallerItem extends Control { - private final String id; - private final VersionIconType iconType; - private final Style style; + private final String id; + private final InstanceIconType iconType; + private final Style style; private final ObjectProperty versionProperty = new SimpleObjectProperty<>(this, "version", null); private final ObjectProperty resolvedStateProperty = new SimpleObjectProperty<>(this, "resolvedState", InstallableState.INSTANCE); @@ -92,15 +92,15 @@ public InstallerItem(String id, Style style) { this.style = style; iconType = switch (id) { - case "game" -> VersionIconType.GRASS; - case "fabric", "fabric-api" -> VersionIconType.FABRIC; - case "legacyfabric", "legacyfabric-api" -> VersionIconType.LEGACY_FABRIC; - case "forge" -> VersionIconType.FORGE; - case "cleanroom" -> VersionIconType.CLEANROOM; - case "liteloader" -> VersionIconType.CHICKEN; - case "optifine" -> VersionIconType.OPTIFINE; - case "quilt", "quilt-api" -> VersionIconType.QUILT; - case "neoforge" -> VersionIconType.NEO_FORGE; + case "game" -> InstanceIconType.GRASS; + case "fabric", "fabric-api" -> InstanceIconType.FABRIC; + case "legacyfabric", "legacyfabric-api" -> InstanceIconType.LEGACY_FABRIC; + case "forge" -> InstanceIconType.FORGE; + case "cleanroom" -> InstanceIconType.CLEANROOM; + case "liteloader" -> InstanceIconType.CHICKEN; + case "optifine" -> InstanceIconType.OPTIFINE; + case "quilt", "quilt-api" -> InstanceIconType.QUILT; + case "neoforge" -> InstanceIconType.NEO_FORGE; default -> null; }; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java index ee79c627cdd..dbdc37148ca 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java @@ -45,7 +45,7 @@ import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion; import org.jackhuang.hmcl.download.quilt.QuiltAPIRemoteVersion; import org.jackhuang.hmcl.download.quilt.QuiltRemoteVersion; -import org.jackhuang.hmcl.setting.VersionIconType; +import org.jackhuang.hmcl.setting.InstanceIconType; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.FXUtils; @@ -240,21 +240,21 @@ public void updateItem(RemoteVersion remoteVersion, boolean empty) { switch (versionType) { case RELEASE -> { twoLineListItem.addTag(i18n("version.game.release")); - imageView.setImage(VersionIconType.GRASS.getIcon()); + imageView.setImage(InstanceIconType.GRASS.getIcon()); } case SNAPSHOT, PENDING, UNOBFUSCATED -> { if (versionType == RemoteVersion.Type.SNAPSHOT && GameVersionNumber.asGameVersion(remoteVersion.getGameVersion()).isAprilFools()) { twoLineListItem.addTag(i18n("version.game.april_fools")); - imageView.setImage(VersionIconType.APRIL_FOOLS.getIcon()); + imageView.setImage(InstanceIconType.APRIL_FOOLS.getIcon()); } else { twoLineListItem.addTag(i18n("version.game.snapshot")); - imageView.setImage(VersionIconType.COMMAND.getIcon()); + imageView.setImage(InstanceIconType.COMMAND.getIcon()); } } default -> { twoLineListItem.addTag(i18n("version.game.old")); - imageView.setImage(VersionIconType.CRAFT_TABLE.getIcon()); + imageView.setImage(InstanceIconType.CRAFT_TABLE.getIcon()); } } @@ -263,25 +263,25 @@ public void updateItem(RemoteVersion remoteVersion, boolean empty) { case UNSUPPORTED -> twoLineListItem.addTagWarning(i18n("version.game.support_status.unsupported")); } } else { - VersionIconType iconType; + InstanceIconType iconType; if (remoteVersion instanceof LiteLoaderRemoteVersion) - iconType = VersionIconType.CHICKEN; + iconType = InstanceIconType.CHICKEN; else if (remoteVersion instanceof OptiFineRemoteVersion) - iconType = VersionIconType.OPTIFINE; + iconType = InstanceIconType.OPTIFINE; else if (remoteVersion instanceof ForgeRemoteVersion) - iconType = VersionIconType.FORGE; + iconType = InstanceIconType.FORGE; else if (remoteVersion instanceof CleanroomRemoteVersion) - iconType = VersionIconType.CLEANROOM; + iconType = InstanceIconType.CLEANROOM; else if (remoteVersion instanceof NeoForgeRemoteVersion) - iconType = VersionIconType.NEO_FORGE; + iconType = InstanceIconType.NEO_FORGE; else if (remoteVersion instanceof LegacyFabricRemoteVersion || remoteVersion instanceof LegacyFabricAPIRemoteVersion) - iconType = VersionIconType.LEGACY_FABRIC; + iconType = InstanceIconType.LEGACY_FABRIC; else if (remoteVersion instanceof FabricRemoteVersion || remoteVersion instanceof FabricAPIRemoteVersion) - iconType = VersionIconType.FABRIC; + iconType = InstanceIconType.FABRIC; else if (remoteVersion instanceof QuiltRemoteVersion || remoteVersion instanceof QuiltAPIRemoteVersion) - iconType = VersionIconType.QUILT; + iconType = InstanceIconType.QUILT; else - iconType = VersionIconType.COMMAND; + iconType = InstanceIconType.COMMAND; imageView.setImage(iconType.getIcon()); String displayGameVersion = I18n.getDisplayVersion(GameVersionNumber.asGameVersion(remoteVersion.getGameVersion())); @@ -394,7 +394,7 @@ private static final class VersionsPageSkin extends SkinBase { RemoteVersionListCell dummyCell = new RemoteVersionListCell(control); dummyCell.twoLineListItem.setTitle("Dummy"); dummyCell.twoLineListItem.setSubtitle("Dummy"); - dummyCell.imageView.setImage(VersionIconType.GRASS.getIcon()); + dummyCell.imageView.setImage(InstanceIconType.GRASS.getIcon()); prepareNode(dummyCell.pane); list.setFixedCellSize(dummyCell.pane.prefHeight(-1)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/game/GameSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/game/GameSettingsPage.java index 0c21d97f735..cca6b3f6f9a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/game/GameSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/game/GameSettingsPage.java @@ -50,7 +50,7 @@ import org.jackhuang.hmcl.ui.*; import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; -import org.jackhuang.hmcl.ui.versions.VersionIconDialog; +import org.jackhuang.hmcl.ui.versions.InstanceIconDialog; import org.jackhuang.hmcl.ui.versions.VersionPage; import org.jackhuang.hmcl.util.Holder; import org.jackhuang.hmcl.util.Pair; @@ -2818,7 +2818,7 @@ private void onExploreIcon() { if (repository == null || instanceId == null) return; - Controllers.dialog(new VersionIconDialog(repository, instanceId, this::loadIcon)); + Controllers.dialog(new InstanceIconDialog(repository, instanceId, this::loadIcon)); } private void onDeleteIcon() { @@ -2828,7 +2828,7 @@ private void onDeleteIcon() { repository.deleteIconFile(instanceId); GameSettings.Instance localGameSettings = repository.getInstanceGameSettingsOrCreate(instanceId); if (localGameSettings != null) { - localGameSettings.iconProperty().setValue(VersionIconType.DEFAULT); + localGameSettings.iconProperty().setValue(InstanceIconType.DEFAULT); } loadIcon(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java index bbf29e7b325..ccd1bc70dce 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameAdvancedListItem.java @@ -23,7 +23,7 @@ import org.jackhuang.hmcl.event.RefreshedVersionsEvent; import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.setting.GameDirectoryManager; -import org.jackhuang.hmcl.setting.VersionIconType; +import org.jackhuang.hmcl.setting.InstanceIconType; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.WeakListenerHolder; import org.jackhuang.hmcl.ui.construct.AdvancedListItem; @@ -75,7 +75,7 @@ private void loadVersion() { } else { setTitle(i18n("version.empty")); setSubtitle(i18n("version.empty.add")); - imageContainer.setImage(VersionIconType.DEFAULT.getIcon()); + imageContainer.setImage(InstanceIconType.DEFAULT.getIcon()); } } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstanceIconDialog.java similarity index 55% rename from HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstanceIconDialog.java index 99378fa6869..bd6ed0561a3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstanceIconDialog.java @@ -21,10 +21,11 @@ import javafx.scene.image.ImageView; import javafx.scene.layout.FlowPane; import javafx.stage.FileChooser; +import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.event.Event; import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.setting.GameSettings; -import org.jackhuang.hmcl.setting.VersionIconType; +import org.jackhuang.hmcl.setting.InstanceIconType; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; @@ -33,18 +34,25 @@ import org.jackhuang.hmcl.util.io.FileUtils; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.Locale; +import java.util.Objects; import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public class VersionIconDialog extends DialogPane { +public class InstanceIconDialog extends DialogPane { + private static final String TIP_KEY = "saveCustomGameIcons"; + + public static final Path INSTANCE_ICONS_DIR = Metadata.HMCL_LOCAL_HOME.resolve("instance_icons"); + private final HMCLGameRepository repository; private final String versionId; private final Runnable onFinish; private final GameSettings.Instance setting; - public VersionIconDialog(HMCLGameRepository repository, String versionId, Runnable onFinish) { + public InstanceIconDialog(HMCLGameRepository repository, String versionId, Runnable onFinish) { this.repository = repository; this.versionId = versionId; this.onFinish = onFinish; @@ -56,21 +64,33 @@ public VersionIconDialog(HMCLGameRepository repository, String versionId, Runnab pane.getChildren().setAll( createCustomIcon(), - createIcon(VersionIconType.GRASS), - createIcon(VersionIconType.CHEST), - createIcon(VersionIconType.CHICKEN), - createIcon(VersionIconType.COMMAND), - createIcon(VersionIconType.APRIL_FOOLS), - createIcon(VersionIconType.OPTIFINE), - createIcon(VersionIconType.CRAFT_TABLE), - createIcon(VersionIconType.FABRIC), - createIcon(VersionIconType.LEGACY_FABRIC), - createIcon(VersionIconType.FORGE), - createIcon(VersionIconType.CLEANROOM), - createIcon(VersionIconType.NEO_FORGE), - createIcon(VersionIconType.FURNACE), - createIcon(VersionIconType.QUILT) + createIcon(InstanceIconType.GRASS), + createIcon(InstanceIconType.CHEST), + createIcon(InstanceIconType.CHICKEN), + createIcon(InstanceIconType.COMMAND), + createIcon(InstanceIconType.APRIL_FOOLS), + createIcon(InstanceIconType.OPTIFINE), + createIcon(InstanceIconType.CRAFT_TABLE), + createIcon(InstanceIconType.FABRIC), + createIcon(InstanceIconType.LEGACY_FABRIC), + createIcon(InstanceIconType.FORGE), + createIcon(InstanceIconType.CLEANROOM), + createIcon(InstanceIconType.NEO_FORGE), + createIcon(InstanceIconType.FURNACE), + createIcon(InstanceIconType.QUILT) ); + if (Files.isDirectory(INSTANCE_ICONS_DIR)) { + try (var stream = Files.list(INSTANCE_ICONS_DIR)) { + pane.getChildren().addAll( + stream.filter(p -> Files.isRegularFile(p) && FXUtils.IMAGE_EXTENSIONS.contains(FileUtils.getExtension(p).toLowerCase(Locale.ROOT))) + .map(this::createIcon) + .filter(Objects::nonNull) + .toList() + ); + } catch (Exception e) { + LOG.warning("Failed to load custom instance icons at " + INSTANCE_ICONS_DIR, e); + } + } } private void exploreIcon() { @@ -80,11 +100,9 @@ private void exploreIcon() { if (selectedFile != null) { try { repository.setVersionIconFile(versionId, selectedFile); - if (setting != null) { - setting.iconProperty().setValue(VersionIconType.DEFAULT); + setting.iconProperty().setValue(InstanceIconType.DEFAULT); } - onAccept(); } catch (IOException | IllegalArgumentException e) { LOG.error("Failed to set icon file: " + selectedFile, e); @@ -102,7 +120,7 @@ private Node createCustomIcon() { return container; } - private Node createIcon(VersionIconType type) { + private Node createIcon(InstanceIconType type) { ImageView imageView = new ImageView(type.getIcon()); imageView.setMouseTransparent(true); RipplerContainer container = new RipplerContainer(imageView); @@ -117,6 +135,33 @@ private Node createIcon(VersionIconType type) { return container; } + private Node createIcon(Path path) { + ImageView imageView; + try { + imageView = new ImageView(FXUtils.loadImage(path, 72, 72, true, true)); + } catch (Exception e) { + LOG.warning("Failed to load custom instance icon at " + path, e); + return null; + } + imageView.setMouseTransparent(true); + FXUtils.limitSize(imageView, 36, 36); + RipplerContainer container = new RipplerContainer(imageView); + FXUtils.setLimitWidth(container, 36); + FXUtils.setLimitHeight(container, 36); + FXUtils.onClicked(container, () -> { + try { + repository.setVersionIconFile(versionId, path); + } catch (IOException e) { + LOG.error("Failed to set icon file: " + path, e); + } + if (setting != null) { + setting.iconProperty().setValue(InstanceIconType.DEFAULT); + onAccept(); + } + }); + return container; + } + @Override protected void onAccept() { repository.onVersionIconChanged.fireEvent(new Event(this)); 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 b9507b84c97..ce8a7b10e2c 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 @@ -44,7 +44,7 @@ import org.jackhuang.hmcl.addon.repository.ModrinthRemoteAddonRepository; import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.setting.DownloadProviders; -import org.jackhuang.hmcl.setting.VersionIconType; +import org.jackhuang.hmcl.setting.InstanceIconType; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; @@ -343,7 +343,7 @@ private Image loadIcon() { LOG.warning("Failed to load mod icons", e); } - return VersionIconType.getIconType(this.localModFile.getModLoaderType()).getIcon(); + return InstanceIconType.getIconType(this.localModFile.getModLoaderType()).getIcon(); } public void loadIcon(ImageContainer imageContainer, @Nullable WeakReference> current) { @@ -359,7 +359,7 @@ public void loadIcon(ImageContainer imageContainer, @Nullable WeakReference(imageFuture); } - imageContainer.setImage(VersionIconType.getIconType(localModFile.getModLoaderType()).getIcon()); + imageContainer.setImage(InstanceIconType.getIconType(localModFile.getModLoaderType()).getIcon()); imageFuture.thenAcceptAsync(image -> { if (current != null) { ObjectProperty infoObjectProperty = current.get(); @@ -543,7 +543,7 @@ final class ModInfoListCell extends MDListCell { content.setMouseTransparent(true); setSelectable(); - imageContainer.setImage(VersionIconType.COMMAND.getIcon()); + imageContainer.setImage(InstanceIconType.COMMAND.getIcon()); FXUtils.installFastTooltip(restoreButton, i18n("mods.restore")); From e27ac16c761349bfdafbfcad7344a814fdd55399 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Tue, 14 Jul 2026 22:47:31 +0800 Subject: [PATCH 2/5] update --- .../hmcl/setting/LauncherSettings.java | 8 +++ .../jackhuang/hmcl/setting/TriPreference.java | 30 +++++++++ .../org/jackhuang/hmcl/ui/Controllers.java | 21 +++++++ .../hmcl/ui/construct/ImageContainer.java | 5 ++ .../hmcl/ui/construct/MessageDialogPane.java | 5 ++ .../jackhuang/hmcl/ui/main/SettingsPage.java | 15 +++++ .../hmcl/ui/versions/InstanceIconDialog.java | 63 +++++++++++++++---- .../resources/assets/lang/I18N.properties | 5 ++ .../resources/assets/lang/I18N_zh.properties | 5 ++ .../assets/lang/I18N_zh_CN.properties | 5 ++ 10 files changed, 149 insertions(+), 13 deletions(-) create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/setting/TriPreference.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/LauncherSettings.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/LauncherSettings.java index 4538f1b01d9..15ad6d275ff 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/LauncherSettings.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/LauncherSettings.java @@ -218,6 +218,14 @@ public BooleanProperty disableAprilFoolsProperty() { return disableAprilFools; } + /// User preference for saving custom game icons. + @SerializedName("saveCustomGameIcons") + private final ObjectProperty saveCustomGameIcons = new SimpleObjectProperty<>(TriPreference.CONFIRM_EACH_TIME); + + public ObjectProperty saveCustomGameIconsProperty() { + return saveCustomGameIcons; + } + /// The common Minecraft directory selection mode. @SerializedName("commonDirectoryType") private final ObjectProperty commonDirectoryType = new RawPreservingObjectProperty<>(EnumCommonDirectory.DEFAULT); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/TriPreference.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/TriPreference.java new file mode 100644 index 00000000000..36179117c3f --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/TriPreference.java @@ -0,0 +1,30 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 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.setting; + +public enum TriPreference { + + /// Always perform the action without asking + ALWAYS, + + /// Skip the action without asking + NEVER, + + /// Let the user decide whether to perform the action each time the event is triggered + CONFIRM_EACH_TIME +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java index 0c70fed237d..83508478d19 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.ui; import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXCheckBox; import com.jfoenix.controls.JFXDialogLayout; import com.jfoenix.validation.base.ValidatorBase; import javafx.animation.KeyFrame; @@ -75,7 +76,9 @@ import java.io.IOException; import java.time.LocalDate; import java.util.List; +import java.util.Objects; import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; import static org.jackhuang.hmcl.setting.SettingsManager.settings; import static org.jackhuang.hmcl.setting.SettingsManager.getAuthlibInjectorServers; @@ -651,6 +654,24 @@ public static void dialogLater(Region content) { decorator.showDialogLater(content); } + public static void askTriPreference(String text, @Nullable Consumer resConsumer, @Nullable Consumer prefConsumer) { + var r = Objects.requireNonNullElse(resConsumer, (__) -> {}); + var p = Objects.requireNonNullElse(prefConsumer, (__) -> {}); + + var check = new JFXCheckBox(i18n("button.do_not_show_again")); + var dialog = new MessageDialogPane.Builder(text, i18n("message.question"), MessageDialogPane.MessageType.QUESTION) + .addActionNoClosing(check) + .yesOrNo(() -> { + r.accept(true); + p.accept(check.isSelected() ? TriPreference.ALWAYS : TriPreference.CONFIRM_EACH_TIME); + }, () -> { + r.accept(false); + p.accept(check.isSelected() ? TriPreference.NEVER : TriPreference.CONFIRM_EACH_TIME); + }) + .build(); + dialog(dialog); + } + public static CompletableFuture prompt(String title, FutureCallback onResult) { return prompt(title, onResult, ""); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImageContainer.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImageContainer.java index c9aa50c8c00..f8c0d49d422 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImageContainer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImageContainer.java @@ -64,6 +64,11 @@ public ImageContainer(double width, double height) { this.getChildren().setAll(imageView); } + public ImageContainer(double size, Image image) { + this(size); + setImage(image); + } + private void updateCornerRadius(double radius) { clip.setArcWidth(radius); clip.setArcHeight(radius); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java index 2bed442a5f4..7e5b7c7f0f1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java @@ -169,6 +169,11 @@ public Builder addAction(String text, @Nullable Runnable action) { return this; } + public Builder addActionNoClosing(Node actionNode) { + dialog.actions.getChildren().add(actionNode); + return this; + } + public Builder ok(@Nullable Runnable ok) { JFXButton btnOk = new JFXButton(i18n("button.ok")); btnOk.getStyleClass().add("dialog-accept"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java index ee8ea5593cf..98f3b12728c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java @@ -31,6 +31,7 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import org.jackhuang.hmcl.Metadata; +import org.jackhuang.hmcl.setting.TriPreference; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; @@ -202,6 +203,20 @@ else if (locale.isSameLanguage(currentLocale)) miscPaneList.getContent().add(disableAprilFools); } + { + LineSelectButton saveCustomGameIconsPane = new LineSelectButton<>(); + saveCustomGameIconsPane.setTitle(i18n("settings.launcher.save_custom_game_icons")); + saveCustomGameIconsPane.valueProperty().bindBidirectional(settings().saveCustomGameIconsProperty()); + saveCustomGameIconsPane.setConverter(a -> switch (a) { + case CONFIRM_EACH_TIME -> i18n("message.tri_pref.confirm_each_time"); + case ALWAYS -> i18n("message.tri_pref.always"); + case NEVER -> i18n("message.tri_pref.never"); + }); + saveCustomGameIconsPane.setItems(TriPreference.values()); + + miscPaneList.getContent().add(saveCustomGameIconsPane); + } + { BorderPane debugPane = new BorderPane(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstanceIconDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstanceIconDialog.java index bd6ed0561a3..21f86acc7fc 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstanceIconDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstanceIconDialog.java @@ -26,16 +26,21 @@ import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.setting.GameSettings; import org.jackhuang.hmcl.setting.InstanceIconType; +import org.jackhuang.hmcl.setting.SettingsManager; +import org.jackhuang.hmcl.setting.TriPreference; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.construct.DialogPane; +import org.jackhuang.hmcl.ui.construct.ImageContainer; import org.jackhuang.hmcl.ui.construct.RipplerContainer; import org.jackhuang.hmcl.util.io.FileUtils; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.Locale; import java.util.Objects; @@ -43,10 +48,11 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class InstanceIconDialog extends DialogPane { - private static final String TIP_KEY = "saveCustomGameIcons"; public static final Path INSTANCE_ICONS_DIR = Metadata.HMCL_LOCAL_HOME.resolve("instance_icons"); + private static final SimpleDateFormat fileNameFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss_"); + private final HMCLGameRepository repository; private final String versionId; private final Runnable onFinish; @@ -98,15 +104,47 @@ private void exploreIcon() { chooser.getExtensionFilters().add(FXUtils.getImageExtensionFilter()); Path selectedFile = FileUtils.toPath(chooser.showOpenDialog(Controllers.getStage())); if (selectedFile != null) { - try { - repository.setVersionIconFile(versionId, selectedFile); - if (setting != null) { - setting.iconProperty().setValue(InstanceIconType.DEFAULT); + TriPreference pref = SettingsManager.settings().saveCustomGameIconsProperty().get(); + if (pref == TriPreference.CONFIRM_EACH_TIME && !INSTANCE_ICONS_DIR.equals(selectedFile.getParent())) { + Controllers.askTriPreference( + i18n("settings.icon.save"), + (b) -> setCustomIcon(selectedFile, b), + (p) -> SettingsManager.settings().saveCustomGameIconsProperty().set(p) + ); + } else { + setCustomIcon(selectedFile, pref == TriPreference.ALWAYS); + } + } + } + + private void setCustomIcon(Path selectedFile, boolean save) { + try { + Path dest; + if (INSTANCE_ICONS_DIR.equals(selectedFile.getParent()) || !save) { + dest = selectedFile; + } else { + String date = fileNameFormat.format(new Date()); + dest = INSTANCE_ICONS_DIR.resolve(date + selectedFile.getFileName()); + { + int i = 1; + String nameBase = date + FileUtils.getNameWithoutExtension(selectedFile); + String ext = FileUtils.getExtension(selectedFile); + while (Files.exists(dest)) { + dest = INSTANCE_ICONS_DIR.resolve(nameBase + "_" + i + "." + ext); + i++; + } } - onAccept(); - } catch (IOException | IllegalArgumentException e) { - LOG.error("Failed to set icon file: " + selectedFile, e); + FileUtils.copyFile(selectedFile, dest); + } + repository.setVersionIconFile(versionId, dest); + + if (setting != null) { + setting.iconProperty().setValue(InstanceIconType.DEFAULT); } + + onAccept(); + } catch (IOException | IllegalArgumentException e) { + LOG.error("Failed to set instance icon file: " + selectedFile, e); } } @@ -136,16 +174,15 @@ private Node createIcon(InstanceIconType type) { } private Node createIcon(Path path) { - ImageView imageView; + ImageContainer imageContainer; try { - imageView = new ImageView(FXUtils.loadImage(path, 72, 72, true, true)); + imageContainer = new ImageContainer(32, FXUtils.loadImage(path, 64, 64, true, true)); } catch (Exception e) { LOG.warning("Failed to load custom instance icon at " + path, e); return null; } - imageView.setMouseTransparent(true); - FXUtils.limitSize(imageView, 36, 36); - RipplerContainer container = new RipplerContainer(imageView); + imageContainer.setMouseTransparent(true); + RipplerContainer container = new RipplerContainer(imageContainer); FXUtils.setLimitWidth(container, 36); FXUtils.setLimitHeight(container, 36); FXUtils.onClicked(container, () -> { diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 711f6d5bcc1..f99128c4a91 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -967,6 +967,9 @@ message.success=Operation successfully completed message.unknown=Unknown message.warning=Warning message.question=Question +message.tri_pref.always=Always +message.tri_pref.never=Never +message.tri_pref.confirm_each_time=Confirm Each Time modpack=Modpacks modpack.choose=Choose Modpack @@ -1532,6 +1535,7 @@ settings.game.working_directory.hint=Enable the "Isolated" option in "Working Di It is recommended to enable this option to avoid mod conflicts, but you will need to move your worlds manually. settings.icon=Icon +settings.icon.save=Save custom instance icon? settings.game_directories.read_only=The game directory file was saved by a newer version of HMCL. The current version of HMCL cannot modify game directories. settings.launcher=Launcher Settings @@ -1581,6 +1585,7 @@ settings.launcher.proxy.password=Password settings.launcher.proxy.port=Port settings.launcher.proxy.socks=SOCKS settings.launcher.proxy.username=Username +settings.launcher.save_custom_game_icons=Save Custom Game Icons settings.launcher.theme=Theme settings.launcher.theme_color=Theme Color settings.launcher.theme_color_style=Color Style diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 3e2c4d3d092..14c5eb62365 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -774,6 +774,9 @@ message.success=完成 message.unknown=未知 message.warning=警告 message.question=確認 +message.tri_pref.always=總是 +message.tri_pref.never=從不 +message.tri_pref.confirm_each_time=每次詢問 modpack=模組包 modpack.choose=選取要安裝的遊戲模組包檔案 @@ -1323,6 +1326,7 @@ settings.game.working_directory.choose=選取執行目錄 settings.game.working_directory.hint=在「執行路徑」選項中選取「各實例獨立」使目前實例獨立存放設定、世界、模組等資料。使用模組時建議開啟此選項以避免不同實例模組衝突。修改此選項後需自行移動世界等檔案。 settings.icon=遊戲圖示 +settings.icon.save=是否儲存自定義例項圖示? settings.game_directories.read_only=遊戲目錄設定檔是由更高版本的 HMCL 儲存的,目前版本的 HMCL 無法新增遊戲目錄。 settings.launcher=啟動器設定 @@ -1372,6 +1376,7 @@ settings.launcher.proxy.password=密碼 settings.launcher.proxy.port=連線埠 settings.launcher.proxy.socks=SOCKS settings.launcher.proxy.username=帳戶 +settings.launcher.save_custom_game_icons=保存自定義遊戲圖示 settings.launcher.theme=主題色 settings.launcher.theme_color=主題色 settings.launcher.theme_color_style=色彩模式 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 e1d798ad008..105f7d92b84 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -779,6 +779,9 @@ message.success=完成 message.unknown=未知 message.warning=警告 message.question=确认 +message.tri_pref.always=总是 +message.tri_pref.never=从不 +message.tri_pref.confirm_each_time=每次询问 modpack=整合包 modpack.choose=选择要安装的游戏整合包文件 @@ -1328,6 +1331,7 @@ settings.game.working_directory.choose=选择运行文件夹 settings.game.working_directory.hint=在“版本隔离”中选择“各实例独立”使当前实例独立存放设置、世界、模组等数据。使用模组时建议启用此选项以避免不同实例模组冲突。修改此选项后需自行移动世界等文件。 settings.icon=游戏图标 +settings.icon.save=是否保存自定义实例图标? settings.game_directories.read_only=游戏文件夹配置文件是由更高版本的 HMCL 保存的,当前版本的 HMCL 无法修改游戏文件夹。 settings.launcher=启动器设置 @@ -1377,6 +1381,7 @@ settings.launcher.proxy.password=密码 settings.launcher.proxy.port=端口 settings.launcher.proxy.socks=SOCKS settings.launcher.proxy.username=账户 +settings.launcher.save_custom_game_icons=保存自定义游戏图标 settings.launcher.theme=主题 settings.launcher.theme_color=主题色 settings.launcher.theme_color_style=色彩模式 From 5a0907b56c6d99cdc0e320eed2a6d1b5439b0fb4 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Tue, 14 Jul 2026 22:52:32 +0800 Subject: [PATCH 3/5] update --- HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java index d4864d72ba1..b86b190e97c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java @@ -53,9 +53,9 @@ * @author huangyuhui */ public class InstallerItem extends Control { - private final String id; + private final String id; private final InstanceIconType iconType; - private final Style style; + private final Style style; private final ObjectProperty versionProperty = new SimpleObjectProperty<>(this, "version", null); private final ObjectProperty resolvedStateProperty = new SimpleObjectProperty<>(this, "resolvedState", InstallableState.INSTANCE); From e498686862d165c2fcdb8e6e6fa0b27ecb94411f Mon Sep 17 00:00:00 2001 From: ToobLac Date: Tue, 14 Jul 2026 22:54:55 +0800 Subject: [PATCH 4/5] update --- .../main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java index 7ff3d3d80fd..80a6749177a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java @@ -573,6 +573,9 @@ public void setVersionIconFile(String id, Path iconFile) throws IOException { throw new IllegalArgumentException("Unsupported icon file: " + ext); } + var dest = getVersionRoot(id).resolve("icon." + ext); + if (dest.equals(iconFile)) return; + deleteIconFile(id); FileUtils.copyFile(iconFile, getVersionRoot(id).resolve("icon." + ext)); From 2ad6a718a4831a29fd50fe652135b84f007781d0 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Tue, 14 Jul 2026 23:19:50 +0800 Subject: [PATCH 5/5] update --- .../hmcl/ui/versions/InstanceIconDialog.java | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstanceIconDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstanceIconDialog.java index 21f86acc7fc..cae2c43b0d6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstanceIconDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstanceIconDialog.java @@ -117,24 +117,29 @@ private void exploreIcon() { } } + private Path saveIcon(Path selectedFile) throws IOException { + String date = fileNameFormat.format(new Date()); + Path dest = INSTANCE_ICONS_DIR.resolve(date + selectedFile.getFileName()); + { + int i = 1; + String nameBase = date + FileUtils.getNameWithoutExtension(selectedFile); + String ext = FileUtils.getExtension(selectedFile); + while (Files.exists(dest)) { + dest = INSTANCE_ICONS_DIR.resolve(nameBase + "_" + i + "." + ext); + i++; + } + } + FileUtils.copyFile(selectedFile, dest); + return dest; + } + private void setCustomIcon(Path selectedFile, boolean save) { try { Path dest; if (INSTANCE_ICONS_DIR.equals(selectedFile.getParent()) || !save) { dest = selectedFile; } else { - String date = fileNameFormat.format(new Date()); - dest = INSTANCE_ICONS_DIR.resolve(date + selectedFile.getFileName()); - { - int i = 1; - String nameBase = date + FileUtils.getNameWithoutExtension(selectedFile); - String ext = FileUtils.getExtension(selectedFile); - while (Files.exists(dest)) { - dest = INSTANCE_ICONS_DIR.resolve(nameBase + "_" + i + "." + ext); - i++; - } - } - FileUtils.copyFile(selectedFile, dest); + dest = saveIcon(selectedFile); } repository.setVersionIconFile(versionId, dest);