diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java index 3f8969aa4..3927f2be9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java @@ -1,6 +1,6 @@ /* * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors + * Copyright (C) 2021 huangyuhui 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 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 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; - } } 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 691cd52dc..8ddc24616 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -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; + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountAdvancedListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountAdvancedListItem.java index b295f6d94..0e0252e18 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountAdvancedListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountAdvancedListItem.java @@ -1,6 +1,6 @@ /* * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors + * Copyright (C) 2021 huangyuhui 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 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 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); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java index 8e3e8dc6f..9bf5e77d9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java @@ -1,6 +1,6 @@ /* * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors + * Copyright (C) 2021 huangyuhui 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 @@ -157,11 +157,16 @@ public class RootPage extends DecoratorTabPage { return accountTab; } - private void selectPage(Tab tab) { + /** + * @return true if the tab is being opened, or false if the tab is being closed + */ + private boolean selectPage(Tab tab) { if (getSelectionModel().getSelectedItem() == tab) { getSelectionModel().select(getMainTab()); + return false; } else { getSelectionModel().select(tab); + return true; } } @@ -173,7 +178,14 @@ public class RootPage extends DecoratorTabPage { // first item in left sidebar AccountAdvancedListItem accountListItem = new AccountAdvancedListItem(); accountListItem.activeProperty().bind(control.accountTab.selectedProperty()); - accountListItem.setOnAction(e -> control.selectPage(control.accountTab)); + accountListItem.setOnAction(e -> { + if (control.selectPage(control.accountTab)) { + // open create account dialog if no account exists + if (Accounts.getAccounts().isEmpty()) { + Controllers.dialog(new AddAccountPane()); + } + } + }); accountListItem.accountProperty().bind(Accounts.selectedAccountProperty()); // second item in left sidebar