From 29e40ec1a5d2afc6f64c094b5860d2d5b12488d7 Mon Sep 17 00:00:00 2001 From: Glavo Date: Fri, 12 Dec 2025 21:12:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9C=A8=20DownloadListPage=20=E4=B8=AD?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=9B=BE=E6=A0=87=E7=BC=93=E5=AD=98=20(#4866?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/jackhuang/hmcl/ui/FXUtils.java | 9 ++- .../hmcl/ui/versions/DownloadListPage.java | 70 ++++++++++++++++++- .../jackhuang/hmcl/util/io/NetworkUtils.java | 12 +++- 3 files changed, 87 insertions(+), 4 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index f0d26b496..c92acf202 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -1188,7 +1188,14 @@ public final class FXUtils { public static Task getRemoteImageTask(String url, int requestedWidth, int requestedHeight, boolean preserveRatio, boolean smooth) { return new CacheFileTask(url) - .thenApplyAsync(file -> loadImage(file, requestedWidth, requestedHeight, preserveRatio, smooth)); + .thenApplyAsync(file -> loadImage(file, requestedWidth, requestedHeight, preserveRatio, smooth)) + .setSignificance(Task.TaskSignificance.MINOR); + } + + public static Task getRemoteImageTask(URI uri, int requestedWidth, int requestedHeight, boolean preserveRatio, boolean smooth) { + return new CacheFileTask(uri) + .thenApplyAsync(file -> loadImage(file, requestedWidth, requestedHeight, preserveRatio, smooth)) + .setSignificance(Task.TaskSignificance.MINOR); } public static ObservableValue newRemoteImage(String url, int requestedWidth, int requestedHeight, boolean preserveRatio, boolean smooth) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java index 4a679ed30..8a9335365 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java @@ -35,6 +35,7 @@ import javafx.scene.control.Control; import javafx.scene.control.Label; import javafx.scene.control.Skin; import javafx.scene.control.SkinBase; +import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; @@ -60,16 +61,24 @@ import org.jackhuang.hmcl.util.Holder; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.i18n.I18n; +import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jackhuang.hmcl.util.javafx.BindingMapping; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; +import org.jetbrains.annotations.NotNull; +import java.lang.ref.WeakReference; +import java.net.URI; import java.util.*; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.stream.Collectors; import static org.jackhuang.hmcl.ui.FXUtils.ignoreEvent; import static org.jackhuang.hmcl.ui.FXUtils.stringConverter; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; public class DownloadListPage extends Control implements DecoratorPage, VersionPage.VersionLoadable { protected final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(); @@ -523,6 +532,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP // ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE); + var iconCache = new WeakHashMap>>(); listView.setCellFactory(x -> new FloatListCell<>(listView) { private final TwoLineListItem content = new TwoLineListItem(); private final ImageView imageView = new ImageView(); @@ -549,8 +559,64 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP dataItem.getCategories().stream() .map(category -> getSkinnable().getLocalizedCategory(category)) .forEach(content::addTag); - if (StringUtils.isNotBlank(dataItem.getIconUrl())) { - imageView.imageProperty().bind(FXUtils.newRemoteImage(dataItem.getIconUrl(), 80, 80, true, true)); + loadIcon(dataItem); + } + + private void loadIcon(RemoteMod mod) { + if (StringUtils.isBlank(mod.getIconUrl())) { + imageView.setImage(null); + return; + } + + WeakReference> cacheRef = iconCache.get(mod.getIconUrl()); + CompletableFuture cache; + if (cacheRef != null && (cache = cacheRef.get()) != null) { + loadIcon(cache, mod.getIconUrl()); + return; + } + + URI iconUrl = NetworkUtils.toURIOrNull(mod.getIconUrl()); + if (iconUrl == null) { + imageView.setImage(null); + return; + } + + CompletableFuture future = new CompletableFuture<>(); + WeakReference> futureRef = new WeakReference<>(future); + iconCache.put(mod.getIconUrl(), futureRef); + + FXUtils.getRemoteImageTask(iconUrl, 80, 80, true, true) + .whenComplete(Schedulers.defaultScheduler(), (result, exception) -> { + if (exception == null) { + future.complete(result); + } else { + LOG.warning("Failed to load image from " + iconUrl, exception); + future.completeExceptionally(exception); + } + }).start(); + loadIcon(future, mod.getIconUrl()); + } + + private void loadIcon(@NotNull CompletableFuture future, + @NotNull String iconUrl) { + Image image; + try { + image = future.getNow(null); + } catch (CancellationException | CompletionException ignored) { + imageView.setImage(null); + return; + } + + if (image != null) { + imageView.setImage(image); + } else { + imageView.setImage(null); + future.thenAcceptAsync(result -> { + RemoteMod item = getItem(); + if (item != null && iconUrl.equals(item.getIconUrl())) { + this.imageView.setImage(result); + } + }, Schedulers.javafx()); } } }); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/NetworkUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/NetworkUtils.java index c0aac47d5..830a852c3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/NetworkUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/NetworkUtils.java @@ -20,6 +20,7 @@ package org.jackhuang.hmcl.util.io; import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.StringUtils; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.*; import java.net.*; @@ -419,6 +420,15 @@ public final class NetworkUtils { public static @NotNull URI toURI(@NotNull URL url) { return toURI(url.toExternalForm()); } - // ==== + public static @Nullable URI toURIOrNull(String uri) { + if (StringUtils.isNotBlank(uri)) { + try { + return toURI(uri); + } catch (Exception ignored) { + } + } + + return null; + } }