diff --git a/HMCL/build.gradle b/HMCL/build.gradle index 11fb43bae..673386f29 100644 --- a/HMCL/build.gradle +++ b/HMCL/build.gradle @@ -1,3 +1,6 @@ +import org.apache.tools.ant.filters.ReplaceTokens +import proguard.gradle.ProGuardTask + import java.security.MessageDigest import java.util.jar.JarFile import java.util.jar.Pack200 @@ -15,11 +18,9 @@ if (buildnumber == null) def versionroot = System.getenv("VERSION_ROOT") if (versionroot == null) - versionroot = "3.0" + versionroot = "3.1" -String mavenGroupId = 'HMCL' String mavenVersion = versionroot + '.' + buildnumber -String bundleName = "Hello Minecraft! Launcher" version = mavenVersion dependencies { @@ -30,7 +31,7 @@ dependencies { task generateSources(type: Sync) { from 'src/main/java' into "$buildDir/generated-src" - filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: [ + filter(ReplaceTokens, tokens: [ 'HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING': mavenVersion ]) } @@ -122,7 +123,7 @@ task makePackGZ(dependsOn: jar) doLast { fileEx.append sha1Hex } -task proguard(type: proguard.gradle.ProGuardTask, dependsOn: jar) { +task proguard(type: ProGuardTask, dependsOn: jar) { ext { def re = jar.classifier injar = jar.archivePath diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java b/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java new file mode 100644 index 000000000..5ad5a770a --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java @@ -0,0 +1,152 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl; + +import com.jfoenix.concurrency.JFXUtilities; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.stage.Stage; +import org.jackhuang.hmcl.setting.Settings; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.ui.Controllers; +import org.jackhuang.hmcl.upgrade.AppDataUpgrader; +import org.jackhuang.hmcl.upgrade.IUpgrader; +import org.jackhuang.hmcl.upgrade.UpdateChecker; +import org.jackhuang.hmcl.util.*; + +import java.io.File; +import java.util.Arrays; +import java.util.ResourceBundle; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; + +public final class Launcher extends Application { + + @Override + public void start(Stage primaryStage) { + Thread.currentThread().setUncaughtExceptionHandler(CRASH_REPORTER); + + try { + // When launcher visibility is set to "hide and reopen" without Platform.implicitExit = false, + // Stage.show() cannot work again because JavaFX Toolkit have already shut down. + Platform.setImplicitExit(false); + Controllers.initialize(primaryStage); + primaryStage.setResizable(false); + primaryStage.setScene(Controllers.getScene()); + + UPDATE_CHECKER.process(false) + .then(Task.of(Schedulers.javafx(), () -> { + if (UPDATE_CHECKER.isOutOfDate()) + Controllers.showUpdate(); + })) + .start(); + + primaryStage.show(); + } catch (Throwable e) { + CRASH_REPORTER.uncaughtException(Thread.currentThread(), e); + } + } + + public static void main(String[] args) { + Thread.setDefaultUncaughtExceptionHandler(CRASH_REPORTER); + + try { + Logging.start(); + + // NetworkUtils.setUserAgentSupplier(() -> "Hello Minecraft! Launcher"); + Constants.UI_THREAD_SCHEDULER = Constants.JAVAFX_UI_THREAD_SCHEDULER; + UPGRADER.parseArguments(VersionNumber.asVersion(VERSION), Arrays.asList(args)); + + Logging.LOG.info("*** " + TITLE + " ***"); + + launch(args); + } catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter. + CRASH_REPORTER.uncaughtException(Thread.currentThread(), e); + } + } + + public static void stopApplication() { + Logging.LOG.info("Stopping application.\n" + StringUtils.getStackTrace(Thread.currentThread().getStackTrace())); + + JFXUtilities.runInFX(() -> { + if (Controllers.getStage() == null) + return; + Controllers.getStage().close(); + Schedulers.shutdown(); + Controllers.shutdown(); + Platform.exit(); + Lang.executeDelayed(OperatingSystem::forceGC, TimeUnit.SECONDS, 5, true); + }); + } + + public static void stopWithoutPlatform() { + Logging.LOG.info("Stopping application without JavaFX Toolkit.\n" + StringUtils.getStackTrace(Thread.currentThread().getStackTrace())); + + JFXUtilities.runInFX(() -> { + if (Controllers.getStage() == null) + return; + Controllers.getStage().close(); + Schedulers.shutdown(); + Controllers.shutdown(); + Lang.executeDelayed(OperatingSystem::forceGC, TimeUnit.SECONDS, 5, true); + }); + } + + public static File getWorkingDirectory(String folder) { + String home = System.getProperty("user.home", "."); + switch (OperatingSystem.CURRENT_OS) { + case LINUX: + return new File(home, "." + folder + "/"); + case WINDOWS: + String appdata = System.getenv("APPDATA"); + return new File(Lang.nonNull(appdata, home), "." + folder + "/"); + case OSX: + return new File(home, "Library/Application Support/" + folder); + default: + return new File(home, folder + "/"); + } + } + + public static String i18n(String key) { + try { + return RESOURCE_BUNDLE.getString(key); + } catch (Exception e) { + Logging.LOG.log(Level.SEVERE, "Cannot find key " + key + " in resource bundle", e); + return key; + } + } + + public static String i18n(String key, Object... formatArgs) { + return String.format(i18n(key), formatArgs); + } + + public static final File MINECRAFT_DIRECTORY = getWorkingDirectory("minecraft"); + public static final File HMCL_DIRECTORY = getWorkingDirectory("hmcl"); + + public static final String VERSION = "@HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@"; + public static final String NAME = "HMCL"; + public static final String TITLE = NAME + " " + VERSION; + public static final ResourceBundle RESOURCE_BUNDLE = Settings.INSTANCE.getLocale().getResourceBundle(); + public static final UpdateChecker UPDATE_CHECKER = new UpdateChecker(VersionNumber.asVersion(VERSION)); + public static final IUpgrader UPGRADER = new AppDataUpgrader(); + public static final CrashReporter CRASH_REPORTER = new CrashReporter(); + + public static final String CONTACT = "http://huangyuhui.duapp.com/hmcl.php"; + public static final String PUBLISH = "http://www.mcbbs.net/thread-142335-1-1.html"; +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Main.java b/HMCL/src/main/java/org/jackhuang/hmcl/Main.java index 47259d278..d604e70f9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Main.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Main.java @@ -17,128 +17,24 @@ */ package org.jackhuang.hmcl; -import com.jfoenix.concurrency.JFXUtilities; -import javafx.application.Application; -import javafx.application.Platform; -import javafx.stage.Stage; -import org.jackhuang.hmcl.setting.Settings; -import org.jackhuang.hmcl.task.Schedulers; -import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.ui.Controllers; -import org.jackhuang.hmcl.upgrade.AppDataUpgrader; -import org.jackhuang.hmcl.upgrade.IUpgrader; -import org.jackhuang.hmcl.upgrade.UpdateChecker; -import org.jackhuang.hmcl.util.*; +import org.jackhuang.hmcl.util.Logging; +import javax.swing.*; import java.io.File; -import java.util.Arrays; -import java.util.ResourceBundle; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -public final class Main extends Application { - - @Override - public void start(Stage primaryStage) { - Thread.currentThread().setUncaughtExceptionHandler(CRASH_REPORTER); - - try { - // When launcher visibility is set to "hide and reopen" without Platform.implicitExit = false, - // Stage.show() cannot work again because JavaFX Toolkit have already shut down. - Platform.setImplicitExit(false); - Controllers.initialize(primaryStage); - primaryStage.setResizable(false); - primaryStage.setScene(Controllers.getScene()); - primaryStage.show(); - } catch (Throwable e) { - CRASH_REPORTER.uncaughtException(Thread.currentThread(), e); - } - } +public final class Main { public static void main(String[] args) { - Thread.setDefaultUncaughtExceptionHandler(CRASH_REPORTER); + String currentDirectory = new File("").getAbsolutePath(); + if (currentDirectory.contains("!")) { + System.err.println("Exclamation mark(!) is not allowed in the path where HMCL is in. Forcibly exit."); - try { - // NetworkUtils.setUserAgentSupplier(() -> "Hello Minecraft! Launcher"); - Constants.UI_THREAD_SCHEDULER = Constants.JAVAFX_UI_THREAD_SCHEDULER; - UPGRADER.parseArguments(VersionNumber.asVersion(VERSION), Arrays.asList(args)); - - Logging.LOG.info("*** " + TITLE + " ***"); - - UPDATE_CHECKER.process(false) - .then(Task.of(Schedulers.javafx(), () -> { - if (UPDATE_CHECKER.isOutOfDate()) - Controllers.showUpdate(); - })) - .start(); - - launch(args); - } catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter. - CRASH_REPORTER.uncaughtException(Thread.currentThread(), e); - } + // No Chinese translation because both Swing and JavaFX cannot render Chinese character properly when exclamation mark exists in the path. + String message = "Exclamation mark(!) is not allowed in the path where HMCL is in.\nThe path is " + currentDirectory; + JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE); + System.exit(1); + } else + Launcher.main(args); } - public static void stopApplication() { - JFXUtilities.runInFX(() -> { - stopWithoutPlatform(); - Platform.exit(); - }); - } - - public static void stopWithoutPlatform() { - JFXUtilities.runInFX(() -> { - if (Controllers.getStage() == null) - return; - Controllers.getStage().close(); - - Logging.LOG.info("Shutting down executor services."); - Schedulers.shutdown(); - - Controllers.shutdown(); - - Lang.executeDelayed(OperatingSystem::forceGC, TimeUnit.SECONDS, 5, true); - }); - } - - public static String i18n(String key) { - try { - return RESOURCE_BUNDLE.getString(key); - } catch (Exception e) { - Logging.LOG.log(Level.SEVERE, "Cannot find key " + key + " in resource bundle", e); - return key; - } - } - - public static String i18n(String key, Object... formatArgs) { - return String.format(i18n(key), formatArgs); - } - - public static File getWorkingDirectory(String folder) { - String home = System.getProperty("user.home", "."); - switch (OperatingSystem.CURRENT_OS) { - case LINUX: - return new File(home, "." + folder + "/"); - case WINDOWS: - String appdata = System.getenv("APPDATA"); - return new File(Lang.nonNull(appdata, home), "." + folder + "/"); - case OSX: - return new File(home, "Library/Application Support/" + folder); - default: - return new File(home, folder + "/"); - } - } - - public static final File MINECRAFT_DIRECTORY = getWorkingDirectory("minecraft"); - public static final File HMCL_DIRECTORY = getWorkingDirectory("hmcl"); - - public static final String VERSION = "@HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@"; - public static final String NAME = "HMCL"; - public static final String TITLE = NAME + " " + VERSION; - public static final ResourceBundle RESOURCE_BUNDLE = Settings.INSTANCE.getLocale().getResourceBundle(); - public static final UpdateChecker UPDATE_CHECKER = new UpdateChecker(VersionNumber.asVersion(VERSION)); - public static final IUpgrader UPGRADER = new AppDataUpgrader(); - public static final CrashReporter CRASH_REPORTER = new CrashReporter(); - - public static final String CONTACT = "https://huangyuhui.duapp.com/hmcl.php"; - public static final String PUBLISH = "http://www.mcbbs.net/thread-142335-1-1.html"; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/event/AccountLoadingEvent.java b/HMCL/src/main/java/org/jackhuang/hmcl/event/AccountLoadingEvent.java new file mode 100644 index 000000000..02485fc97 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/event/AccountLoadingEvent.java @@ -0,0 +1,61 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2018 huangyuhui + * + * 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 {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.event; + +import org.jackhuang.hmcl.auth.Account; +import org.jackhuang.hmcl.setting.Profile; +import org.jackhuang.hmcl.util.ToStringBuilder; + +import java.util.Collection; +import java.util.Collections; + +/** + * This event gets fired when loading accounts. + *
+ * This event is fired on the {@link org.jackhuang.hmcl.event.EventBus#EVENT_BUS} + * + * @author huangyuhui + */ +public class AccountLoadingEvent extends Event { + + private final Collection accounts; + + /** + * Constructor. + * + * @param source {@link org.jackhuang.hmcl.setting.Settings} + */ + public AccountLoadingEvent(Object source, Collection accounts) { + super(source); + this.accounts = Collections.unmodifiableCollection(accounts); + } + + + public Collection getAccounts() { + return accounts; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("source", source) + .append("accounts", accounts) + .toString(); + } +} + diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/event/OutOfDateEvent.java b/HMCL/src/main/java/org/jackhuang/hmcl/event/OutOfDateEvent.java index 9f5d25840..fe6fbfd4b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/event/OutOfDateEvent.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/event/OutOfDateEvent.java @@ -17,6 +17,7 @@ */ package org.jackhuang.hmcl.event; +import org.jackhuang.hmcl.util.ToStringBuilder; import org.jackhuang.hmcl.util.VersionNumber; /** @@ -42,4 +43,11 @@ public final class OutOfDateEvent extends Event { return true; } + @Override + public String toString() { + return new ToStringBuilder(this) + .append("source", getSource()) + .append("version", getVersion()) + .toString(); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/event/ProfileChangedEvent.java b/HMCL/src/main/java/org/jackhuang/hmcl/event/ProfileChangedEvent.java index 87d229e44..2155d94cc 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/event/ProfileChangedEvent.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/event/ProfileChangedEvent.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.event; import org.jackhuang.hmcl.setting.Profile; +import org.jackhuang.hmcl.util.ToStringBuilder; /** * This event gets fired when the selected profile changed. @@ -44,4 +45,11 @@ public final class ProfileChangedEvent extends Event { return profile; } + @Override + public String toString() { + return new ToStringBuilder(this) + .append("source", source) + .append("profile", profile) + .toString(); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/event/ProfileLoadingEvent.java b/HMCL/src/main/java/org/jackhuang/hmcl/event/ProfileLoadingEvent.java index f5ef78dc8..81ff05379 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/event/ProfileLoadingEvent.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/event/ProfileLoadingEvent.java @@ -17,6 +17,12 @@ */ package org.jackhuang.hmcl.event; +import org.jackhuang.hmcl.setting.Profile; +import org.jackhuang.hmcl.util.ToStringBuilder; + +import java.util.Collection; +import java.util.Collections; + /** * This event gets fired when loading profiles. *
@@ -26,14 +32,29 @@ package org.jackhuang.hmcl.event; */ public class ProfileLoadingEvent extends Event { + private final Collection profiles; + /** * Constructor. * * @param source {@link org.jackhuang.hmcl.setting.Settings} */ - public ProfileLoadingEvent(Object source) { + public ProfileLoadingEvent(Object source, Collection profiles) { super(source); + + this.profiles = Collections.unmodifiableCollection(profiles); } + public Collection getProfiles() { + return profiles; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("source", source) + .append("profiles", profiles) + .toString(); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/AccountHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/AccountHelper.java index 2cfcbf058..a3e50cc26 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/AccountHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/AccountHelper.java @@ -19,7 +19,7 @@ package org.jackhuang.hmcl.game; import javafx.geometry.Rectangle2D; import javafx.scene.image.Image; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.yggdrasil.GameProfile; import org.jackhuang.hmcl.auth.yggdrasil.Texture; @@ -43,7 +43,7 @@ public final class AccountHelper { public static final AccountHelper INSTANCE = new AccountHelper(); private AccountHelper() {} - public static final File SKIN_DIR = new File(Main.HMCL_DIRECTORY, "skins"); + public static final File SKIN_DIR = new File(Launcher.HMCL_DIRECTORY, "skins"); public static void loadSkins() { loadSkins(Proxy.NO_PROXY); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java index a999a6e1a..6bef2d069 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java @@ -17,7 +17,7 @@ */ package org.jackhuang.hmcl.game; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.auth.AuthInfo; import org.jackhuang.hmcl.launch.DefaultLauncher; import org.jackhuang.hmcl.launch.ProcessListener; @@ -45,7 +45,7 @@ public final class HMCLGameLauncher extends DefaultLauncher { protected void appendJvmArgs(List result) { super.appendJvmArgs(result); - result.add("-Dminecraft.launcher.version=" + Main.VERSION); - result.add("-Dminecraft.launcher.brand=" + Main.NAME); + result.add("-Dminecraft.launcher.version=" + Launcher.VERSION); + result.add("-Dminecraft.launcher.brand=" + Launcher.NAME); } } 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 ee7695306..b98f7355f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java @@ -173,10 +173,20 @@ public class HMCLGameRepository extends DefaultGameRepository { return new File(getVersionRoot(id), "icon.png"); } - public void saveVersionSetting(String id) { + public boolean saveVersionSetting(String id) { if (!versionSettings.containsKey(id)) - return; - Lang.invoke(() -> FileUtils.writeText(getVersionSettingFile(id), GSON.toJson(versionSettings.get(id)))); + return false; + File file = getVersionSettingFile(id); + if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile())) + return false; + + try { + FileUtils.writeText(file, GSON.toJson(versionSettings.get(id))); + return true; + } catch (IOException e) { + Logging.LOG.log(Level.SEVERE, "Unable to save version setting of " + id, e); + return false; + } } public boolean forbidsVersion(String id) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java index df29d0ad1..0fcb82976 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java @@ -42,7 +42,6 @@ public final class HMCLModpackInstallTask extends Task { private final String name; private final HMCLGameRepository repository; private final Modpack modpack; - private final File run; private final List dependencies = new LinkedList<>(); private final List dependents = new LinkedList<>(); @@ -52,8 +51,8 @@ public final class HMCLModpackInstallTask extends Task { this.zipFile = zipFile; this.name = name; this.modpack = modpack; - this.run = repository.getRunDirectory(name); + File run = repository.getRunDirectory(name); File json = repository.getModpackConfiguration(name); if (repository.hasVersion(name) && !json.exists()) throw new IllegalArgumentException("Version " + name + " already exists"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackManager.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackManager.java index 8791b578d..89544be28 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackManager.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackManager.java @@ -26,7 +26,6 @@ import org.jackhuang.hmcl.util.StringUtils; import java.io.File; import java.io.IOException; -import java.util.Arrays; import java.util.List; /** diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index 715ea22be..3fa53c0c1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -19,13 +19,12 @@ package org.jackhuang.hmcl.game; import com.jfoenix.concurrency.JFXUtilities; import javafx.application.Platform; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.AuthInfo; import org.jackhuang.hmcl.auth.AuthenticationException; import org.jackhuang.hmcl.auth.ServerDisconnectException; import org.jackhuang.hmcl.download.DefaultDependencyManager; -import org.jackhuang.hmcl.download.MaintainTask; import org.jackhuang.hmcl.launch.*; import org.jackhuang.hmcl.mod.CurseCompletionTask; import org.jackhuang.hmcl.setting.LauncherVisibility; @@ -51,38 +50,34 @@ public final class LauncherHelper { public static final LauncherHelper INSTANCE = new LauncherHelper(); private LauncherHelper(){} - private TaskExecutor executor; public static final Queue PROCESSES = new ConcurrentLinkedQueue<>(); private final TaskExecutorDialogPane launchingStepsPane = new TaskExecutorDialogPane(() -> {}); - public void launch(String selectedVersion, File scriptFile) { - Profile profile = Settings.INSTANCE.getSelectedProfile(); - GameRepository repository = profile.getRepository(); - Account account = Settings.INSTANCE.getSelectedAccount(); + public void launch(Profile profile, Account account, String selectedVersion, File scriptFile) { if (account == null) - throw new IllegalStateException("No account"); + throw new IllegalArgumentException("No account"); + + GameRepository repository = profile.getRepository(); Version version = repository.getResolvedVersion(selectedVersion); VersionSetting setting = profile.getVersionSetting(selectedVersion); Platform.runLater(() -> { try { - checkGameState(profile, setting, version, () -> Schedulers.newThread().schedule(() -> launch0(selectedVersion, scriptFile))); + checkGameState(profile, setting, version, () -> Schedulers.newThread().schedule(() -> launch0(profile, account, selectedVersion, scriptFile))); } catch (InterruptedException ignore) { } }); } - private void launch0(String selectedVersion, File scriptFile) { - Profile profile = Settings.INSTANCE.getSelectedProfile(); + private void launch0(Profile profile, Account account, String selectedVersion, File scriptFile) { GameRepository repository = profile.getRepository(); DefaultDependencyManager dependencyManager = profile.getDependency(); Version version = repository.getResolvedVersion(selectedVersion); - Account account = Settings.INSTANCE.getSelectedAccount(); VersionSetting setting = profile.getVersionSetting(selectedVersion); Optional gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version)); - executor = Task.of(Schedulers.javafx(), () -> Controllers.dialog(launchingStepsPane)) + TaskExecutor executor = Task.of(Schedulers.javafx(), () -> Controllers.dialog(launchingStepsPane)) .then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES))) .then(variables -> { if (setting.isNotCheckGame()) @@ -93,7 +88,7 @@ public final class LauncherHelper { .then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.MODS))) .then(new CurseCompletionTask(dependencyManager, selectedVersion)) .then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN))) - .then(Task.of(Main.i18n("account.methods"), variables -> { + .then(Task.of(Launcher.i18n("account.methods"), variables -> { try { try { variables.set("account", account.logIn()); @@ -105,7 +100,6 @@ public final class LauncherHelper { } } catch (AuthenticationException e) { variables.set("account", DialogController.logIn(account)); - JFXUtilities.runInFX(() -> Controllers.dialog(launchingStepsPane)); } })) .then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LAUNCHING))) @@ -117,12 +111,12 @@ public final class LauncherHelper { .then(variables -> { DefaultLauncher launcher = variables.get("launcher"); if (scriptFile == null) { - return new LaunchTask<>(launcher::launch).setName(Main.i18n("version.launch")); + return new LaunchTask<>(launcher::launch).setName(Launcher.i18n("version.launch")); } else { return new LaunchTask<>(() -> { launcher.makeLaunchScript(scriptFile); return null; - }).setName(Main.i18n("version.launch_script")); + }).setName(Launcher.i18n("version.launch_script")); } }) .then(Task.of(variables -> { @@ -130,15 +124,17 @@ public final class LauncherHelper { ManagedProcess process = variables.get(LaunchTask.LAUNCH_ID); PROCESSES.add(process); if (setting.getLauncherVisibility() == LauncherVisibility.CLOSE) - Main.stopApplication(); + Launcher.stopApplication(); else launchingStepsPane.setCancel(() -> { process.stop(); Controllers.closeDialog(); }); } else - Platform.runLater(() -> - Controllers.dialog(Main.i18n("version.launch_script.success", scriptFile.getAbsolutePath()))); + Platform.runLater(() -> { + Controllers.closeDialog(); + Controllers.dialog(Launcher.i18n("version.launch_script.success", scriptFile.getAbsolutePath())); + }); })) .executor(); @@ -158,16 +154,14 @@ public final class LauncherHelper { public void onStop(boolean success, TaskExecutor executor) { if (!success) { Platform.runLater(() -> { + Controllers.closeDialog(); if (executor.getLastException() != null) Controllers.dialog(I18nException.getStackTrace(executor.getLastException()), - scriptFile == null ? Main.i18n("launch.failed") : Main.i18n("version.launch_script.failed"), + scriptFile == null ? Launcher.i18n("launch.failed") : Launcher.i18n("version.launch_script.failed"), MessageBox.ERROR_MESSAGE, Controllers::closeDialog); - else - Controllers.closeDialog(); }); } - - LauncherHelper.this.executor = null; + launchingStepsPane.setExecutor(null); } }); @@ -180,35 +174,35 @@ public final class LauncherHelper { VersionNumber gameVersion = VersionNumber.asVersion(GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse("Unknown")); JavaVersion java = setting.getJavaVersion(); if (java == null) { - Controllers.dialog(Main.i18n("launch.wrong_javadir"), Main.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept); + Controllers.dialog(Launcher.i18n("launch.wrong_javadir"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept); setting.setJava(null); java = JavaVersion.fromCurrentEnvironment(); flag = true; } - if (java.getParsedVersion() < JavaVersion.JAVA_8) { - Controllers.dialog(Main.i18n("launch.advice.newer_java"), Main.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept); + if (!flag && java.getParsedVersion() < JavaVersion.JAVA_8) { + Controllers.dialog(Launcher.i18n("launch.advice.newer_java"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept); flag = true; } - if (java.getParsedVersion() >= JavaVersion.JAVA_9 && gameVersion.compareTo(VersionNumber.asVersion("1.12.5")) < 0 && version.getMainClass().contains("launchwrapper")) { - Controllers.dialog(Main.i18n("launch.advice.java9"), Main.i18n("message.error"), MessageBox.ERROR_MESSAGE, null); + if (!flag && java.getParsedVersion() >= JavaVersion.JAVA_9 && gameVersion.compareTo(VersionNumber.asVersion("1.12.5")) < 0 && version.getMainClass().contains("launchwrapper")) { + Controllers.dialog(Launcher.i18n("launch.advice.java9"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, null); suggest = false; flag = true; } - if (java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 && + if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 && org.jackhuang.hmcl.util.Platform.IS_64_BIT) { - Controllers.dialog(Main.i18n("launch.advice.different_platform"), Main.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept); + Controllers.dialog(Launcher.i18n("launch.advice.different_platform"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept); flag = true; } - if (java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 && + if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 && setting.getMaxMemory() > 1.5 * 1024) { - Controllers.dialog(Main.i18n("launch.advice.too_large_memory_for_32bit"), Main.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept); + Controllers.dialog(Launcher.i18n("launch.advice.too_large_memory_for_32bit"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept); flag = true; } - if (OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) { - Controllers.dialog(Main.i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), Main.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept); + if (!flag && OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) { + Controllers.dialog(Launcher.i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept); flag = true; } @@ -225,10 +219,8 @@ public final class LauncherHelper { } public void emitStatus(LoadingState state) { - launchingStepsPane.setTitle(state.getLocalizedMessage()); launchingStepsPane.setSubtitle((state.ordinal() + 1) + " / " + LoadingState.values().length); - Controllers.dialog(launchingStepsPane); } private void checkExit(LauncherVisibility v) { @@ -245,8 +237,8 @@ public final class LauncherHelper { Platform.runLater(() -> { // Shut down the platform when user closed log window. Platform.setImplicitExit(true); - // If we use Main.stop(), log window will be halt immediately. - Main.stopWithoutPlatform(); + // If we use Launcher.stop(), log window will be halt immediately. + Launcher.stopWithoutPlatform(); }); break; } @@ -264,11 +256,11 @@ public final class LauncherHelper { try { setResult(supplier.get()); } catch (PermissionException e) { - throw new I18nException(Main.i18n("launch.failed.executable_permission"), e); + throw new I18nException(Launcher.i18n("launch.failed.executable_permission"), e); } catch (ProcessCreationException e) { - throw new I18nException(Main.i18n("launch.failed.creating_process") + e.getLocalizedMessage(), e); + throw new I18nException(Launcher.i18n("launch.failed.creating_process") + e.getLocalizedMessage(), e); } catch (NotDecompressingNativesException e) { - throw new I18nException(Main.i18n("launch.failed.decompressing_natives") + e.getLocalizedMessage(), e); + throw new I18nException(Launcher.i18n("launch.failed.decompressing_natives") + e.getLocalizedMessage(), e); } } @@ -360,7 +352,8 @@ public final class LauncherHelper { }); break; case CLOSE: - throw new Error("Never come to here"); + // Never come to here. + break; case KEEP: // No operations here break; @@ -385,10 +378,10 @@ public final class LauncherHelper { switch (exitType) { case JVM_ERROR: - logWindow.setTitle(Main.i18n("launch.failed.cannot_create_jvm")); + logWindow.setTitle(Launcher.i18n("launch.failed.cannot_create_jvm")); break; case APPLICATION_ERROR: - logWindow.setTitle(Main.i18n("launch.failed.exited_abnormally")); + logWindow.setTitle(Launcher.i18n("launch.failed.exited_abnormally")); break; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LoadingState.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LoadingState.java index da1ab1c10..9f71f91d8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LoadingState.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LoadingState.java @@ -17,7 +17,7 @@ */ package org.jackhuang.hmcl.game; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; public enum LoadingState { DEPENDENCIES("launch.state.dependencies"), @@ -33,6 +33,6 @@ public enum LoadingState { } public String getLocalizedMessage() { - return Main.i18n(key); + return Launcher.i18n(key); } } 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 b4e31c246..b1d6ac5e9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java @@ -17,8 +17,7 @@ */ package org.jackhuang.hmcl.setting; -import com.google.gson.JsonParseException; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.AccountFactory; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; @@ -31,6 +30,8 @@ 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.FileDownloadTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.task.TaskResult; import org.jackhuang.hmcl.util.*; import java.io.File; @@ -74,8 +75,8 @@ public final class Accounts { private static String downloadAuthlibInjector() throws Exception { AuthlibInjectorBuildInfo buildInfo = AuthlibInjectorBuildInfo.requestBuildInfo(); - File jar = new File(Main.HMCL_DIRECTORY, "authlib-injector.jar"); - File local = new File(Main.HMCL_DIRECTORY, "authlib-injector.txt"); + File jar = new File(Launcher.HMCL_DIRECTORY, "authlib-injector.jar"); + File local = new File(Launcher.HMCL_DIRECTORY, "authlib-injector.txt"); int buildNumber = 0; try { buildNumber = Integer.parseInt(FileUtils.readText(local)); @@ -88,17 +89,17 @@ public final class Accounts { return jar.getAbsolutePath(); } - public static String getAuthlibInjectorServerName(String serverIp) { + public static String getAuthlibInjectorServerName(String serverIp) throws Exception { if (AUTHLIB_INJECTOR_SERVER_NAMES.containsKey(serverIp)) return AUTHLIB_INJECTOR_SERVER_NAMES.get(serverIp); else { - try { - AuthlibInjectorServerResponse response = Constants.GSON.fromJson(NetworkUtils.doGet(NetworkUtils.toURL(serverIp)), AuthlibInjectorServerResponse.class); - AUTHLIB_INJECTOR_SERVER_NAMES.put(serverIp, response.getMeta().getServerName()); - return response.getMeta().getServerName(); - } catch (JsonParseException | IOException | NullPointerException e) { - return null; - } + AuthlibInjectorServerResponse response = Constants.GSON.fromJson(NetworkUtils.doGet(NetworkUtils.toURL(serverIp)), AuthlibInjectorServerResponse.class); + AUTHLIB_INJECTOR_SERVER_NAMES.put(serverIp, response.getMeta().getServerName()); + return response.getMeta().getServerName(); } } + + public static TaskResult getAuthlibInjectorServerNameAsync(AuthlibInjectorAccount account) { + return Task.ofResult("serverName", () -> Accounts.getAuthlibInjectorServerName(account.getServerBaseURL())); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java index be3ed382a..afd722a3c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java @@ -18,7 +18,7 @@ package org.jackhuang.hmcl.setting; import com.google.gson.annotations.SerializedName; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.util.JavaVersion; import java.util.*; @@ -35,7 +35,7 @@ final class Config { private String backgroundImage = null; @SerializedName("commonpath") - private String commonDirectory = Main.MINECRAFT_DIRECTORY.getAbsolutePath(); + private String commonDirectory = Launcher.MINECRAFT_DIRECTORY.getAbsolutePath(); @SerializedName("hasProxy") private boolean hasProxy = false; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java index fe69bba78..8b38c133f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java @@ -23,7 +23,6 @@ import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.download.MojangDownloadProvider; import org.jackhuang.hmcl.util.Lang; -import java.util.Arrays; import java.util.List; public final class DownloadProviders { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Locales.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Locales.java index e5b625fed..f23274680 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Locales.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Locales.java @@ -20,7 +20,6 @@ package org.jackhuang.hmcl.setting; import org.jackhuang.hmcl.ui.construct.UTF8Control; import org.jackhuang.hmcl.util.Lang; -import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.ResourceBundle; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.java index 4d56d93d6..6b7c4257b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.java @@ -24,6 +24,7 @@ import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.mod.ModManager; import org.jackhuang.hmcl.util.ImmediateObjectProperty; import org.jackhuang.hmcl.util.ImmediateStringProperty; +import org.jackhuang.hmcl.util.ToStringBuilder; import java.io.File; import java.lang.reflect.Type; @@ -146,6 +147,14 @@ public final class Profile { vs.setUsesGlobal(true); } + @Override + public String toString() { + return new ToStringBuilder(this) + .append("gameDir", getGameDir()) + .append("name", getName()) + .toString(); + } + public void addPropertyChangedListener(InvalidationListener listener) { nameProperty.addListener(listener); globalProperty.addListener(listener); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profiles.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profiles.java index 0f3158d7a..eb22f9607 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profiles.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profiles.java @@ -17,7 +17,7 @@ */ package org.jackhuang.hmcl.setting; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; public final class Profiles { private Profiles() { @@ -26,9 +26,9 @@ public final class Profiles { public static String getProfileDisplayName(Profile profile) { switch (profile.getName()) { case Settings.DEFAULT_PROFILE: - return Main.i18n("profile.default"); + return Launcher.i18n("profile.default"); case Settings.HOME_PROFILE: - return Main.i18n("profile.home"); + return Launcher.i18n("profile.home"); default: return profile.getName(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Proxies.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Proxies.java index db174bff3..919351d30 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Proxies.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Proxies.java @@ -20,7 +20,6 @@ package org.jackhuang.hmcl.setting; import org.jackhuang.hmcl.util.Lang; import java.net.Proxy; -import java.util.Arrays; import java.util.List; /** diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java index ef22ca06b..584d2e6a5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java @@ -23,11 +23,12 @@ import javafx.beans.property.ObjectProperty; import javafx.beans.property.StringProperty; import javafx.beans.value.ObservableValue; import javafx.scene.text.Font; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.AccountFactory; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.event.AccountLoadingEvent; import org.jackhuang.hmcl.event.EventBus; import org.jackhuang.hmcl.event.ProfileChangedEvent; import org.jackhuang.hmcl.event.ProfileLoadingEvent; @@ -62,7 +63,7 @@ public class Settings { private final Config SETTINGS = initSettings(); - private Map accounts = new HashMap<>(); + private final Map accounts = new HashMap<>(); { loadProxy(); @@ -368,6 +369,7 @@ public class Settings { public void addAccount(Account account) { accounts.put(Accounts.getAccountId(account), account); + onAccountLoading(); } public Account getAccount(String name, String character) { @@ -381,12 +383,14 @@ public class Settings { public void deleteAccount(String name, String character) { accounts.remove(Accounts.getAccountId(name, character)); + onAccountLoading(); selectedAccount.get(); } public void deleteAccount(Account account) { accounts.remove(Accounts.getAccountId(account)); + onAccountLoading(); selectedAccount.get(); } @@ -469,7 +473,7 @@ public class Settings { checkProfileMap(); if (!hasProfile(SETTINGS.getSelectedProfile())) { - SETTINGS.setSelectedProfile(getProfileMap().keySet().stream().findFirst().get()); + getProfileMap().keySet().stream().findFirst().ifPresent(SETTINGS::setSelectedProfile); Schedulers.computation().schedule(this::onProfileChanged); } return getProfile(SETTINGS.getSelectedProfile()); @@ -506,6 +510,7 @@ public class Settings { throw new IllegalArgumentException("Profile's name is empty"); getProfileMap().put(ver.getName(), ver); + Schedulers.computation().schedule(this::onProfileLoading); ver.nameProperty().setChangedListener(this::profileNameChanged); @@ -525,12 +530,12 @@ public class Settings { private void checkProfileMap() { if (getProfileMap().isEmpty()) { getProfileMap().put(DEFAULT_PROFILE, new Profile(DEFAULT_PROFILE)); - getProfileMap().put(HOME_PROFILE, new Profile(HOME_PROFILE, Main.MINECRAFT_DIRECTORY)); + getProfileMap().put(HOME_PROFILE, new Profile(HOME_PROFILE, Launcher.MINECRAFT_DIRECTORY)); } } private void onProfileChanged() { - EventBus.EVENT_BUS.fireEvent(new ProfileChangedEvent(SETTINGS, getSelectedProfile())); + EventBus.EVENT_BUS.fireEvent(new ProfileChangedEvent(this, getSelectedProfile())); getSelectedProfile().getRepository().refreshVersionsAsync().start(); } @@ -543,7 +548,11 @@ public class Settings { * Invoked by loading GUI phase. */ public void onProfileLoading() { - EventBus.EVENT_BUS.fireEvent(new ProfileLoadingEvent(SETTINGS)); + EventBus.EVENT_BUS.fireEvent(new ProfileLoadingEvent(this, getProfiles())); onProfileChanged(); } + + public void onAccountLoading() { + EventBus.EVENT_BUS.fireEvent(new AccountLoadingEvent(this, getAccounts())); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java index 6e3e3bf61..834ca4203 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java @@ -19,7 +19,7 @@ package org.jackhuang.hmcl.setting; import com.google.gson.*; import javafx.beans.InvalidationListener; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.game.LaunchOptions; import org.jackhuang.hmcl.util.*; @@ -482,8 +482,8 @@ public final class VersionSetting { return new LaunchOptions.Builder() .setGameDir(gameDir) .setJava(javaVersion) - .setVersionName(Main.TITLE) - .setProfileName(Main.TITLE) + .setVersionName(Launcher.TITLE) + .setProfileName(Launcher.TITLE) .setMinecraftArgs(getMinecraftArgs()) .setJavaArgs(getJavaArgs()) .setMaxMemory(getMaxMemory()) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountItem.java deleted file mode 100644 index 57c0db788..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountItem.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Hello Minecraft! Launcher. - * Copyright (C) 2018 huangyuhui - * - * 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 {http://www.gnu.org/licenses/}. - */ -package org.jackhuang.hmcl.ui; - -import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.JFXProgressBar; -import com.jfoenix.controls.JFXRadioButton; -import javafx.beans.binding.Bindings; -import javafx.event.EventHandler; -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.control.ToggleGroup; -import javafx.scene.effect.BlurType; -import javafx.scene.effect.DropShadow; -import javafx.scene.image.ImageView; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Pane; -import javafx.scene.layout.StackPane; -import javafx.scene.layout.VBox; -import javafx.scene.paint.Color; -import org.jackhuang.hmcl.auth.Account; -import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; -import org.jackhuang.hmcl.auth.offline.OfflineAccount; -import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; -import org.jackhuang.hmcl.game.AccountHelper; -import org.jackhuang.hmcl.setting.Accounts; -import org.jackhuang.hmcl.setting.Settings; -import org.jackhuang.hmcl.setting.Theme; -import org.jackhuang.hmcl.task.Schedulers; -import org.jackhuang.hmcl.task.Task; - -public final class AccountItem extends StackPane { - - private final Account account; - - @FXML - private Pane icon; - @FXML private VBox content; - @FXML private StackPane header; - @FXML private StackPane body; - @FXML private JFXButton btnDelete; - @FXML private JFXButton btnRefresh; - @FXML private Label lblUser; - @FXML private Label lblType; - @FXML private Label lblEmail; - @FXML private Label lblServer; - @FXML private Label lblCurrentAccount; - @FXML private JFXRadioButton chkSelected; - @FXML private JFXProgressBar pgsSkin; - @FXML private ImageView portraitView; - @FXML private HBox buttonPane; - - public AccountItem(int i, Account account, ToggleGroup toggleGroup) { - this.account = account; - - FXUtils.loadFXML(this, "/assets/fxml/account-item.fxml"); - - setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -0.5, 1.0)); - - chkSelected.setToggleGroup(toggleGroup); - btnDelete.setGraphic(SVG.delete(Theme.blackFillBinding(), 15, 15)); - btnRefresh.setGraphic(SVG.refresh(Theme.blackFillBinding(), 15, 15)); - - // create image view - icon.translateYProperty().bind(Bindings.createDoubleBinding(() -> header.getBoundsInParent().getHeight() - icon.getHeight() / 2 - 16, header.boundsInParentProperty(), icon.heightProperty())); - - chkSelected.getProperties().put("account", account); - setSelected(Settings.INSTANCE.getSelectedAccount() == account); - - lblUser.setText(account.getCharacter()); - lblType.setText(AccountsPage.accountType(account)); - lblEmail.setText(account.getUsername()); - - if (account instanceof AuthlibInjectorAccount) { - Task.ofResult("serverName", () -> Accounts.getAuthlibInjectorServerName(((AuthlibInjectorAccount) account).getServerBaseURL())) - .subscribe(Schedulers.javafx(), variables -> lblServer.setText(variables.get("serverName"))); - } - - if (account instanceof YggdrasilAccount) { - btnRefresh.setOnMouseClicked(e -> { - pgsSkin.setVisible(true); - AccountHelper.refreshSkinAsync((YggdrasilAccount) account) - .subscribe(Schedulers.javafx(), this::loadSkin); - }); - AccountHelper.loadSkinAsync((YggdrasilAccount) account) - .subscribe(Schedulers.javafx(), this::loadSkin); - } else - loadSkin(); - - if (account instanceof OfflineAccount) { // Offline Account cannot be refreshed, - buttonPane.getChildren().remove(btnRefresh); - } - } - - private void loadSkin() { - pgsSkin.setVisible(false); - portraitView.setViewport(AccountHelper.getViewport(4)); - if (account instanceof YggdrasilAccount) - portraitView.setImage(AccountHelper.getSkin((YggdrasilAccount) account, 4)); - else - portraitView.setImage(AccountHelper.getDefaultSkin(account, 4)); - FXUtils.limitSize(portraitView, 32, 32); - } - - public Account getAccount() { - return account; - } - - public void setSelected(boolean selected) { - lblCurrentAccount.setVisible(selected); - chkSelected.setSelected(selected); - } - - public void setOnDeleteButtonMouseClicked(EventHandler eventHandler) { - btnDelete.setOnMouseClicked(eventHandler); - } -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountLoginPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountLoginPane.java index 955b1b750..4d66c6935 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountLoginPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountLoginPane.java @@ -73,7 +73,7 @@ public class AccountLoginPane extends StackPane { } else if (account instanceof NoSelectedCharacterException) { dialog.close(); } else if (account instanceof Exception) { - lblCreationWarning.setText(AccountsPage.accountException((Exception) account)); + lblCreationWarning.setText(AddAccountPane.accountException((Exception) account)); } progressBar.setVisible(false); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountPage.java new file mode 100644 index 000000000..18d5a96c0 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountPage.java @@ -0,0 +1,152 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.ui; + +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXProgressBar; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.StackPane; +import org.jackhuang.hmcl.Launcher; +import org.jackhuang.hmcl.auth.Account; +import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; +import org.jackhuang.hmcl.auth.offline.OfflineAccount; +import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; +import org.jackhuang.hmcl.game.AccountHelper; +import org.jackhuang.hmcl.setting.Accounts; +import org.jackhuang.hmcl.setting.Settings; +import org.jackhuang.hmcl.setting.Theme; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.ui.construct.ComponentList; +import org.jackhuang.hmcl.ui.wizard.DecoratorPage; + +import java.util.Optional; + +public class AccountPage extends StackPane implements DecoratorPage { + private final StringProperty title; + private final ObjectProperty onDelete = new SimpleObjectProperty<>(this, "onDelete"); + private final VersionListItem item; + private final Account account; + + @FXML + private Label lblType; + @FXML + private Label lblServer; + @FXML + private Label lblCharacter; + @FXML + private Label lblEmail; + @FXML + private BorderPane paneServer; + @FXML + private BorderPane paneEmail; + @FXML + private ComponentList componentList; + @FXML + private JFXButton btnDelete; + @FXML + private JFXButton btnRefresh; + @FXML + private JFXProgressBar progressBar; + + public AccountPage(Account account, VersionListItem item) { + this.account = account; + this.item = item; + + title = new SimpleStringProperty(this, "title", Launcher.i18n("account") + " - " + account.getCharacter()); + + FXUtils.loadFXML(this, "/assets/fxml/account.fxml"); + + FXUtils.setLimitWidth(this, 300); + if (account instanceof AuthlibInjectorAccount) { + Accounts.getAuthlibInjectorServerNameAsync((AuthlibInjectorAccount) account) + .subscribe(Schedulers.javafx(), variables -> lblServer.setText(variables.get("serverName"))); + FXUtils.setLimitHeight(this, 182); + } else { + componentList.removeChildren(paneServer); + + if (account instanceof OfflineAccount) { + componentList.removeChildren(paneEmail); + FXUtils.setLimitHeight(this, 110); + } else + FXUtils.setLimitHeight(this, 145); + } + + btnDelete.setGraphic(SVG.delete(Theme.blackFillBinding(), 15, 15)); + btnRefresh.setGraphic(SVG.refresh(Theme.blackFillBinding(), 15, 15)); + + lblCharacter.setText(account.getCharacter()); + lblType.setText(AddAccountPane.accountType(account)); + lblEmail.setText(account.getUsername()); + + btnRefresh.setVisible(account instanceof YggdrasilAccount); + } + + @FXML + private void onDelete() { + Settings.INSTANCE.deleteAccount(account); + Optional.ofNullable(onDelete.get()).ifPresent(Runnable::run); + } + + @FXML + private void onRefresh() { + if (account instanceof YggdrasilAccount) { + progressBar.setVisible(true); + AccountHelper.refreshSkinAsync((YggdrasilAccount) account) + .finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> { + progressBar.setVisible(false); + + if (isDependentsSucceeded) { + Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4); + item.setImage(image, AccountHelper.getViewport(4)); + } + }).start(); + } + } + + public String getTitle() { + return title.get(); + } + + @Override + public StringProperty titleProperty() { + return title; + } + + public void setTitle(String title) { + this.title.set(title); + } + + public Runnable getOnDelete() { + return onDelete.get(); + } + + public ObjectProperty onDeleteProperty() { + return onDelete; + } + + public void setOnDelete(Runnable onDelete) { + this.onDelete.set(onDelete); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AddAccountPane.java similarity index 61% rename from HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountsPage.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/AddAccountPane.java index 34c9c31b9..643ad47da 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AddAccountPane.java @@ -1,6 +1,6 @@ /* * Hello Minecraft! Launcher. - * Copyright (C) 2018 huangyuhui + * Copyright (C) 2017 huangyuhui * * 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 @@ -19,22 +19,17 @@ package org.jackhuang.hmcl.ui; import com.jfoenix.concurrency.JFXUtilities; import com.jfoenix.controls.*; -import javafx.application.Platform; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; import javafx.fxml.FXML; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.Hyperlink; import javafx.scene.control.Label; -import javafx.scene.control.ScrollPane; -import javafx.scene.control.ToggleGroup; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.auth.*; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; import org.jackhuang.hmcl.auth.offline.OfflineAccount; @@ -48,22 +43,18 @@ import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.construct.AdvancedListBox; import org.jackhuang.hmcl.ui.construct.IconedItem; import org.jackhuang.hmcl.ui.construct.Validator; -import org.jackhuang.hmcl.ui.wizard.DecoratorPage; +import org.jackhuang.hmcl.util.Logging; import java.util.Collection; -import java.util.LinkedList; import java.util.List; import java.util.Optional; import java.util.concurrent.CountDownLatch; +import java.util.logging.Level; import java.util.stream.Collectors; +import java.util.stream.Stream; -public final class AccountsPage extends StackPane implements DecoratorPage { - private final StringProperty title = new SimpleStringProperty(this, "title", Main.i18n("account")); +public class AddAccountPane extends StackPane { - @FXML - private ScrollPane scrollPane; - @FXML private JFXMasonryPane masonryPane; - @FXML private JFXDialog dialog; @FXML private JFXTextField txtUsername; @FXML private JFXPasswordField txtPassword; @FXML private Label lblCreationWarning; @@ -73,16 +64,17 @@ public final class AccountsPage extends StackPane implements DecoratorPage { @FXML private JFXProgressBar progressBar; @FXML private Label lblAddInjectorServer; @FXML private Hyperlink linkAddInjectorServer; + @FXML private JFXDialogLayout layout; + private final Runnable finalization; - { - FXUtils.loadFXML(this, "/assets/fxml/account.fxml"); + public AddAccountPane(Runnable finalization) { + this.finalization = finalization; - getChildren().remove(dialog); - dialog.setDialogContainer(this); + FXUtils.loadFXML(this, "/assets/fxml/account-add.fxml"); - FXUtils.smoothScrolling(scrollPane); + loadServers(); - cboType.getItems().setAll(Main.i18n("account.methods.offline"), Main.i18n("account.methods.yggdrasil"), Main.i18n("account.methods.authlib_injector")); + cboType.getItems().setAll(Launcher.i18n("account.methods.offline"), Launcher.i18n("account.methods.yggdrasil"), Launcher.i18n("account.methods.authlib_injector")); cboType.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> { txtPassword.setVisible(newValue.intValue() != 0); lblPassword.setVisible(newValue.intValue() != 0); @@ -98,42 +90,20 @@ public final class AccountsPage extends StackPane implements DecoratorPage { txtPassword.setOnAction(e -> onCreationAccept()); txtUsername.setOnAction(e -> onCreationAccept()); - txtUsername.getValidators().add(new Validator(Main.i18n("input.email"), str -> !txtPassword.isVisible() || str.contains("@"))); + txtUsername.getValidators().add(new Validator(Launcher.i18n("input.email"), str -> !txtPassword.isVisible() || str.contains("@"))); - FXUtils.onChangeAndOperate(Settings.INSTANCE.selectedAccountProperty(), account -> { - for (Node node : masonryPane.getChildren()) - if (node instanceof AccountItem) - ((AccountItem) node).setSelected(account == ((AccountItem) node).getAccount()); - }); - - loadAccounts(); - loadServers(); - - if (Settings.INSTANCE.getAccounts().isEmpty()) - addNewAccount(); } - public void loadAccounts() { - List children = new LinkedList<>(); - int i = 0; - ToggleGroup group = new ToggleGroup(); - for (Account account : Settings.INSTANCE.getAccounts()) { - children.add(buildNode(++i, account, group)); - } - group.selectedToggleProperty().addListener((a, b, newValue) -> { - if (newValue != null) - Settings.INSTANCE.setSelectedAccount((Account) newValue.getProperties().get("account")); - }); - FXUtils.resetChildren(masonryPane, children); - Platform.runLater(() -> { - masonryPane.requestLayout(); - scrollPane.requestLayout(); - }); - } - - public void loadServers() { + private void loadServers() { Task.ofResult("list", () -> Settings.INSTANCE.getAuthlibInjectorServerURLs().parallelStream() - .map(serverURL -> new TwoLineListItem(Accounts.getAuthlibInjectorServerName(serverURL), serverURL)) + .flatMap(serverURL -> { + try { + return Stream.of(new TwoLineListItem(Accounts.getAuthlibInjectorServerName(serverURL), serverURL)); + } catch (Exception e) { + Logging.LOG.log(Level.WARNING, "Authlib-injector server root " + serverURL + " cannot be recognized.", e); + return Stream.empty(); + } + }) .collect(Collectors.toList())) .subscribe(Task.of(Schedulers.javafx(), variables -> { cboServers.getItems().setAll(variables.>get("list")); @@ -142,28 +112,6 @@ public final class AccountsPage extends StackPane implements DecoratorPage { })); } - private Node buildNode(int i, Account account, ToggleGroup group) { - AccountItem item = new AccountItem(i, account, group); - item.setOnDeleteButtonMouseClicked(e -> { - Settings.INSTANCE.deleteAccount(account); - Platform.runLater(this::loadAccounts); - }); - return item; - } - - @FXML - private void addNewAccount() { - txtUsername.setText(""); - txtPassword.setText(""); - lblCreationWarning.setText(""); - dialog.show(); - } - - @FXML - private void onAddInjecterServer() { - Controllers.navigate(Controllers.getServersPage()); - } - @FXML private void onCreationAccept() { int type = cboType.getSelectionModel().getSelectedIndex(); @@ -173,23 +121,22 @@ public final class AccountsPage extends StackPane implements DecoratorPage { progressBar.setVisible(true); lblCreationWarning.setText(""); Task.ofResult("create_account", () -> { - AccountFactory factory; - switch (type) { - case 0: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.OFFLINE_ACCOUNT_KEY); break; - case 1: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.YGGDRASIL_ACCOUNT_KEY); break; - case 2: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.AUTHLIB_INJECTOR_ACCOUNT_KEY); break; - default: throw new Error(); - } + AccountFactory factory; + switch (type) { + case 0: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.OFFLINE_ACCOUNT_KEY); break; + case 1: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.YGGDRASIL_ACCOUNT_KEY); break; + case 2: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.AUTHLIB_INJECTOR_ACCOUNT_KEY); break; + default: throw new Error(); + } - return factory.create(new Selector(), username, password, apiRoot, Settings.INSTANCE.getProxy()); + return factory.create(new Selector(), username, password, apiRoot, Settings.INSTANCE.getProxy()); }).finalized(Schedulers.javafx(), variables -> { Settings.INSTANCE.addAccount(variables.get("create_account")); - dialog.close(); - loadAccounts(); progressBar.setVisible(false); + finalization.run(); }, exception -> { if (exception instanceof NoSelectedCharacterException) { - dialog.close(); + finalization.run(); } else { lblCreationWarning.setText(accountException(exception)); } @@ -199,46 +146,24 @@ public final class AccountsPage extends StackPane implements DecoratorPage { @FXML private void onCreationCancel() { - dialog.close(); + finalization.run(); } - public String getTitle() { - return title.get(); + @FXML + private void onAddInjecterServer() { + finalization.run(); + Controllers.navigate(Controllers.getServersPage()); } - @Override - public StringProperty titleProperty() { - return title; + private void showSelector(Node node) { + getChildren().setAll(node); } - public void setTitle(String title) { - this.title.set(title); + private void closeSelector() { + getChildren().setAll(layout); } - public static String accountException(Exception exception) { - if (exception instanceof InvalidCredentialsException) { - return Main.i18n("account.failed.invalid_credentials"); - } else if (exception instanceof NoCharacterException) { - return Main.i18n("account.failed.no_charactor"); - } else if (exception instanceof ServerDisconnectException) { - return Main.i18n("account.failed.connect_authentication_server"); - } else if (exception instanceof InvalidTokenException) { - return Main.i18n("account.failed.invalid_token"); - } else if (exception instanceof InvalidPasswordException) { - return Main.i18n("account.failed.invalid_password"); - } else { - return exception.getClass() + ": " + exception.getLocalizedMessage(); - } - } - - public static String accountType(Account account) { - if (account instanceof OfflineAccount) return Main.i18n("account.methods.offline"); - else if (account instanceof AuthlibInjectorAccount) return Main.i18n("account.methods.authlib_injector"); - else if (account instanceof YggdrasilAccount) return Main.i18n("account.methods.yggdrasil"); - else throw new Error(Main.i18n("account.methods.no_method") + ": " + account); - } - - private static class Selector extends BorderPane implements CharacterSelector { + private class Selector extends BorderPane implements CharacterSelector { private final AdvancedListBox listBox = new AdvancedListBox(); private final JFXButton cancel = new JFXButton(); @@ -248,11 +173,11 @@ public final class AccountsPage extends StackPane implements DecoratorPage { { setStyle("-fx-padding: 8px;"); - cancel.setText(Main.i18n("button.cancel")); + cancel.setText(Launcher.i18n("button.cancel")); StackPane.setAlignment(cancel, Pos.BOTTOM_RIGHT); cancel.setOnMouseClicked(e -> latch.countDown()); - listBox.startCategory(Main.i18n("account.choose")); + listBox.startCategory(Launcher.i18n("account.choose")); setCenter(listBox); @@ -293,20 +218,43 @@ public final class AccountsPage extends StackPane implements DecoratorPage { listBox.add(accountItem); } - JFXUtilities.runInFX(() -> Controllers.dialog(this)); + JFXUtilities.runInFX(() -> showSelector(this)); try { latch.await(); - JFXUtilities.runInFX(Controllers::closeDialog); - if (selectedProfile == null) throw new NoSelectedCharacterException(account); + JFXUtilities.runInFX(AddAccountPane.this::closeSelector); + return selectedProfile; } catch (InterruptedException ignore) { throw new NoSelectedCharacterException(account); } } } + + public static String accountException(Exception exception) { + if (exception instanceof InvalidCredentialsException) { + return Launcher.i18n("account.failed.invalid_credentials"); + } else if (exception instanceof NoCharacterException) { + return Launcher.i18n("account.failed.no_charactor"); + } else if (exception instanceof ServerDisconnectException) { + return Launcher.i18n("account.failed.connect_authentication_server"); + } else if (exception instanceof InvalidTokenException) { + return Launcher.i18n("account.failed.invalid_token"); + } else if (exception instanceof InvalidPasswordException) { + return Launcher.i18n("account.failed.invalid_password"); + } else { + return exception.getClass() + ": " + exception.getLocalizedMessage(); + } + } + + public static String accountType(Account account) { + if (account instanceof OfflineAccount) return Launcher.i18n("account.methods.offline"); + else if (account instanceof AuthlibInjectorAccount) return Launcher.i18n("account.methods.authlib_injector"); + else if (account instanceof YggdrasilAccount) return Launcher.i18n("account.methods.yggdrasil"); + else throw new Error(Launcher.i18n("account.methods.no_method") + ": " + account); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AuthlibInjectorServersPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AuthlibInjectorServersPage.java index f9e874077..dd5e20352 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AuthlibInjectorServersPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AuthlibInjectorServersPage.java @@ -9,7 +9,7 @@ import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServerInfo; import org.jackhuang.hmcl.setting.Accounts; import org.jackhuang.hmcl.setting.Settings; @@ -18,14 +18,16 @@ import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.animation.TransitionHandler; import org.jackhuang.hmcl.ui.wizard.DecoratorPage; +import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.NetworkUtils; import java.util.Collection; -import java.util.Objects; +import java.util.logging.Level; import java.util.stream.Collectors; +import java.util.stream.Stream; public class AuthlibInjectorServersPage extends StackPane implements DecoratorPage { - private final StringProperty title = new SimpleStringProperty(this, "title", Main.i18n("account.injector.server")); + private final StringProperty title = new SimpleStringProperty(this, "title", Launcher.i18n("account.injector.server")); @FXML private ScrollPane scrollPane; @FXML private StackPane addServerContainer; @@ -69,7 +71,14 @@ public class AuthlibInjectorServersPage extends StackPane implements DecoratorPa spinner.setVisible(true); Task.ofResult("list", () -> Settings.INSTANCE.getAuthlibInjectorServerURLs().parallelStream() - .map(serverURL -> new AuthlibInjectorServerItem(new AuthlibInjectorServerInfo(serverURL, Accounts.getAuthlibInjectorServerName(serverURL)), this::removeServer)) + .flatMap(serverURL -> { + try { + return Stream.of(new AuthlibInjectorServerItem(new AuthlibInjectorServerInfo(serverURL, Accounts.getAuthlibInjectorServerName(serverURL)), this::removeServer)); + } catch (Exception e) { + Logging.LOG.log(Level.WARNING, "Authlib-injector server root " + serverURL + " cannot be recognized.", e); + return Stream.empty(); + } + }) .collect(Collectors.toList())) .subscribe(Task.of(Schedulers.javafx(), variables -> { listPane.getChildren().setAll(variables.>get("list")); @@ -105,7 +114,7 @@ public class AuthlibInjectorServersPage extends StackPane implements DecoratorPa progressBar.setVisible(true); addServerPane.setDisable(true); - Task.ofResult("serverName", () -> Objects.requireNonNull(Accounts.getAuthlibInjectorServerName(serverIp))) + Task.ofResult("serverName", () -> Accounts.getAuthlibInjectorServerName(serverIp)) .finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> { progressBar.setVisible(false); addServerPane.setDisable(false); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java index c734d4d83..52dad1769 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -17,13 +17,14 @@ */ package org.jackhuang.hmcl.ui; +import com.jfoenix.concurrency.JFXUtilities; import com.jfoenix.controls.JFXDialog; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.scene.layout.Region; import javafx.stage.Stage; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.setting.Settings; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.TaskExecutor; @@ -32,6 +33,7 @@ import org.jackhuang.hmcl.ui.construct.MessageBox; import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane; import org.jackhuang.hmcl.util.JavaVersion; +import org.jackhuang.hmcl.util.Lang; import java.util.function.Consumer; @@ -93,7 +95,9 @@ public final class Controllers { public static void initialize(Stage stage) { Controllers.stage = stage; - decorator = new Decorator(stage, getMainPage(), Main.TITLE, false, true); + stage.setOnCloseRequest(e -> Launcher.stopApplication()); + + decorator = new Decorator(stage, getMainPage(), Launcher.TITLE, false, true); decorator.showPage(null); leftPaneController = new LeftPaneController(decorator.getLeftPane()); @@ -110,7 +114,7 @@ public final class Controllers { stage.setMaxHeight(521); stage.getIcons().add(new Image("/assets/img/icon.png")); - stage.setTitle(Main.TITLE); + stage.setTitle(Launcher.TITLE); } public static Region getDialogContent() { @@ -119,7 +123,7 @@ public final class Controllers { public static JFXDialog dialog(Region content) { // TODO: temp fix - decorator.showDialog(new Region()); + decorator.showDialog(Lang.apply(new Region(), region -> region.getProperties().put("controllers", true))); return decorator.showDialog(content); } @@ -147,12 +151,15 @@ public final class Controllers { dialog(new InputDialogPane(text, decorator.getDialog(), onResult)); } + public static void taskDialog(TaskExecutor executor, String title, String subtitle) { + taskDialog(executor, title, subtitle, null); + } + public static void taskDialog(TaskExecutor executor, String title, String subtitle, Runnable onCancel) { TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel); pane.setTitle(title); pane.setSubtitle(subtitle); pane.setExecutor(executor); - executor.start(); dialog(pane); } @@ -168,7 +175,7 @@ public final class Controllers { } public static void showUpdate() { - getDecorator().showUpdate(); + getLeftPaneController().showUpdate(); } public static void shutdown() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java index e9e4eb16c..285c6e640 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java @@ -27,7 +27,7 @@ import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; import javafx.stage.Stage; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; /** * @author huangyuhui @@ -36,10 +36,10 @@ public class CrashWindow extends Stage { public CrashWindow(String text) { Label lblCrash = new Label(); - if (Main.UPDATE_CHECKER.isOutOfDate()) - lblCrash.setText(Main.i18n("launcher.crash_out_dated")); + if (Launcher.UPDATE_CHECKER.isOutOfDate()) + lblCrash.setText(Launcher.i18n("launcher.crash_out_dated")); else - lblCrash.setText(Main.i18n("launcher.crash")); + lblCrash.setText(Launcher.i18n("launcher.crash")); lblCrash.setWrapText(true); TextArea textArea = new TextArea(); @@ -47,8 +47,8 @@ public class CrashWindow extends Stage { textArea.setEditable(false); Button btnContact = new Button(); - btnContact.setText(Main.i18n("launcher.contact")); - btnContact.setOnMouseClicked(event -> FXUtils.openLink(Main.CONTACT)); + btnContact.setText(Launcher.i18n("launcher.contact")); + btnContact.setOnMouseClicked(event -> FXUtils.openLink(Launcher.CONTACT)); HBox box = new HBox(); box.setStyle("-fx-padding: 8px;"); box.getChildren().add(btnContact); @@ -65,7 +65,7 @@ public class CrashWindow extends Stage { Scene scene = new Scene(pane, 800, 480); setScene(scene); getIcons().add(new Image("/assets/img/icon.png")); - setTitle(Main.i18n("message.error")); + setTitle(Launcher.i18n("message.error")); setOnCloseRequest(e -> System.exit(1)); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Decorator.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Decorator.java index 096a39c56..863fb4c3d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Decorator.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Decorator.java @@ -17,6 +17,7 @@ */ package org.jackhuang.hmcl.ui; +import com.jfoenix.concurrency.JFXUtilities; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXDialog; import com.jfoenix.controls.JFXDrawer; @@ -45,7 +46,7 @@ import javafx.scene.shape.Rectangle; import javafx.stage.Screen; import javafx.stage.Stage; import javafx.stage.StageStyle; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.setting.EnumBackgroundImage; import org.jackhuang.hmcl.setting.Settings; import org.jackhuang.hmcl.setting.Theme; @@ -53,6 +54,7 @@ import org.jackhuang.hmcl.ui.animation.AnimationProducer; import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.animation.TransitionHandler; import org.jackhuang.hmcl.ui.construct.AdvancedListBox; +import org.jackhuang.hmcl.ui.construct.StackContainerPane; import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogWizardDisplayer; import org.jackhuang.hmcl.ui.wizard.*; import org.jackhuang.hmcl.util.FileUtils; @@ -74,7 +76,7 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza private static final SVGGlyph close = Lang.apply(new SVGGlyph(0, "CLOSE", "M810 274l-238 238 238 238-60 60-238-238-238 238-60-60 238-238-238-238 60-60 238 238 238-238z", Color.WHITE), glyph -> { glyph.setPrefSize(12, 12); glyph.setSize(12, 12); }); - private final ObjectProperty onCloseButtonAction = new SimpleObjectProperty<>(Main::stopApplication); + private final ObjectProperty onCloseButtonAction; private final BooleanProperty customMaximize = new SimpleBooleanProperty(false); private final Stage primaryStage; @@ -82,11 +84,13 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza private final boolean max, min; private final WizardController wizardController = new WizardController(this); private final Queue cancelQueue = new ConcurrentLinkedQueue<>(); + private final JFXDialog dialog; private double xOffset, yOffset, newX, newY, initX, initY; - private boolean allowMove, isDragging, dialogShown, maximized; + private boolean allowMove, isDragging, maximized; private BoundingBox originalBox, maximizedBox; private final TransitionHandler animationHandler; + private final StackContainerPane dialogPane = new StackContainerPane(); @FXML private StackPane contentPlaceHolder; @@ -119,16 +123,12 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza @FXML private JFXHamburger titleBurger; @FXML - private JFXDialog dialog; - @FXML private JFXButton btnMin; @FXML private JFXButton btnMax; @FXML private JFXButton btnClose; @FXML - private HBox updatePane; - @FXML private HBox navLeft; public Decorator(Stage primaryStage, Node mainPage, String title) { @@ -143,8 +143,7 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza FXUtils.loadFXML(this, "/assets/fxml/decorator.fxml"); - updatePane.setCursor(Cursor.HAND); - updatePane.setOnMouseClicked(event -> Main.UPDATE_CHECKER.checkOutdate()); + onCloseButtonAction = new SimpleObjectProperty<>(this, "onCloseButtonAction", Launcher::stopApplication); primaryStage.initStyle(StageStyle.UNDECORATED); btnClose.setGraphic(close); @@ -168,9 +167,21 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza btnMax.fire(); }); + dialog = new JFXDialog() { + @Override + public void close() { + dialogPane.pop(); + if (dialogPane.getChildren().isEmpty()) + Platform.runLater(() -> { + if (dialogPane.getChildren().isEmpty()) + super.close(); + }); + } + }; + dialog.setOverlayClose(false); + drawerWrapper.getChildren().add(0, dialog); dialog.setDialogContainer(drawerWrapper); - dialog.setOnDialogClosed(e -> dialogShown = false); - dialog.setOnDialogOpened(e -> dialogShown = true); + dialog.setContent(dialogPane); if (!min) buttonsContainer.getChildren().remove(btnMin); if (!max) buttonsContainer.getChildren().remove(btnMax); @@ -474,10 +485,6 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza } } - public void showUpdate() { - updatePane.setVisible(true); - } - private void showCloseNavButton() { navLeft.getChildren().add(closeNavButton); } @@ -538,10 +545,10 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza } } - public JFXDialog showDialog(Region content) { - dialog.setContent(content); - if (!dialogShown) + public JFXDialog showDialog(Node node) { + if (dialogPane.isEmpty()) dialog.show(); + dialogPane.push(node); return dialog; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/DialogController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/DialogController.java index 97325326c..f3d7c48e2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/DialogController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/DialogController.java @@ -37,11 +37,7 @@ public final class DialogController { AccountLoginPane pane = new AccountLoginPane(account, it -> { res.set(it); latch.countDown(); - Controllers.closeDialog(); - }, () -> { - latch.countDown(); - Controllers.closeDialog(); - }); + }, latch::countDown); pane.setDialog(Controllers.dialog(pane)); }); latch.await(); 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 b8ddba831..d62349ce6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -23,6 +23,7 @@ import javafx.animation.Animation; import javafx.animation.Interpolator; import javafx.animation.KeyFrame; import javafx.animation.Timeline; +import javafx.application.Platform; import javafx.beans.property.Property; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; @@ -41,14 +42,17 @@ import javafx.scene.input.ScrollEvent; import javafx.scene.layout.Region; import javafx.scene.shape.Rectangle; import javafx.util.Duration; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.util.*; import java.io.File; import java.io.IOException; import java.net.URI; import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.logging.Level; import static org.jackhuang.hmcl.util.ReflectionHelper.call; @@ -184,7 +188,7 @@ public final class FXUtils { } public static void loadFXML(Node node, String absolutePath) { - FXMLLoader loader = new FXMLLoader(node.getClass().getResource(absolutePath), Main.RESOURCE_BUNDLE); + FXMLLoader loader = new FXMLLoader(node.getClass().getResource(absolutePath), Launcher.RESOURCE_BUNDLE); loader.setRoot(node); loader.setController(node); Lang.invoke((ExceptionalSupplier) loader::load); @@ -368,6 +372,31 @@ public final class FXUtils { }); } + public static T runInUIThread(Supplier supplier) { + if (javafx.application.Platform.isFxApplicationThread()) { + return supplier.get(); + } else { + CountDownLatch doneLatch = new CountDownLatch(1); + AtomicReference reference = new AtomicReference<>(); + Platform.runLater(() -> { + try { + reference.set(supplier.get()); + } finally { + doneLatch.countDown(); + } + + }); + + try { + doneLatch.await(); + } catch (InterruptedException var3) { + Thread.currentThread().interrupt(); + } + + return reference.get(); + } + } + public static final Image DEFAULT_ICON = new Image("/assets/img/icon.png"); public static final Interpolator SINE = new Interpolator() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerController.java index 10c614e0d..4a5ce5886 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerController.java @@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui; import javafx.fxml.FXML; import javafx.scene.control.ScrollPane; import javafx.scene.layout.VBox; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.download.MaintainTask; import org.jackhuang.hmcl.download.game.VersionJsonSaveTask; import org.jackhuang.hmcl.game.GameVersion; @@ -89,7 +90,7 @@ public class InstallerController { Optional gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)); if (!gameVersion.isPresent()) - Controllers.dialog("version.cannot_read"); + Controllers.dialog(Launcher.i18n("version.cannot_read")); else Controllers.getDecorator().startWizard(new InstallerWizardProvider(profile, gameVersion.get(), version, forge, liteLoader, optiFine)); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java index f227d90ca..225b8d8df 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java @@ -21,7 +21,7 @@ import com.jfoenix.effects.JFXDepthManager; import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.layout.BorderPane; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import java.util.function.Consumer; @@ -44,7 +44,7 @@ public class InstallerItem extends BorderPane { setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;"); JFXDepthManager.setDepth(this, 1); lblInstallerArtifact.setText(artifact); - lblInstallerVersion.setText(Main.i18n("archive.version") + ": " + version); + lblInstallerVersion.setText(Launcher.i18n("archive.version") + ": " + version); } @FXML diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.java index b67caaf05..6b845f048 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.java @@ -19,27 +19,26 @@ package org.jackhuang.hmcl.ui; import com.jfoenix.concurrency.JFXUtilities; import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXPopup; import javafx.application.Platform; import javafx.scene.Node; +import javafx.scene.control.Tooltip; import javafx.scene.image.Image; -import javafx.scene.layout.BorderPane; +import javafx.scene.input.MouseButton; import javafx.scene.layout.VBox; -import javafx.scene.text.Text; -import org.jackhuang.hmcl.Main; +import javafx.scene.paint.Color; +import org.jackhuang.hmcl.Launcher; +import org.jackhuang.hmcl.auth.Account; +import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; +import org.jackhuang.hmcl.auth.offline.OfflineAccount; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; -import org.jackhuang.hmcl.event.EventBus; -import org.jackhuang.hmcl.event.ProfileChangedEvent; -import org.jackhuang.hmcl.event.ProfileLoadingEvent; -import org.jackhuang.hmcl.event.RefreshedVersionsEvent; +import org.jackhuang.hmcl.event.*; import org.jackhuang.hmcl.game.AccountHelper; import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.game.ModpackHelper; import org.jackhuang.hmcl.mod.Modpack; import org.jackhuang.hmcl.mod.UnsupportedModpackException; -import org.jackhuang.hmcl.setting.Profile; -import org.jackhuang.hmcl.setting.Profiles; -import org.jackhuang.hmcl.setting.Settings; -import org.jackhuang.hmcl.setting.Theme; +import org.jackhuang.hmcl.setting.*; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.construct.AdvancedListBox; @@ -55,50 +54,56 @@ import java.util.Objects; public final class LeftPaneController { private final AdvancedListBox leftPane; private final VBox profilePane = new VBox(); - private final VersionListItem accountItem = new VersionListItem("", ""); + private final VBox accountPane = new VBox(); + private final IconedItem launcherSettingsItem; + private final VersionListItem missingAccountItem = new VersionListItem(Launcher.i18n("account.missing"), Launcher.i18n("message.unknown")); public LeftPaneController(AdvancedListBox leftPane) { this.leftPane = leftPane; - leftPane.startCategory(Main.i18n("account").toUpperCase()) - .add(Lang.apply(new RipplerContainer(accountItem), rippler -> { - rippler.setOnMouseClicked(e -> Controllers.navigate(new AccountsPage())); - accountItem.setOnSettingsButtonClicked(() -> Controllers.navigate(new AccountsPage())); - })) - .startCategory(Main.i18n("launcher").toUpperCase()) - .add(Lang.apply(new IconedItem(SVG.gear(Theme.blackFillBinding(), 20, 20), Main.i18n("settings.launcher")), iconedItem -> { - iconedItem.prefWidthProperty().bind(leftPane.widthProperty()); - iconedItem.setOnMouseClicked(e -> Controllers.navigate(Controllers.getSettingsPage())); - })) - .add(new ClassTitle(Lang.apply(new BorderPane(), borderPane -> { - borderPane.setLeft(Lang.apply(new VBox(), vBox -> vBox.getChildren().setAll(new Text(Main.i18n("profile.title").toUpperCase())))); - JFXButton addProfileButton = new JFXButton(); - addProfileButton.setGraphic(SVG.plus(Theme.blackFillBinding(), 10, 10)); - addProfileButton.getStyleClass().add("toggle-icon-tiny"); - addProfileButton.setOnMouseClicked(e -> + this.launcherSettingsItem = Lang.apply(new IconedItem(SVG.gear(Theme.blackFillBinding(), 20, 20), Launcher.i18n("settings.launcher")), iconedItem -> { + iconedItem.prefWidthProperty().bind(leftPane.widthProperty()); + iconedItem.setOnMouseClicked(e -> Controllers.navigate(Controllers.getSettingsPage())); + }); + + leftPane + .add(new ClassTitle(Launcher.i18n("account").toUpperCase(), Lang.apply(new JFXButton(), button -> { + button.setGraphic(SVG.plus(Theme.blackFillBinding(), 10, 10)); + button.getStyleClass().add("toggle-icon-tiny"); + button.setOnMouseClicked(e -> addNewAccount()); + }))) + .add(accountPane) + .startCategory(Launcher.i18n("launcher").toUpperCase()) + .add(launcherSettingsItem) + .add(new ClassTitle(Launcher.i18n("profile.title").toUpperCase(), Lang.apply(new JFXButton(), button -> { + button.setGraphic(SVG.plus(Theme.blackFillBinding(), 10, 10)); + button.getStyleClass().add("toggle-icon-tiny"); + button.setOnMouseClicked(e -> Controllers.getDecorator().showPage(new ProfilePage(null))); - borderPane.setRight(addProfileButton); }))) .add(profilePane); + EventBus.EVENT_BUS.channel(AccountLoadingEvent.class).register(this::onAccountsLoading); EventBus.EVENT_BUS.channel(ProfileLoadingEvent.class).register(this::onProfilesLoading); EventBus.EVENT_BUS.channel(ProfileChangedEvent.class).register(this::onProfileChanged); EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(this::onRefreshedVersions); - FXUtils.onChangeAndOperate(Settings.INSTANCE.selectedAccountProperty(), it -> { - if (it == null) { - accountItem.setVersionName(Main.i18n("account.missing")); - accountItem.setGameVersion(Main.i18n("message.unknown")); - } else { - accountItem.setVersionName(it.getCharacter()); - accountItem.setGameVersion(AccountsPage.accountType(it)); - } + FXUtils.onChangeAndOperate(Settings.INSTANCE.selectedAccountProperty(), this::onSelectedAccountChanged); + onAccountsLoading(); + } - if (it instanceof YggdrasilAccount) { - Image image = AccountHelper.getSkin((YggdrasilAccount) it, 4); - accountItem.setImage(image, AccountHelper.getViewport(4)); - } else - accountItem.setImage(AccountHelper.getDefaultSkin(it, 4), AccountHelper.getViewport(4)); + private void addNewAccount() { + Controllers.dialog(new AddAccountPane(Controllers::closeDialog)); + } + + private void onSelectedAccountChanged(Account newAccount) { + Platform.runLater(() -> { + for (Node node : accountPane.getChildren()) { + if (node instanceof RipplerContainer && node.getProperties().get("account") instanceof Account) { + boolean current = Objects.equals(node.getProperties().get("account"), newAccount); + ((RipplerContainer) node).setSelected(current); + } + } }); } @@ -110,7 +115,7 @@ public final class LeftPaneController { if (node instanceof RipplerContainer && node.getProperties().get("profile") instanceof String) { boolean current = Objects.equals(node.getProperties().get("profile"), profile.getName()); ((RipplerContainer) node).setSelected(current); - ((VersionListItem) ((RipplerContainer) node).getContainer()).setGameVersion(current ? Main.i18n("profile.selected") : ""); + ((VersionListItem) ((RipplerContainer) node).getContainer()).setGameVersion(current ? Launcher.i18n("profile.selected") : ""); } } }); @@ -121,7 +126,7 @@ public final class LeftPaneController { for (Profile profile : Settings.INSTANCE.getProfiles()) { VersionListItem item = new VersionListItem(Profiles.getProfileDisplayName(profile)); RipplerContainer ripplerContainer = new RipplerContainer(item); - item.setOnSettingsButtonClicked(() -> Controllers.getDecorator().showPage(new ProfilePage(profile))); + item.setOnSettingsButtonClicked(e -> Controllers.getDecorator().showPage(new ProfilePage(profile))); ripplerContainer.setOnMouseClicked(e -> Settings.INSTANCE.setSelectedProfile(profile)); ripplerContainer.getProperties().put("profile", profile.getName()); ripplerContainer.maxWidthProperty().bind(leftPane.widthProperty()); @@ -130,6 +135,62 @@ public final class LeftPaneController { Platform.runLater(() -> profilePane.getChildren().setAll(list)); } + private static String accountType(Account account) { + if (account instanceof OfflineAccount) return Launcher.i18n("account.methods.offline"); + else if (account instanceof YggdrasilAccount) return account.getUsername(); + else throw new Error(Launcher.i18n("account.methods.no_method") + ": " + account); + } + + private void onAccountsLoading() { + LinkedList list = new LinkedList<>(); + Account selectedAccount = Settings.INSTANCE.getSelectedAccount(); + for (Account account : Settings.INSTANCE.getAccounts()) { + VersionListItem item = new VersionListItem(account.getCharacter(), accountType(account)); + RipplerContainer ripplerContainer = new RipplerContainer(item); + item.setOnSettingsButtonClicked(e -> { + AccountPage accountPage = new AccountPage(account, item); + JFXPopup popup = new JFXPopup(accountPage); + accountPage.setOnDelete(popup::hide); + popup.show((Node) e.getSource(), JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, e.getX(), e.getY()); + }); + ripplerContainer.setOnMouseClicked(e -> { + if (e.getButton() == MouseButton.PRIMARY) + Settings.INSTANCE.setSelectedAccount(account); + }); + ripplerContainer.getProperties().put("account", account); + ripplerContainer.maxWidthProperty().bind(leftPane.widthProperty()); + + if (account instanceof YggdrasilAccount) { + Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4); + item.setImage(image, AccountHelper.getViewport(4)); + } else + item.setImage(AccountHelper.getDefaultSkin(account, 4), AccountHelper.getViewport(4)); + + if (account instanceof AuthlibInjectorAccount) + Accounts.getAuthlibInjectorServerNameAsync((AuthlibInjectorAccount) account) + .subscribe(Schedulers.javafx(), variables -> FXUtils.installTooltip(ripplerContainer, 500, 5000, 0, new Tooltip(variables.get("serverName")))); + + if (selectedAccount == account) + ripplerContainer.setSelected(true); + + list.add(ripplerContainer); + } + + if (Settings.INSTANCE.getAccounts().isEmpty()) { + RipplerContainer container = new RipplerContainer(missingAccountItem); + missingAccountItem.setOnSettingsButtonClicked(e -> addNewAccount()); + container.setOnMouseClicked(e -> addNewAccount()); + list.add(container); + } + + Platform.runLater(() -> accountPane.getChildren().setAll(list)); + } + + public void showUpdate() { + launcherSettingsItem.setText(Launcher.i18n("update.found")); + launcherSettingsItem.setTextFill(Color.RED); + } + private boolean checkedModpack = false; private void onRefreshedVersions(RefreshedVersionsEvent event) { @@ -148,8 +209,8 @@ public final class LeftPaneController { .with(Task.of(Schedulers.javafx(), () -> { Controllers.closeDialog(); checkAccount(); - })).executor(), - Main.i18n("modpack.installing"), "", null); + })).executor(true), + Launcher.i18n("modpack.installing"), "", null); flag = false; } catch (UnsupportedModpackException ignore) { } @@ -162,8 +223,8 @@ public final class LeftPaneController { }); } - private void checkAccount() { + public void checkAccount() { if (Settings.INSTANCE.getAccounts().isEmpty()) - Controllers.navigate(new AccountsPage()); + addNewAccount(); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java index 1547b138e..7801ef900 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LogWindow.java @@ -30,7 +30,7 @@ import javafx.scene.layout.StackPane; import javafx.scene.web.WebEngine; import javafx.scene.web.WebView; import javafx.stage.Stage; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.event.Event; import org.jackhuang.hmcl.event.EventManager; import org.jackhuang.hmcl.game.LauncherHelper; @@ -63,7 +63,7 @@ public final class LogWindow extends Stage { public LogWindow() { setScene(new Scene(impl, 800, 480)); getScene().getStylesheets().addAll(Settings.INSTANCE.getTheme().getStylesheets()); - setTitle(Main.i18n("logwindow.title")); + setTitle(Launcher.i18n("logwindow.title")); getIcons().add(new Image("/assets/img/icon.png")); } 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 29c7c00a2..c69ded871 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/MainPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/MainPage.java @@ -18,10 +18,7 @@ package org.jackhuang.hmcl.ui; import com.jfoenix.concurrency.JFXUtilities; -import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.JFXListView; -import com.jfoenix.controls.JFXMasonryPane; -import com.jfoenix.controls.JFXPopup; +import com.jfoenix.controls.*; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.fxml.FXML; @@ -30,10 +27,11 @@ import javafx.scene.image.Image; import javafx.scene.input.MouseButton; import javafx.scene.layout.StackPane; import javafx.stage.FileChooser; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.event.EventBus; import org.jackhuang.hmcl.event.ProfileChangedEvent; import org.jackhuang.hmcl.event.RefreshedVersionsEvent; +import org.jackhuang.hmcl.event.RefreshingVersionsEvent; import org.jackhuang.hmcl.game.*; import org.jackhuang.hmcl.mod.MismatchedModpackTypeException; import org.jackhuang.hmcl.mod.UnsupportedModpackException; @@ -46,54 +44,58 @@ import org.jackhuang.hmcl.ui.construct.MessageBox; import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane; import org.jackhuang.hmcl.ui.download.DownloadWizardProvider; import org.jackhuang.hmcl.ui.wizard.DecoratorPage; +import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.OperatingSystem; import org.jackhuang.hmcl.util.StringUtils; import java.io.File; import java.io.IOException; +import java.util.Collections; import java.util.LinkedList; import java.util.List; public final class MainPage extends StackPane implements DecoratorPage { - private final StringProperty title = new SimpleStringProperty(this, "title", Main.i18n("main_page")); + private final StringProperty title = new SimpleStringProperty(this, "title", Launcher.i18n("main_page")); private Profile profile; - private String rightClickedVersion; @FXML private JFXButton btnRefresh; - + @FXML + private StackPane contentPane; @FXML private JFXButton btnAdd; - + @FXML + private JFXSpinner spinner; @FXML private JFXMasonryPane masonryPane; - @FXML - private JFXListView versionList; - - private final JFXPopup versionPopup; - { FXUtils.loadFXML(this, "/assets/fxml/main.fxml"); + loadingVersions(); + EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(event -> { if (event.getSource() == profile.getRepository()) loadVersions((HMCLGameRepository) event.getSource()); }); - EventBus.EVENT_BUS.channel(ProfileChangedEvent.class).register(event -> this.profile = event.getProfile()); + EventBus.EVENT_BUS.channel(RefreshingVersionsEvent.class).register(event -> { + if (event.getSource() == profile.getRepository()) + JFXUtilities.runInFXAndWait(this::loadingVersions); + }); + EventBus.EVENT_BUS.channel(ProfileChangedEvent.class).register(event -> { + this.profile = event.getProfile(); + }); - versionPopup = new JFXPopup(versionList); - getChildren().remove(versionList); - - btnAdd.setOnMouseClicked(e -> Controllers.getDecorator().startWizard(new DownloadWizardProvider(), Main.i18n("install"))); - FXUtils.installTooltip(btnAdd, Main.i18n("install")); + btnAdd.setOnMouseClicked(e -> Controllers.getDecorator().startWizard(new DownloadWizardProvider(), Launcher.i18n("install"))); + FXUtils.installTooltip(btnAdd, Launcher.i18n("install")); btnRefresh.setOnMouseClicked(e -> Settings.INSTANCE.getSelectedProfile().getRepository().refreshVersionsAsync().start()); - FXUtils.installTooltip(btnRefresh, Main.i18n("button.refresh")); + FXUtils.installTooltip(btnRefresh, Launcher.i18n("button.refresh")); } private Node buildNode(HMCLGameRepository repository, Version version, String game) { + Profile profile = repository.getProfile(); String id = version.getId(); VersionItem item = new VersionItem(); item.setUpdate(repository.isModpack(id)); @@ -103,36 +105,37 @@ public final class MainPage extends StackPane implements DecoratorPage { StringBuilder libraries = new StringBuilder(); for (Library library : version.getLibraries()) { if (library.getGroupId().equalsIgnoreCase("net.minecraftforge") && library.getArtifactId().equalsIgnoreCase("forge")) { - libraries.append(Main.i18n("install.installer.forge")).append(": ").append(StringUtils.removeSuffix(StringUtils.removePrefix(library.getVersion().replaceAll("(?i)forge", "").replace(game, "").trim(), "-"), "-")).append("\n"); + libraries.append(Launcher.i18n("install.installer.forge")).append(": ").append(StringUtils.removeSuffix(StringUtils.removePrefix(library.getVersion().replaceAll("(?i)forge", "").replace(game, "").trim(), "-"), "-")).append("\n"); } if (library.getGroupId().equalsIgnoreCase("com.mumfrey") && library.getArtifactId().equalsIgnoreCase("liteloader")) { - libraries.append(Main.i18n("install.installer.liteloader")).append(": ").append(StringUtils.removeSuffix(StringUtils.removePrefix(library.getVersion().replaceAll("(?i)liteloader", "").replace(game, "").trim(), "-"), "-")).append("\n"); + libraries.append(Launcher.i18n("install.installer.liteloader")).append(": ").append(StringUtils.removeSuffix(StringUtils.removePrefix(library.getVersion().replaceAll("(?i)liteloader", "").replace(game, "").trim(), "-"), "-")).append("\n"); } if (library.getGroupId().equalsIgnoreCase("net.optifine") && library.getArtifactId().equalsIgnoreCase("optifine")) { - libraries.append(Main.i18n("install.installer.optifine")).append(": ").append(StringUtils.removeSuffix(StringUtils.removePrefix(library.getVersion().replaceAll("(?i)optifine", "").replace(game, "").trim(), "-"), "-")).append("\n"); + libraries.append(Launcher.i18n("install.installer.optifine")).append(": ").append(StringUtils.removeSuffix(StringUtils.removePrefix(library.getVersion().replaceAll("(?i)optifine", "").replace(game, "").trim(), "-"), "-")).append("\n"); } } item.setLibraries(libraries.toString()); item.setOnLaunchButtonClicked(e -> { if (Settings.INSTANCE.getSelectedAccount() == null) - Controllers.dialog(Main.i18n("login.empty_username")); + Controllers.getLeftPaneController().checkAccount(); else - LauncherHelper.INSTANCE.launch(id, null); + LauncherHelper.INSTANCE.launch(profile, Settings.INSTANCE.getSelectedAccount(), id, null); }); item.setOnScriptButtonClicked(e -> { if (Settings.INSTANCE.getSelectedAccount() == null) - Controllers.dialog(Main.i18n("login.empty_username")); + Controllers.dialog(Launcher.i18n("login.empty_username")); else { FileChooser chooser = new FileChooser(); - chooser.setInitialDirectory(repository.getRunDirectory(id)); - chooser.setTitle(Main.i18n("version.launch_script.save")); + if (repository.getRunDirectory(id).isDirectory()) + chooser.setInitialDirectory(repository.getRunDirectory(id)); + chooser.setTitle(Launcher.i18n("version.launch_script.save")); chooser.getExtensionFilters().add(OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS - ? new FileChooser.ExtensionFilter(Main.i18n("extension.bat"), "*.bat") - : new FileChooser.ExtensionFilter(Main.i18n("extension.sh"), "*.sh")); + ? new FileChooser.ExtensionFilter(Launcher.i18n("extension.bat"), "*.bat") + : new FileChooser.ExtensionFilter(Launcher.i18n("extension.sh"), "*.sh")); File file = chooser.showSaveDialog(Controllers.getStage()); if (file != null) - LauncherHelper.INSTANCE.launch(id, file); + LauncherHelper.INSTANCE.launch(profile, Settings.INSTANCE.getSelectedAccount(), id, file); } }); item.setOnSettingsButtonClicked(e -> { @@ -141,37 +144,62 @@ public final class MainPage extends StackPane implements DecoratorPage { }); item.setOnUpdateButtonClicked(event -> { FileChooser chooser = new FileChooser(); - chooser.setTitle(Main.i18n("modpack.choose")); - chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Main.i18n("modpack"), "*.zip")); + chooser.setTitle(Launcher.i18n("modpack.choose")); + chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Launcher.i18n("modpack"), "*.zip")); File selectedFile = chooser.showOpenDialog(Controllers.getStage()); if (selectedFile != null) { - TaskExecutorDialogPane pane = new TaskExecutorDialogPane(null); try { TaskExecutor executor = ModpackHelper.getUpdateTask(profile, selectedFile, id, ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(id))) - .then(Task.of(Schedulers.javafx(), Controllers::closeDialog)).executor(); - pane.setExecutor(executor); - pane.setTitle(Main.i18n("modpack.update")); - executor.start(); - Controllers.dialog(pane); + .then(Task.of(Schedulers.javafx(), Controllers::closeDialog)).executor(true); + Controllers.taskDialog(executor, Launcher.i18n("modpack.update"), "", null); } catch (UnsupportedModpackException e) { - Controllers.dialog(Main.i18n("modpack.unsupported"), Main.i18n("message.error"), MessageBox.ERROR_MESSAGE); + Controllers.closeDialog(); + Controllers.dialog(Launcher.i18n("modpack.unsupported"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE); } catch (MismatchedModpackTypeException e) { - Controllers.dialog(Main.i18n("modpack.mismatched_type"), Main.i18n("message.error"), MessageBox.ERROR_MESSAGE); + Controllers.closeDialog(); + Controllers.dialog(Launcher.i18n("modpack.mismatched_type"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE); } catch (IOException e) { - Controllers.dialog(Main.i18n("modpack.invalid"), Main.i18n("message.error"), MessageBox.ERROR_MESSAGE); + Controllers.closeDialog(); + Controllers.dialog(Launcher.i18n("modpack.invalid"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE); } } }); item.setOnMouseClicked(event -> { if (event.getButton() == MouseButton.SECONDARY) { - rightClickedVersion = id; - versionList.getSelectionModel().select(-1); + JFXListView versionList = new JFXListView<>(); + JFXPopup versionPopup = new JFXPopup(versionList); + versionList.getStyleClass().add("option-list-view"); + FXUtils.setLimitWidth(versionList, 150); + versionList.getItems().setAll(Lang.immutableListOf( + Launcher.i18n("version.manage.rename"), + Launcher.i18n("version.manage.remove"), + Launcher.i18n("modpack.export"), + Launcher.i18n("folder.game") + )); + versionList.setOnMouseClicked(e ->{ + versionPopup.hide(); + switch (versionList.getSelectionModel().getSelectedIndex()) { + case 0: + VersionPage.renameVersion(profile, id); + break; + case 1: + VersionPage.deleteVersion(profile, id); + break; + case 2: + VersionPage.exportVersion(profile, id); + break; + case 3: + FXUtils.openFolder(repository.getRunDirectory(id)); + break; + default: + throw new Error(); + }}); versionPopup.show(item, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX(), event.getY()); } else if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) { if (Settings.INSTANCE.getSelectedAccount() == null) - Controllers.dialog(Main.i18n("login.empty_username")); + Controllers.dialog(Launcher.i18n("login.empty_username")); else - LauncherHelper.INSTANCE.launch(id, null); + LauncherHelper.INSTANCE.launch(profile, Settings.INSTANCE.getSelectedAccount(), id, null); } }); File iconFile = repository.getVersionIcon(id); @@ -180,33 +208,22 @@ public final class MainPage extends StackPane implements DecoratorPage { return item; } + private void loadingVersions() { + getChildren().setAll(spinner); + FXUtils.resetChildren(masonryPane, Collections.emptyList()); + } + private void loadVersions(HMCLGameRepository repository) { List children = new LinkedList<>(); for (Version version : repository.getVersions()) { children.add(buildNode(repository, version, GameVersion.minecraftVersion(repository.getVersionJar(version.getId())).orElse("Unknown"))); } - JFXUtilities.runInFX(() -> FXUtils.resetChildren(masonryPane, children)); - } - - @FXML - private void onVersionManagement() { - versionPopup.hide(); - switch (versionList.getSelectionModel().getSelectedIndex()) { - case 0: - VersionPage.renameVersion(profile, rightClickedVersion); - break; - case 1: - VersionPage.deleteVersion(profile, rightClickedVersion); - break; - case 2: - VersionPage.exportVersion(profile, rightClickedVersion); - break; - case 3: - FXUtils.openFolder(profile.getRepository().getRunDirectory(rightClickedVersion)); - break; - default: - throw new Error(); - } + JFXUtilities.runInFX(() -> { + if (profile == repository.getProfile()) { + getChildren().setAll(contentPane); + FXUtils.resetChildren(masonryPane, children); + } + }); } public String getTitle() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ModController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ModController.java index 6cea184f7..459e398f1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ModController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ModController.java @@ -17,16 +17,16 @@ */ package org.jackhuang.hmcl.ui; +import com.jfoenix.concurrency.JFXUtilities; import com.jfoenix.controls.JFXSpinner; import com.jfoenix.controls.JFXTabPane; -import javafx.application.Platform; import javafx.fxml.FXML; import javafx.scene.control.ScrollPane; import javafx.scene.input.TransferMode; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.stage.FileChooser; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.mod.ModInfo; import org.jackhuang.hmcl.mod.ModManager; import org.jackhuang.hmcl.task.Schedulers; @@ -93,7 +93,7 @@ public final class ModController { this.versionId = versionId; Task.of(variables -> { synchronized (ModController.this) { - Platform.runLater(() -> { + JFXUtilities.runInFX(() -> { rootPane.getChildren().remove(contentPane); spinner.setVisible(true); }); @@ -121,25 +121,24 @@ public final class ModController { list.add(item); } - Platform.runLater(() -> { - rootPane.getChildren().add(contentPane); - spinner.setVisible(false); - }); variables.set("list", list); } - }).finalized(Schedulers.javafx(), variables -> { - FXUtils.onWeakChangeAndOperate(parentTab.getSelectionModel().selectedItemProperty(), newValue -> { - if (newValue != null && newValue.getUserData() == ModController.this) - modPane.getChildren().setAll(variables.>get("list")); - }); - }, null).start(); + }).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> { + rootPane.getChildren().add(contentPane); + spinner.setVisible(false); + if (isDependentsSucceeded) + FXUtils.onWeakChangeAndOperate(parentTab.getSelectionModel().selectedItemProperty(), newValue -> { + if (newValue != null && newValue.getUserData() == ModController.this) + modPane.getChildren().setAll(variables.>get("list")); + }); + }).start(); } @FXML private void onAdd() { FileChooser chooser = new FileChooser(); - chooser.setTitle(Main.i18n("mods.choose_mod")); - chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(Main.i18n("extension.mod"), "*.jar", "*.zip", "*.litemod")); + chooser.setTitle(Launcher.i18n("mods.choose_mod")); + chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(Launcher.i18n("extension.mod"), "*.jar", "*.zip", "*.litemod")); List res = chooser.showOpenMultipleDialog(Controllers.getStage()); // It's guaranteed that succeeded and failed are thread safe here. @@ -161,10 +160,10 @@ public final class ModController { }).with(Task.of(Schedulers.javafx(), variables -> { List prompt = new LinkedList<>(); if (!succeeded.isEmpty()) - prompt.add(Main.i18n("mods.add.success", String.join(", ", succeeded))); + prompt.add(Launcher.i18n("mods.add.success", String.join(", ", succeeded))); if (!failed.isEmpty()) - prompt.add(Main.i18n("mods.add.failed", String.join(", ", failed))); - Controllers.dialog(String.join("\n", prompt), Main.i18n("mods.add")); + prompt.add(Launcher.i18n("mods.add.failed", String.join(", ", failed))); + Controllers.dialog(String.join("\n", prompt), Launcher.i18n("mods.add")); loadMods(modManager, versionId); })).start(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ModItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ModItem.java index 6c16b627b..7de389ad7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ModItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ModItem.java @@ -22,7 +22,7 @@ import com.jfoenix.controls.JFXCheckBox; import com.jfoenix.effects.JFXDepthManager; import javafx.geometry.Pos; import javafx.scene.layout.BorderPane; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.mod.ModInfo; import org.jackhuang.hmcl.setting.Theme; @@ -40,7 +40,7 @@ public final class ModItem extends BorderPane { setCenter(modItem); JFXButton btnRemove = new JFXButton(); - FXUtils.installTooltip(btnRemove, Main.i18n("mods.remove")); + FXUtils.installTooltip(btnRemove, Launcher.i18n("mods.remove")); btnRemove.setOnMouseClicked(e -> deleteCallback.accept(this)); btnRemove.getStyleClass().add("toggle-icon4"); BorderPane.setAlignment(btnRemove, Pos.CENTER); @@ -50,7 +50,7 @@ public final class ModItem extends BorderPane { setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;"); JFXDepthManager.setDepth(this, 1); modItem.setTitle(info.getFileName()); - modItem.setSubtitle(info.getName() + ", " + Main.i18n("archive.version") + ": " + info.getVersion() + ", " + Main.i18n("archive.game_version") + ": " + info.getGameVersion() + ", " + Main.i18n("archive.author") + ": " + info.getAuthors()); + modItem.setSubtitle(info.getName() + ", " + Launcher.i18n("archive.version") + ": " + info.getVersion() + ", " + Launcher.i18n("archive.game_version") + ": " + info.getGameVersion() + ", " + Launcher.i18n("archive.author") + ": " + info.getAuthors()); chkEnabled.setSelected(info.isActive()); chkEnabled.selectedProperty().addListener((a, b, newValue) -> info.activeProperty().set(newValue)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ProfilePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ProfilePage.java index 1384c1df7..c579d135d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ProfilePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ProfilePage.java @@ -23,7 +23,7 @@ import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.fxml.FXML; import javafx.scene.layout.StackPane; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profiles; import org.jackhuang.hmcl.setting.Settings; @@ -54,7 +54,7 @@ public final class ProfilePage extends StackPane implements DecoratorPage { String profileDisplayName = Optional.ofNullable(profile).map(Profiles::getProfileDisplayName).orElse(""); title = new SimpleStringProperty(this, "title", - profile == null ? Main.i18n("profile.new") : Main.i18n("profile") + " - " + profileDisplayName); + profile == null ? Launcher.i18n("profile.new") : Launcher.i18n("profile") + " - " + profileDisplayName); location = new SimpleStringProperty(this, "location", Optional.ofNullable(profile).map(Profile::getGameDir).map(File::getAbsolutePath).orElse("")); 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 3ae536068..16e28e2ab 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SettingsPage.java @@ -34,9 +34,12 @@ import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.scene.text.Font; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.setting.*; -import org.jackhuang.hmcl.ui.construct.*; +import org.jackhuang.hmcl.ui.construct.FileItem; +import org.jackhuang.hmcl.ui.construct.FontComboBox; +import org.jackhuang.hmcl.ui.construct.MultiFileItem; +import org.jackhuang.hmcl.ui.construct.Validator; import org.jackhuang.hmcl.ui.wizard.DecoratorPage; import org.jackhuang.hmcl.util.Lang; @@ -46,7 +49,7 @@ import java.util.Collections; import java.util.stream.Collectors; public final class SettingsPage extends StackPane implements DecoratorPage { - private final StringProperty title = new SimpleStringProperty(this, "title", Main.i18n("settings.launcher")); + private final StringProperty title = new SimpleStringProperty(this, "title", Launcher.i18n("settings.launcher")); @FXML private JFXTextField txtProxyHost; @@ -174,12 +177,12 @@ public final class SettingsPage extends StackPane implements DecoratorPage { fileCommonLocation.setProperty(Settings.INSTANCE.commonPathProperty()); - FXUtils.installTooltip(btnUpdate, Main.i18n("update.tooltip")); + FXUtils.installTooltip(btnUpdate, Launcher.i18n("update.tooltip")); checkUpdate(); // background backgroundItem.loadChildren(Collections.singletonList( - backgroundItem.createChildren(Main.i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT) + backgroundItem.createChildren(Launcher.i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT) )); FXUtils.bindString(backgroundItem.getTxtCustom(), Settings.INSTANCE.backgroundImageProperty()); @@ -196,8 +199,8 @@ public final class SettingsPage extends StackPane implements DecoratorPage { // theme JFXColorPicker picker = new JFXColorPicker(Color.web(Settings.INSTANCE.getTheme().getColor()), null); - picker.setCustomColorText(Main.i18n("color.custom")); - picker.setRecentColorsText(Main.i18n("color.recent")); + picker.setCustomColorText(Launcher.i18n("color.custom")); + picker.setRecentColorsText(Launcher.i18n("color.recent")); picker.getCustomColors().setAll(Arrays.stream(Theme.VALUES).map(Theme::getColor).map(Color::web).collect(Collectors.toList())); picker.setOnAction(e -> { Theme theme = Theme.custom(Theme.getColorDisplayName(picker.getValue())); @@ -211,7 +214,7 @@ public final class SettingsPage extends StackPane implements DecoratorPage { private void initBackgroundItemSubtitle() { switch (Settings.INSTANCE.getBackgroundImageType()) { case DEFAULT: - backgroundItem.setSubtitle(Main.i18n("launcher.background.default")); + backgroundItem.setSubtitle(Launcher.i18n("launcher.background.default")); break; case CUSTOM: backgroundItem.setSubtitle(Settings.INSTANCE.getBackgroundImage()); @@ -233,25 +236,25 @@ public final class SettingsPage extends StackPane implements DecoratorPage { } public void checkUpdate() { - btnUpdate.setVisible(Main.UPDATE_CHECKER.isOutOfDate()); + btnUpdate.setVisible(Launcher.UPDATE_CHECKER.isOutOfDate()); - if (Main.UPDATE_CHECKER.isOutOfDate()) { - lblUpdateSub.setText(Main.i18n("update.newest_version", Main.UPDATE_CHECKER.getNewVersion().toString())); + if (Launcher.UPDATE_CHECKER.isOutOfDate()) { + lblUpdateSub.setText(Launcher.i18n("update.newest_version", Launcher.UPDATE_CHECKER.getNewVersion().toString())); lblUpdateSub.getStyleClass().setAll("update-label"); - lblUpdate.setText(Main.i18n("update.found")); + lblUpdate.setText(Launcher.i18n("update.found")); lblUpdate.getStyleClass().setAll("update-label"); } else { - lblUpdateSub.setText(Main.i18n("update.latest")); + lblUpdateSub.setText(Launcher.i18n("update.latest")); lblUpdateSub.getStyleClass().setAll("subtitle-label"); - lblUpdate.setText(Main.i18n("update")); + lblUpdate.setText(Launcher.i18n("update")); lblUpdate.getStyleClass().setAll(); } } @FXML private void onUpdate() { - Main.UPDATE_CHECKER.checkOutdate(); + Launcher.UPDATE_CHECKER.checkOutdate(); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionItem.java index f18996d27..7843d3d7e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionItem.java @@ -32,7 +32,7 @@ import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.setting.Theme; import java.util.Optional; @@ -73,10 +73,10 @@ public final class VersionItem extends StackPane { btnLaunch.setGraphic(SVG.launch(Theme.blackFillBinding(), 15, 15)); btnScript.setGraphic(SVG.script(Theme.blackFillBinding(), 15, 15)); - FXUtils.installTooltip(btnSettings, Main.i18n("version.settings")); - FXUtils.installTooltip(btnUpdate, Main.i18n("version.update")); - FXUtils.installTooltip(btnLaunch, Main.i18n("version.launch")); - FXUtils.installTooltip(btnScript, Main.i18n("version.launch_script")); + FXUtils.installTooltip(btnSettings, Launcher.i18n("version.settings")); + FXUtils.installTooltip(btnUpdate, Launcher.i18n("version.update")); + FXUtils.installTooltip(btnLaunch, Launcher.i18n("version.launch")); + FXUtils.installTooltip(btnScript, Launcher.i18n("version.launch_script")); icon.translateYProperty().bind(Bindings.createDoubleBinding(() -> header.getBoundsInParent().getHeight() - icon.getHeight() / 2 - 16, header.boundsInParentProperty(), icon.heightProperty())); FXUtils.limitSize(iconView, 32, 32); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionListItem.java index 7078d9dd9..140c64fd2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionListItem.java @@ -17,11 +17,14 @@ */ package org.jackhuang.hmcl.ui; +import com.jfoenix.controls.JFXButton; +import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.geometry.Rectangle2D; import javafx.scene.control.Label; import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; import javafx.scene.layout.StackPane; public final class VersionListItem extends StackPane { @@ -33,7 +36,7 @@ public final class VersionListItem extends StackPane { private Label lblGameVersion; @FXML private ImageView imageView; - private Runnable handler; + @FXML private JFXButton btnSettings; public VersionListItem(String versionName) { this(versionName, ""); @@ -48,13 +51,8 @@ public final class VersionListItem extends StackPane { FXUtils.limitSize(imageView, 32, 32); } - @FXML - private void onSettings() { - handler.run(); - } - - public void setOnSettingsButtonClicked(Runnable handler) { - this.handler = handler; + public void setOnSettingsButtonClicked(EventHandler handler) { + btnSettings.setOnMouseClicked(handler); } public void setVersionName(String versionName) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionPage.java index a819a94a1..eb66d705f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionPage.java @@ -26,7 +26,7 @@ import javafx.beans.property.StringProperty; import javafx.fxml.FXML; import javafx.scene.control.Tab; import javafx.scene.layout.StackPane; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.ui.export.ExportWizardProvider; @@ -79,17 +79,17 @@ public final class VersionPage extends StackPane implements DecoratorPage { browsePopup = new JFXPopup(browseList); managementPopup = new JFXPopup(managementList); - FXUtils.installTooltip(btnDelete, Main.i18n("version.manage.remove")); - FXUtils.installTooltip(btnBrowseMenu, Main.i18n("settings.game.exploration")); - FXUtils.installTooltip(btnManagementMenu, Main.i18n("settings.game.management")); - FXUtils.installTooltip(btnExport, Main.i18n("modpack.export")); + FXUtils.installTooltip(btnDelete, Launcher.i18n("version.manage.remove")); + FXUtils.installTooltip(btnBrowseMenu, Launcher.i18n("settings.game.exploration")); + FXUtils.installTooltip(btnManagementMenu, Launcher.i18n("settings.game.management")); + FXUtils.installTooltip(btnExport, Launcher.i18n("modpack.export")); } public void load(String id, Profile profile) { this.version = id; this.profile = profile; - title.set(Main.i18n("settings.game") + " - " + id); + title.set(Launcher.i18n("settings.game") + " - " + id); versionSettingsController.loadVersionSetting(profile, id); modController.setParentTab(tabPane); @@ -185,7 +185,7 @@ public final class VersionPage extends StackPane implements DecoratorPage { } public static void deleteVersion(Profile profile, String version) { - Controllers.confirmDialog(Main.i18n("version.manage.remove.confirm", version), Main.i18n("message.confirm"), () -> { + Controllers.confirmDialog(Launcher.i18n("version.manage.remove.confirm", version), Launcher.i18n("message.confirm"), () -> { if (profile.getRepository().removeVersionFromDisk(version)) { profile.getRepository().refreshVersionsAsync().start(); Controllers.navigate(null); @@ -194,7 +194,7 @@ public final class VersionPage extends StackPane implements DecoratorPage { } public static void renameVersion(Profile profile, String version) { - Controllers.inputDialog(Main.i18n("version.manage.rename.message"), res -> { + Controllers.inputDialog(Launcher.i18n("version.manage.rename.message"), res -> { if (profile.getRepository().renameVersion(version, res)) { profile.getRepository().refreshVersionsAsync().start(); Controllers.navigate(null); @@ -203,6 +203,6 @@ public final class VersionPage extends StackPane implements DecoratorPage { } public static void exportVersion(Profile profile, String version) { - Controllers.getDecorator().startWizard(new ExportWizardProvider(profile, version), Main.i18n("modpack.wizard")); + Controllers.getDecorator().startWizard(new ExportWizardProvider(profile, version), Launcher.i18n("modpack.wizard")); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionSettingsController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionSettingsController.java index 13829715c..a62277b82 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionSettingsController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionSettingsController.java @@ -17,7 +17,10 @@ */ package org.jackhuang.hmcl.ui; -import com.jfoenix.controls.*; +import com.jfoenix.controls.JFXCheckBox; +import com.jfoenix.controls.JFXComboBox; +import com.jfoenix.controls.JFXTextField; +import com.jfoenix.controls.JFXToggleButton; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.scene.Node; @@ -27,7 +30,7 @@ import javafx.scene.control.Toggle; import javafx.scene.image.Image; import javafx.scene.layout.VBox; import javafx.stage.FileChooser; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.setting.EnumGameDirectory; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.VersionSetting; @@ -77,7 +80,7 @@ public final class VersionSettingsController { @FXML private void initialize() { - lblPhysicalMemory.setText(Main.i18n("settings.physical_memory") + ": " + OperatingSystem.TOTAL_MEMORY + "MB"); + lblPhysicalMemory.setText(Launcher.i18n("settings.physical_memory") + ": " + OperatingSystem.TOTAL_MEMORY + "MB"); FXUtils.smoothScrolling(scroll); @@ -91,13 +94,13 @@ public final class VersionSettingsController { javaItem.getExtensionFilters().add(new FileChooser.ExtensionFilter("Java", "java.exe", "javaw.exe")); gameDirItem.loadChildren(Arrays.asList( - gameDirItem.createChildren(Main.i18n("settings.advanced.game_dir.default"), EnumGameDirectory.ROOT_FOLDER), - gameDirItem.createChildren(Main.i18n("settings.advanced.game_dir.independent"), EnumGameDirectory.VERSION_FOLDER) + gameDirItem.createChildren(Launcher.i18n("settings.advanced.game_dir.default"), EnumGameDirectory.ROOT_FOLDER), + gameDirItem.createChildren(Launcher.i18n("settings.advanced.game_dir.independent"), EnumGameDirectory.VERSION_FOLDER) )); globalItem.loadChildren(Arrays.asList( - globalItem.createChildren(Main.i18n("settings.type.global"), true), - globalItem.createChildren(Main.i18n("settings.type.special"), false) + globalItem.createChildren(Launcher.i18n("settings.type.global"), true), + globalItem.createChildren(Launcher.i18n("settings.type.special"), false) )); } @@ -189,7 +192,7 @@ public final class VersionSettingsController { }); versionSetting.usesGlobalProperty().setChangedListenerAndOperate(it -> - globalItem.setSubtitle(Main.i18n(versionSetting.isUsesGlobal() ? "settings.type.global" : "settings.type.special"))); + globalItem.setSubtitle(Launcher.i18n(versionSetting.isUsesGlobal() ? "settings.type.global" : "settings.type.special"))); gameDirItem.getGroup().getToggles().stream() .filter(it -> it.getUserData() == versionSetting.getGameDirType()) @@ -229,7 +232,7 @@ public final class VersionSettingsController { @FXML private void onExploreIcon() { FileChooser chooser = new FileChooser(); - chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Main.i18n("extension.png"), "*.png")); + chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Launcher.i18n("extension.png"), "*.png")); File selectedFile = chooser.showOpenDialog(Controllers.getStage()); if (selectedFile != null) { File iconFile = profile.getRepository().getVersionIcon(versionId); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ClassTitle.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ClassTitle.java index 90abf3c26..88c168d43 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ClassTitle.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ClassTitle.java @@ -18,11 +18,13 @@ package org.jackhuang.hmcl.ui.construct; import javafx.scene.Node; +import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.scene.text.Text; +import org.jackhuang.hmcl.util.Lang; /** * @author huangyuhui @@ -48,6 +50,13 @@ public class ClassTitle extends StackPane { getStyleClass().add("class-title"); } + public ClassTitle(String text, Node rightNode) { + this(Lang.apply(new BorderPane(), borderPane -> { + borderPane.setLeft(Lang.apply(new VBox(), vBox -> vBox.getChildren().setAll(new Text(text)))); + borderPane.setRight(rightNode); + })); + } + public Node getContent() { return content; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java index b79bfb431..6a3439202 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ComponentList.java @@ -60,9 +60,14 @@ public class ComponentList extends StackPane { child.getStyleClass().add("options-list-item-ahead"); else child.getStyleClass().add("options-list-item"); + child.getProperties().put("node", node); vbox.getChildren().add(child); } + public void removeChildren(Node node) { + vbox.getChildren().removeIf(node1 -> node1.getProperties().get("node") == node); + } + public String getTitle() { return title.get(); } 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 0bbfa6bb9..b80c23113 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 @@ -26,7 +26,7 @@ import javafx.scene.control.Tooltip; import javafx.scene.layout.BorderPane; import javafx.scene.layout.VBox; import javafx.stage.DirectoryChooser; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; @@ -54,7 +54,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, Main.i18n("button.edit")); + FXUtils.installTooltip(right, Launcher.i18n("button.edit")); setRight(right); Tooltip tip = new Tooltip(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedItem.java index 46c4aab27..38ecd9933 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/IconedItem.java @@ -21,21 +21,19 @@ import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.layout.HBox; +import javafx.scene.paint.Paint; public class IconedItem extends RipplerContainer { - private final Node icon; - private final String text; public IconedItem(Node icon, String text) { super(createHBox(icon, text)); - this.icon = icon; - this.text = text; } private static HBox createHBox(Node icon, String text) { HBox hBox = new HBox(); icon.setMouseTransparent(true); Label textLabel = new Label(text); + textLabel.setId("label"); textLabel.setAlignment(Pos.CENTER); textLabel.setMouseTransparent(true); hBox.getChildren().addAll(icon, textLabel); @@ -43,4 +41,12 @@ public class IconedItem extends RipplerContainer { hBox.setAlignment(Pos.CENTER_LEFT); return hBox; } + + public void setText(String text) { + ((Label) lookup("#label")).setText(text); + } + + public void setTextFill(Paint paint) { + ((Label) lookup("#label")).setTextFill(paint); + } } 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 ee6932e65..178d6b10a 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 @@ -15,7 +15,7 @@ import javafx.scene.input.MouseEvent; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; @@ -41,7 +41,7 @@ public final class ImagePickerItem extends BorderPane { selectButton.onMouseClickedProperty().bind(onSelectButtonClicked); selectButton.getStyleClass().add("toggle-icon4"); - FXUtils.installTooltip(selectButton, Main.i18n("button.edit")); + FXUtils.installTooltip(selectButton, Launcher.i18n("button.edit")); HBox hBox = new HBox(); hBox.getChildren().setAll(imageView, selectButton); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageBox.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageBox.java index 71402e1ad..528e0b55a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageBox.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageBox.java @@ -17,10 +17,12 @@ */ package org.jackhuang.hmcl.ui.construct; +import com.jfoenix.concurrency.JFXUtilities; import javafx.scene.control.Alert; import javafx.scene.control.ButtonType; import javafx.scene.control.TextInputDialog; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; +import org.jackhuang.hmcl.ui.FXUtils; import javax.swing.*; import java.util.Optional; @@ -29,7 +31,7 @@ public final class MessageBox { private MessageBox() { } - private static final String TITLE = Main.i18n("message.info"); + private static final String TITLE = Launcher.i18n("message.info"); /** * User Operation: Yes @@ -123,30 +125,32 @@ public final class MessageBox { } public static int confirm(String message, String title, int option) { - Alert alert = new Alert(Alert.AlertType.CONFIRMATION); - alert.setTitle(title); - alert.setHeaderText(title); - alert.setContentText(message); - switch (option) { - case YES_NO_OPTION: - alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO); - break; - case YES_NO_CANCEL_OPTION: - alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO, ButtonType.CANCEL); - break; - case OK_CANCEL_OPTION: - alert.getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL); - break; - default: - throw new IllegalArgumentException("Unrecognized message box option " + option); - } - Optional buttonType = alert.showAndWait(); - if (!buttonType.isPresent()) return CLOSED_OPTION; - else if (buttonType.get() == ButtonType.OK) return OK_OPTION; - else if (buttonType.get() == ButtonType.YES) return YES_OPTION; - else if (buttonType.get() == ButtonType.NO) return NO_OPTION; - else if (buttonType.get() == ButtonType.CANCEL) return CANCEL_OPTION; - else throw new IllegalStateException("Unrecognized button type:" + buttonType.get()); + return FXUtils.runInUIThread(() -> { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION); + alert.setTitle(title); + alert.setHeaderText(title); + alert.setContentText(message); + switch (option) { + case YES_NO_OPTION: + alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO); + break; + case YES_NO_CANCEL_OPTION: + alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO, ButtonType.CANCEL); + break; + case OK_CANCEL_OPTION: + alert.getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL); + break; + default: + throw new IllegalArgumentException("Unrecognized message box option " + option); + } + Optional buttonType = alert.showAndWait(); + if (!buttonType.isPresent()) return CLOSED_OPTION; + else if (buttonType.get() == ButtonType.OK) return OK_OPTION; + else if (buttonType.get() == ButtonType.YES) return YES_OPTION; + else if (buttonType.get() == ButtonType.NO) return NO_OPTION; + else if (buttonType.get() == ButtonType.CANCEL) return CANCEL_OPTION; + else throw new IllegalStateException("Unexpected button type:" + buttonType.get()); + }); } public static Optional input(String message) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java index 340b4e6ea..ca9d30dec 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java @@ -23,7 +23,7 @@ import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; @@ -92,8 +92,8 @@ public final class MessageDialogPane extends StackPane { Optional.ofNullable(onCancel).ifPresent(Runnable::run); }); - acceptButton.setText(Main.i18n("button.yes")); - cancelButton.setText(Main.i18n("button.no")); + acceptButton.setText(Launcher.i18n("button.yes")); + cancelButton.setText(Launcher.i18n("button.no")); actions.getChildren().add(cancelButton); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiColorItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiColorItem.java index 42285ea61..8e7ec20e3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiColorItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiColorItem.java @@ -5,17 +5,15 @@ import com.jfoenix.controls.JFXRadioButton; import javafx.beans.NamedArg; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; -import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.control.Toggle; import javafx.scene.control.ToggleGroup; -import javafx.scene.control.Tooltip; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.ui.FXUtils; import java.util.Collection; @@ -23,8 +21,8 @@ import java.util.Optional; import java.util.function.Consumer; public class MultiColorItem extends ComponentList { - private final StringProperty customTitle = new SimpleStringProperty(this, "customTitle", Main.i18n("selector.custom")); - private final StringProperty chooserTitle = new SimpleStringProperty(this, "chooserTitle", Main.i18n("selector.choose_file")); + private final StringProperty customTitle = new SimpleStringProperty(this, "customTitle", Launcher.i18n("selector.custom")); + private final StringProperty chooserTitle = new SimpleStringProperty(this, "chooserTitle", Launcher.i18n("selector.choose_file")); private final ToggleGroup group = new ToggleGroup(); private final JFXColorPicker colorPicker = new JFXColorPicker(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java index c37fdc6ac..b0d1aa76e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java @@ -38,7 +38,7 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.stage.DirectoryChooser; import javafx.stage.FileChooser; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.setting.Theme; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; @@ -49,8 +49,8 @@ import java.util.Collection; import java.util.function.Consumer; public class MultiFileItem extends ComponentList { - private final StringProperty customTitle = new SimpleStringProperty(this, "customTitle", Main.i18n("selector.custom")); - private final StringProperty chooserTitle = new SimpleStringProperty(this, "chooserTitle", Main.i18n("selector.choose_file")); + private final StringProperty customTitle = new SimpleStringProperty(this, "customTitle", Launcher.i18n("selector.custom")); + private final StringProperty chooserTitle = new SimpleStringProperty(this, "chooserTitle", Launcher.i18n("selector.choose_file")); private final BooleanProperty directory = new SimpleBooleanProperty(this, "directory", false); private final SimpleStringProperty tooltip = new SimpleStringProperty(this, "tooltip"); private final ObservableList extensionFilters = FXCollections.observableArrayList(); @@ -153,7 +153,7 @@ public class MultiFileItem extends ComponentList { public void onExploreJavaDir() { DirectoryChooser chooser = new DirectoryChooser(); - chooser.setTitle(Main.i18n(getChooserTitle())); + chooser.setTitle(Launcher.i18n(getChooserTitle())); File selectedDir = chooser.showDialog(Controllers.getStage()); if (selectedDir != null) txtCustom.setText(selectedDir.getAbsolutePath()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/StackContainerPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/StackContainerPane.java new file mode 100644 index 000000000..78f2a75d0 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/StackContainerPane.java @@ -0,0 +1,45 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.ui.construct; + +import javafx.scene.Node; +import javafx.scene.layout.StackPane; + +import java.util.Stack; + +public class StackContainerPane extends StackPane { + private final Stack stack = new Stack<>(); + + public void push(Node node) { + if (node.getProperties().containsKey("controllers")) + stack.push(node); + getChildren().setAll(node); + } + + public void pop() { + stack.pop(); + if (stack.isEmpty()) + getChildren().setAll(); + else + getChildren().setAll(stack.peek()); + } + + public boolean isEmpty() { + return stack.isEmpty(); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java index 249b4dd57..0038117a9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java @@ -57,7 +57,9 @@ public class TaskExecutorDialogPane extends StackPane { public void setExecutor(TaskExecutor executor) { this.executor = executor; - taskListPane.setExecutor(executor); + + if (executor != null) + taskListPane.setExecutor(executor); } public StringProperty titleProperty() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogWizardDisplayer.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogWizardDisplayer.java index f79b14805..6394fb4a9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogWizardDisplayer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogWizardDisplayer.java @@ -19,7 +19,7 @@ package org.jackhuang.hmcl.ui.construct; import com.jfoenix.concurrency.JFXUtilities; import javafx.beans.property.StringProperty; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.TaskExecutor; import org.jackhuang.hmcl.task.TaskListener; @@ -38,7 +38,7 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay Controllers.navigate(null); }); - pane.setTitle(Main.i18n("message.doing")); + pane.setTitle(Launcher.i18n("message.doing")); pane.setProgress(Double.MAX_VALUE); if (settings.containsKey("title")) { Object title = settings.get("title"); @@ -64,7 +64,7 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay if (settings.containsKey("success_message") && settings.get("success_message") instanceof String) JFXUtilities.runInFX(() -> Controllers.dialog((String) settings.get("success_message"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null))); else if (!settings.containsKey("forbid_success_message")) - JFXUtilities.runInFX(() -> Controllers.dialog(Main.i18n("message.success"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null))); + JFXUtilities.runInFX(() -> Controllers.dialog(Launcher.i18n("message.success"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null))); } else { if (executor.getLastException() == null) return; @@ -72,7 +72,7 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay if (settings.containsKey("failure_message") && settings.get("failure_message") instanceof String) JFXUtilities.runInFX(() -> Controllers.dialog(appendix, (String) settings.get("failure_message"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null))); else if (!settings.containsKey("forbid_failure_message")) - JFXUtilities.runInFX(() -> Controllers.dialog(appendix, Main.i18n("wizard.failed"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null))); + JFXUtilities.runInFX(() -> Controllers.dialog(appendix, Launcher.i18n("wizard.failed"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null))); } } }); 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 9d5c7c2a6..40bdbb3bd 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 @@ -23,7 +23,7 @@ import javafx.scene.control.Label; import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.download.forge.ForgeInstallTask; import org.jackhuang.hmcl.download.game.GameAssetDownloadTask; import org.jackhuang.hmcl.download.game.GameAssetRefreshTask; @@ -62,29 +62,29 @@ public final class TaskListPane extends StackPane { return; if (task instanceof GameAssetRefreshTask) { - task.setName(Main.i18n("assets.download")); + task.setName(Launcher.i18n("assets.download")); } else if (task instanceof GameAssetDownloadTask) { - task.setName(Main.i18n("assets.download_all")); + task.setName(Launcher.i18n("assets.download_all")); } else if (task instanceof ForgeInstallTask) { - task.setName(Main.i18n("install.installer.install", Main.i18n("install.installer.forge"))); + task.setName(Launcher.i18n("install.installer.install", Launcher.i18n("install.installer.forge"))); } else if (task instanceof LiteLoaderInstallTask) { - task.setName(Main.i18n("install.installer.install", Main.i18n("install.installer.liteloader"))); + task.setName(Launcher.i18n("install.installer.install", Launcher.i18n("install.installer.liteloader"))); } else if (task instanceof OptiFineInstallTask) { - task.setName(Main.i18n("install.installer.install", Main.i18n("install.installer.optifine"))); + task.setName(Launcher.i18n("install.installer.install", Launcher.i18n("install.installer.optifine"))); } else if (task instanceof CurseCompletionTask) { - task.setName(Main.i18n("modpack.type.curse.completion")); + task.setName(Launcher.i18n("modpack.type.curse.completion")); } else if (task instanceof ModpackInstallTask) { - task.setName(Main.i18n("modpack.installing")); + task.setName(Launcher.i18n("modpack.installing")); } else if (task instanceof CurseInstallTask) { - task.setName(Main.i18n("modpack.install", Main.i18n("modpack.type.curse"))); + task.setName(Launcher.i18n("modpack.install", Launcher.i18n("modpack.type.curse"))); } else if (task instanceof MultiMCModpackInstallTask) { - task.setName(Main.i18n("modpack.install", Main.i18n("modpack.type.multimc"))); + task.setName(Launcher.i18n("modpack.install", Launcher.i18n("modpack.type.multimc"))); } else if (task instanceof HMCLModpackInstallTask) { - task.setName(Main.i18n("modpack.install", Main.i18n("modpack.type.hmcl"))); + task.setName(Launcher.i18n("modpack.install", Launcher.i18n("modpack.type.hmcl"))); } else if (task instanceof HMCLModpackExportTask) { - task.setName(Main.i18n("modpack.export")); + task.setName(Launcher.i18n("modpack.export")); } else if (task instanceof MinecraftInstanceTask) { - task.setName(Main.i18n("modpack.scan")); + task.setName(Launcher.i18n("modpack.scan")); } ProgressListNode node = new ProgressListNode(task); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java index 597a9f2a9..71d2c9f28 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/AdditionalInstallersPage.java @@ -22,7 +22,7 @@ import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.download.RemoteVersion; import org.jackhuang.hmcl.game.GameRepository; @@ -34,7 +34,7 @@ import org.jackhuang.hmcl.util.Lang; import java.util.Map; import java.util.Optional; -import static org.jackhuang.hmcl.Main.i18n; +import static org.jackhuang.hmcl.Launcher.i18n; class AdditionalInstallersPage extends StackPane implements WizardPage { private final InstallerWizardProvider provider; @@ -100,7 +100,7 @@ class AdditionalInstallersPage extends StackPane implements WizardPage { @Override public String getTitle() { - return Main.i18n("settings.tabs.installers"); + return Launcher.i18n("settings.tabs.installers"); } private String getVersion(String id) { @@ -109,24 +109,24 @@ class AdditionalInstallersPage extends StackPane implements WizardPage { @Override public void onNavigate(Map settings) { - lblGameVersion.setText(Main.i18n("install.new_game.current_game_version") + ": " + provider.getGameVersion()); + lblGameVersion.setText(Launcher.i18n("install.new_game.current_game_version") + ": " + provider.getGameVersion()); btnForge.setDisable(provider.getForge() != null); if (provider.getForge() != null || controller.getSettings().containsKey("forge")) - lblForge.setText(Main.i18n("install.installer.version", Main.i18n("install.installer.forge")) + ": " + Lang.nonNull(provider.getForge(), getVersion("forge"))); + lblForge.setText(Launcher.i18n("install.installer.version", Launcher.i18n("install.installer.forge")) + ": " + Lang.nonNull(provider.getForge(), getVersion("forge"))); else - lblForge.setText(Main.i18n("install.installer.not_installed", Main.i18n("install.installer.forge"))); + lblForge.setText(Launcher.i18n("install.installer.not_installed", Launcher.i18n("install.installer.forge"))); btnLiteLoader.setDisable(provider.getLiteLoader() != null); if (provider.getLiteLoader() != null || controller.getSettings().containsKey("liteloader")) - lblLiteLoader.setText(Main.i18n("install.installer.version", Main.i18n("install.installer.liteloader")) + ": " + Lang.nonNull(provider.getLiteLoader(), getVersion("liteloader"))); + lblLiteLoader.setText(Launcher.i18n("install.installer.version", Launcher.i18n("install.installer.liteloader")) + ": " + Lang.nonNull(provider.getLiteLoader(), getVersion("liteloader"))); else - lblLiteLoader.setText(Main.i18n("install.installer.not_installed", Main.i18n("install.installer.liteloader"))); + lblLiteLoader.setText(Launcher.i18n("install.installer.not_installed", Launcher.i18n("install.installer.liteloader"))); btnOptiFine.setDisable(provider.getOptiFine() != null); if (provider.getOptiFine() != null || controller.getSettings().containsKey("optifine")) - lblOptiFine.setText(Main.i18n("install.installer.version", Main.i18n("install.installer.optifine")) + ": " + Lang.nonNull(provider.getOptiFine(), getVersion("optifine"))); + lblOptiFine.setText(Launcher.i18n("install.installer.version", Launcher.i18n("install.installer.optifine")) + ": " + Lang.nonNull(provider.getOptiFine(), getVersion("optifine"))); else - lblOptiFine.setText(Main.i18n("install.installer.not_installed", Main.i18n("install.installer.optifine"))); + lblOptiFine.setText(Launcher.i18n("install.installer.not_installed", Launcher.i18n("install.installer.optifine"))); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadWizardProvider.java index 2c3787c8d..b4f0ea869 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadWizardProvider.java @@ -18,7 +18,7 @@ package org.jackhuang.hmcl.ui.download; import javafx.scene.Node; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.download.GameBuilder; import org.jackhuang.hmcl.download.RemoteVersion; @@ -34,7 +34,7 @@ import org.jackhuang.hmcl.util.Lang; import java.io.File; import java.util.Map; -import static org.jackhuang.hmcl.Main.i18n; +import static org.jackhuang.hmcl.Launcher.i18n; public final class DownloadWizardProvider implements WizardProvider { private Profile profile; @@ -77,8 +77,8 @@ public final class DownloadWizardProvider implements WizardProvider { @Override public Object finish(Map settings) { - settings.put("success_message", Main.i18n("install.success")); - settings.put("failure_message", Main.i18n("install.failed")); + settings.put("success_message", Launcher.i18n("install.success")); + settings.put("failure_message", Launcher.i18n("install.failed")); switch (Lang.parseInt(settings.get(InstallTypePage.INSTALL_TYPE), -1)) { case 0: return finishVersionDownloadingAsync(settings); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallTypePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallTypePage.java index 2445e59a2..5c8212ee3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallTypePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallTypePage.java @@ -20,7 +20,7 @@ package org.jackhuang.hmcl.ui.download; import com.jfoenix.controls.JFXListView; import javafx.fxml.FXML; import javafx.scene.layout.StackPane; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.wizard.WizardController; import org.jackhuang.hmcl.ui.wizard.WizardPage; @@ -28,12 +28,10 @@ import org.jackhuang.hmcl.ui.wizard.WizardPage; import java.util.Map; public final class InstallTypePage extends StackPane implements WizardPage { - private final WizardController controller; @FXML private JFXListView list; public InstallTypePage(WizardController controller) { - this.controller = controller; FXUtils.loadFXML(this, "/assets/fxml/download/dltype.fxml"); list.setOnMouseClicked(e -> { @@ -51,7 +49,7 @@ public final class InstallTypePage extends StackPane implements WizardPage { @Override public String getTitle() { - return Main.i18n("install.select"); + return Launcher.i18n("install.select"); } public static final String INSTALL_TYPE = "INSTALL_TYPE"; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallerWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallerWizardProvider.java index 9272dc7e3..d8e77a3b7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallerWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallerWizardProvider.java @@ -18,7 +18,7 @@ package org.jackhuang.hmcl.ui.download; import javafx.scene.Node; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider; import org.jackhuang.hmcl.download.RemoteVersion; import org.jackhuang.hmcl.game.Version; @@ -80,8 +80,8 @@ public final class InstallerWizardProvider implements WizardProvider { @Override public Object finish(Map settings) { - settings.put("success_message", Main.i18n("install.success")); - settings.put("failure_message", Main.i18n("install.failed")); + settings.put("success_message", Launcher.i18n("install.success")); + settings.put("failure_message", Launcher.i18n("install.failed")); Task ret = Task.empty(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java index 6f81fc2bb..df52ef8c7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java @@ -23,7 +23,7 @@ import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.download.RemoteVersion; import org.jackhuang.hmcl.game.GameRepository; @@ -34,12 +34,9 @@ import org.jackhuang.hmcl.ui.wizard.WizardPage; import org.jackhuang.hmcl.util.StringUtils; import java.util.Map; -import java.util.Optional; public class InstallersPage extends StackPane implements WizardPage { private final WizardController controller; - private final GameRepository repository; - private final DownloadProvider downloadProvider; @FXML private VBox list; @@ -73,37 +70,35 @@ public class InstallersPage extends StackPane implements WizardPage { public InstallersPage(WizardController controller, GameRepository repository, DownloadProvider downloadProvider) { this.controller = controller; - this.repository = repository; - this.downloadProvider = downloadProvider; FXUtils.loadFXML(this, "/assets/fxml/download/installers.fxml"); String gameVersion = ((RemoteVersion) controller.getSettings().get("game")).getGameVersion(); Validator hasVersion = new Validator(s -> !repository.hasVersion(s) && StringUtils.isNotBlank(s)); - hasVersion.setMessage(Main.i18n("install.new_game.already_exists")); + hasVersion.setMessage(Launcher.i18n("install.new_game.already_exists")); txtName.getValidators().add(hasVersion); txtName.textProperty().addListener(e -> btnInstall.setDisable(!txtName.validate())); txtName.setText(gameVersion); btnForge.setOnMouseClicked(e -> { controller.getSettings().put(INSTALLER_TYPE, 0); - controller.onNext(new VersionsPage(controller, Main.i18n("install.installer.choose", Main.i18n("install.installer.forge")), gameVersion, downloadProvider, "forge", () -> controller.onPrev(false))); + controller.onNext(new VersionsPage(controller, Launcher.i18n("install.installer.choose", Launcher.i18n("install.installer.forge")), gameVersion, downloadProvider, "forge", () -> controller.onPrev(false))); }); btnLiteLoader.setOnMouseClicked(e -> { controller.getSettings().put(INSTALLER_TYPE, 1); - controller.onNext(new VersionsPage(controller, Main.i18n("install.installer.choose", Main.i18n("install.installer.liteloader")), gameVersion, downloadProvider, "liteloader", () -> controller.onPrev(false))); + controller.onNext(new VersionsPage(controller, Launcher.i18n("install.installer.choose", Launcher.i18n("install.installer.liteloader")), gameVersion, downloadProvider, "liteloader", () -> controller.onPrev(false))); }); btnOptiFine.setOnMouseClicked(e -> { controller.getSettings().put(INSTALLER_TYPE, 2); - controller.onNext(new VersionsPage(controller, Main.i18n("install.installer.choose", Main.i18n("install.installer.optifine")), gameVersion, downloadProvider, "optifine", () -> controller.onPrev(false))); + controller.onNext(new VersionsPage(controller, Launcher.i18n("install.installer.choose", Launcher.i18n("install.installer.optifine")), gameVersion, downloadProvider, "optifine", () -> controller.onPrev(false))); }); } @Override public String getTitle() { - return Main.i18n("install.new_game"); + return Launcher.i18n("install.new_game"); } private String getVersion(String id) { @@ -112,21 +107,21 @@ public class InstallersPage extends StackPane implements WizardPage { @Override public void onNavigate(Map settings) { - lblGameVersion.setText(Main.i18n("install.new_game.current_game_version") + ": " + getVersion("game")); + lblGameVersion.setText(Launcher.i18n("install.new_game.current_game_version") + ": " + getVersion("game")); if (controller.getSettings().containsKey("forge")) - lblForge.setText(Main.i18n("install.installer.version", Main.i18n("install.installer.forge")) + ": " + getVersion("forge")); + lblForge.setText(Launcher.i18n("install.installer.version", Launcher.i18n("install.installer.forge")) + ": " + getVersion("forge")); else - lblForge.setText(Main.i18n("install.installer.not_installed", Main.i18n("install.installer.forge"))); + lblForge.setText(Launcher.i18n("install.installer.not_installed", Launcher.i18n("install.installer.forge"))); if (controller.getSettings().containsKey("liteloader")) - lblLiteLoader.setText(Main.i18n("install.installer.version", Main.i18n("install.installer.liteloader")) + ": " + getVersion("liteloader")); + lblLiteLoader.setText(Launcher.i18n("install.installer.version", Launcher.i18n("install.installer.liteloader")) + ": " + getVersion("liteloader")); else - lblLiteLoader.setText(Main.i18n("install.installer.not_installed", Main.i18n("install.installer.liteloader"))); + lblLiteLoader.setText(Launcher.i18n("install.installer.not_installed", Launcher.i18n("install.installer.liteloader"))); if (controller.getSettings().containsKey("optifine")) - lblOptiFine.setText(Main.i18n("install.installer.version", Main.i18n("install.installer.optifine")) + ": " + getVersion("optifine")); + lblOptiFine.setText(Launcher.i18n("install.installer.version", Launcher.i18n("install.installer.optifine")) + ": " + getVersion("optifine")); else - lblOptiFine.setText(Main.i18n("install.installer.not_installed", Main.i18n("install.installer.optifine"))); + lblOptiFine.setText(Launcher.i18n("install.installer.not_installed", Launcher.i18n("install.installer.optifine"))); } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java index 072f55ff5..28853e893 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java @@ -25,7 +25,7 @@ import javafx.scene.control.Label; import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.stage.FileChooser; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.game.ModpackHelper; import org.jackhuang.hmcl.mod.Modpack; import org.jackhuang.hmcl.mod.UnsupportedModpackException; @@ -75,16 +75,16 @@ public final class ModpackPage extends StackPane implements WizardPage { Profile profile = (Profile) controller.getSettings().get("PROFILE"); FileChooser chooser = new FileChooser(); - chooser.setTitle(Main.i18n("modpack.choose")); - chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Main.i18n("modpack"), "*.zip")); + chooser.setTitle(Launcher.i18n("modpack.choose")); + chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Launcher.i18n("modpack"), "*.zip")); File selectedFile = chooser.showOpenDialog(Controllers.getStage()); if (selectedFile == null) Platform.runLater(() -> Controllers.navigate(null)); else { controller.getSettings().put(MODPACK_FILE, selectedFile); lblModpackLocation.setText(selectedFile.getAbsolutePath()); txtModpackName.getValidators().addAll( - new Validator(Main.i18n("install.new_game.already_exists"), str -> !profile.getRepository().hasVersion(str) && StringUtils.isNotBlank(str)), - new Validator(Main.i18n("version.forbidden_name"), str -> !profile.getRepository().forbidsVersion(str)) + new Validator(Launcher.i18n("install.new_game.already_exists"), str -> !profile.getRepository().hasVersion(str) && StringUtils.isNotBlank(str)), + new Validator(Launcher.i18n("version.forbidden_name"), str -> !profile.getRepository().forbidsVersion(str)) ); txtModpackName.textProperty().addListener(e -> btnInstall.setDisable(!txtModpackName.validate())); @@ -96,7 +96,7 @@ public final class ModpackPage extends StackPane implements WizardPage { lblAuthor.setText(manifest.getAuthor()); txtModpackName.setText(manifest.getName() + (StringUtils.isBlank(manifest.getVersion()) ? "" : "-" + manifest.getVersion())); } catch (UnsupportedModpackException e) { - txtModpackName.setText(Main.i18n("modpack.task.install.error")); + txtModpackName.setText(Launcher.i18n("modpack.task.install.error")); } } } @@ -118,14 +118,14 @@ public final class ModpackPage extends StackPane implements WizardPage { if (manifest != null) { WebStage stage = new WebStage(); stage.getWebView().getEngine().loadContent(manifest.getDescription()); - stage.setTitle(Main.i18n("modpack.wizard.step.3")); + stage.setTitle(Launcher.i18n("modpack.wizard.step.3")); stage.showAndWait(); } } @Override public String getTitle() { - return Main.i18n("modpack.task.install"); + return Launcher.i18n("modpack.task.install"); } public static final String MODPACK_FILE = "MODPACK_FILE"; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java index bf02c6990..c8611384a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java @@ -44,11 +44,9 @@ import java.util.Objects; import java.util.stream.Collectors; public final class VersionsPage extends StackPane implements WizardPage, Refreshable { - private final WizardController controller; private final String gameVersion; private final DownloadProvider downloadProvider; private final String libraryId; - private final Runnable callback; private final String title; @FXML @@ -73,12 +71,10 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh private TaskExecutor executor; public VersionsPage(WizardController controller, String title, String gameVersion, DownloadProvider downloadProvider, String libraryId, Runnable callback) { - this.controller = controller; this.title = title; this.gameVersion = gameVersion; this.downloadProvider = downloadProvider; this.libraryId = libraryId; - this.callback = callback; this.versionList = downloadProvider.getVersionListById(libraryId); FXUtils.loadFXML(this, "/assets/fxml/download/versions.fxml"); @@ -93,10 +89,10 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh chkSnapshot.selectedProperty().addListener(listener); chkOld.selectedProperty().addListener(listener); - list.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> { - if (newValue == null) + list.setOnMouseClicked(e -> { + if (list.getSelectionModel().getSelectedIndex() < 0) return; - controller.getSettings().put(libraryId, newValue.getRemoteVersion()); + controller.getSettings().put(libraryId, list.getSelectionModel().getSelectedItem().getRemoteVersion()); callback.run(); }); refresh(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPageItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPageItem.java index 99ecf5571..79f2633fc 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPageItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPageItem.java @@ -23,7 +23,7 @@ import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.download.RemoteVersion; import org.jackhuang.hmcl.download.game.GameRemoteVersionTag; import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersionTag; @@ -57,15 +57,15 @@ public final class VersionsPageItem extends StackPane { if (remoteVersion.getTag() instanceof GameRemoteVersionTag) { switch (((GameRemoteVersionTag) remoteVersion.getTag()).getType()) { case RELEASE: - lblGameVersion.setText(Main.i18n("version.game.release")); + lblGameVersion.setText(Launcher.i18n("version.game.release")); imageView.setImage(new Image("/assets/img/icon.png", 32, 32, false, true)); break; case SNAPSHOT: - lblGameVersion.setText(Main.i18n("version.game.snapshot")); + lblGameVersion.setText(Launcher.i18n("version.game.snapshot")); imageView.setImage(new Image("/assets/img/command.png", 32, 32, false, true)); break; default: - lblGameVersion.setText(Main.i18n("version.game.old")); + lblGameVersion.setText(Launcher.i18n("version.game.old")); imageView.setImage(new Image("/assets/img/grass.png", 32, 32, false, true)); break; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackFileSelectionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackFileSelectionPage.java index 82cb9f1b4..16910cc5f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackFileSelectionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackFileSelectionPage.java @@ -25,7 +25,7 @@ import javafx.scene.control.Label; import javafx.scene.control.TreeItem; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.game.ModAdviser; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.ui.FXUtils; @@ -66,6 +66,9 @@ public final class ModpackFileSelectionPage extends StackPane implements WizardP } private CheckBoxTreeItem getTreeItem(File file, String basePath) { + if (!file.exists()) + return null; + ModAdviser.ModSuggestion state = ModAdviser.ModSuggestion.SUGGESTED; if (basePath.length() > "minecraft/".length()) { state = adviser.advise(StringUtils.substringAfter(basePath, "minecraft/") + (file.isDirectory() ? "/" : ""), file.isDirectory()); @@ -147,23 +150,23 @@ public final class ModpackFileSelectionPage extends StackPane implements WizardP @Override public String getTitle() { - return Main.i18n("modpack.wizard.step.2.title"); + return Launcher.i18n("modpack.wizard.step.2.title"); } public static final String MODPACK_FILE_SELECTION = "modpack.accepted"; private static final Map TRANSLATION = Lang.mapOf( - new Pair<>("minecraft/servers.dat", Main.i18n("modpack.files.servers_dat")), - new Pair<>("minecraft/saves", Main.i18n("modpack.files.saves")), - new Pair<>("minecraft/mods", Main.i18n("modpack.files.mods")), - new Pair<>("minecraft/config", Main.i18n("modpack.files.config")), - new Pair<>("minecraft/liteconfig", Main.i18n("modpack.files.liteconfig")), - new Pair<>("minecraft/resourcepacks", Main.i18n("modpack.files.resourcepacks")), - new Pair<>("minecraft/resources", Main.i18n("modpack.files.resourcepacks")), - new Pair<>("minecraft/options.txt", Main.i18n("modpack.files.options_txt")), - new Pair<>("minecraft/optionsshaders.txt", Main.i18n("modpack.files.optionsshaders_txt")), - new Pair<>("minecraft/mods/VoxelMods", Main.i18n("modpack.files.mods.voxelmods")), - new Pair<>("minecraft/dumps", Main.i18n("modpack.files.dumps")), - new Pair<>("minecraft/blueprints", Main.i18n("modpack.files.blueprints")), - new Pair<>("minecraft/scripts", Main.i18n("modpack.files.scripts")) + new Pair<>("minecraft/servers.dat", Launcher.i18n("modpack.files.servers_dat")), + new Pair<>("minecraft/saves", Launcher.i18n("modpack.files.saves")), + new Pair<>("minecraft/mods", Launcher.i18n("modpack.files.mods")), + new Pair<>("minecraft/config", Launcher.i18n("modpack.files.config")), + new Pair<>("minecraft/liteconfig", Launcher.i18n("modpack.files.liteconfig")), + new Pair<>("minecraft/resourcepacks", Launcher.i18n("modpack.files.resourcepacks")), + new Pair<>("minecraft/resources", Launcher.i18n("modpack.files.resourcepacks")), + new Pair<>("minecraft/options.txt", Launcher.i18n("modpack.files.options_txt")), + new Pair<>("minecraft/optionsshaders.txt", Launcher.i18n("modpack.files.optionsshaders_txt")), + new Pair<>("minecraft/mods/VoxelMods", Launcher.i18n("modpack.files.mods.voxelmods")), + new Pair<>("minecraft/dumps", Launcher.i18n("modpack.files.dumps")), + new Pair<>("minecraft/blueprints", Launcher.i18n("modpack.files.blueprints")), + new Pair<>("minecraft/scripts", Launcher.i18n("modpack.files.scripts")) ); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackInfoPage.java index 3911c154e..e903d6f5d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ModpackInfoPage.java @@ -26,7 +26,7 @@ import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.layout.StackPane; import javafx.stage.FileChooser; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.setting.Settings; import org.jackhuang.hmcl.ui.Controllers; @@ -72,8 +72,8 @@ public final class ModpackInfoPage extends StackPane implements WizardPage { @FXML private void onNext() { FileChooser fileChooser = new FileChooser(); - fileChooser.setTitle(Main.i18n("modpack.wizard.step.initialization.save")); - fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Main.i18n("modpack"), "*.zip")); + fileChooser.setTitle(Launcher.i18n("modpack.wizard.step.initialization.save")); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Launcher.i18n("modpack"), "*.zip")); File file = fileChooser.showSaveDialog(Controllers.getStage()); if (file == null) { Controllers.navigate(null); @@ -100,7 +100,7 @@ public final class ModpackInfoPage extends StackPane implements WizardPage { @Override public String getTitle() { - return Main.i18n("modpack.wizard.step.1.title"); + return Launcher.i18n("modpack.wizard.step.1.title"); } public static final String MODPACK_NAME = "modpack.name"; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/AppDataUpgrader.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/AppDataUpgrader.java index 12df8ecb2..5ac4d50d1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/AppDataUpgrader.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/AppDataUpgrader.java @@ -18,12 +18,13 @@ package org.jackhuang.hmcl.upgrade; import com.google.gson.JsonParseException; -import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; -import org.jackhuang.hmcl.Main; +import com.jfoenix.concurrency.JFXUtilities; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.task.TaskExecutor; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.construct.MessageBox; import org.jackhuang.hmcl.util.*; @@ -55,12 +56,19 @@ public class AppDataUpgrader extends IUpgrader { if (mainClass != null) { ArrayList al = new ArrayList<>(args); al.add("--noupdate"); - AccessController.doPrivileged((PrivilegedExceptionAction) () -> { - new URLClassLoader(new URL[]{jar.toURI().toURL()}, - ClassLoader.getSystemClassLoader().getParent()).loadClass(mainClass) - .getMethod("main", String[].class).invoke(null, new Object[]{al.toArray(new String[0])}); - return null; - }); + ClassLoader pre = Thread.currentThread().getContextClassLoader(); + try { + AccessController.doPrivileged((PrivilegedExceptionAction) () -> { + Logging.stop(); + ClassLoader now = new URLClassLoader(new URL[]{jar.toURI().toURL()}, ClassLoader.getSystemClassLoader().getParent()); + Thread.currentThread().setContextClassLoader(now); + now.loadClass(mainClass).getMethod("main", String[].class).invoke(null, new Object[]{al.toArray(new String[0])}); + return null; + }); + } finally { + Logging.start(); + Thread.currentThread().setContextClassLoader(pre); + } return true; } } @@ -97,18 +105,18 @@ public class AppDataUpgrader extends IUpgrader { if (!(ver instanceof IntVersionNumber)) return; IntVersionNumber version = (IntVersionNumber) ver; - checker.requestDownloadLink().then(Task.of(Schedulers.javafx(), variables -> { + checker.requestDownloadLink().then(Task.of(variables -> { Map map = variables.get(UpdateChecker.REQUEST_DOWNLOAD_LINK_ID); - if (MessageBox.confirm(Main.i18n("update.newest_version") + version.toString() + "\n" - + Main.i18n("update.should_open_link"), + if (MessageBox.confirm(Launcher.i18n("update.newest_version", version.toString()) + "\n" + + Launcher.i18n("update.should_open_link"), MessageBox.YES_NO_OPTION) == MessageBox.YES_OPTION) if (map != null && map.containsKey("jar") && !StringUtils.isBlank(map.get("jar"))) try { String hash = null; if (map.containsKey("jarsha1")) hash = map.get("jarsha1"); - Controllers.dialog(Main.i18n("message.downloading")); + Controllers.dialog(Launcher.i18n("message.downloading")); if (new AppDataUpgraderJarTask(NetworkUtils.toURL(map.get("jar")), version.toString(), hash).test()) { new ProcessBuilder(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath(), "-jar", AppDataUpgraderJarTask.getSelf(version.toString()).getAbsolutePath()) .directory(new File("").getAbsoluteFile()).start(); @@ -123,31 +131,31 @@ public class AppDataUpgrader extends IUpgrader { String hash = null; if (map.containsKey("packsha1")) hash = map.get("packsha1"); - Controllers.dialog(Main.i18n("message.downloading")); - if (new AppDataUpgraderPackGzTask(NetworkUtils.toURL(map.get("pack")), version.toString(), hash).test()) { + Task task = new AppDataUpgraderPackGzTask(NetworkUtils.toURL(map.get("pack")), version.toString(), hash); + TaskExecutor executor = task.executor(); + JFXUtilities.runInFX(() -> Controllers.taskDialog(executor, Launcher.i18n("message.downloading"), "", null)); + if (executor.test()) { new ProcessBuilder(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath(), "-jar", AppDataUpgraderPackGzTask.getSelf(version.toString()).getAbsolutePath()) .directory(new File("").getAbsoluteFile()).start(); System.exit(0); } - Controllers.closeDialog(); + JFXUtilities.runInFX(Controllers::closeDialog); } catch (IOException ex) { Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex); } else { - String url = Main.PUBLISH; + String url = Launcher.PUBLISH; if (map != null) if (map.containsKey(OperatingSystem.CURRENT_OS.getCheckedName())) url = map.get(OperatingSystem.CURRENT_OS.getCheckedName()); else if (map.containsKey(OperatingSystem.UNKNOWN.getCheckedName())) url = map.get(OperatingSystem.UNKNOWN.getCheckedName()); - if (url == null) - url = Main.PUBLISH; try { java.awt.Desktop.getDesktop().browse(new URI(url)); } catch (URISyntaxException | IOException e) { Logging.LOG.log(Level.SEVERE, "Failed to browse uri: " + url, e); OperatingSystem.setClipboard(url); - MessageBox.show(Main.i18n("update.no_browser")); + MessageBox.show(Launcher.i18n("update.no_browser")); } } })).start(); @@ -155,7 +163,7 @@ public class AppDataUpgrader extends IUpgrader { public static class AppDataUpgraderPackGzTask extends Task { - public static final File BASE_FOLDER = Main.HMCL_DIRECTORY; + public static final File BASE_FOLDER = Launcher.HMCL_DIRECTORY; public static final File HMCL_VER_FILE = new File(BASE_FOLDER, "hmclver.json"); public static File getSelf(String ver) { @@ -177,7 +185,7 @@ public class AppDataUpgrader extends IUpgrader { @Override public Collection getDependents() { - return Arrays.asList(new FileDownloadTask(downloadLink, tempFile, Proxy.NO_PROXY, hash)); + return Collections.singleton(new FileDownloadTask(downloadLink, tempFile, Proxy.NO_PROXY, hash)); } @Override @@ -205,7 +213,7 @@ public class AppDataUpgrader extends IUpgrader { public static class AppDataUpgraderJarTask extends Task { - public static final File BASE_FOLDER = Main.getWorkingDirectory("hmcl"); + public static final File BASE_FOLDER = Launcher.getWorkingDirectory("hmcl"); public static final File HMCL_VER_FILE = new File(BASE_FOLDER, "hmclver.json"); public static File getSelf(String ver) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/NewFileUpgrader.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/NewFileUpgrader.java index 728626a0a..5437a0c01 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/NewFileUpgrader.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/NewFileUpgrader.java @@ -17,7 +17,7 @@ */ package org.jackhuang.hmcl.upgrade; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.util.Charsets; @@ -51,7 +51,7 @@ public class NewFileUpgrader extends IUpgrader { URL url = requestDownloadLink(); if (url == null) return; File newf = new File(url.getFile()); - Controllers.dialog(Main.i18n("message.downloading")); + Controllers.dialog(Launcher.i18n("message.downloading")); if (new FileDownloadTask(url, newf).test()) { try { new ProcessBuilder(newf.getCanonicalPath(), "--removeOldLauncher", getRealPath()) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChecker.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChecker.java index 2fc852dbd..9dcd9eed9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChecker.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChecker.java @@ -18,7 +18,7 @@ package org.jackhuang.hmcl.upgrade; import com.google.gson.JsonSyntaxException; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.event.Event; import org.jackhuang.hmcl.event.EventBus; import org.jackhuang.hmcl.event.OutOfDateEvent; @@ -67,18 +67,18 @@ public final class UpdateChecker { return new TaskResult() { @Override public void execute() throws Exception { - if (Main.VERSION.contains("@")) + if (Launcher.VERSION.contains("@")) return; if (value == null) { - versionString = NetworkUtils.doGet(NetworkUtils.toURL("https://huangyuhui.duapp.com/hmcl/update.php?version=" + Main.VERSION)); + versionString = NetworkUtils.doGet(NetworkUtils.toURL("https://huangyuhui.duapp.com/hmcl/update.php?version=" + Launcher.VERSION)); value = VersionNumber.asVersion(versionString); } if (value == null) { Logging.LOG.warning("Unable to check update..."); if (showMessage) - MessageBox.show(Main.i18n("update.failed")); + MessageBox.show(Launcher.i18n("update.failed")); } else if (base.compareTo(value) < 0) outOfDate = true; if (outOfDate) @@ -115,7 +115,7 @@ public final class UpdateChecker { public void execute() { if (download_link == null) try { - download_link = Constants.GSON.>fromJson(NetworkUtils.doGet(NetworkUtils.toURL("https://huangyuhui.duapp.com/update_link.php?type=hmcl")), Map.class); + download_link = Constants.GSON.>fromJson(NetworkUtils.doGet(NetworkUtils.toURL("https://huangyuhui.duapp.com/hmcl/update_link.php")), Map.class); } catch (JsonSyntaxException | IOException e) { Logging.LOG.log(Level.SEVERE, "Failed to get update link.", e); } @@ -134,7 +134,7 @@ public final class UpdateChecker { public void checkOutdate() { if (outOfDate) if (EventBus.EVENT_BUS.fireEvent(new OutOfDateEvent(this, getNewVersion())) != Event.Result.DENY) { - Main.UPGRADER.download(this, getNewVersion()); + Launcher.UPGRADER.download(this, getNewVersion()); } } } 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 594997994..7297f455c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java @@ -18,7 +18,7 @@ package org.jackhuang.hmcl.util; import javafx.application.Platform; -import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.ui.CrashWindow; import org.jackhuang.hmcl.ui.construct.MessageBox; @@ -36,15 +36,17 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler { private static final HashMap SOURCE = new HashMap() { { - put("javafx.fxml.LoadException", Main.i18n("crash.NoClassDefFound")); - put("UnsatisfiedLinkError", Main.i18n("crash.user_fault")); - put("java.lang.NoClassDefFoundError", Main.i18n("crash.NoClassDefFound")); - put("java.lang.VerifyError", Main.i18n("crash.NoClassDefFound")); - put("java.lang.NoSuchMethodError", Main.i18n("crash.NoClassDefFound")); - put("java.lang.IncompatibleClassChangeError", Main.i18n("crash.NoClassDefFound")); - put("java.lang.ClassFormatError", Main.i18n("crash.NoClassDefFound")); + put("javafx.fxml.LoadException", Launcher.i18n("crash.NoClassDefFound")); + put("Location is not set", Launcher.i18n("crash.NoClassDefFound")); + put("UnsatisfiedLinkError", Launcher.i18n("crash.user_fault")); + put("java.lang.NoClassDefFoundError", Launcher.i18n("crash.NoClassDefFound")); + put("java.lang.VerifyError", Launcher.i18n("crash.NoClassDefFound")); + put("java.lang.NoSuchMethodError", Launcher.i18n("crash.NoClassDefFound")); + put("java.lang.IncompatibleClassChangeError", Launcher.i18n("crash.NoClassDefFound")); + put("java.lang.ClassFormatError", Launcher.i18n("crash.NoClassDefFound")); put("java.lang.OutOfMemoryError", "FUCKING MEMORY LIMIT!"); - put("Trampoline", Main.i18n("launcher.update_java")); + put("Trampoline", Launcher.i18n("launcher.update_java")); + put("com.sun.javafx.css.StyleManager.findMatchingStyles", Launcher.i18n("launcher.update_java")); put("NoSuchAlgorithmException", "Has your operating system been installed completely or is a ghost system?"); } }; @@ -78,24 +80,22 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler { THROWABLE_SET.add(stackTrace); try { - StringBuilder builder = new StringBuilder(); - builder.append("---- Hello Minecraft! Crash Report ----\n"); - builder.append(" Version: " + Main.VERSION + "\n"); - builder.append(" Time: ").append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())).append("\n"); - builder.append(" Thread: ").append(t.toString()).append("\n"); - builder.append("\n Content: \n "); - builder.append(stackTrace).append("\n\n"); - builder.append("-- System Details --\n"); - builder.append(" Operating System: ").append(System.getProperty("os.name")).append(' ').append(OperatingSystem.SYSTEM_VERSION).append("\n"); - builder.append(" Java Version: ").append(System.getProperty("java.version")).append(", ").append(System.getProperty("java.vendor")).append("\n"); - builder.append(" Java VM Version: ").append(System.getProperty("java.vm.name")).append(" (").append(System.getProperty("java.vm.info")).append("), ").append(System.getProperty("java.vm.vendor")).append("\n"); - String text = builder.toString(); + String text = "---- Hello Minecraft! Crash Report ----\n" + + " Version: " + Launcher.VERSION + "\n" + + " Time: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "\n" + + " Thread: " + t.toString() + "\n" + + "\n Content: \n " + + stackTrace + "\n\n" + + "-- 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"; Logging.LOG.log(Level.SEVERE, text); if (checkThrowable(e) && !text.contains("OpenJDK")) { Platform.runLater(() -> new CrashWindow(text).show()); - if (!Main.UPDATE_CHECKER.isOutOfDate()) + if (!Launcher.UPDATE_CHECKER.isOutOfDate()) reportToServer(text); } } catch (Throwable ex) { @@ -110,7 +110,8 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler { Thread t = new Thread(() -> { HashMap map = new HashMap<>(); map.put("crash_report", text); - map.put("version", Main.VERSION); + map.put("version", Launcher.VERSION); + map.put("log", Logging.getLogs()); try { String response = NetworkUtils.doPost(NetworkUtils.toURL("https://huangyuhui.duapp.com/hmcl/crash.php"), map); if (StringUtils.isNotBlank(response)) diff --git a/HMCL/src/main/resources/assets/fxml/account-add.fxml b/HMCL/src/main/resources/assets/fxml/account-add.fxml new file mode 100644 index 000000000..f6ff6f095 --- /dev/null +++ b/HMCL/src/main/resources/assets/fxml/account-add.fxml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HMCL/src/main/resources/assets/fxml/account-item.fxml b/HMCL/src/main/resources/assets/fxml/account-item.fxml deleted file mode 100644 index 347b92021..000000000 --- a/HMCL/src/main/resources/assets/fxml/account-item.fxml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/HMCL/src/main/resources/assets/fxml/account.fxml b/HMCL/src/main/resources/assets/fxml/account.fxml index 75fc2add3..98666ef13 100644 --- a/HMCL/src/main/resources/assets/fxml/account.fxml +++ b/HMCL/src/main/resources/assets/fxml/account.fxml @@ -1,75 +1,82 @@ - - + - - + + - - - - - - - - - - - + - - - - - - - - - - - - + - - - - - - - - - - - + + + + + + + + + + diff --git a/HMCL/src/main/resources/assets/fxml/authlib-injector-servers.fxml b/HMCL/src/main/resources/assets/fxml/authlib-injector-servers.fxml index 05ea1066d..e37c03188 100644 --- a/HMCL/src/main/resources/assets/fxml/authlib-injector-servers.fxml +++ b/HMCL/src/main/resources/assets/fxml/authlib-injector-servers.fxml @@ -32,9 +32,11 @@