diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/AccountHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/AccountHelper.java deleted file mode 100644 index c13d65e4b..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/AccountHelper.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2019 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.jackhuang.hmcl.game; - -import javafx.embed.swing.SwingFXUtils; -import javafx.scene.image.Image; -import org.jackhuang.hmcl.Metadata; -import org.jackhuang.hmcl.auth.Account; -import org.jackhuang.hmcl.auth.yggdrasil.GameProfile; -import org.jackhuang.hmcl.auth.yggdrasil.Texture; -import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; -import org.jackhuang.hmcl.setting.Accounts; -import org.jackhuang.hmcl.task.FileDownloadTask; -import org.jackhuang.hmcl.task.Scheduler; -import org.jackhuang.hmcl.task.Schedulers; -import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.ui.DialogController; -import org.jackhuang.hmcl.util.io.NetworkUtils; - -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; -import java.io.File; -import java.util.*; - -public final class AccountHelper { - - private AccountHelper() {} - - public static final File SKIN_DIR = Metadata.HMCL_DIRECTORY.resolve("skins").toFile(); - - public static void loadSkins() { - for (Account account : Accounts.getAccounts()) { - if (account instanceof YggdrasilAccount) { - new SkinLoadTask((YggdrasilAccount) account, false).start(); - } - } - } - - public static Task loadSkinAsync(YggdrasilAccount account) { - return new SkinLoadTask(account, false); - } - - public static Task refreshSkinAsync(YggdrasilAccount account) { - return new SkinLoadTask(account, true); - } - - private static File getSkinFile(UUID uuid) { - return new File(SKIN_DIR, uuid + ".png"); - } - - public static Image getSkin(YggdrasilAccount account) { - return getSkin(account, 1); - } - - public static Image getSkin(YggdrasilAccount account, double scaleRatio) { - UUID uuid = account.getUUID(); - if (uuid == null) - return getSteveSkin(scaleRatio); - - File file = getSkinFile(uuid); - if (file.exists()) { - Image original = new Image("file:" + file.getAbsolutePath()); - if (original.isError()) - return getDefaultSkin(uuid, scaleRatio); - - return new Image("file:" + file.getAbsolutePath(), - original.getWidth() * scaleRatio, - original.getHeight() * scaleRatio, - false, false); - } - return getDefaultSkin(uuid, scaleRatio); - } - - public static Image getSkinImmediately(YggdrasilAccount account, GameProfile profile, double scaleRatio) throws Exception { - File file = getSkinFile(profile.getId()); - downloadSkin(account, profile, true); - if (!file.exists()) - return getDefaultSkin(profile.getId(), scaleRatio); - - String url = "file:" + file.getAbsolutePath(); - return scale(url, scaleRatio); - } - - public static Image getHead(Image skin, int scaleRatio) { - final int size = 8 * scaleRatio; - final int faceOffset = (int) Math.round(scaleRatio * 4d / 9d); - BufferedImage image = SwingFXUtils.fromFXImage(skin, null); - BufferedImage head = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); - Graphics2D g2d = head.createGraphics(); - g2d.drawImage(image, faceOffset, faceOffset, size - faceOffset, size - faceOffset, - size, size, size + size, size + size, null); - if (image.getHeight() > 32) { - g2d.drawImage(image, 0, 0, size, size, - 40 * scaleRatio, 8 * scaleRatio, 48 * scaleRatio, 16 * scaleRatio, null); - } - return SwingFXUtils.toFXImage(head, null); - } - - private static class SkinLoadTask extends Task { - private final YggdrasilAccount account; - private final boolean refresh; - private final List dependencies = new LinkedList<>(); - - public SkinLoadTask(YggdrasilAccount account, boolean refresh) { - this.account = account; - this.refresh = refresh; - } - - @Override - public Scheduler getScheduler() { - return Schedulers.io(); - } - - @Override - public Collection getDependencies() { - return dependencies; - } - - @Override - public void execute() throws Exception { - if (!account.isLoggedIn() && (account.getCharacter() == null || refresh)) - DialogController.logIn(account); - - downloadSkin(account, refresh); - } - } - - private static void downloadSkin(YggdrasilAccount account, GameProfile profile, boolean refresh) throws Exception { - account.clearCache(); - - File file = getSkinFile(profile.getId()); - if (!refresh && file.exists()) - return; - Optional texture = account.getSkin(profile); - if (!texture.isPresent()) return; - String url = texture.get().getUrl(); - new FileDownloadTask(NetworkUtils.toURL(url), file).run(); - } - - private static void downloadSkin(YggdrasilAccount account, boolean refresh) throws Exception { - account.clearCache(); - - if (account.getCharacter() == null) return; - File file = getSkinFile(account.getUUID()); - if (!refresh && file.exists()) { - Image original = new Image("file:" + file.getAbsolutePath()); - if (!original.isError()) - return; - } - Optional texture = account.getSkin(); - if (!texture.isPresent()) return; - String url = texture.get().getUrl(); - new FileDownloadTask(NetworkUtils.toURL(url), file).run(); - } - - public static Image scale(String url, double scaleRatio) { - Image origin = new Image(url); - return new Image(url, - origin.getWidth() * scaleRatio, - origin.getHeight() * scaleRatio, - false, false); - } - - public static Image getSteveSkin(double scaleRatio) { - return scale("/assets/img/steve.png", scaleRatio); - } - - public static Image getAlexSkin(double scaleRatio) { - return scale("/assets/img/alex.png", scaleRatio); - } - - public static Image getDefaultSkin(UUID uuid, double scaleRatio) { - int type = uuid.hashCode() & 1; - if (type == 1) - return getAlexSkin(scaleRatio); - else - return getSteveSkin(scaleRatio); - } -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java index 4c0146bcf..563aba480 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java @@ -80,13 +80,6 @@ public class HMCLGameRepository extends DefaultGameRepository { } } - @Override - public void refreshVersions() { - EventBus.EVENT_BUS.fireEvent(new RefreshingVersionsEvent(this)); - refreshVersionsImpl(); - EventBus.EVENT_BUS.fireEvent(new RefreshedVersionsEvent(this)); - } - public void changeDirectory(File newDirectory) { setBaseDirectory(newDirectory); refreshVersionsAsync().start(); @@ -168,6 +161,8 @@ public class HMCLGameRepository extends DefaultGameRepository { return new Image("file:" + iconFile.getAbsolutePath()); else if ("net.minecraft.launchwrapper.Launch".equals(version.getMainClass())) return new Image("/assets/img/furnace.png"); + else if ("cpw.mods.modlauncher.Launcher".equals(version.getMainClass())) + return new Image("/assets/img/furnace.png"); else return new Image("/assets/img/grass.png"); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java new file mode 100644 index 000000000..f1d967a40 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java @@ -0,0 +1,209 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2019 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.game; + +import static java.util.Collections.singletonMap; +import static org.jackhuang.hmcl.util.Lang.threadPool; +import static org.jackhuang.hmcl.util.Logging.LOG; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.EnumMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; + +import javax.imageio.ImageIO; + +import org.jackhuang.hmcl.Metadata; +import org.jackhuang.hmcl.auth.Account; +import org.jackhuang.hmcl.auth.ServerResponseMalformedException; +import org.jackhuang.hmcl.auth.yggdrasil.Texture; +import org.jackhuang.hmcl.auth.yggdrasil.TextureModel; +import org.jackhuang.hmcl.auth.yggdrasil.TextureType; +import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; +import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService; +import org.jackhuang.hmcl.task.FileDownloadTask; +import org.jackhuang.hmcl.util.javafx.MultiStepBinding; + +import javafx.beans.binding.Bindings; +import javafx.beans.binding.ObjectBinding; +import javafx.embed.swing.SwingFXUtils; +import javafx.scene.image.Image; + +/** + * @author yushijinhun + */ +public final class TexturesLoader { + + private TexturesLoader() { + } + + // ==== Texture Loading ==== + public static class LoadedTexture { + private final BufferedImage image; + private final Map metadata; + + public LoadedTexture(BufferedImage image, Map metadata) { + this.image = image; + this.metadata = metadata; + } + + public BufferedImage getImage() { + return image; + } + + public Map getMetadata() { + return metadata; + } + } + + private static final ThreadPoolExecutor POOL = threadPool("TexturesDownload", true, 2, 10, TimeUnit.SECONDS); + private static final Path TEXTURES_DIR = Metadata.MINECRAFT_DIRECTORY.resolve("assets").resolve("skins"); + + private static Path getTexturePath(Texture texture) { + String url = texture.getUrl(); + int slash = url.lastIndexOf('/'); + int dot = url.lastIndexOf('.'); + if (dot < slash) { + dot = url.length(); + } + String hash = url.substring(slash + 1, dot); + String prefix = hash.length() > 2 ? hash.substring(0, 2) : "xx"; + return TEXTURES_DIR.resolve(prefix).resolve(hash); + } + + public static LoadedTexture loadTexture(Texture texture) throws IOException { + Path file = getTexturePath(texture); + if (!Files.isRegularFile(file)) { + // download it + try { + new FileDownloadTask(new URL(texture.getUrl()), file.toFile()).run(); + LOG.info("Texture downloaded: " + texture.getUrl()); + } catch (Exception e) { + if (Files.isRegularFile(file)) { + // concurrency conflict? + LOG.log(Level.WARNING, "Failed to download texture " + texture.getUrl() + ", but the file is available", e); + } else { + throw new IOException("Failed to download texture " + texture.getUrl()); + } + } + } + + BufferedImage img; + try (InputStream in = Files.newInputStream(file)) { + img = ImageIO.read(in); + } + return new LoadedTexture(img, texture.getMetadata()); + } + // ==== + + // ==== Skins ==== + private final static Map DEFAULT_SKINS = new EnumMap<>(TextureModel.class); + static { + loadDefaultSkin("/assets/img/steve.png", TextureModel.STEVE); + loadDefaultSkin("/assets/img/alex.png", TextureModel.ALEX); + } + private static void loadDefaultSkin(String path, TextureModel model) { + try (InputStream in = TexturesLoader.class.getResourceAsStream(path)) { + DEFAULT_SKINS.put(model, new LoadedTexture(ImageIO.read(in), singletonMap("model", model.modelName))); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public static LoadedTexture getDefaultSkin(TextureModel model) { + return DEFAULT_SKINS.get(model); + } + + public static ObjectBinding skinBinding(YggdrasilService service, UUID uuid) { + LoadedTexture uuidFallback = getDefaultSkin(TextureModel.detectUUID(uuid)); + return MultiStepBinding.of(service.getProfileRepository().binding(uuid)) + .map(profile -> profile + .flatMap(it -> { + try { + return YggdrasilService.getTextures(it); + } catch (ServerResponseMalformedException e) { + LOG.log(Level.WARNING, "Failed to parse texture payload", e); + return Optional.empty(); + } + }) + .flatMap(it -> Optional.ofNullable(it.get(TextureType.SKIN)))) + .asyncMap(it -> { + if (it.isPresent()) { + Texture texture = it.get(); + try { + return loadTexture(texture); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to load texture " + texture.getUrl() + ", using fallback texture", e); + return getDefaultSkin(TextureModel.detectModelName(texture.getMetadata())); + } + } else { + return uuidFallback; + } + }, uuidFallback, POOL); + } + // ==== + + // ==== Avatar ==== + public static BufferedImage toAvatar(BufferedImage skin, int size) { + BufferedImage avatar = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = avatar.createGraphics(); + + int scale = skin.getWidth() / 64; + int faceOffset = (int) Math.round(size / 18.0); + g.drawImage(skin, + faceOffset, faceOffset, size - faceOffset, size - faceOffset, + 8 * scale, 8 * scale, 16 * scale, 16 * scale, + null); + + if (skin.getWidth() == skin.getHeight()) { + g.drawImage(skin, + 0, 0, size, size, + 40 * scale, 8 * scale, 48 * scale, 16 * scale, null); + } + + g.dispose(); + return avatar; + } + + public static ObjectBinding fxAvatarBinding(YggdrasilService service, UUID uuid, int size) { + return MultiStepBinding.of(skinBinding(service, uuid)) + .map(it -> toAvatar(it.image, size)) + .map(img -> SwingFXUtils.toFXImage(img, null)); + } + + public static ObjectBinding fxAvatarBinding(Account account, int size) { + if (account instanceof YggdrasilAccount) { + return fxAvatarBinding(((YggdrasilAccount) account).getYggdrasilService(), account.getUUID(), size); + } else { + return Bindings.createObjectBinding( + () -> SwingFXUtils.toFXImage(toAvatar(getDefaultSkin(TextureModel.detectUUID(account.getUUID())).image, size), null)); + } + } + // ==== +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java index dc5ee4405..c2097f290 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java @@ -35,7 +35,6 @@ import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; import org.jackhuang.hmcl.auth.authlibinjector.SimpleAuthlibInjectorArtifactProvider; import org.jackhuang.hmcl.auth.offline.OfflineAccount; import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory; -import org.jackhuang.hmcl.auth.yggdrasil.MojangYggdrasilProvider; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory; import org.jackhuang.hmcl.task.Schedulers; @@ -63,7 +62,7 @@ public final class Accounts { private Accounts() {} public static final OfflineAccountFactory FACTORY_OFFLINE = OfflineAccountFactory.INSTANCE; - public static final YggdrasilAccountFactory FACTORY_YGGDRASIL = new YggdrasilAccountFactory(MojangYggdrasilProvider.INSTANCE); + public static final YggdrasilAccountFactory FACTORY_MOJANG = YggdrasilAccountFactory.MOJANG; public static final AuthlibInjectorAccountFactory FACTORY_AUTHLIB_INJECTOR = new AuthlibInjectorAccountFactory(createAuthlibInjectorArtifactProvider(), Accounts::getOrCreateAuthlibInjectorServer); // ==== login type / account factory mapping ==== @@ -71,7 +70,7 @@ public final class Accounts { private static final Map, String> factory2type = new HashMap<>(); static { type2factory.put("offline", FACTORY_OFFLINE); - type2factory.put("yggdrasil", FACTORY_YGGDRASIL); + type2factory.put("yggdrasil", FACTORY_MOJANG); type2factory.put("authlibInjector", FACTORY_AUTHLIB_INJECTOR); type2factory.forEach((type, factory) -> factory2type.put(factory, type)); @@ -94,7 +93,7 @@ public final class Accounts { else if (account instanceof AuthlibInjectorAccount) return FACTORY_AUTHLIB_INJECTOR; else if (account instanceof YggdrasilAccount) - return FACTORY_YGGDRASIL; + return FACTORY_MOJANG; else throw new IllegalArgumentException("Failed to determine account type: " + account); } @@ -279,7 +278,7 @@ public final class Accounts { // ==== Login type name i18n === private static Map, String> unlocalizedLoginTypeNames = mapOf( pair(Accounts.FACTORY_OFFLINE, "account.methods.offline"), - pair(Accounts.FACTORY_YGGDRASIL, "account.methods.yggdrasil"), + pair(Accounts.FACTORY_MOJANG, "account.methods.yggdrasil"), pair(Accounts.FACTORY_AUTHLIB_INJECTOR, "account.methods.authlib_injector")); public static String getLocalizedLoginTypeName(AccountFactory factory) { 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 dd108dfcd..614ee0295 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -234,8 +234,20 @@ public final class FXUtils { } } - public static void installTooltip(Node node, String tooltip) { - installTooltip(node, 0, 5000, 0, new Tooltip(tooltip)); + public static void installFastTooltip(Node node, Tooltip tooltip) { + installTooltip(node, 50, 5000, 0, tooltip); + } + + public static void installFastTooltip(Node node, String tooltip) { + installFastTooltip(node, new Tooltip(tooltip)); + } + + public static void installSlowTooltip(Node node, Tooltip tooltip) { + installTooltip(node, 500, 5000, 0, tooltip); + } + + public static void installSlowTooltip(Node node, String tooltip) { + installSlowTooltip(node, new Tooltip(tooltip)); } public static void installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java index 1e8cc6897..887afa40c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ListPageSkin.java @@ -19,12 +19,13 @@ package org.jackhuang.hmcl.ui; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXScrollPane; - import javafx.beans.binding.Bindings; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.ScrollPane; import javafx.scene.control.SkinBase; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import org.jackhuang.hmcl.setting.Theme; @@ -36,6 +37,7 @@ public class ListPageSkin extends SkinBase> { super(skinnable); SpinnerPane spinnerPane = new SpinnerPane(); + Pane placeholder = new Pane(); StackPane contentPane = new StackPane(); { @@ -48,9 +50,12 @@ public class ListPageSkin extends SkinBase> { list.setSpacing(10); list.setPadding(new Insets(10)); + VBox content = new VBox(); + content.getChildren().setAll(list, placeholder); + Bindings.bindContent(list.getChildren(), skinnable.itemsProperty()); - scrollPane.setContent(list); + scrollPane.setContent(content); JFXScrollPane.smoothScrolling(scrollPane); } @@ -86,7 +91,13 @@ public class ListPageSkin extends SkinBase> { }); } - contentPane.getChildren().setAll(scrollPane, vBox); + // Keep a blank space to prevent buttons from blocking up mod items. + BorderPane group = new BorderPane(); + group.setPickOnBounds(false); + group.setBottom(vBox); + placeholder.minHeightProperty().bind(vBox.heightProperty()); + + contentPane.getChildren().setAll(scrollPane, group); } spinnerPane.loadingProperty().bind(skinnable.loadingProperty()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/MainPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/MainPage.java index 378e0db7c..6b6d65a54 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/MainPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/MainPage.java @@ -87,11 +87,8 @@ public final class MainPage extends StackPane implements DecoratorPage { lblIcon.setGraphic(SVG.update(Theme.whiteFillBinding(), 20, 20)); TwoLineListItem prompt = new TwoLineListItem(); - prompt.setTitleFill(Color.WHITE); - prompt.setSubtitleFill(Color.WHITE); prompt.setSubtitle(i18n("update.bubble.subtitle")); prompt.setPickOnBounds(false); - prompt.setStyle("-jfx-title-font-weight: BOLD;"); prompt.titleProperty().bind(latestVersionProperty()); hBox.getChildren().setAll(lblIcon, prompt); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java index 079ae3266..75bf402b5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java @@ -129,7 +129,7 @@ public final class SettingsPage extends SettingsView implements DecoratorPage { config().commonDirectoryProperty(), config().commonDirTypeProperty())); // ==== Update ==== - FXUtils.installTooltip(btnUpdate, i18n("update.tooltip")); + FXUtils.installFastTooltip(btnUpdate, i18n("update.tooltip")); updateListener = any -> { btnUpdate.setVisible(UpdateChecker.isOutdated()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsView.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsView.java index 566880966..07638486c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsView.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsView.java @@ -359,7 +359,7 @@ public abstract class SettingsView extends StackPane { HBox hBox = new HBox(); hBox.setSpacing(3); - cboFont = new FontComboBox(12, false); + cboFont = new FontComboBox(12); txtFontSize = new JFXTextField(); FXUtils.setLimitWidth(txtFontSize, 50); hBox.getChildren().setAll(cboFont, txtFontSize); 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 d521df319..3aca65468 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 @@ -17,15 +17,15 @@ */ package org.jackhuang.hmcl.ui.account; +import javafx.beans.binding.Bindings; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.scene.image.Image; import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.offline.OfflineAccount; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; -import org.jackhuang.hmcl.game.AccountHelper; +import org.jackhuang.hmcl.game.TexturesLoader; import org.jackhuang.hmcl.setting.Theme; -import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.construct.AdvancedListItem; @@ -38,23 +38,15 @@ public class AccountAdvancedListItem extends AdvancedListItem { protected void invalidated() { Account account = get(); if (account == null) { + titleProperty().unbind(); setTitle(i18n("account.missing")); setSubtitle(i18n("account.missing.add")); + imageProperty().unbind(); setImage(new Image("/assets/img/craft_table.png")); } else { - setTitle(account.getCharacter()); + titleProperty().bind(Bindings.createStringBinding(account::getCharacter, account)); setSubtitle(accountSubtitle(account)); - - final int scaleRatio = 4; - Image defaultSkin = AccountHelper.getDefaultSkin(account.getUUID(), scaleRatio); - setImage(AccountHelper.getHead(defaultSkin, scaleRatio)); - - if (account instanceof YggdrasilAccount) { - AccountHelper.loadSkinAsync((YggdrasilAccount) account).subscribe(Schedulers.javafx(), () -> { - Image image = AccountHelper.getSkin((YggdrasilAccount) account, scaleRatio); - setImage(AccountHelper.getHead(image, scaleRatio)); - }); - } + imageProperty().bind(TexturesLoader.fxAvatarBinding(account, 32)); } } }; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java index 867fe8e56..1d1e92fef 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java @@ -17,21 +17,28 @@ */ package org.jackhuang.hmcl.ui.account; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.StringBinding; import javafx.beans.property.*; import javafx.scene.control.RadioButton; import javafx.scene.control.Skin; import javafx.scene.image.Image; import org.jackhuang.hmcl.auth.Account; +import org.jackhuang.hmcl.auth.AuthenticationException; +import org.jackhuang.hmcl.auth.CredentialExpiredException; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; import org.jackhuang.hmcl.auth.offline.OfflineAccount; -import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; -import org.jackhuang.hmcl.game.AccountHelper; +import org.jackhuang.hmcl.game.TexturesLoader; import org.jackhuang.hmcl.setting.Accounts; -import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.ui.DialogController; +import static org.jackhuang.hmcl.util.Lang.thread; +import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import java.util.logging.Level; + public class AccountListItem extends RadioButton { private final Account account; @@ -44,23 +51,24 @@ public class AccountListItem extends RadioButton { getStyleClass().clear(); setUserData(account); - StringBuilder subtitleString = new StringBuilder(Accounts.getLocalizedLoginTypeName(Accounts.getAccountFactory(account))); + String loginTypeName = Accounts.getLocalizedLoginTypeName(Accounts.getAccountFactory(account)); if (account instanceof AuthlibInjectorAccount) { AuthlibInjectorServer server = ((AuthlibInjectorAccount) account).getServer(); - subtitleString.append(", ").append(i18n("account.injector.server")).append(": ").append(server.getName()); + subtitle.bind(Bindings.concat( + loginTypeName, ", ", i18n("account.injector.server"), ": ", + Bindings.createStringBinding(server::getName, server))); + } else { + subtitle.set(loginTypeName); } - if (account instanceof OfflineAccount) - title.set(account.getCharacter()); - else - title.set(account.getUsername() + " - " + account.getCharacter()); - subtitle.set(subtitleString.toString()); + StringBinding characterName = Bindings.createStringBinding(account::getCharacter, account); + if (account instanceof OfflineAccount) { + title.bind(characterName); + } else { + title.bind(Bindings.concat(account.getUsername(), " - ", characterName)); + } - final int scaleRatio = 4; - Image image = account instanceof YggdrasilAccount ? - AccountHelper.getSkin((YggdrasilAccount) account, scaleRatio) : - AccountHelper.getDefaultSkin(account.getUUID(), scaleRatio); - this.image.set(AccountHelper.getHead(image, scaleRatio)); + image.bind(TexturesLoader.fxAvatarBinding(account, 32)); } @Override @@ -69,19 +77,20 @@ public class AccountListItem extends RadioButton { } public void refresh() { - if (account instanceof YggdrasilAccount) { - // progressBar.setVisible(true); - AccountHelper.refreshSkinAsync((YggdrasilAccount) account) - .finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> { - // progressBar.setVisible(false); - - if (isDependentsSucceeded) { - final int scaleRatio = 4; - Image image = AccountHelper.getSkin((YggdrasilAccount) account, scaleRatio); - this.image.set(AccountHelper.getHead(image, scaleRatio)); - } - }).start(); - } + account.clearCache(); + thread(() -> { + try { + account.logIn(); + } catch (CredentialExpiredException e) { + try { + DialogController.logIn(account); + } catch (Exception e1) { + LOG.log(Level.WARNING, "Failed to refresh " + account + " with password", e1); + } + } catch (AuthenticationException e) { + LOG.log(Level.WARNING, "Failed to refresh " + account + " with token", e); + } + }); } public void remove() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java index 2ff6b2ace..0eca64e0e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItemSkin.java @@ -21,18 +21,26 @@ import com.jfoenix.concurrency.JFXUtilities; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXRadioButton; import com.jfoenix.effects.JFXDepthManager; + +import javafx.beans.binding.Bindings; import javafx.geometry.Pos; +import javafx.scene.control.Label; import javafx.scene.control.SkinBase; +import javafx.scene.control.Tooltip; import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; + import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; -import org.jackhuang.hmcl.ui.construct.TwoLineListItem; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; +import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; + public class AccountListItemSkin extends SkinBase { public AccountListItemSkin(AccountListItem skinnable) { @@ -58,8 +66,22 @@ public class AccountListItemSkin extends SkinBase { FXUtils.limitSize(imageView, 32, 32); imageView.imageProperty().bind(skinnable.imageProperty()); - TwoLineListItem item = new TwoLineListItem(); + Label title = new Label(); + title.getStyleClass().add("title"); + title.textProperty().bind(skinnable.titleProperty()); + Label subtitle = new Label(); + subtitle.getStyleClass().add("subtitle"); + subtitle.textProperty().bind(skinnable.subtitleProperty()); + if (skinnable.getAccount() instanceof AuthlibInjectorAccount) { + Tooltip tooltip = new Tooltip(); + AuthlibInjectorServer server = ((AuthlibInjectorAccount) skinnable.getAccount()).getServer(); + tooltip.textProperty().bind(Bindings.createStringBinding(server::toString, server)); + FXUtils.installSlowTooltip(subtitle, tooltip); + } + VBox item = new VBox(title, subtitle); + item.getStyleClass().add("two-line-list-item"); BorderPane.setAlignment(item, Pos.CENTER); + center.getChildren().setAll(imageView, item); root.setCenter(center); @@ -69,7 +91,7 @@ public class AccountListItemSkin extends SkinBase { btnRefresh.setOnMouseClicked(e -> skinnable.refresh()); btnRefresh.getStyleClass().add("toggle-icon4"); btnRefresh.setGraphic(SVG.refresh(Theme.blackFillBinding(), -1, -1)); - JFXUtilities.runInFX(() -> FXUtils.installTooltip(btnRefresh, i18n("button.refresh"))); + JFXUtilities.runInFX(() -> FXUtils.installFastTooltip(btnRefresh, i18n("button.refresh"))); right.getChildren().add(btnRefresh); JFXButton btnRemove = new JFXButton(); @@ -77,14 +99,12 @@ public class AccountListItemSkin extends SkinBase { btnRemove.getStyleClass().add("toggle-icon4"); BorderPane.setAlignment(btnRemove, Pos.CENTER); btnRemove.setGraphic(SVG.delete(Theme.blackFillBinding(), -1, -1)); - JFXUtilities.runInFX(() -> FXUtils.installTooltip(btnRemove, i18n("button.delete"))); + JFXUtilities.runInFX(() -> FXUtils.installFastTooltip(btnRemove, i18n("button.delete"))); right.getChildren().add(btnRemove); root.setRight(right); root.setStyle("-fx-background-color: white; -fx-padding: 8 8 8 0;"); JFXDepthManager.setDepth(root, 1); - item.titleProperty().bind(skinnable.titleProperty()); - item.subtitleProperty().bind(skinnable.subtitleProperty()); getChildren().setAll(root); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountLoginPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountLoginPane.java index 95ca81e8a..3a758bed5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountLoginPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountLoginPane.java @@ -37,8 +37,7 @@ public class AccountLoginPane extends StackPane { private final Consumer success; private final Runnable failed; - @FXML - private Label lblUsername; + @FXML private Label lblUsername; @FXML private JFXPasswordField txtPassword; @FXML private Label lblCreationWarning; @FXML private JFXProgressBar progressBar; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAccountPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAccountPane.java index 0e117dc90..dacc36120 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAccountPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAccountPane.java @@ -19,13 +19,17 @@ package org.jackhuang.hmcl.ui.account; import com.jfoenix.concurrency.JFXUtilities; import com.jfoenix.controls.*; + +import javafx.application.Platform; import javafx.beans.binding.Bindings; +import javafx.beans.property.ListProperty; import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.collections.FXCollections; import javafx.fxml.FXML; import javafx.geometry.Pos; import javafx.scene.control.Hyperlink; import javafx.scene.control.Label; -import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; @@ -35,20 +39,22 @@ import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloadException; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; import org.jackhuang.hmcl.auth.yggdrasil.GameProfile; import org.jackhuang.hmcl.auth.yggdrasil.RemoteAuthenticationException; -import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; -import org.jackhuang.hmcl.game.AccountHelper; +import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService; +import org.jackhuang.hmcl.game.TexturesLoader; import org.jackhuang.hmcl.setting.Accounts; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.construct.*; -import org.jackhuang.hmcl.util.Logging; +import org.jackhuang.hmcl.util.javafx.MultiStepBinding; +import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.concurrent.CountDownLatch; -import java.util.logging.Level; - +import static java.util.Collections.emptyList; +import static java.util.Collections.unmodifiableList; import static java.util.Objects.requireNonNull; import static org.jackhuang.hmcl.setting.ConfigHolder.config; import static org.jackhuang.hmcl.ui.FXUtils.*; @@ -63,12 +69,13 @@ public class AddAccountPane extends StackPane { @FXML private JFXComboBox> cboType; @FXML private JFXComboBox cboServers; @FXML private Label lblInjectorServer; - @FXML private Hyperlink linkManageInjectorServers; - @FXML private JFXDialogLayout layout; @FXML private JFXButton btnAccept; @FXML private JFXButton btnAddServer; @FXML private JFXButton btnManageServer; @FXML private SpinnerPane acceptPane; + @FXML private HBox linksContainer; + + private ListProperty links = new SimpleListProperty<>();; public AddAccountPane() { FXUtils.loadFXML(this, "/assets/fxml/account-add.fxml"); @@ -79,7 +86,7 @@ public class AddAccountPane extends StackPane { cboServers.getItems().addListener(onInvalidating(this::selectDefaultServer)); selectDefaultServer(); - cboType.getItems().setAll(Accounts.FACTORY_OFFLINE, Accounts.FACTORY_YGGDRASIL, Accounts.FACTORY_AUTHLIB_INJECTOR); + cboType.getItems().setAll(Accounts.FACTORY_OFFLINE, Accounts.FACTORY_MOJANG, Accounts.FACTORY_AUTHLIB_INJECTOR); cboType.setConverter(stringConverter(Accounts::getLocalizedLoginTypeName)); // try selecting the preferred login type cboType.getSelectionModel().select( @@ -117,6 +124,34 @@ public class AddAccountPane extends StackPane { txtUsername.textProperty(), txtPassword.textProperty(), txtPassword.visibleProperty(), cboServers.getSelectionModel().selectedItemProperty(), cboServers.visibleProperty())); + + // authlib-injector links + links.bind(MultiStepBinding.of(cboServers.getSelectionModel().selectedItemProperty()) + .map(AddAccountPane::createHyperlinks) + .map(FXCollections::observableList)); + Bindings.bindContent(linksContainer.getChildren(), links); + linksContainer.visibleProperty().bind(cboServers.visibleProperty()); + } + + private static final String[] ALLOWED_LINKS = { "register" }; + + public static List createHyperlinks(AuthlibInjectorServer server) { + if (server == null) { + return emptyList(); + } + + Map links = server.getLinks(); + List result = new ArrayList<>(); + for (String key : ALLOWED_LINKS) { + String value = links.get(key); + if (value != null) { + Hyperlink link = new Hyperlink(i18n("account.injector.link." + key)); + FXUtils.installSlowTooltip(link, value); + link.setOnAction(e -> FXUtils.openLink(value)); + result.add(link); + } + } + return unmodifiableList(result); } /** @@ -212,7 +247,7 @@ public class AddAccountPane extends StackPane { private final CountDownLatch latch = new CountDownLatch(1); private GameProfile selectedProfile = null; - { + public Selector() { setStyle("-fx-padding: 8px;"); cancel.setText(i18n("button.cancel")); @@ -230,45 +265,33 @@ public class AddAccountPane extends StackPane { } @Override - public GameProfile select(Account account, List names) throws NoSelectedCharacterException { - if (!(account instanceof YggdrasilAccount)) - return CharacterSelector.DEFAULT.select(account, names); - YggdrasilAccount yggdrasilAccount = (YggdrasilAccount) account; + public GameProfile select(YggdrasilService service, List profiles) throws NoSelectedCharacterException { + Platform.runLater(() -> { + for (GameProfile profile : profiles) { + ImageView portraitView = new ImageView(); + portraitView.setSmooth(false); + portraitView.imageProperty().bind(TexturesLoader.fxAvatarBinding(service, profile.getId(), 32)); + FXUtils.limitSize(portraitView, 32, 32); - for (GameProfile profile : names) { - Image image; - final int scaleRatio = 4; - try { - image = AccountHelper.getSkinImmediately(yggdrasilAccount, profile, scaleRatio); - } catch (Exception e) { - Logging.LOG.log(Level.WARNING, "Failed to get skin for " + profile.getName(), e); - image = AccountHelper.getDefaultSkin(profile.getId(), scaleRatio); + IconedItem accountItem = new IconedItem(portraitView, profile.getName()); + accountItem.setOnMouseClicked(e -> { + selectedProfile = profile; + latch.countDown(); + }); + listBox.add(accountItem); } - - ImageView portraitView = new ImageView(); - portraitView.setSmooth(false); - portraitView.setImage(AccountHelper.getHead(image, scaleRatio)); - FXUtils.limitSize(portraitView, 32, 32); - - IconedItem accountItem = new IconedItem(portraitView, profile.getName()); - accountItem.setOnMouseClicked(e -> { - selectedProfile = profile; - latch.countDown(); - }); - listBox.add(accountItem); - } - - JFXUtilities.runInFX(() -> Controllers.dialog(this)); + Controllers.dialog(this); + }); try { latch.await(); if (selectedProfile == null) - throw new NoSelectedCharacterException(account); + throw new NoSelectedCharacterException(); return selectedProfile; } catch (InterruptedException ignore) { - throw new NoSelectedCharacterException(account); + throw new NoSelectedCharacterException(); } finally { JFXUtilities.runInFX(() -> Selector.this.fireEvent(new DialogCloseEvent())); } @@ -296,6 +319,8 @@ public class AddAccountPane extends StackPane { return exception.getMessage(); } else if (exception instanceof AuthlibInjectorDownloadException) { return i18n("account.failed.injector_download_failure"); + } else if (exception instanceof CharacterDeletedException) { + return i18n("account.failed.character_deleted"); } else if (exception.getClass() == AuthenticationException.class) { return exception.getLocalizedMessage(); } else { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AuthlibInjectorServerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AuthlibInjectorServerItem.java index 3f261e070..f8a777695 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AuthlibInjectorServerItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AuthlibInjectorServerItem.java @@ -19,6 +19,8 @@ package org.jackhuang.hmcl.ui.account; import com.jfoenix.controls.JFXButton; import com.jfoenix.effects.JFXDepthManager; + +import javafx.beans.binding.Bindings; import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.layout.BorderPane; @@ -33,17 +35,17 @@ public final class AuthlibInjectorServerItem extends BorderPane { private final AuthlibInjectorServer server; private final Label lblServerName = new Label(); - private final Label lblServerIp = new Label(); + private final Label lblServerUrl = new Label(); public AuthlibInjectorServerItem(AuthlibInjectorServer server, Consumer deleteCallback) { this.server = server; lblServerName.setStyle("-fx-font-size: 15;"); - lblServerIp.setStyle("-fx-font-size: 10;"); + lblServerUrl.setStyle("-fx-font-size: 10;"); VBox center = new VBox(); BorderPane.setAlignment(center, Pos.CENTER); - center.getChildren().addAll(lblServerName, lblServerIp); + center.getChildren().addAll(lblServerName, lblServerUrl); setCenter(center); JFXButton right = new JFXButton(); @@ -55,8 +57,8 @@ public final class AuthlibInjectorServerItem extends BorderPane { setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;"); JFXDepthManager.setDepth(this, 1); - lblServerName.setText(server.getName()); - lblServerIp.setText(server.getUrl()); + lblServerName.textProperty().bind(Bindings.createStringBinding(server::getName, server)); + lblServerUrl.setText(server.getUrl()); } public AuthlibInjectorServer getServer() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java index 56af42ac8..7cbd5ac61 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FileItem.java @@ -61,7 +61,7 @@ public class FileItem extends BorderPane { right.setGraphic(SVG.pencil(Theme.blackFillBinding(), 15, 15)); right.getStyleClass().add("toggle-icon4"); right.setOnMouseClicked(e -> onExplore()); - FXUtils.installTooltip(right, i18n("button.edit")); + FXUtils.installFastTooltip(right, i18n("button.edit")); setRight(right); Tooltip tip = new Tooltip(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FontComboBox.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FontComboBox.java index 81f2130de..abb1d10f3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FontComboBox.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/FontComboBox.java @@ -24,27 +24,27 @@ import static javafx.collections.FXCollections.singletonObservableList; import org.jackhuang.hmcl.util.javafx.MultiStepBinding; import com.jfoenix.controls.JFXComboBox; +import com.jfoenix.controls.JFXListCell; import javafx.beans.NamedArg; import javafx.beans.binding.Bindings; -import javafx.scene.control.ListCell; import javafx.scene.text.Font; public class FontComboBox extends JFXComboBox { private boolean loaded = false; - public FontComboBox(@NamedArg(value = "fontSize", defaultValue = "12.0") double fontSize, - @NamedArg(value = "enableStyle", defaultValue = "false") boolean enableStyle) { + public FontComboBox(@NamedArg(value = "fontSize", defaultValue = "12.0") double fontSize) { styleProperty().bind(Bindings.concat("-fx-font-family: \"", valueProperty(), "\"")); - setCellFactory(listView -> new ListCell() { + setCellFactory(listView -> new JFXListCell() { @Override - protected void updateItem(String item, boolean empty) { + public void updateItem(String item, boolean empty) { super.updateItem(item, empty); - if (item != null) { + if (!empty) { setText(item); - setFont(new Font(item, fontSize)); + setGraphic(null); + setStyle("-fx-font-family: \"" + item + "\""); } } }); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedMenuItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedMenuItem.java index a008af282..49d915229 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedMenuItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedMenuItem.java @@ -30,7 +30,7 @@ public class IconedMenuItem extends IconedItem { } public IconedMenuItem addTooltip(String tooltip) { - FXUtils.installTooltip(this, tooltip); + FXUtils.installFastTooltip(this, tooltip); return this; } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImagePickerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImagePickerItem.java index 6c8b3afef..1a4bd7620 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImagePickerItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ImagePickerItem.java @@ -63,7 +63,7 @@ public final class ImagePickerItem extends BorderPane { deleteButton.onMouseClickedProperty().bind(onDeleteButtonClicked); deleteButton.getStyleClass().add("toggle-icon4"); - FXUtils.installTooltip(selectButton, i18n("button.edit")); + FXUtils.installFastTooltip(selectButton, i18n("button.edit")); HBox hBox = new HBox(); hBox.getChildren().setAll(imageView, selectButton, deleteButton); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java index 590402baa..9db91119f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java @@ -17,15 +17,11 @@ */ package org.jackhuang.hmcl.ui.construct; -import com.jfoenix.controls.JFXListView; import com.jfoenix.controls.JFXProgressBar; import javafx.application.Platform; -import javafx.beans.binding.Bindings; import javafx.beans.property.ReadOnlyIntegerProperty; import javafx.beans.property.ReadOnlyIntegerWrapper; -import javafx.geometry.Insets; import javafx.scene.control.Label; -import javafx.scene.control.ListCell; import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; import org.jackhuang.hmcl.download.forge.ForgeInstallTask; @@ -45,16 +41,15 @@ import java.util.Map; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public final class TaskListPane extends StackPane { - private final JFXListView listBox = new JFXListView<>(); + private final AdvancedListBox listBox = new AdvancedListBox(); private final Map nodes = new HashMap<>(); private final ReadOnlyIntegerWrapper finishedTasks = new ReadOnlyIntegerWrapper(); private final ReadOnlyIntegerWrapper totTasks = new ReadOnlyIntegerWrapper(); public TaskListPane() { - getChildren().setAll(listBox); + listBox.setSpacing(0); - listBox.setPadding(Insets.EMPTY); - listBox.setCellFactory(listView -> new ProgressListNode()); + getChildren().setAll(listBox); } public ReadOnlyIntegerProperty finishedTasksProperty() { @@ -70,7 +65,7 @@ public final class TaskListPane extends StackPane { @Override public void onStart() { Platform.runLater(() -> { - listBox.getItems().clear(); + listBox.clear(); finishedTasks.set(0); totTasks.set(0); }); @@ -112,66 +107,64 @@ public final class TaskListPane extends StackPane { task.setName(i18n("modpack.scan")); } - Platform.runLater(() -> listBox.getItems().add(task)); + ProgressListNode node = new ProgressListNode(task); + nodes.put(task, node); + Platform.runLater(() -> listBox.add(node)); } @Override public void onFinished(Task task) { + ProgressListNode node = nodes.remove(task); + if (node == null) + return; + node.unbind(); Platform.runLater(() -> { - if (listBox.getItems().remove(task)) - finishedTasks.set(finishedTasks.getValue() + 1); + listBox.remove(node); + finishedTasks.set(finishedTasks.getValue() + 1); + }); + } + + @Override + public void onFailed(Task task, Throwable throwable) { + ProgressListNode node = nodes.remove(task); + if (node == null) + return; + Platform.runLater(() -> { + node.setThrowable(throwable); + finishedTasks.set(finishedTasks.getValue() + 1); }); } }); } - private static class ProgressListNode extends ListCell { - private final BorderPane borderPane = new BorderPane(); + private static class ProgressListNode extends BorderPane { private final JFXProgressBar bar = new JFXProgressBar(); private final Label title = new Label(); private final Label state = new Label(); - { - borderPane.setLeft(title); - borderPane.setRight(state); - borderPane.setBottom(bar); - borderPane.setMinWidth(0); - borderPane.setPrefWidth(1); + public ProgressListNode(Task task) { + bar.progressProperty().bind(task.progressProperty()); + title.setText(task.getName()); + state.textProperty().bind(task.messageProperty()); - setPadding(Insets.EMPTY); + setLeft(title); + setRight(state); + setBottom(bar); bar.minWidthProperty().bind(widthProperty()); bar.prefWidthProperty().bind(widthProperty()); bar.maxWidthProperty().bind(widthProperty()); } - @Override - protected void updateItem(Task item, boolean empty) { - boolean wasEmpty = isEmpty(); - Task oldTask = getItem(); + public void unbind() { + bar.progressProperty().unbind(); + state.textProperty().unbind(); + } - if (!wasEmpty && oldTask != null) { - bar.progressProperty().unbind(); - state.textProperty().unbind(); - } - - super.updateItem(item, empty); - - if (empty || item == null) { - setGraphic(null); - } else { - setGraphic(borderPane); - bar.visibleProperty().bind(Bindings.createBooleanBinding(() -> item.progressProperty().get() != -1, item.progressProperty())); - bar.progressProperty().bind(item.progressProperty()); - state.textProperty().bind(Bindings.createObjectBinding(() -> { - if (item.getState() == Task.TaskState.FAILED) { - return item.getLastException().getLocalizedMessage(); - } else { - return item.messageProperty().get(); - } - }, item.messageProperty(), item.stateProperty())); - title.setText(item.getName()); - } + public void setThrowable(Throwable throwable) { + unbind(); + state.setText(throwable.getLocalizedMessage()); + bar.setProgress(0); } } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java index 48d9f9b12..158576d92 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TwoLineListItem.java @@ -19,32 +19,15 @@ package org.jackhuang.hmcl.ui.construct; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; -import javafx.css.CssMetaData; -import javafx.css.SimpleStyleableObjectProperty; -import javafx.css.Styleable; -import javafx.css.StyleableObjectProperty; -import javafx.css.StyleablePropertyFactory; import javafx.scene.control.Label; -import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; -import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; -import javafx.scene.text.Font; -import java.util.List; - -public class TwoLineListItem extends StackPane { +public class TwoLineListItem extends VBox { private static final String DEFAULT_STYLE_CLASS = "two-line-list-item"; private final StringProperty title = new SimpleStringProperty(this, "title"); private final StringProperty subtitle = new SimpleStringProperty(this, "subtitle"); - private final StyleableObjectProperty titleFont = new SimpleStyleableObjectProperty<>(StyleableProperties.TITLE_FONT, this, "title-font", Font.font(15)); - private final StyleableObjectProperty subtitleFont = new SimpleStyleableObjectProperty<>(StyleableProperties.SUBTITLE_FONT, this, "subtitle-font", Font.getDefault()); - - private final StyleableObjectProperty titleFill = new SimpleStyleableObjectProperty<>(StyleableProperties.TITLE_FILL, this, "title-fill", Color.BLACK); - private final StyleableObjectProperty subtitleFill = new SimpleStyleableObjectProperty<>(StyleableProperties.SUBTITLE_FILL, this, "subtitle-fill", Color.GRAY); - public TwoLineListItem(String titleString, String subtitleString) { this(); @@ -55,19 +38,14 @@ public class TwoLineListItem extends StackPane { public TwoLineListItem() { setMouseTransparent(true); Label lblTitle = new Label(); - lblTitle.textFillProperty().bind(titleFill); - lblTitle.fontProperty().bind(titleFont); + lblTitle.getStyleClass().add("title"); lblTitle.textProperty().bind(title); Label lblSubtitle = new Label(); - lblSubtitle.textFillProperty().bind(subtitleFill); - lblSubtitle.fontProperty().bind(subtitleFont); + lblSubtitle.getStyleClass().add("subtitle"); lblSubtitle.textProperty().bind(subtitle); - VBox vbox = new VBox(); - vbox.getChildren().setAll(lblTitle, lblSubtitle); - getChildren().setAll(vbox); - + getChildren().setAll(lblTitle, lblSubtitle); getStyleClass().add(DEFAULT_STYLE_CLASS); } @@ -95,74 +73,8 @@ public class TwoLineListItem extends StackPane { this.subtitle.set(subtitle); } - public Font getTitleFont() { - return titleFont.get(); - } - - public StyleableObjectProperty titleFontProperty() { - return titleFont; - } - - public void setTitleFont(Font titleFont) { - this.titleFont.set(titleFont); - } - - public Font getSubtitleFont() { - return subtitleFont.get(); - } - - public StyleableObjectProperty subtitleFontProperty() { - return subtitleFont; - } - - public void setSubtitleFont(Font subtitleFont) { - this.subtitleFont.set(subtitleFont); - } - - public Paint getTitleFill() { - return titleFill.get(); - } - - public StyleableObjectProperty titleFillProperty() { - return titleFill; - } - - public void setTitleFill(Paint titleFill) { - this.titleFill.set(titleFill); - } - - public Paint getSubtitleFill() { - return subtitleFill.get(); - } - - public StyleableObjectProperty subtitleFillProperty() { - return subtitleFill; - } - - public void setSubtitleFill(Paint subtitleFill) { - this.subtitleFill.set(subtitleFill); - } - @Override public String toString() { return getTitle(); } - - @Override - public List> getCssMetaData() { - return getClassCssMetaData(); - } - - public static List> getClassCssMetaData() { - return StyleableProperties.FACTORY.getCssMetaData(); - } - - private static class StyleableProperties { - private static final StyleablePropertyFactory FACTORY = new StyleablePropertyFactory<>(StackPane.getClassCssMetaData()); - - private static final CssMetaData TITLE_FONT = FACTORY.createFontCssMetaData("-jfx-title-font", s -> s.titleFont, Font.font(15)); - private static final CssMetaData SUBTITLE_FONT = FACTORY.createFontCssMetaData("-jfx-subtitle-font", s -> s.subtitleFont); - private static final CssMetaData TITLE_FILL = FACTORY.createPaintCssMetaData("-jfx-title-fill", s -> s.titleFill); - private static final CssMetaData SUBTITLE_FILL = FACTORY.createPaintCssMetaData("-jfx-subtitle-fill", s -> s.subtitleFill, Color.GREY); - } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java index 09d9f7331..a06c75813 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.ui.download; import javafx.scene.Node; +import org.jackhuang.hmcl.download.game.LibraryDownloadException; import org.jackhuang.hmcl.game.ModpackHelper; import org.jackhuang.hmcl.mod.CurseCompletionException; import org.jackhuang.hmcl.mod.MismatchedModpackTypeException; @@ -111,6 +112,8 @@ public class ModpackInstallWizardProvider implements WizardProvider { } else { Controllers.dialog(i18n("modpack.type.curse.tolerable_error"), i18n("install.success"), MessageBox.INFORMATION_MESSAGE, next); } + } else if (exception instanceof LibraryDownloadException) { + Controllers.dialog(i18n("launch.failed.download_library", ((LibraryDownloadException) exception).getLibrary().getName()) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageBox.ERROR_MESSAGE, next); } else if (exception instanceof DownloadException) { Controllers.dialog(StringUtils.getStackTrace(exception), i18n("install.failed.downloading"), MessageBox.ERROR_MESSAGE, next); } else { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VanillaInstallWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VanillaInstallWizardProvider.java index 58227a22a..b0105819d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VanillaInstallWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VanillaInstallWizardProvider.java @@ -21,6 +21,7 @@ import javafx.scene.Node; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.download.GameBuilder; import org.jackhuang.hmcl.download.RemoteVersion; +import org.jackhuang.hmcl.download.game.LibraryDownloadException; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.task.DownloadException; import org.jackhuang.hmcl.task.Schedulers; @@ -73,7 +74,9 @@ public final class VanillaInstallWizardProvider implements WizardProvider { settings.put("failure_callback", new FailureCallback() { @Override public void onFail(Map settings, Exception exception, Runnable next) { - if (exception instanceof DownloadException) { + if (exception instanceof LibraryDownloadException) { + Controllers.dialog(i18n("launch.failed.download_library", ((LibraryDownloadException) exception).getLibrary().getName()) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageBox.ERROR_MESSAGE, next); + } else if (exception instanceof DownloadException) { Controllers.dialog(StringUtils.getStackTrace(exception), i18n("install.failed.downloading"), MessageBox.ERROR_MESSAGE, next); } else { Controllers.dialog(StringUtils.getStackTrace(exception), i18n("install.failed"), MessageBox.ERROR_MESSAGE, next); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListItem.java index a7a8d04d7..7c8501b53 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListItem.java @@ -44,7 +44,7 @@ public class DatapackListItem extends BorderPane { setCenter(modItem); JFXButton btnRemove = new JFXButton(); - FXUtils.installTooltip(btnRemove, i18n("datapack.remove")); + FXUtils.installFastTooltip(btnRemove, i18n("datapack.remove")); btnRemove.setOnMouseClicked(e -> deleteCallback.accept(this)); btnRemove.getStyleClass().add("toggle-icon4"); BorderPane.setAlignment(btnRemove, Pos.CENTER); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java index 8b5013f30..a6de9f3f8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameListItemSkin.java @@ -73,7 +73,7 @@ public class GameListItemSkin extends SkinBase { btnUpgrade.setOnMouseClicked(e -> skinnable.update()); btnUpgrade.getStyleClass().add("toggle-icon4"); btnUpgrade.setGraphic(SVG.update(Theme.blackFillBinding(), -1, -1)); - JFXUtilities.runInFX(() -> FXUtils.installTooltip(btnUpgrade, i18n("version.update"))); + JFXUtilities.runInFX(() -> FXUtils.installFastTooltip(btnUpgrade, i18n("version.update"))); right.getChildren().add(btnUpgrade); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModItem.java index f9af3a7be..0a6eb2251 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModItem.java @@ -47,7 +47,7 @@ public final class ModItem extends BorderPane { JFXButton btnRemove = new JFXButton(); JFXUtilities.runInFX(() -> { - FXUtils.installTooltip(btnRemove, i18n("mods.remove")); + FXUtils.installFastTooltip(btnRemove, i18n("mods.remove")); }); btnRemove.setOnMouseClicked(e -> deleteCallback.accept(this)); btnRemove.getStyleClass().add("toggle-icon4"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java index bc933e727..9b089ec8e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionPage.java @@ -99,13 +99,13 @@ public final class VersionPage extends StackPane implements DecoratorPage { new IconedMenuItem(null, i18n("version.manage.clean"), FXUtils.withJFXPopupClosing(() -> Versions.cleanVersion(profile, version), managementPopup)).addTooltip(i18n("version.manage.clean.tooltip")) ); - FXUtils.installTooltip(btnDelete, i18n("version.manage.remove")); - FXUtils.installTooltip(btnBrowseMenu, i18n("settings.game.exploration")); - FXUtils.installTooltip(btnManagementMenu, i18n("settings.game.management")); - FXUtils.installTooltip(btnExport, i18n("modpack.export")); + FXUtils.installFastTooltip(btnDelete, i18n("version.manage.remove")); + FXUtils.installFastTooltip(btnBrowseMenu, i18n("settings.game.exploration")); + FXUtils.installFastTooltip(btnManagementMenu, i18n("settings.game.management")); + FXUtils.installFastTooltip(btnExport, i18n("modpack.export")); btnTestGame.setGraphic(SVG.launch(Theme.whiteFillBinding(), 20, 20)); - FXUtils.installTooltip(btnTestGame, i18n("version.launch.test")); + FXUtils.installFastTooltip(btnTestGame, i18n("version.launch.test")); setEventHandler(Navigator.NavigationEvent.NAVIGATED, this::onNavigated); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java index 5ec170322..b5b0b45e7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java @@ -35,6 +35,7 @@ import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.stage.FileChooser; import org.jackhuang.hmcl.setting.EnumGameDirectory; +import org.jackhuang.hmcl.setting.LauncherVisibility; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profiles; import org.jackhuang.hmcl.setting.VersionSetting; @@ -60,6 +61,7 @@ import java.util.List; import java.util.logging.Level; import java.util.stream.Collectors; +import static org.jackhuang.hmcl.ui.FXUtils.stringConverter; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public final class VersionSettingsPage extends StackPane implements DecoratorPage { @@ -85,7 +87,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag @FXML private ComponentList advancedSettingsPane; @FXML private ComponentList componentList; @FXML private ComponentList iconPickerItemWrapper; - @FXML private JFXComboBox cboLauncherVisibility; + @FXML private JFXComboBox cboLauncherVisibility; @FXML private JFXCheckBox chkFullscreen; @FXML private Label lblPhysicalMemory; @FXML private JFXToggleButton chkNoJVMArgs; @@ -100,6 +102,9 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag public VersionSettingsPage() { FXUtils.loadFXML(this, "/assets/fxml/version/version-settings.fxml"); + + cboLauncherVisibility.getItems().setAll(LauncherVisibility.values()); + cboLauncherVisibility.setConverter(stringConverter(e -> i18n("settings.advanced.launcher_visibility." + e.name().toLowerCase()))); } @FXML diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java index a810a21d2..45c91f1c1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java @@ -81,8 +81,7 @@ public class Versions { public static void updateGameAssets(Profile profile, String version) { Version resolvedVersion = profile.getRepository().getResolvedVersion(version); - TaskExecutor executor = new GameAssetIndexDownloadTask(profile.getDependency(), resolvedVersion) - .then(new GameAssetDownloadTask(profile.getDependency(), resolvedVersion)) + TaskExecutor executor = new GameAssetDownloadTask(profile.getDependency(), resolvedVersion, GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY) .executor(); Controllers.taskDialog(executor, i18n("version.manage.redownload_assets_index")); executor.start(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java index aad0ae4d6..f5f99e31f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java @@ -103,7 +103,10 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler { "-- System Details --\n" + " Operating System: " + System.getProperty("os.name") + ' ' + OperatingSystem.SYSTEM_VERSION + "\n" + " Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor") + "\n" + - " Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor") + "\n"; + " Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor") + "\n" + + " JVM Max Memory: " + Runtime.getRuntime().maxMemory() + "\n" + + " JVM Total Memory: " + Runtime.getRuntime().totalMemory() + "\n" + + " JVM Free Memory: " + Runtime.getRuntime().freeMemory() + "\n"; LOG.log(Level.SEVERE, text); diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index aef827b80..68fcfcf4d 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -104,20 +104,24 @@ -fx-padding: 4 0 4 0; } +.two-line-list-item > .title { + -fx-text-fill: black; + -fx-font-size: 15px; +} + +.two-line-list-item > .subtitle { + -fx-text-fill: gray; +} + .bubble { -fx-background-color: gray; -fx-background-radius: 2px; + -fx-text-fill: white; } -.bubble .two-line-list-item { - -jfx-title-fill: white; - -jfx-subtitle-fill: white; -} - -.two-line-list-item { - -jfx-title-font-size: 15px; - -jfx-title-fill: black; - -jfx-subtitle-fill: gray; +.bubble .two-line-list-item > .title, +.bubble .two-line-list-item > .subtitle { + -fx-text-fill: white; } .window-title-bar .separator { @@ -338,7 +342,6 @@ .jfx-tool-bar HBox { -fx-alignment: center; -/* -fx-spacing: 25.0;*/ -fx-padding: 0.0 5.0; } @@ -443,33 +446,6 @@ -fx-fill: -fx-base-check-color; } -.custom-jfx-radio-button { - -fx-font-size: 16.0px; -} - -.custom-jfx-radio-button .radio { - -fx-stroke-width: 2.0px; - -fx-fill: transparent; -} - -.custom-jfx-radio-button-blue { - -fx-text-fill: -fx-base-color; - -jfx-selected-color: -fx-base-color; - -jfx-unselected-color: #212121; -} - -.custom-jfx-radio-button-red { - -fx-text-fill: #f44336; - -jfx-selected-color: #f44336; - -jfx-unselected-color: #b71c1c; -} - -.custom-jfx-radio-button-green { - -fx-text-fill: #4caf50; - -jfx-selected-color: #4caf50; - -jfx-unselected-color: #1b5e20; -} - /******************************************************************************* * * * JFX Slider * @@ -510,18 +486,12 @@ -fx-font-size: 10.0; } -/******************************************************/ - /******************************************************************************* * * * JFX Rippler * * * *******************************************************************************/ -/*.jfx-rippler { - -fx-rippler-fill: -fx-base-color; - -fx-mask-type: RECT; -}*/ .jfx-rippler:hover { -fx-cursor: hand; } @@ -532,62 +502,24 @@ * * *******************************************************************************/ -.custom-jfx-button-raised .jfx-rippler { - -jfx-rippler-fill: YELLOW; -} - -.custom-jfx-button-raised { - -fx-padding: 0.7em 0.57em; - -fx-font-size: 14.0px; - -jfx-button-type: RAISED; - -fx-background-color: rgb(102.0, 153.0, 102.0); - -fx-pref-width: 200.0; - -fx-text-fill: WHITE; -} - -.circle-jfx-button-raised .jfx-rippler { - -jfx-rippler-fill: YELLOW; -} - -.circle-jfx-button-raised { - -fx-padding: 0.7em 0.57em; - -fx-font-size: 14.0px; - -jfx-button-type: RAISED; - -fx-background-color: rgb(102.0, 153.0, 102.0); - -fx-pref-width: 200.0; - -fx-text-fill: WHITE; - -jfx-mask-type: CIRCLE; -} - .jfx-button-raised { - -fx-text-fill: white; -fx-background-color: -fx-base-color; - -fx-font-size: 14px; } -.jfx-button-raised .jfx-rippler { - -jfx-rippler-fill: white; -} - -.jfx-button-raised .label { - -fx-text-fill: white; +.jfx-button-raised, .jfx-button-raised * { + -fx-text-fill: -fx-base-text-fill; -fx-font-size: 14px; } .jfx-button-border { - -fx-text-fill: -fx-base-color; -fx-border-color: gray; -fx-border-radius: 5px; -fx-border-width: 0.2px; -fx-padding: 8px; } -.jfx-button-border .jfx-rippler { - -jfx-rippler-fill: -fx-base-check-color; -} - -.jfx-button-border .label { - -fx-text-fill: -fx-base-color; +.jfx-button-border, .jfx-button-border * { + -fx-text-fill: -fx-base-darker-color; } .jfx-button-raised-round { @@ -606,20 +538,6 @@ -jfx-checked-color: -fx-base-check-color; } -.custom-jfx-check-box { - -jfx-checked-color: RED; -} - -.custom-jfx-check-box-all-colored { - -jfx-checked-color: -fx-base-color; - -jfx-unchecked-color: -fx-base-color; - -fx-text-fill: -fx-base-color; -} - -.custom-jfx-check-box-text-colored { - -fx-text-fill: rgb(153.0, 0.0, 0.0); -} - /******************************************************************************* * * * JFX Progress Bar * @@ -643,15 +561,6 @@ -fx-background-color: -fx-base-check-color; } -.custom-jfx-progress-bar > .bar { - -fx-background-color: rgb(255.0, 128.0, 0.0); -} - -.custom-jfx-progress-bar-stroke > .bar { - -fx-background-color: -fx-base-color; - -fx-padding: 6; -} - /******************************************************************************* * * * JFX Textfield * @@ -678,20 +587,12 @@ * * *******************************************************************************/ -.jfx-list-cell:odd, -.jfx-list-cell:even, -.list-cell:odd, -.list-cell:even { +.jfx-list-cell, .list-cell { -fx-background-color: WHITE; } -.list-cell:selected, .jfx-list-cell:selected { - -fx-background-insets: 0.0; - -fx-text-fill: BLACK; -} - -.jfx-list-cell:filled:hover, -.jfx-list-cell:selected .label { +.list-cell:selected, .jfx-list-cell:selected, +.list-cell:hover, .jfx-list-cell:hover { -fx-text-fill: black; } @@ -712,47 +613,6 @@ -jfx-expanded: false; } -.custom-jfx-list-view .jfx-list-cell:odd:selected > .jfx-rippler > StackPane, -.custom-jfx-list-view .jfx-list-cell:even:selected > .jfx-rippler > StackPane { - -fx-background-color: rgba(255, 0, 0, 0.2); -} - -.custom-jfx-list-view { - -fx-background-insets: 0.0; - -jfx-cell-horizontal-margin: 0.0; - -jfx-cell-vertical-margin: 5.0; - -jfx-expanded: false; - -fx-max-width: 200.0px; - /* important to hide the list change of height */ - -fx-background-color: TRANSPARENT; -} - -.custom-jfx-list-view .jfx-rippler { - -jfx-rippler-fill: RED; -} - -.custom-jfx-list-view1 { - -jfx-vertical-gap: 10.0; - -fx-pref-width: 150px; - -fx-background-color: transparent; -} - -.custom-jfx-list-view-icon, -.jfx-list-cell:selected .label .custom-jfx-list-view-icon { - -fx-fill: -fx-base-color; - -fx-padding: 0.0 10.0 0.0 5.0; - -fx-cursor: hand; -} - -.custom-jfx-list-view-icon-container { - -fx-pref-width: 40px; -} - -.custom-jfx-list-view .sublist-item { - -fx-border-color: #e0e0e0; - -fx-border-width: 1 0 1 0; -} - .options-list { -fx-background-color: transparent; -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 5, 0.06, -0.5, 1); @@ -802,20 +662,6 @@ -jfx-mask-type: CIRCLE; } -.custom-jfx-list-view .jfx-list-cell .sublist-header > .drop-icon { - -fx-background-color: GRAY; -} - -.custom-jfx-list-view .jfx-list-cell:filled:hover .sublist-header > .drop-icon { - -fx-background-color: BLACK; -} - -/*******************************************************************************/ -/*******************************************************************************/ -/*******************************************************************************/ -/*******************************************************************************/ -/*******************************************************************************/ -/*******************************************************************************/ /******************************************************************************* * * * JFX SUBLIST IMPORTANT * @@ -855,10 +701,6 @@ -fx-padding: 0 0 0 12; } -/*.custom-jfx-list-view .sublist-container { - -fx-padding : 0 0 5 0; -}*/ - /******************************************************************************* * * * JFX Toggle Button * @@ -869,14 +711,6 @@ -jfx-toggle-color: -fx-base-check-color; } -.custom-jfx-toggle-button { - -jfx-toggle-color: #4285F4; -} - -.custom-jfx-toggle-button-red { - -jfx-toggle-color: red; -} - .toggle-label { -fx-font-size: 14.0px; } @@ -1109,31 +943,10 @@ -fx-fill: #D34336; } -.combo-box-popup .list-view .jfx-list-cell .label, -.combo-box-popup .list-view .jfx-list-cell:filled:hover .label { - -fx-text-fill: BLACK; -} - -.combo-box-popup .list-view .jfx-list-cell .custom-jfx-list-view-icon, -.combo-box-popup .list-view .jfx-list-cell:filled:hover .custom-jfx-list-view-icon, -.combo-box-popup .list-view .jfx-list-cell:selected .custom-jfx-list-view-icon { - -fx-fill: -fx-base-color; -} - -.combo-box-popup .list-view .jfx-list-cell:odd:selected > .jfx-rippler > StackPane, -.combo-box-popup .list-view .jfx-list-cell:even:selected > .jfx-rippler > StackPane { - -fx-background-color: rgba(0.0, 0.0, 255.0, 0.2); -} - .combo-box-popup .list-view .jfx-list-cell { -fx-background-insets: 0.0; } -.combo-box-popup .list-view .jfx-list-cell:odd, -.combo-box-popup .list-view .jfx-list-cell:even { - -fx-background-color: WHITE; -} - .combo-box-popup .list-view .jfx-list-cell .jfx-rippler { -jfx-rippler-fill: -fx-base-color; } @@ -1387,12 +1200,3 @@ .fit-width { -fx-pref-width: 100%; } -/* -.jfx-scroll-pane .main-header { - -fx-background-image: url("../bg1.jpg"); -} - -.jfx-scroll-pane .condensed-header { - -fx-background-image: url("../bg4.jpg"); -} -*/ \ No newline at end of file diff --git a/HMCL/src/main/resources/assets/fxml/account-add.fxml b/HMCL/src/main/resources/assets/fxml/account-add.fxml index 857a4c846..ccc5290b3 100644 --- a/HMCL/src/main/resources/assets/fxml/account-add.fxml +++ b/HMCL/src/main/resources/assets/fxml/account-add.fxml @@ -32,6 +32,7 @@ + diff --git a/HMCL/src/main/resources/assets/fxml/version/version-settings.fxml b/HMCL/src/main/resources/assets/fxml/version/version-settings.fxml index f07f6cf7c..a66289999 100644 --- a/HMCL/src/main/resources/assets/fxml/version/version-settings.fxml +++ b/HMCL/src/main/resources/assets/fxml/version/version-settings.fxml @@ -60,16 +60,7 @@