修复模组列表页面加载模组图标相关问题 (#4638)
1. 修复高分屏上模组图标模糊的问题; 2. 修复快速滚动时可能会显示其他模组的图标的问题; 3. 修复快速滚动时可能重复加载图标的问题。
This commit is contained in:
@@ -18,6 +18,7 @@
|
|||||||
package org.jackhuang.hmcl.setting;
|
package org.jackhuang.hmcl.setting;
|
||||||
|
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
|
import org.jackhuang.hmcl.mod.ModLoaderType;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
|
||||||
public enum VersionIconType {
|
public enum VersionIconType {
|
||||||
@@ -39,6 +40,18 @@ public enum VersionIconType {
|
|||||||
|
|
||||||
// Please append new items at last
|
// 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;
|
private final String resourceUrl;
|
||||||
|
|
||||||
VersionIconType(String resourceUrl) {
|
VersionIconType(String resourceUrl) {
|
||||||
@@ -48,8 +61,4 @@ public enum VersionIconType {
|
|||||||
public Image getIcon() {
|
public Image getIcon() {
|
||||||
return FXUtils.newBuiltinImage(resourceUrl);
|
return FXUtils.newBuiltinImage(resourceUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Image getIcon(int size) {
|
|
||||||
return FXUtils.newBuiltinImage(resourceUrl, size, size, true, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject;
|
|||||||
import javafx.animation.PauseTransition;
|
import javafx.animation.PauseTransition;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.css.PseudoClass;
|
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.ContainerAnimations;
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.*;
|
import org.jackhuang.hmcl.ui.construct.*;
|
||||||
import org.jackhuang.hmcl.util.Holder;
|
import org.jackhuang.hmcl.util.*;
|
||||||
import org.jackhuang.hmcl.util.Lazy;
|
|
||||||
import org.jackhuang.hmcl.util.Pair;
|
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
|
||||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
@@ -67,10 +65,12 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.lang.ref.SoftReference;
|
import java.lang.ref.SoftReference;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
import java.nio.file.FileSystem;
|
import java.nio.file.FileSystem;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@@ -289,12 +289,34 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Task<Image> loadModIcon(LocalModFile modFile, int size) {
|
static final class ModInfoObject extends RecursiveTreeObject<ModInfoObject> implements Comparable<ModInfoObject> {
|
||||||
return Task.supplyAsync(() -> {
|
private final BooleanProperty active;
|
||||||
|
private final LocalModFile localModFile;
|
||||||
|
private final @Nullable ModTranslations.Mod modTranslations;
|
||||||
|
|
||||||
|
private SoftReference<CompletableFuture<Image>> 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<String> iconPaths = new ArrayList<>();
|
List<String> iconPaths = new ArrayList<>();
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(modFile.getLogoPath())) {
|
if (StringUtils.isNotBlank(this.localModFile.getLogoPath())) {
|
||||||
iconPaths.add(modFile.getLogoPath());
|
iconPaths.add(this.localModFile.getLogoPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
iconPaths.addAll(List.of(
|
iconPaths.addAll(List.of(
|
||||||
@@ -318,7 +340,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
|||||||
"resources/mod_icon.png"
|
"resources/mod_icon.png"
|
||||||
));
|
));
|
||||||
|
|
||||||
String modId = modFile.getId();
|
String modId = this.localModFile.getId();
|
||||||
if (StringUtils.isNotBlank(modId)) {
|
if (StringUtils.isNotBlank(modId)) {
|
||||||
iconPaths.addAll(List.of(
|
iconPaths.addAll(List.of(
|
||||||
"assets/" + modId + "/icon.png",
|
"assets/" + modId + "/icon.png",
|
||||||
@@ -337,14 +359,12 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modFile.getFile())) {
|
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(this.localModFile.getFile())) {
|
||||||
for (String path : iconPaths) {
|
for (String path : iconPaths) {
|
||||||
Path iconPath = fs.getPath(path);
|
Path iconPath = fs.getPath(path);
|
||||||
if (Files.exists(iconPath)) {
|
if (Files.exists(iconPath)) {
|
||||||
Image image = FXUtils.loadImage(iconPath, size, size, true, true);
|
Image image = FXUtils.loadImage(iconPath, 80, 80, true, true);
|
||||||
if (!image.isError() &&
|
if (!image.isError() && image.getWidth() > 0 && image.getHeight() > 0 &&
|
||||||
image.getWidth() > 0 &&
|
|
||||||
image.getHeight() > 0 &&
|
|
||||||
Math.abs(image.getWidth() - image.getHeight()) < 1) {
|
Math.abs(image.getWidth() - image.getHeight()) < 1) {
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
@@ -354,40 +374,34 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
|||||||
LOG.warning("Failed to load mod icons", e);
|
LOG.warning("Failed to load mod icons", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
VersionIconType defaultIcon = switch (modFile.getModLoaderType()) {
|
return VersionIconType.getIconType(this.localModFile.getModLoaderType()).getIcon();
|
||||||
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<ModInfoObject> implements Comparable<ModInfoObject> {
|
public void loadIcon(ImageView imageView, @Nullable WeakReference<ObjectProperty<ModInfoObject>> current) {
|
||||||
private final BooleanProperty active;
|
SoftReference<CompletableFuture<Image>> iconCache = this.iconCache;
|
||||||
private final LocalModFile localModFile;
|
CompletableFuture<Image> imageFuture;
|
||||||
private final @Nullable ModTranslations.Mod modTranslations;
|
if (iconCache != null && (imageFuture = iconCache.get()) != null) {
|
||||||
|
Image image = imageFuture.getNow(null);
|
||||||
private SoftReference<Image> iconCache;
|
if (image != null) {
|
||||||
|
imageView.setImage(image);
|
||||||
ModInfoObject(LocalModFile localModFile) {
|
return;
|
||||||
this.localModFile = localModFile;
|
}
|
||||||
this.active = localModFile.activeProperty();
|
} else {
|
||||||
|
imageFuture = CompletableFuture.supplyAsync(this::loadIcon, Schedulers.io());
|
||||||
this.modTranslations = ModTranslations.MOD.getMod(localModFile.getId(), localModFile.getName());
|
this.iconCache = new SoftReference<>(imageFuture);
|
||||||
|
}
|
||||||
|
imageView.setImage(VersionIconType.getIconType(localModFile.getModLoaderType()).getIcon());
|
||||||
|
imageFuture.thenAcceptAsync(image -> {
|
||||||
|
if (current != null) {
|
||||||
|
ObjectProperty<ModInfoObject> infoObjectProperty = current.get();
|
||||||
|
if (infoObjectProperty == null || infoObjectProperty.get() != this) {
|
||||||
|
// The current ListCell has already switched to another object
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalModFile getModInfo() {
|
imageView.setImage(image);
|
||||||
return localModFile;
|
}, Schedulers.javafx());
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable ModTranslations.Mod getModTranslations() {
|
|
||||||
return modTranslations;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -397,7 +411,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ModInfoDialog extends JFXDialogLayout {
|
final class ModInfoDialog extends JFXDialogLayout {
|
||||||
|
|
||||||
ModInfoDialog(ModInfoObject modInfo) {
|
ModInfoDialog(ModInfoObject modInfo) {
|
||||||
HBox titleContainer = new HBox();
|
HBox titleContainer = new HBox();
|
||||||
@@ -408,10 +422,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
|||||||
|
|
||||||
ImageView imageView = new ImageView();
|
ImageView imageView = new ImageView();
|
||||||
FXUtils.limitSize(imageView, 40, 40);
|
FXUtils.limitSize(imageView, 40, 40);
|
||||||
loadModIcon(modInfo.getModInfo(), 40)
|
modInfo.loadIcon(imageView, null);
|
||||||
.whenComplete(Schedulers.javafx(), (image, exception) -> {
|
|
||||||
imageView.setImage(image);
|
|
||||||
}).start();
|
|
||||||
|
|
||||||
TwoLineListItem title = new TwoLineListItem();
|
TwoLineListItem title = new TwoLineListItem();
|
||||||
if (modInfo.getModTranslations() != null && I18n.isUseChinese())
|
if (modInfo.getModTranslations() != null && I18n.isUseChinese())
|
||||||
@@ -584,7 +595,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
|||||||
imageView.setFitWidth(24);
|
imageView.setFitWidth(24);
|
||||||
imageView.setFitHeight(24);
|
imageView.setFitHeight(24);
|
||||||
imageView.setPreserveRatio(true);
|
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.getStyleClass().add("toggle-icon4");
|
||||||
restoreButton.setGraphic(FXUtils.limitingSize(SVG.RESTORE.createIcon(Theme.blackFill(), 24), 24, 24));
|
restoreButton.setGraphic(FXUtils.limitingSize(SVG.RESTORE.createIcon(Theme.blackFill(), 24), 24, 24));
|
||||||
@@ -620,18 +631,7 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
|||||||
LocalModFile modInfo = dataItem.getModInfo();
|
LocalModFile modInfo = dataItem.getModInfo();
|
||||||
ModTranslations.Mod modTranslations = dataItem.getModTranslations();
|
ModTranslations.Mod modTranslations = dataItem.getModTranslations();
|
||||||
|
|
||||||
SoftReference<Image> iconCache = dataItem.iconCache;
|
dataItem.loadIcon(imageView, new WeakReference<>(this.itemProperty()));
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
String displayName = modInfo.getName();
|
String displayName = modInfo.getName();
|
||||||
if (modTranslations != null && I18n.isUseChinese()) {
|
if (modTranslations != null && I18n.isUseChinese()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user