Merge commit '9bef8b432b9095bdb470f7a1e7c1ce8fcab37c01' into javafx

This commit is contained in:
huanghongxun 2021-08-28 16:41:29 +08:00
commit dc3caea2f4
8 changed files with 115 additions and 77 deletions

View File

@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> 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
@ -20,14 +20,13 @@ package org.jackhuang.hmcl.game;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.scene.image.Image;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.ServerResponseMalformedException;
import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount;
import org.jackhuang.hmcl.auth.yggdrasil.*;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.util.ResourceNotFoundError;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
@ -231,34 +230,18 @@ public final class TexturesLoader {
public static ObjectBinding<Image> fxAvatarBinding(YggdrasilService service, UUID uuid, int size) {
return BindingMapping.of(skinBinding(service, uuid))
.map(it -> toAvatar(it.image, size))
.map(TexturesLoader::toFXImage);
.map(FXUtils::toFXImage);
}
public static ObjectBinding<Image> fxAvatarBinding(Account account, int size) {
if (account instanceof YggdrasilAccount || account instanceof MicrosoftAccount) {
return BindingMapping.of(skinBinding(account))
.map(it -> toAvatar(it.image, size))
.map(TexturesLoader::toFXImage);
.map(FXUtils::toFXImage);
} else {
return Bindings.createObjectBinding(
() -> toFXImage(toAvatar(getDefaultSkin(TextureModel.detectUUID(account.getUUID())).image, size)));
() -> FXUtils.toFXImage(toAvatar(getDefaultSkin(TextureModel.detectUUID(account.getUUID())).image, size)));
}
}
// ====
// Based on https://stackoverflow.com/a/57552025
// Fix #874: Use it instead of SwingFXUtils.toFXImage
private static WritableImage toFXImage(BufferedImage image) {
WritableImage wr = new WritableImage(image.getWidth(), image.getHeight());
PixelWriter pw = wr.getPixelWriter();
final int iw = image.getWidth();
final int ih = image.getHeight();
for (int x = 0; x < iw; x++) {
for (int y = 0; y < ih; y++) {
pw.setArgb(x, y, image.getRGB(x, y));
}
}
return wr;
}
}

View File

@ -38,6 +38,8 @@ import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.TransferMode;
@ -55,6 +57,7 @@ import org.jackhuang.hmcl.util.javafx.ExtendedProperties;
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
@ -552,4 +555,20 @@ public final class FXUtils {
}
});
}
// Based on https://stackoverflow.com/a/57552025
// Fix #874: Use it instead of SwingFXUtils.toFXImage
public static WritableImage toFXImage(BufferedImage image) {
WritableImage wr = new WritableImage(image.getWidth(), image.getHeight());
PixelWriter pw = wr.getPixelWriter();
final int iw = image.getWidth();
final int ih = image.getHeight();
for (int x = 0; x < iw; x++) {
for (int y = 0; y < ih; y++) {
pw.setArgb(x, y, image.getRGB(x, y));
}
}
return wr;
}
}

View File

@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> 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
@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui.account;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.Tooltip;
@ -27,14 +28,19 @@ import javafx.scene.image.ImageView;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.auth.yggdrasil.TextureModel;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.game.TexturesLoader;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
import static javafx.beans.binding.Bindings.createStringBinding;
import static org.jackhuang.hmcl.setting.Accounts.getAccountFactory;
import static org.jackhuang.hmcl.setting.Accounts.getLocalizedLoginTypeName;
import static org.jackhuang.hmcl.ui.FXUtils.toFXImage;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class AccountAdvancedListItem extends AdvancedListItem {
@ -48,16 +54,18 @@ public class AccountAdvancedListItem extends AdvancedListItem {
Account account = get();
if (account == null) {
titleProperty().unbind();
subtitleProperty().unbind();
imageView.imageProperty().unbind();
tooltip.textProperty().unbind();
setTitle(i18n("account.missing"));
setSubtitle(i18n("account.missing.add"));
imageView.imageProperty().unbind();
imageView.setImage(newImage("/assets/img/steve.png"));
tooltip.setText("");
imageView.setImage(toFXImage(TexturesLoader.toAvatar(TexturesLoader.getDefaultSkin(TextureModel.STEVE).getImage(), 32)));
tooltip.setText(i18n("account.create"));
} else {
titleProperty().bind(Bindings.createStringBinding(account::getCharacter, account));
setSubtitle(accountSubtitle(account));
titleProperty().bind(BindingMapping.of(account, Account::getCharacter));
subtitleProperty().bind(accountSubtitle(account));
imageView.imageProperty().bind(TexturesLoader.fxAvatarBinding(account, 32));
tooltip.setText(account.getCharacter() + " " + accountTooltip(account));
tooltip.textProperty().bind(accountTooltip(account));
}
}
};
@ -88,23 +96,27 @@ public class AccountAdvancedListItem extends AdvancedListItem {
return account;
}
private static String accountSubtitle(Account account) {
String loginTypeName = Accounts.getLocalizedLoginTypeName(Accounts.getAccountFactory(account));
private static ObservableValue<String> accountSubtitle(Account account) {
if (account instanceof AuthlibInjectorAccount) {
return ((AuthlibInjectorAccount) account).getServer().getName();
return BindingMapping.of(((AuthlibInjectorAccount) account).getServer(), AuthlibInjectorServer::getName);
} else {
return loginTypeName;
return createStringBinding(() -> getLocalizedLoginTypeName(getAccountFactory(account)));
}
}
private static String accountTooltip(Account account) {
private static ObservableValue<String> accountTooltip(Account account) {
if (account instanceof AuthlibInjectorAccount) {
AuthlibInjectorServer server = ((AuthlibInjectorAccount) account).getServer();
return account.getUsername() + ", " + i18n("account.injector.server") + ": " + server.getName();
return Bindings.format("%s (%s) (%s)",
BindingMapping.of(account, Account::getCharacter),
account.getUsername(),
BindingMapping.of(server, AuthlibInjectorServer::getName));
} else if (account instanceof YggdrasilAccount) {
return account.getUsername();
return Bindings.format("%s (%s)",
BindingMapping.of(account, Account::getCharacter),
account.getUsername());
} else {
return "";
return BindingMapping.of(account, Account::getCharacter);
}
}

View File

@ -57,6 +57,7 @@ import static java.util.logging.Level.WARNING;
import static java.util.stream.Collectors.toList;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.io.FileUtils.getExtension;
@ -86,33 +87,39 @@ public class DecoratorController {
setupAuthlibInjectorDnD();
// pass key events to current dialog
// pass key events to current dialog / current page
decorator.addEventFilter(KeyEvent.ANY, e -> {
if (dialogPane == null || !dialogPane.peek().isPresent()) {
return;
}
Node currentDialog = dialogPane.peek().get();
if (!(e.getTarget() instanceof Node)) {
return;
return; // event source can't be determined
}
boolean targetInDialog = false;
Node newTarget;
if (dialogPane != null && dialogPane.peek().isPresent()) {
newTarget = dialogPane.peek().get(); // current dialog
} else {
newTarget = navigator.getCurrentPage(); // current page
}
boolean needsRedirect = true;
Node t = (Node) e.getTarget();
while (t != null) {
if (t == currentDialog) {
targetInDialog = true;
if (t == newTarget) {
// current event target is in newTarget
needsRedirect = false;
break;
}
t = t.getParent();
}
if (targetInDialog) {
if (!needsRedirect) {
return;
}
e.consume();
currentDialog.fireEvent(e.copyFor(e.getSource(), currentDialog));
newTarget.fireEvent(e.copyFor(e.getSource(), newTarget));
});
// press ESC to go back
onEscPressed(navigator, this::back);
}
public Decorator getDecorator() {
@ -229,10 +236,15 @@ public class DecoratorController {
if (navigator.getCurrentPage() instanceof DecoratorPage) {
DecoratorPage page = (DecoratorPage) navigator.getCurrentPage();
if (page.back())
navigator.close();
if (page.back()) {
if (navigator.canGoBack()) {
navigator.close();
}
}
} else {
navigator.close();
if (navigator.canGoBack()) {
navigator.close();
}
}
}

View File

@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> 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
@ -66,7 +66,8 @@ public class RootPage extends DecoratorTabPage {
public RootPage() {
setLeftPaneWidth(200);
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(event -> onRefreshedVersions((HMCLGameRepository) event.getSource()));
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class)
.register(event -> onRefreshedVersions((HMCLGameRepository) event.getSource()));
Profile profile = Profiles.getSelectedProfile();
if (profile != null && profile.getRepository().isLoaded())
@ -78,7 +79,8 @@ public class RootPage extends DecoratorTabPage {
@Override
public boolean back() {
if (mainTab.isSelected()) return true;
if (mainTab.isSelected())
return true;
else {
getSelectionModel().select(mainTab);
return false;
@ -102,20 +104,23 @@ public class RootPage extends DecoratorTabPage {
MainPage mainPage = new MainPage();
FXUtils.applyDragListener(mainPage, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
File modpack = modpacks.get(0);
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack), i18n("install.modpack"));
Controllers.getDecorator().startWizard(
new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack),
i18n("install.modpack"));
});
FXUtils.onChangeAndOperate(Profiles.selectedVersionProperty(), mainPage::setCurrentGame);
mainPage.showUpdateProperty().bind(UpdateChecker.outdatedProperty());
mainPage.latestVersionProperty().bind(
BindingMapping.of(UpdateChecker.latestVersionProperty())
.map(version -> version == null ? "" : i18n("update.bubble.title", version.getVersion())));
mainPage.latestVersionProperty().bind(BindingMapping.of(UpdateChecker.latestVersionProperty())
.map(version -> version == null ? "" : i18n("update.bubble.title", version.getVersion())));
Profiles.registerVersionsListener(profile -> {
HMCLGameRepository repository = profile.getRepository();
List<Version> children = repository.getVersions().parallelStream()
.filter(version -> !version.isHidden())
.sorted(Comparator.comparing((Version version) -> version.getReleaseTime() == null ? new Date(0L) : version.getReleaseTime())
.sorted(Comparator
.comparing((Version version) -> version.getReleaseTime() == null ? new Date(0L)
: version.getReleaseTime())
.thenComparing(a -> VersionNumber.asVersion(a.getId())))
.collect(Collectors.toList());
runInFX(() -> {
@ -135,7 +140,13 @@ public class RootPage extends DecoratorTabPage {
// first item in left sidebar
AccountAdvancedListItem accountListItem = new AccountAdvancedListItem();
accountListItem.setOnAction(e -> Controllers.navigate(Controllers.getAccountListPage()));
accountListItem.setOnAction(e -> {
Controllers.navigate(Controllers.getAccountListPage());
if (Accounts.getAccounts().isEmpty()) {
Controllers.dialog(new AddAccountPane());
}
});
accountListItem.accountProperty().bind(Accounts.selectedAccountProperty());
// second item in left sidebar
@ -158,20 +169,16 @@ public class RootPage extends DecoratorTabPage {
// fifth item in left sidebar
AdvancedListItem launcherSettingsItem = new AdvancedListItem();
launcherSettingsItem.setLeftGraphic(AdvancedListItem.createImageView(newImage("/assets/img/command.png")).getKey());
launcherSettingsItem
.setLeftGraphic(AdvancedListItem.createImageView(newImage("/assets/img/command.png")).getKey());
launcherSettingsItem.setActionButtonVisible(false);
launcherSettingsItem.setTitle(i18n("settings.launcher"));
launcherSettingsItem.setOnAction(e -> Controllers.navigate(Controllers.getSettingsPage()));
// the left sidebar
AdvancedListBox sideBar = new AdvancedListBox()
.startCategory(i18n("account").toUpperCase())
.add(accountListItem)
.startCategory(i18n("version").toUpperCase())
.add(gameListItem)
.add(gameItem)
.startCategory(i18n("launcher").toUpperCase())
.add(launcherSettingsItem);
AdvancedListBox sideBar = new AdvancedListBox().startCategory(i18n("account").toUpperCase())
.add(accountListItem).startCategory(i18n("version").toUpperCase()).add(gameListItem).add(gameItem)
.startCategory(i18n("launcher").toUpperCase()).add(launcherSettingsItem);
// the root page, with the sidebar in left, navigator in center.
BorderPane root = new BorderPane();
@ -195,7 +202,8 @@ public class RootPage extends DecoratorTabPage {
private boolean checkedAccont = false;
public void checkAccount() {
if (checkedAccont) return;
if (checkedAccont)
return;
checkedAccont = true;
if (Accounts.getAccounts().isEmpty())
Platform.runLater(this::addNewAccount);
@ -217,8 +225,11 @@ public class RootPage extends DecoratorTabPage {
File modpackFile = new File("modpack.zip").getAbsoluteFile();
if (modpackFile.exists()) {
Task.supplyAsync(() -> CompressingUtils.findSuitableEncoding(modpackFile.toPath()))
.thenApplyAsync(encoding -> ModpackHelper.readModpackManifest(modpackFile.toPath(), encoding))
.thenApplyAsync(modpack -> ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
.thenApplyAsync(
encoding -> ModpackHelper.readModpackManifest(modpackFile.toPath(), encoding))
.thenApplyAsync(modpack -> ModpackHelper
.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(),
modpack)
.withRunAsync(Schedulers.javafx(), this::checkAccount).executor())
.thenAcceptAsync(Schedulers.javafx(), executor -> {
Controllers.taskDialog(executor, i18n("modpack.installing"));

View File

@ -88,7 +88,7 @@ public final class SelfDependencyPatcher {
static class DependencyDescriptor {
private static final String REPOSITORY_URL = "https://maven.aliyun.com/repository/central/";
private static final String REPOSITORY_URL = System.getProperty("hmcl.openjfx.repo", "https://maven.aliyun.com/repository/central/");
private static final Path DEPENDENCIES_DIR_PATH = HMCL_DIRECTORY.resolve("dependencies");
private static String currentArchClassifier() {

View File

@ -55,7 +55,7 @@ public class LaunchOptions implements Serializable {
private String preLaunchCommand;
private NativesDirectoryType nativesDirType;
private String nativesDir;
private ProcessPriority processPriority;
private ProcessPriority processPriority = ProcessPriority.NORMAL;
/**
* The game directory
@ -497,7 +497,7 @@ public class LaunchOptions implements Serializable {
return this;
}
public Builder setProcessPriority(ProcessPriority processPriority) {
public Builder setProcessPriority(@NotNull ProcessPriority processPriority) {
options.processPriority = processPriority;
return this;
}

View File

@ -53,3 +53,4 @@ Make sure you have Java installed with JavaFX 8 at least. Liberica full JDK 8~16
|`-Dhmcl.version.override=<version>`|Override the version number.|
|`-Dhmcl.update_source.override=<url>`|Override the update source.|
|`-Dhmcl.authlibinjector.location=<path>`|Use specified authlib-injector (instead of downloading one).|
|`-Dhmcl.openjfx.repo=<maven repository url>`|Download OpenJFX from specified Maven repository. Default value is `https://maven.aliyun.com/repository/central/`.|