From 2e878e43cb9a6df86f2274976164435834129796 Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 9 Oct 2025 21:33:35 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=A8=A1=E7=BB=84=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E9=A1=B5=E9=9D=A2=E5=8A=A0=E8=BD=BD=E6=A8=A1=E7=BB=84?= =?UTF-8?q?=E5=9B=BE=E6=A0=87=E7=9B=B8=E5=85=B3=E9=97=AE=E9=A2=98=20(#4638?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 修复高分屏上模组图标模糊的问题; 2. 修复快速滚动时可能会显示其他模组的图标的问题; 3. 修复快速滚动时可能重复加载图标的问题。 --- .../hmcl/setting/VersionIconType.java | 17 ++- .../hmcl/ui/versions/ModListPageSkin.java | 126 +++++++++--------- 2 files changed, 76 insertions(+), 67 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java index 703c0bf82..09675738c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.setting; import javafx.scene.image.Image; +import org.jackhuang.hmcl.mod.ModLoaderType; import org.jackhuang.hmcl.ui.FXUtils; public enum VersionIconType { @@ -39,6 +40,18 @@ public enum VersionIconType { // Please append new items at last + public static VersionIconType getIconType(ModLoaderType modLoaderType) { + return switch (modLoaderType) { + case FORGE -> VersionIconType.FORGE; + case NEO_FORGED -> VersionIconType.NEO_FORGE; + case FABRIC -> VersionIconType.FABRIC; + case QUILT -> VersionIconType.QUILT; + case LITE_LOADER -> VersionIconType.CHICKEN; + case CLEANROOM -> VersionIconType.CLEANROOM; + default -> VersionIconType.COMMAND; + }; + } + private final String resourceUrl; VersionIconType(String resourceUrl) { @@ -48,8 +61,4 @@ public enum VersionIconType { public Image getIcon() { return FXUtils.newBuiltinImage(resourceUrl); } - - public Image getIcon(int size) { - return FXUtils.newBuiltinImage(resourceUrl, size, size, true, true); - } } 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 23aaacc72..f3e6c1007 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 @@ -22,6 +22,7 @@ import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject; import javafx.animation.PauseTransition; import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; import javafx.beans.value.ChangeListener; import javafx.collections.ListChangeListener; import javafx.css.PseudoClass; @@ -55,10 +56,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.*; -import org.jackhuang.hmcl.util.Holder; -import org.jackhuang.hmcl.util.Lazy; -import org.jackhuang.hmcl.util.Pair; -import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.*; import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; @@ -67,10 +65,12 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; +import java.util.concurrent.CompletableFuture; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -289,12 +289,34 @@ final class ModListPageSkin extends SkinBase { } } - private static Task loadModIcon(LocalModFile modFile, int size) { - return Task.supplyAsync(() -> { + static final class ModInfoObject extends RecursiveTreeObject implements Comparable { + private final BooleanProperty active; + private final LocalModFile localModFile; + private final @Nullable ModTranslations.Mod modTranslations; + + private SoftReference> iconCache; + + ModInfoObject(LocalModFile localModFile) { + this.localModFile = localModFile; + this.active = localModFile.activeProperty(); + + this.modTranslations = ModTranslations.MOD.getMod(localModFile.getId(), localModFile.getName()); + } + + LocalModFile getModInfo() { + return localModFile; + } + + public @Nullable ModTranslations.Mod getModTranslations() { + return modTranslations; + } + + @FXThread + private Image loadIcon() { List iconPaths = new ArrayList<>(); - if (StringUtils.isNotBlank(modFile.getLogoPath())) { - iconPaths.add(modFile.getLogoPath()); + if (StringUtils.isNotBlank(this.localModFile.getLogoPath())) { + iconPaths.add(this.localModFile.getLogoPath()); } iconPaths.addAll(List.of( @@ -318,7 +340,7 @@ final class ModListPageSkin extends SkinBase { "resources/mod_icon.png" )); - String modId = modFile.getId(); + String modId = this.localModFile.getId(); if (StringUtils.isNotBlank(modId)) { iconPaths.addAll(List.of( "assets/" + modId + "/icon.png", @@ -337,14 +359,12 @@ final class ModListPageSkin extends SkinBase { )); } - try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modFile.getFile())) { + try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(this.localModFile.getFile())) { for (String path : iconPaths) { Path iconPath = fs.getPath(path); if (Files.exists(iconPath)) { - Image image = FXUtils.loadImage(iconPath, size, size, true, true); - if (!image.isError() && - image.getWidth() > 0 && - image.getHeight() > 0 && + Image image = FXUtils.loadImage(iconPath, 80, 80, true, true); + if (!image.isError() && image.getWidth() > 0 && image.getHeight() > 0 && Math.abs(image.getWidth() - image.getHeight()) < 1) { return image; } @@ -354,40 +374,34 @@ final class ModListPageSkin extends SkinBase { LOG.warning("Failed to load mod icons", e); } - VersionIconType defaultIcon = switch (modFile.getModLoaderType()) { - case FORGE -> VersionIconType.FORGE; - case NEO_FORGED -> VersionIconType.NEO_FORGE; - case FABRIC -> VersionIconType.FABRIC; - case QUILT -> VersionIconType.QUILT; - case LITE_LOADER -> VersionIconType.CHICKEN; - case CLEANROOM -> VersionIconType.CLEANROOM; - default -> VersionIconType.COMMAND; - }; - - return defaultIcon.getIcon(size); - }); - } - - static class ModInfoObject extends RecursiveTreeObject implements Comparable { - private final BooleanProperty active; - private final LocalModFile localModFile; - private final @Nullable ModTranslations.Mod modTranslations; - - private SoftReference iconCache; - - ModInfoObject(LocalModFile localModFile) { - this.localModFile = localModFile; - this.active = localModFile.activeProperty(); - - this.modTranslations = ModTranslations.MOD.getMod(localModFile.getId(), localModFile.getName()); + return VersionIconType.getIconType(this.localModFile.getModLoaderType()).getIcon(); } - LocalModFile getModInfo() { - return localModFile; - } + public void loadIcon(ImageView imageView, @Nullable WeakReference> current) { + SoftReference> iconCache = this.iconCache; + CompletableFuture imageFuture; + if (iconCache != null && (imageFuture = iconCache.get()) != null) { + Image image = imageFuture.getNow(null); + if (image != null) { + imageView.setImage(image); + return; + } + } else { + imageFuture = CompletableFuture.supplyAsync(this::loadIcon, Schedulers.io()); + this.iconCache = new SoftReference<>(imageFuture); + } + imageView.setImage(VersionIconType.getIconType(localModFile.getModLoaderType()).getIcon()); + imageFuture.thenAcceptAsync(image -> { + if (current != null) { + ObjectProperty infoObjectProperty = current.get(); + if (infoObjectProperty == null || infoObjectProperty.get() != this) { + // The current ListCell has already switched to another object + return; + } + } - public @Nullable ModTranslations.Mod getModTranslations() { - return modTranslations; + imageView.setImage(image); + }, Schedulers.javafx()); } @Override @@ -397,7 +411,7 @@ final class ModListPageSkin extends SkinBase { } } - class ModInfoDialog extends JFXDialogLayout { + final class ModInfoDialog extends JFXDialogLayout { ModInfoDialog(ModInfoObject modInfo) { HBox titleContainer = new HBox(); @@ -408,10 +422,7 @@ final class ModListPageSkin extends SkinBase { ImageView imageView = new ImageView(); FXUtils.limitSize(imageView, 40, 40); - loadModIcon(modInfo.getModInfo(), 40) - .whenComplete(Schedulers.javafx(), (image, exception) -> { - imageView.setImage(image); - }).start(); + modInfo.loadIcon(imageView, null); TwoLineListItem title = new TwoLineListItem(); if (modInfo.getModTranslations() != null && I18n.isUseChinese()) @@ -584,7 +595,7 @@ final class ModListPageSkin extends SkinBase { imageView.setFitWidth(24); imageView.setFitHeight(24); imageView.setPreserveRatio(true); - imageView.setImage(FXUtils.newBuiltinImage("/assets/img/command.png", 24, 24, true, true)); + imageView.setImage(VersionIconType.COMMAND.getIcon()); restoreButton.getStyleClass().add("toggle-icon4"); restoreButton.setGraphic(FXUtils.limitingSize(SVG.RESTORE.createIcon(Theme.blackFill(), 24), 24, 24)); @@ -620,18 +631,7 @@ final class ModListPageSkin extends SkinBase { LocalModFile modInfo = dataItem.getModInfo(); ModTranslations.Mod modTranslations = dataItem.getModTranslations(); - SoftReference iconCache = dataItem.iconCache; - Image icon; - if (iconCache != null && (icon = iconCache.get()) != null) { - imageView.setImage(icon); - } else { - loadModIcon(modInfo, 24) - .whenComplete(Schedulers.javafx(), (image, exception) -> { - dataItem.iconCache = new SoftReference<>(image); - imageView.setImage(image); - }).start(); - } - + dataItem.loadIcon(imageView, new WeakReference<>(this.itemProperty())); String displayName = modInfo.getName(); if (modTranslations != null && I18n.isUseChinese()) {