mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-08-03 19:36:53 -04:00
update
This commit is contained in:
parent
081f01b9ae
commit
2303ca3f4c
@ -29,6 +29,7 @@ import javafx.beans.binding.Bindings;
|
|||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.value.*;
|
import javafx.beans.value.*;
|
||||||
import javafx.collections.ObservableMap;
|
import javafx.collections.ObservableMap;
|
||||||
import javafx.event.Event;
|
import javafx.event.Event;
|
||||||
@ -60,6 +61,8 @@ import javafx.util.StringConverter;
|
|||||||
import org.glavo.png.PNGType;
|
import org.glavo.png.PNGType;
|
||||||
import org.glavo.png.PNGWriter;
|
import org.glavo.png.PNGWriter;
|
||||||
import org.glavo.png.javafx.PNGJavaFXUtils;
|
import org.glavo.png.javafx.PNGJavaFXUtils;
|
||||||
|
import org.jackhuang.hmcl.task.CacheFileTask;
|
||||||
|
import org.jackhuang.hmcl.task.Schedulers;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||||
import org.jackhuang.hmcl.util.*;
|
import org.jackhuang.hmcl.util.*;
|
||||||
@ -87,8 +90,12 @@ import java.lang.invoke.MethodHandles;
|
|||||||
import java.lang.invoke.MethodType;
|
import java.lang.invoke.MethodType;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
@ -978,6 +985,55 @@ public final class FXUtils {
|
|||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Task<Image> getRemoteImageTask(String url, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth) {
|
||||||
|
return new CacheFileTask(URI.create(url))
|
||||||
|
.thenApplyAsync(file -> {
|
||||||
|
try (var channel = FileChannel.open(file, StandardOpenOption.READ)) {
|
||||||
|
var header = new byte[12];
|
||||||
|
var buffer = ByteBuffer.wrap(header);
|
||||||
|
|
||||||
|
//noinspection StatementWithEmptyBody
|
||||||
|
while (channel.read(buffer) > 0) {
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.position(0L);
|
||||||
|
if (!buffer.hasRemaining()) {
|
||||||
|
// WebP File
|
||||||
|
if (header[0] == 'R' && header[1] == 'I' && header[2] == 'F' && header[3] == 'F' &&
|
||||||
|
header[8] == 'W' && header[9] == 'E' && header[10] == 'B' && header[11] == 'P') {
|
||||||
|
|
||||||
|
WebPImageReaderSpi spi = new WebPImageReaderSpi();
|
||||||
|
ImageReader reader = spi.createReaderInstance(null);
|
||||||
|
|
||||||
|
try (ImageInputStream imageInput = ImageIO.createImageInputStream(Channels.newInputStream(channel))) {
|
||||||
|
reader.setInput(imageInput, true, true);
|
||||||
|
return SwingFXUtils.toFXImage(reader.read(0, reader.getDefaultReadParam()),
|
||||||
|
requestedWidth, requestedHeight, preserveRatio, smooth);
|
||||||
|
} finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Image(Channels.newInputStream(channel), requestedWidth, requestedHeight, preserveRatio, smooth);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ObservableValue<Image> newRemoteImage(String url, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth) {
|
||||||
|
var image = new SimpleObjectProperty<Image>();
|
||||||
|
getRemoteImageTask(url, requestedWidth, requestedHeight, preserveRatio, smooth)
|
||||||
|
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||||
|
if (exception == null) {
|
||||||
|
image.set(result);
|
||||||
|
} else {
|
||||||
|
LOG.warning("An exception encountered while loading remote image: " + url, exception);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.start();
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
public static JFXButton newRaisedButton(String text) {
|
public static JFXButton newRaisedButton(String text) {
|
||||||
JFXButton button = new JFXButton(text);
|
JFXButton button = new JFXButton(text);
|
||||||
button.getStyleClass().add("jfx-button-raised");
|
button.getStyleClass().add("jfx-button-raised");
|
||||||
|
@ -22,6 +22,7 @@ import javafx.scene.image.Image;
|
|||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
import javafx.scene.text.TextFlow;
|
import javafx.scene.text.TextFlow;
|
||||||
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jsoup.nodes.Node;
|
import org.jsoup.nodes.Node;
|
||||||
import org.jsoup.nodes.TextNode;
|
import org.jsoup.nodes.TextNode;
|
||||||
|
|
||||||
@ -190,11 +191,11 @@ public final class HTMLRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Image image = FXUtils.newRemoteImage(uri.toString(), width, height, true, true, false);
|
Task<Image> task = FXUtils.getRemoteImageTask(uri.toString(), width, height, true, true);
|
||||||
if (image.isError()) {
|
task.start();
|
||||||
LOG.warning("Failed to load image: " + uri, image.getException());
|
|
||||||
} else {
|
try {
|
||||||
ImageView imageView = new ImageView(image);
|
ImageView imageView = new ImageView(task.getResult());
|
||||||
if (hyperlink != null) {
|
if (hyperlink != null) {
|
||||||
URI target = resolveLink(hyperlink);
|
URI target = resolveLink(hyperlink);
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
@ -204,6 +205,8 @@ public final class HTMLRenderer {
|
|||||||
}
|
}
|
||||||
children.add(imageView);
|
children.add(imageView);
|
||||||
return;
|
return;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
LOG.warning("Failed to load image: " + uri, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,6 @@ import javafx.scene.control.Control;
|
|||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.Skin;
|
import javafx.scene.control.Skin;
|
||||||
import javafx.scene.control.SkinBase;
|
import javafx.scene.control.SkinBase;
|
||||||
import javafx.scene.image.Image;
|
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.input.KeyCode;
|
import javafx.scene.input.KeyCode;
|
||||||
import javafx.scene.input.KeyEvent;
|
import javafx.scene.input.KeyEvent;
|
||||||
@ -45,7 +44,6 @@ import org.jackhuang.hmcl.mod.RemoteMod;
|
|||||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.task.GetTask;
|
|
||||||
import org.jackhuang.hmcl.task.Schedulers;
|
import org.jackhuang.hmcl.task.Schedulers;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
@ -63,8 +61,6 @@ import org.jackhuang.hmcl.util.i18n.I18n;
|
|||||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -546,7 +542,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
|||||||
|
|
||||||
if (StringUtils.isNotBlank(dataItem.getIconUrl())) {
|
if (StringUtils.isNotBlank(dataItem.getIconUrl())) {
|
||||||
LOG.debug("Icon: " + dataItem.getIconUrl());
|
LOG.debug("Icon: " + dataItem.getIconUrl());
|
||||||
imageView.setImage(FXUtils.newRemoteImage(dataItem.getIconUrl(), 40, 40, true, true, true));
|
imageView.imageProperty().bind(FXUtils.newRemoteImage(dataItem.getIconUrl(), 40, 40, true, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -220,7 +220,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
|||||||
{
|
{
|
||||||
ImageView imageView = new ImageView();
|
ImageView imageView = new ImageView();
|
||||||
if (StringUtils.isNotBlank(getSkinnable().addon.getIconUrl())) {
|
if (StringUtils.isNotBlank(getSkinnable().addon.getIconUrl())) {
|
||||||
imageView.setImage(FXUtils.newRemoteImage(getSkinnable().addon.getIconUrl(), 40, 40, true, true, true));
|
imageView.imageProperty().bind(FXUtils.newRemoteImage(getSkinnable().addon.getIconUrl(), 40, 40, true, true));
|
||||||
}
|
}
|
||||||
descriptionPane.getChildren().add(FXUtils.limitingSize(imageView, 40, 40));
|
descriptionPane.getChildren().add(FXUtils.limitingSize(imageView, 40, 40));
|
||||||
|
|
||||||
@ -359,7 +359,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
|||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(addon.getIconUrl())) {
|
if (StringUtils.isNotBlank(addon.getIconUrl())) {
|
||||||
imageView.setImage(FXUtils.newRemoteImage(addon.getIconUrl(), 40, 40, true, true, true));
|
imageView.imageProperty().bind(FXUtils.newRemoteImage(addon.getIconUrl(), 40, 40, true, true));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
content.setTitle(i18n("mods.broken_dependency.title"));
|
content.setTitle(i18n("mods.broken_dependency.title"));
|
||||||
|
@ -120,5 +120,40 @@ public final class SwingFXUtils {
|
|||||||
pw.setPixels(0, 0, bw, bh, pf, data, offset, scan);
|
pw.setPixels(0, 0, bw, bh, pf, data, offset, scan);
|
||||||
return wimg;
|
return wimg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static WritableImage toFXImage(BufferedImage bimg, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth) {
|
||||||
|
int width = (int) requestedWidth;
|
||||||
|
int height = (int) requestedHeight;
|
||||||
|
|
||||||
|
assert width > 0 && height > 0;
|
||||||
|
|
||||||
|
// Calculate actual dimensions if preserveRatio is true
|
||||||
|
if (preserveRatio) {
|
||||||
|
double originalWidth = bimg.getWidth();
|
||||||
|
double originalHeight = bimg.getHeight();
|
||||||
|
double scaleX = requestedWidth / originalWidth;
|
||||||
|
double scaleY = requestedHeight / originalHeight;
|
||||||
|
double scale = Math.min(scaleX, scaleY);
|
||||||
|
|
||||||
|
width = (int) (originalWidth * scale);
|
||||||
|
height = (int) (originalHeight * scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create scaled BufferedImage
|
||||||
|
BufferedImage scaledImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);
|
||||||
|
Graphics2D g2d = scaledImage.createGraphics();
|
||||||
|
|
||||||
|
if (smooth) {
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
}
|
||||||
|
|
||||||
|
g2d.drawImage(bimg, 0, 0, width, height, null);
|
||||||
|
g2d.dispose();
|
||||||
|
|
||||||
|
// Convert to JavaFX Image using the existing method
|
||||||
|
return toFXImage(scaledImage, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@ -94,10 +93,7 @@ public final class CacheFileTask extends FetchTask<Path> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
repository.cacheRemoteFile(connection, temp);
|
setResult(repository.cacheRemoteFile(connection, temp));
|
||||||
setResult(repository.getCachedRemoteFile(connection.getURL().toURI()));
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
Files.deleteIfExists(temp);
|
Files.deleteIfExists(temp);
|
||||||
|
@ -200,20 +200,20 @@ public class CacheRepository {
|
|||||||
// conn.setRequestProperty("If-Modified-Since", eTagItem.getRemoteLastModified());
|
// conn.setRequestProperty("If-Modified-Since", eTagItem.getRemoteLastModified());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cacheRemoteFile(URLConnection connection, Path downloaded) throws IOException {
|
public Path cacheRemoteFile(URLConnection connection, Path downloaded) throws IOException {
|
||||||
cacheData(connection, () -> {
|
return cacheData(connection, () -> {
|
||||||
String hash = DigestUtils.digestToString(SHA1, downloaded);
|
String hash = DigestUtils.digestToString(SHA1, downloaded);
|
||||||
Path cached = cacheFile(downloaded, SHA1, hash);
|
Path cached = cacheFile(downloaded, SHA1, hash);
|
||||||
return new CacheResult(hash, cached);
|
return new CacheResult(hash, cached);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cacheText(URLConnection connection, String text) throws IOException {
|
public Path cacheText(URLConnection connection, String text) throws IOException {
|
||||||
cacheBytes(connection, text.getBytes(UTF_8));
|
return cacheBytes(connection, text.getBytes(UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cacheBytes(URLConnection connection, byte[] bytes) throws IOException {
|
public Path cacheBytes(URLConnection connection, byte[] bytes) throws IOException {
|
||||||
cacheData(connection, () -> {
|
return cacheData(connection, () -> {
|
||||||
String hash = DigestUtils.digestToString(SHA1, bytes);
|
String hash = DigestUtils.digestToString(SHA1, bytes);
|
||||||
Path cached = getFile(SHA1, hash);
|
Path cached = getFile(SHA1, hash);
|
||||||
FileUtils.writeBytes(cached, bytes);
|
FileUtils.writeBytes(cached, bytes);
|
||||||
@ -221,9 +221,9 @@ public class CacheRepository {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cacheData(URLConnection connection, ExceptionalSupplier<CacheResult, IOException> cacheSupplier) throws IOException {
|
private Path cacheData(URLConnection connection, ExceptionalSupplier<CacheResult, IOException> cacheSupplier) throws IOException {
|
||||||
String eTag = connection.getHeaderField("ETag");
|
String eTag = connection.getHeaderField("ETag");
|
||||||
if (StringUtils.isBlank(eTag)) return;
|
if (StringUtils.isBlank(eTag)) return null;
|
||||||
URI uri;
|
URI uri;
|
||||||
try {
|
try {
|
||||||
uri = NetworkUtils.dropQuery(connection.getURL().toURI());
|
uri = NetworkUtils.dropQuery(connection.getURL().toURI());
|
||||||
@ -240,6 +240,7 @@ public class CacheRepository {
|
|||||||
} finally {
|
} finally {
|
||||||
lock.writeLock().unlock();
|
lock.writeLock().unlock();
|
||||||
}
|
}
|
||||||
|
return cacheResult.cachedFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class CacheResult {
|
private static final class CacheResult {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user