diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/LauncherHelper.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/LauncherHelper.kt index ad3c0af18..98aba9e72 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/LauncherHelper.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/LauncherHelper.kt @@ -17,27 +17,86 @@ */ package org.jackhuang.hmcl.game +import org.jackhuang.hmcl.auth.AuthenticationException +import org.jackhuang.hmcl.launch.DefaultLauncher import org.jackhuang.hmcl.mod.CurseForgeModpackCompletionTask import org.jackhuang.hmcl.setting.Settings -import org.jackhuang.hmcl.task.Scheduler +import org.jackhuang.hmcl.task.* +import org.jackhuang.hmcl.ui.Controllers +import org.jackhuang.hmcl.ui.DialogController +import org.jackhuang.hmcl.ui.LaunchingStepsPane +import org.jackhuang.hmcl.ui.runOnUiThread + object LauncherHelper { + val launchingStepsPane = LaunchingStepsPane() + fun launch() { val profile = Settings.selectedProfile val repository = profile.repository val dependency = profile.dependency val account = Settings.selectedAccount ?: throw IllegalStateException("No account here") val version = repository.getVersion(profile.selectedVersion) - val launcher = HMCLGameLauncher( - repository = repository, - versionId = profile.selectedVersion, - options = profile.getVersionSetting(profile.selectedVersion).toLaunchOptions(profile.gameDir), - account = account.logIn(Settings.proxy) - ) + var finished = 0 - dependency.checkGameCompletionAsync(version) + Controllers.dialog(launchingStepsPane) + task(Scheduler.JAVAFX) { emitStatus(LoadingState.DEPENDENCIES) } + .then(dependency.checkGameCompletionAsync(version)) + + .then(task(Scheduler.JAVAFX) { emitStatus(LoadingState.MODS) }) .then(CurseForgeModpackCompletionTask(dependency, profile.selectedVersion)) - .then(launcher.launchAsync()) - .subscribe(Scheduler.JAVAFX) { println("lalala") } + + .then(task(Scheduler.JAVAFX) { emitStatus(LoadingState.LOGIN) }) + .then(task { + try { + it["account"] = account.logIn(Settings.proxy) + } catch (e: AuthenticationException) { + it["account"] = DialogController.logIn(account) + runOnUiThread { Controllers.dialog(launchingStepsPane) } + } + }) + + .then(task(Scheduler.JAVAFX) { emitStatus(LoadingState.LAUNCHING) }) + .then(task { + it["launcher"] = HMCLGameLauncher( + repository = repository, + versionId = profile.selectedVersion, + options = profile.getVersionSetting(profile.selectedVersion).toLaunchOptions(profile.gameDir), + account = it["account"] + ) + }) + .then { it.get("launcher").launchAsync() } + + .then(task(Scheduler.JAVAFX) { emitStatus(LoadingState.DONE) }) + .executor() + .apply { + taskListener = object : TaskListener { + override fun onFinished(task: Task) { + ++finished + runOnUiThread { launchingStepsPane.pgsTasks.progress = 1.0 * finished / totTask.get() } + } + + override fun onTerminate() { + runOnUiThread { Controllers.closeDialog() } + } + + override fun end() { + runOnUiThread { Controllers.closeDialog() } + } + } + }.start() + } + + fun emitStatus(state: LoadingState) { + launchingStepsPane.lblCurrentState.text = state.toString() + launchingStepsPane.lblSteps.text = "${state.ordinal + 1} / ${LoadingState.values().size}" + } + + enum class LoadingState { + DEPENDENCIES, + MODS, + LOGIN, + LAUNCHING, + DONE } } \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/AccountSkin.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/AccountHelper.kt similarity index 96% rename from HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/AccountSkin.kt rename to HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/AccountHelper.kt index d89570d86..aa0af1e65 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/AccountSkin.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/AccountHelper.kt @@ -23,10 +23,11 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount import org.jackhuang.hmcl.task.FileDownloadTask import org.jackhuang.hmcl.task.Scheduler import org.jackhuang.hmcl.task.Task +import org.jackhuang.hmcl.ui.DialogController import org.jackhuang.hmcl.util.toURL import java.net.Proxy -object AccountSkin { +object AccountHelper { val SKIN_DIR = Main.APPDATA.resolve("skins") fun loadSkins(proxy: Proxy = Settings.proxy) { @@ -49,7 +50,7 @@ object AccountSkin { override fun execute() { if (account.canLogIn && (account.selectedProfile == null || refresh)) - account.logIn(proxy) + DialogController.logIn(account) val profile = account.selectedProfile ?: return val name = profile.name ?: return val url = "http://skins.minecraft.net/MinecraftSkins/$name.png" diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountItem.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountItem.kt index f58e331a8..60a1e3dc9 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountItem.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountItem.kt @@ -18,10 +18,8 @@ package org.jackhuang.hmcl.ui import com.jfoenix.controls.JFXButton -import com.jfoenix.controls.JFXCheckBox import com.jfoenix.controls.JFXProgressBar import com.jfoenix.controls.JFXRadioButton -import com.jfoenix.effects.JFXDepthManager import javafx.beans.binding.Bindings import javafx.fxml.FXML import javafx.geometry.Rectangle2D @@ -30,13 +28,15 @@ import javafx.scene.control.ToggleGroup import javafx.scene.effect.BlurType import javafx.scene.effect.DropShadow import javafx.scene.image.ImageView +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.OfflineAccount import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount -import org.jackhuang.hmcl.setting.AccountSkin +import org.jackhuang.hmcl.setting.AccountHelper import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.task.Scheduler import java.util.concurrent.Callable @@ -53,6 +53,7 @@ class AccountItem(i: Int, val account: Account, group: ToggleGroup) : StackPane( @FXML lateinit var lblType: Label @FXML lateinit var pgsSkin: JFXProgressBar @FXML lateinit var portraitView: ImageView + @FXML lateinit var buttonPane: HBox init { loadFXML("/assets/fxml/account-item.fxml") @@ -78,11 +79,19 @@ class AccountItem(i: Int, val account: Account, group: ToggleGroup) : StackPane( lblUser.text = account.username lblType.text = accountType(account) - if (account is YggdrasilAccount) + if (account is YggdrasilAccount) { btnRefresh.setOnMouseClicked { pgsSkin.isVisible = true - AccountSkin.refreshSkinAsync(account).subscribe(Scheduler.JAVAFX) { loadSkin() } + AccountHelper.refreshSkinAsync(account) + .subscribe(Scheduler.JAVAFX) { loadSkin() } } + AccountHelper.loadSkinAsync(account) + .subscribe(Scheduler.JAVAFX) { loadSkin() } + } + + if (account is OfflineAccount) { // Offline Account cannot be refreshed, + buttonPane.children -= btnRefresh + } } fun loadSkin() { @@ -91,7 +100,7 @@ class AccountItem(i: Int, val account: Account, group: ToggleGroup) : StackPane( pgsSkin.isVisible = false val size = 8.0 * 4 portraitView.viewport = Rectangle2D(size, size, size, size) - portraitView.image = AccountSkin.getSkin(account, 4.0) + portraitView.image = AccountHelper.getSkin(account, 4.0) portraitView.fitHeight = 32.0 portraitView.fitWidth = 32.0 } diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountsPage.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountsPage.kt index ef15ea8ea..8273e86ec 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountsPage.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountsPage.kt @@ -32,7 +32,6 @@ import org.jackhuang.hmcl.auth.Account import org.jackhuang.hmcl.auth.OfflineAccount import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount import org.jackhuang.hmcl.i18n -import org.jackhuang.hmcl.setting.AccountSkin import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.task.Scheduler import org.jackhuang.hmcl.task.taskResult @@ -117,11 +116,6 @@ class AccountsPage() : StackPane(), DecoratorPage { Settings.deleteAccount(account.username) Platform.runLater(this@AccountsPage::loadAccounts) } - - if (account is YggdrasilAccount) - AccountSkin.loadSkinAsync(account).subscribe(Scheduler.JAVAFX) { - loadSkin() - } } } @@ -136,6 +130,7 @@ class AccountsPage() : StackPane(), DecoratorPage { val username = txtUsername.text val password = txtPassword.text progressBar.isVisible = true + lblCreationWarning.text = "" taskResult("create_account") { try { val account = when (type) { diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Controllers.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Controllers.kt index ddea79e8a..a27328e23 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Controllers.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Controllers.kt @@ -17,9 +17,11 @@ */ package org.jackhuang.hmcl.ui +import com.jfoenix.controls.JFXDialog import javafx.fxml.FXMLLoader import javafx.scene.Node import javafx.scene.Scene +import javafx.scene.layout.Region import javafx.stage.Stage import org.jackhuang.hmcl.Main @@ -32,7 +34,6 @@ object Controllers { val versionPane = VersionPage() lateinit var leftPaneController: LeftPaneController - lateinit var sidePaneController: SidePaneController lateinit var decorator: Decorator @@ -42,7 +43,6 @@ object Controllers { decorator = Decorator(stage, mainPane, Main.TITLE, max = false) decorator.showPage(null) leftPaneController = LeftPaneController(decorator.leftPane) - sidePaneController = SidePaneController(decorator.sidePane, decorator.drawer) decorator.isCustomMaximize = false @@ -54,6 +54,14 @@ object Controllers { stage.minHeight = 480.0 } + fun dialog(content: Region): JFXDialog { + return decorator.showDialog(content) + } + + fun closeDialog() { + decorator.dialog.close() + } + fun navigate(node: Node?) { decorator.showPage(node) } diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Decorator.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Decorator.kt index ccf1332a2..07dc60b41 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Decorator.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Decorator.kt @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.ui import com.jfoenix.controls.JFXButton +import com.jfoenix.controls.JFXDialog import com.jfoenix.controls.JFXDrawer import com.jfoenix.controls.JFXHamburger import com.jfoenix.effects.JFXDepthManager @@ -64,6 +65,7 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, priva private var allowMove: Boolean = false private var isDragging: Boolean = false private var windowDecoratorAnimation: Timeline? = null + private var dialogShown = false @FXML lateinit var contentPlaceHolder: StackPane @FXML lateinit var drawerWrapper: StackPane @FXML lateinit var titleContainer: BorderPane @@ -78,9 +80,9 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, priva @FXML lateinit var lblTitle: Label @FXML lateinit var leftPane: AdvancedListBox @FXML lateinit var drawer: JFXDrawer - @FXML lateinit var sidePane: AdvancedListBox @FXML lateinit var titleBurgerContainer: StackPane @FXML lateinit var titleBurger: JFXHamburger + @FXML lateinit var dialog: JFXDialog private val onCloseButtonActionProperty: ObjectProperty = SimpleObjectProperty(Runnable { Main.stop() }) @JvmName("onCloseButtonActionProperty") get @@ -125,6 +127,11 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, priva } } + drawerWrapper.children -= dialog + dialog.dialogContainer = drawerWrapper + dialog.setOnDialogClosed { dialogShown = false } + dialog.setOnDialogOpened { dialogShown = true } + if (!min) buttonsContainer.children.remove(btnMin) if (!max) buttonsContainer.children.remove(btnMax) @@ -136,25 +143,6 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, priva setOverflowHidden(lookup("#contentPlaceHolderRoot") as Pane) setOverflowHidden(drawerWrapper) - - // init the title hamburger icon - drawer.setOnDrawerOpening { - val animation = titleBurger.getAnimation() - animation.setRate(1.0) - animation.play() - } - drawer.setOnDrawerClosing { - val animation = titleBurger.getAnimation() - animation.setRate(-1.0) - animation.play() - } - titleBurgerContainer.setOnMouseClicked({ - if (drawer.isHidden || drawer.isHiding) { - drawer.open() - } else { - drawer.close() - } - }) } fun onMouseMoved(mouseEvent: MouseEvent) { @@ -423,6 +411,13 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, priva } } + fun showDialog(content: Region): JFXDialog { + dialog.content = content + if (!dialogShown) + dialog.show() + return dialog + } + fun startWizard(wizardProvider: WizardProvider, category: String? = null) { this.category = category wizardController.provider = wizardProvider diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/DialogController.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/DialogController.kt new file mode 100644 index 000000000..21a157f08 --- /dev/null +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/DialogController.kt @@ -0,0 +1,52 @@ +/* + * 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 org.jackhuang.hmcl.auth.Account +import org.jackhuang.hmcl.auth.AuthInfo +import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount +import org.jackhuang.hmcl.task.SilentException +import java.util.concurrent.CountDownLatch +import java.util.concurrent.atomic.AtomicReference + +object DialogController { + + fun logIn(account: Account): AuthInfo? { + if (account is YggdrasilAccount) { + val latch = CountDownLatch(1) + val res = AtomicReference(null) + runOnUiThread { + val pane = YggdrasilAccountLoginPane(account, success = { + res.set(it) + latch.countDown() + Controllers.closeDialog() + }, failed = { + latch.countDown() + Controllers.closeDialog() + }) + pane.dialog = Controllers.dialog(pane) + } + latch.await() + if (res.get() == null) + throw SilentException() + else + return res.get() + } + return null + } +} \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/FXUtils.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/FXUtils.kt index fd57e68d2..6a996e227 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/FXUtils.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/FXUtils.kt @@ -98,9 +98,7 @@ fun ListView<*>.smoothScrolling() { fun ScrollPane.smoothScrolling() = JFXScrollPane.smoothScrolling(this) -fun runOnUiThread(runnable: () -> Unit) = { - JFXUtilities.runInFX(runnable) -} +fun runOnUiThread(runnable: () -> Unit) = JFXUtilities.runInFX(runnable) fun takeSnapshot(node: Parent, width: Double, height: Double): WritableImage { val scene = Scene(node, width, height) diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SidePaneController.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LaunchingStepsPane.kt similarity index 65% rename from HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SidePaneController.kt rename to HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LaunchingStepsPane.kt index 666d6143a..877495692 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SidePaneController.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LaunchingStepsPane.kt @@ -17,9 +17,20 @@ */ package org.jackhuang.hmcl.ui -import com.jfoenix.controls.JFXDrawer +import com.jfoenix.controls.JFXProgressBar +import javafx.fxml.FXML +import javafx.scene.control.Label +import javafx.scene.layout.StackPane -class SidePaneController(sidePane: AdvancedListBox, drawer: JFXDrawer) { +class LaunchingStepsPane(): StackPane() { + @FXML lateinit var pgsTasks: JFXProgressBar + @FXML lateinit var lblCurrentState: Label + @FXML lateinit var lblSteps: Label init { + loadFXML("/assets/fxml/launching-steps.fxml") + + limitHeight(200.0) + limitWidth(400.0) } + } \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/YggdrasilAccountLoginPane.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/YggdrasilAccountLoginPane.kt new file mode 100644 index 000000000..8a6f16794 --- /dev/null +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/YggdrasilAccountLoginPane.kt @@ -0,0 +1,80 @@ +/* + * 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.JFXDialog +import com.jfoenix.controls.JFXPasswordField +import com.jfoenix.controls.JFXProgressBar +import javafx.fxml.FXML +import javafx.scene.control.Label +import javafx.scene.layout.StackPane +import org.jackhuang.hmcl.auth.AuthInfo +import org.jackhuang.hmcl.auth.yggdrasil.InvalidCredentialsException +import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount +import org.jackhuang.hmcl.i18n +import org.jackhuang.hmcl.setting.Settings +import org.jackhuang.hmcl.task.Scheduler +import org.jackhuang.hmcl.task.taskResult + +class YggdrasilAccountLoginPane(private val oldAccount: YggdrasilAccount, private val success: (AuthInfo) -> Unit, private val failed: () -> Unit) : StackPane() { + @FXML lateinit var lblUsername: Label + @FXML lateinit var txtPassword: JFXPasswordField + @FXML lateinit var lblCreationWarning: Label + @FXML lateinit var progressBar: JFXProgressBar + lateinit var dialog: JFXDialog + + init { + loadFXML("/assets/fxml/yggdrasil-account-login.fxml") + + lblUsername.text = oldAccount.username + txtPassword.setOnAction { + onAccept() + } + } + + fun onAccept() { + val username = oldAccount.username + val password = txtPassword.text + progressBar.isVisible = true + lblCreationWarning.text = "" + taskResult("login") { + try { + val account = YggdrasilAccount.fromUsername(username, password) + account.logIn(Settings.proxy) + } catch (e: Exception) { + e + } + }.subscribe(Scheduler.JAVAFX) { + val account: Any = it["login"] + if (account is AuthInfo) { + success(account) + dialog.close() + } else if (account is InvalidCredentialsException) { + lblCreationWarning.text = i18n("login.wrong_password") + } else if (account is Exception) { + lblCreationWarning.text = account.javaClass.toString() + ": " + account.localizedMessage + } + progressBar.isVisible = false + } + } + + fun onCancel() { + failed() + dialog.close() + } +} \ No newline at end of file diff --git a/HMCL/src/main/resources/assets/fxml/account-item.fxml b/HMCL/src/main/resources/assets/fxml/account-item.fxml index 9c206bdb0..b41ac51c0 100644 --- a/HMCL/src/main/resources/assets/fxml/account-item.fxml +++ b/HMCL/src/main/resources/assets/fxml/account-item.fxml @@ -31,7 +31,7 @@ - + diff --git a/HMCL/src/main/resources/assets/fxml/account.fxml b/HMCL/src/main/resources/assets/fxml/account.fxml index cb79dfecc..dd6dacb6d 100644 --- a/HMCL/src/main/resources/assets/fxml/account.fxml +++ b/HMCL/src/main/resources/assets/fxml/account.fxml @@ -29,7 +29,7 @@ - + diff --git a/HMCL/src/main/resources/assets/fxml/decorator.fxml b/HMCL/src/main/resources/assets/fxml/decorator.fxml index 23450ea0b..622f4012f 100644 --- a/HMCL/src/main/resources/assets/fxml/decorator.fxml +++ b/HMCL/src/main/resources/assets/fxml/decorator.fxml @@ -6,8 +6,8 @@ - + @@ -18,42 +18,36 @@
- - - - - - - - - - - -
- -
- -
-
-
- - - -
+ + + + + +
+ +
+ +
+
+
+ + + +
+
+
+
+ + + + + + - -
- - - - - - - - -
- - +
+
+
@@ -65,18 +59,8 @@
- - - - - - - - - +
@@ -87,7 +71,8 @@ - + @@ -97,12 +82,14 @@ - + - + diff --git a/HMCL/src/main/resources/assets/fxml/launching-steps.fxml b/HMCL/src/main/resources/assets/fxml/launching-steps.fxml new file mode 100644 index 000000000..7ecc786da --- /dev/null +++ b/HMCL/src/main/resources/assets/fxml/launching-steps.fxml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/HMCL/src/main/resources/assets/fxml/yggdrasil-account-login.fxml b/HMCL/src/main/resources/assets/fxml/yggdrasil-account-login.fxml new file mode 100644 index 000000000..5b963e000 --- /dev/null +++ b/HMCL/src/main/resources/assets/fxml/yggdrasil-account-login.fxml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 628b724b4..1bcbb995d 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -167,8 +167,8 @@ ui.label.version=版本 ui.label.password=密碼 ui.label.profile=配置 -ui.message.first_load=請在左邊輸入您的帳號 -ui.message.enter_password=請在左邊輸入您的密碼 +ui.message.first_load=請輸入您的帳號 +ui.message.enter_password=請輸入您的密碼 ui.message.launching=啟動中 ui.message.making=生成中 ui.message.sure_remove=真的要刪除配置%s嗎? diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index a0c15aa91..cbf6e30ee 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -167,8 +167,8 @@ ui.label.version=版本 ui.label.password=密码 ui.label.profile=配置 -ui.message.first_load=请在左边输入您的账号 -ui.message.enter_password=请在左边输入您的密码 +ui.message.first_load=请输入您的账号 +ui.message.enter_password=请输入您的密码 ui.message.launching=启动中 ui.message.making=生成中 ui.message.sure_remove=真的要删除配置%s吗? diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/DefaultDependencyManager.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/DefaultDependencyManager.kt index 812e11141..279c0fa1a 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/DefaultDependencyManager.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/DefaultDependencyManager.kt @@ -43,20 +43,14 @@ class DefaultDependencyManager(override val repository: DefaultGameRepository, o override fun installLibraryAsync(gameVersion: String, version: Version, libraryId: String, libraryVersion: String): Task { if (libraryId == "forge") - return ForgeInstallTask(this, gameVersion, version, libraryVersion) then { task -> - val newVersion = task.result!! - VersionJSONSaveTask(this@DefaultDependencyManager, newVersion) - } + return ForgeInstallTask(this, gameVersion, version, libraryVersion) + .then { VersionJSONSaveTask(this, it["version"]) } else if (libraryId == "liteloader") - return LiteLoaderInstallTask(this, gameVersion, version, libraryVersion) then { task -> - val newVersion = task.result!! - VersionJSONSaveTask(this@DefaultDependencyManager, newVersion) - } + return LiteLoaderInstallTask(this, gameVersion, version, libraryVersion) + .then { VersionJSONSaveTask(this, it["version"]) } else if (libraryId == "optifine") - return OptiFineInstallTask(this, gameVersion, version, libraryVersion) then { task -> - val newVersion = task.result!! - VersionJSONSaveTask(this@DefaultDependencyManager, newVersion) - } + return OptiFineInstallTask(this, gameVersion, version, libraryVersion) + .then { VersionJSONSaveTask(this, it["version"]) } else throw IllegalArgumentException("Library id $libraryId is unrecognized.") } diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/DefaultGameBuilder.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/DefaultGameBuilder.kt index c37794874..c8a4e7a0c 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/DefaultGameBuilder.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/DefaultGameBuilder.kt @@ -28,53 +28,48 @@ class DefaultGameBuilder(val dependencyManager: DefaultDependencyManager): GameB override fun buildAsync(): Task { val gameVersion = gameVersion - return VersionJSONDownloadTask(gameVersion = gameVersion) then a@{ task -> - var version = GSON.fromJson(task.result!!) ?: return@a null - version = version.copy(id = name, jar = null) - var result = ParallelTask( - GameAssetDownloadTask(dependencyManager, version), - GameLoggingDownloadTask(dependencyManager, version), - GameDownloadTask(version), - GameLibrariesTask(dependencyManager, version) // Game libraries will be downloaded for multiple times partly, this time is for vanilla libraries. - ) then VersionJSONSaveTask(dependencyManager, version) + return VersionJSONDownloadTask(gameVersion, dependencyManager, "raw_version_json") + .then { + var version = GSON.fromJson(it["raw_version_json"])!! + it["version"] = version + version = version.copy(id = name, jar = null) + var result = ParallelTask( + GameAssetDownloadTask(dependencyManager, version), + GameLoggingDownloadTask(dependencyManager, version), + GameDownloadTask(version), + GameLibrariesTask(dependencyManager, version) // Game libraries will be downloaded for multiple times partly, this time is for vanilla libraries. + ) then VersionJSONSaveTask(dependencyManager, version) - if (toolVersions.containsKey("forge")) - result = result then libraryTaskHelper(gameVersion, version, "forge") - if (toolVersions.containsKey("liteloader")) - result = result then libraryTaskHelper(gameVersion, version, "liteloader") - if (toolVersions.containsKey("optifine")) - result = result then libraryTaskHelper(gameVersion, version, "optifine") - result - } + if (toolVersions.containsKey("forge")) + result = result then libraryTaskHelper(gameVersion, "forge") + if (toolVersions.containsKey("liteloader")) + result = result then libraryTaskHelper(gameVersion, "liteloader") + if (toolVersions.containsKey("optifine")) + result = result then libraryTaskHelper(gameVersion, "optifine") + result + } } - private fun libraryTaskHelper(gameVersion: String, version: Version, libraryId: String): Task.(Task) -> Task = { prev -> - var thisVersion = version - if (prev is TaskResult<*> && prev.result is Version) { - thisVersion = prev.result as Version - } - dependencyManager.installLibraryAsync(gameVersion, thisVersion, libraryId, toolVersions[libraryId]!!) + private fun libraryTaskHelper(gameVersion: String, libraryId: String): (AutoTypingMap) -> Task = { + dependencyManager.installLibraryAsync(gameVersion, it["version"], libraryId, toolVersions[libraryId]!!) } - inner class VersionJSONDownloadTask(val gameVersion: String): Task() { + private class VersionJSONDownloadTask(val gameVersion: String, val dependencyManager: DefaultDependencyManager, val id: String): Task() { override val dependents: MutableCollection = LinkedList() override val dependencies: MutableCollection = LinkedList() - var httpTask: GetTask? = null - val result: String? get() = httpTask?.result - val gameVersionList: VersionList<*> = dependencyManager.getVersionList("game") + private val gameVersionList: VersionList<*> = dependencyManager.getVersionList("game") init { if (!gameVersionList.loaded) - dependents += gameVersionList.refreshAsync(downloadProvider) + dependents += gameVersionList.refreshAsync(dependencyManager.downloadProvider) } override fun execute() { val remoteVersion = gameVersionList.getVersions(gameVersion).firstOrNull() ?: throw Error("Cannot find specific version $gameVersion in remote repository") - val jsonURL = downloadProvider.injectURL(remoteVersion.url) - httpTask = GetTask(jsonURL.toURL(), proxy = dependencyManager.proxy) - dependencies += httpTask!! + val jsonURL = dependencyManager.downloadProvider.injectURL(remoteVersion.url) + dependencies += GetTask(jsonURL.toURL(), proxy = dependencyManager.proxy, id = id) } } diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/ForgeInstallTask.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/ForgeInstallTask.kt index 192cfc552..55af35e0e 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/ForgeInstallTask.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/ForgeInstallTask.kt @@ -38,11 +38,11 @@ class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager, lateinit var remote: RemoteVersion<*> override val dependents = mutableListOf() override val dependencies = mutableListOf() - override val id = ID + override val id = "version" init { if (!forgeVersionList.loaded) - dependents += forgeVersionList.refreshAsync(dependencyManager.downloadProvider) then { + dependents += forgeVersionList.refreshAsync(dependencyManager.downloadProvider).then { remote = forgeVersionList.getVersion(gameVersion, remoteVersion) ?: throw IllegalArgumentException("Remote forge version $gameVersion, $remoteVersion not found") FileDownloadTask(remote.url.toURL(), installer) } @@ -76,8 +76,4 @@ class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager, check(installer.delete(), { "Unable to delete installer file $installer" }) } - - companion object { - const val ID = "forge_install_task" - } } \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/LiteLoaderInstallTask.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/LiteLoaderInstallTask.kt index 7c1a18991..5259b8ebf 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/LiteLoaderInstallTask.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/LiteLoaderInstallTask.kt @@ -37,11 +37,11 @@ class LiteLoaderInstallTask(private val dependencyManager: DefaultDependencyMana lateinit var remote: RemoteVersion override val dependents = mutableListOf() override val dependencies = mutableListOf() - override val id = ID + override val id = "version" init { if (!liteLoaderVersionList.loaded) - dependents += LiteLoaderVersionList.refreshAsync(dependencyManager.downloadProvider) then { + dependents += LiteLoaderVersionList.refreshAsync(dependencyManager.downloadProvider).then { remote = liteLoaderVersionList.getVersion(gameVersion, remoteVersion) ?: throw IllegalArgumentException("Remote LiteLoader version $gameVersion, $remoteVersion not found") null } @@ -70,8 +70,4 @@ class LiteLoaderInstallTask(private val dependencyManager: DefaultDependencyMana ) dependencies += GameLibrariesTask(dependencyManager, tempVersion) } - - companion object { - const val ID = "lite_loader_install_task" - } } \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/OptiFineInstallTask.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/OptiFineInstallTask.kt index 112aaa6de..3c63b1054 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/OptiFineInstallTask.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/OptiFineInstallTask.kt @@ -34,7 +34,7 @@ class OptiFineInstallTask(private val dependencyManager: DefaultDependencyManage lateinit var remote: RemoteVersion<*> override val dependents = mutableListOf() override val dependencies = mutableListOf() - override val id = ID + override val id = "version" init { if (!optiFineVersionList.loaded) @@ -75,8 +75,4 @@ class OptiFineInstallTask(private val dependencyManager: DefaultDependencyManage result = version.copy(libraries = merge(version.libraries, libraries), mainClass = mainClass, minecraftArguments = arg) dependencies += GameLibrariesTask(dependencyManager, version.copy(libraries = libraries)) } - - companion object { - const val ID = "optifine_install_task" - } } \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/CoupleTask.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/CoupleTask.kt index 6141f8e31..361d4187a 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/CoupleTask.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/CoupleTask.kt @@ -17,14 +17,16 @@ */ package org.jackhuang.hmcl.task -internal class CoupleTask(private val pred: P, private val succ: Task.(P) -> Task?, override val reliant: Boolean) : Task() { +import org.jackhuang.hmcl.util.AutoTypingMap + +internal class CoupleTask(pred: P, private val succ: (AutoTypingMap) -> Task?, override val reliant: Boolean) : Task() { override val hidden: Boolean = true override val dependents: Collection = listOf(pred) override val dependencies: MutableCollection = mutableListOf() override fun execute() { - val task = this.succ(pred) + val task = this.succ(variables!!) if (task != null) dependencies += task } @@ -33,9 +35,9 @@ internal class CoupleTask(private val pred: P, private val succ: Task.( /** * @param b A runnable that decides what to do next, You can also do something here. */ -infix fun T.then(b: Task.(T) -> Task?): Task = CoupleTask(this, b, true) +infix fun T.then(b: (AutoTypingMap) -> Task?): Task = CoupleTask(this, b, true) /** * @param b A runnable that decides what to do next, You can also do something here. */ -infix fun T.with(b: Task.(T) -> Task?): Task = CoupleTask(this, b, false) \ No newline at end of file +infix fun T.with(b: (AutoTypingMap) -> Task?): Task = CoupleTask(this, b, false) \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/GetTask.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/GetTask.kt index 6cd6f1363..4eecf5ab4 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/GetTask.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/GetTask.kt @@ -25,9 +25,8 @@ import java.net.Proxy import java.net.URL import java.nio.charset.Charset -class GetTask @JvmOverloads constructor(val url: URL, val encoding: Charset = Charsets.UTF_8, private val retry: Int = 5, private val proxy: Proxy = Proxy.NO_PROXY): TaskResult() { +class GetTask @JvmOverloads constructor(val url: URL, val encoding: Charset = Charsets.UTF_8, private val retry: Int = 5, private val proxy: Proxy = Proxy.NO_PROXY, override val id: String = ID): TaskResult() { override val scheduler: Scheduler = Scheduler.IO - override val id = ID override fun execute() { var exception: IOException? = null diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/Task.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/Task.kt index 457d99ed1..9fe4a1b68 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/Task.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/Task.kt @@ -81,11 +81,15 @@ abstract class Task { protected fun updateProgress(progress: Double) { val now = System.currentTimeMillis() if (now - lastTime >= progressInterval) { - progressPropertyImpl.updateAsync(progress, progressUpdate) + updateProgressImmediately(progress) lastTime = now } } + protected fun updateProgressImmediately(progress: Double) { + progressPropertyImpl.updateAsync(progress, progressUpdate) + } + private val messageUpdate = AtomicReference() private val messagePropertyImpl = ReadOnlyStringWrapper(this, "message", null) val messageProperty: ReadOnlyStringProperty = messagePropertyImpl.readOnlyProperty diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskExecutor.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskExecutor.kt index 02d4b7769..2d1fa1ad6 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskExecutor.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskExecutor.kt @@ -49,13 +49,13 @@ class TaskExecutor() { * Start the subscription and run all registered tasks asynchronously. */ fun start() { - workerQueue.add(Scheduler.Schedulers.NEW_THREAD.schedule(Callable { + workerQueue.add(Scheduler.NEW_THREAD.schedule { totTask.addAndGet(taskQueue.size) while (!taskQueue.isEmpty()) { if (canceled) break val task = taskQueue.poll() if (task != null) { - val future = task.scheduler.schedule(Callable { executeTask(task); Unit }) + val future = task.scheduler.schedule { executeTask(task) } try { future?.get() } catch (e: InterruptedException) { @@ -66,7 +66,9 @@ class TaskExecutor() { } if (canceled || Thread.interrupted()) taskListener?.onTerminate() - })) + else + taskListener?.end() + }) } /** diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskListener.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskListener.kt index c97d30e3b..2b2bef4cd 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskListener.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskListener.kt @@ -20,8 +20,9 @@ package org.jackhuang.hmcl.task import java.util.* interface TaskListener : EventListener { - fun onReady(task: Task) - fun onFinished(task: Task) - fun onFailed(task: Task) - fun onTerminate() + fun onReady(task: Task) {} + fun onFinished(task: Task) {} + fun onFailed(task: Task) {} + fun onTerminate() {} + fun end() {} } \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Logging.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Logging.kt index 45d66a5d1..257fd6258 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Logging.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Logging.kt @@ -26,14 +26,14 @@ import java.util.logging.* import java.util.logging.Formatter val LOG = Logger.getLogger("HMCL").apply { - level = Level.FINEST + level = Level.FINER useParentHandlers = false addHandler(FileHandler("hmcl.log").apply { - level = Level.FINEST + level = Level.FINER formatter = DefaultFormatter }) addHandler(ConsoleHandler().apply { - level = Level.FINEST + level = Level.FINER formatter = DefaultFormatter }) }