Launching progress

This commit is contained in:
huangyuhui 2017-08-19 21:51:46 +08:00
parent 27939c6b61
commit 383453f9af
28 changed files with 413 additions and 185 deletions

View File

@ -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<DefaultLauncher>("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
}
}

View File

@ -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"

View File

@ -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
}

View File

@ -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) {

View File

@ -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)
}

View File

@ -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<Runnable> = 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

View File

@ -0,0 +1,52 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* 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<AuthInfo>(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
}
}

View File

@ -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)

View File

@ -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)
}
}

View File

@ -0,0 +1,80 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* 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()
}
}

View File

@ -31,7 +31,7 @@
<StackPane fx:id="body" style="-fx-background-radius: 0 0 2 2; -fx-background-color: rgb(255,255,255,0.87); -fx-padding: 8;" minHeight="40">
<BorderPane>
<left>
<HBox spacing="8">
<HBox fx:id="buttonPane" spacing="8">
<JFXButton fx:id="btnRefresh" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30" />
<JFXButton fx:id="btnDelete" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30" />
</HBox>

View File

@ -29,7 +29,7 @@
</JFXButton>
</AnchorPane>
<JFXDialog fx:id="dialog" transitionType="CENTER">
<JFXDialog fx:id="dialog">
<StackPane>
<JFXDialogLayout>
<heading>

View File

@ -6,8 +6,8 @@
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.shape.Rectangle?>
<?import java.lang.String?>
<?import org.jackhuang.hmcl.ui.AdvancedListBox?>
<?import java.lang.String?>
<fx:root xmlns="http://javafx.com/javafx"
type="StackPane"
xmlns:fx="http://javafx.com/fxml">
@ -18,42 +18,36 @@
<BorderPane>
<center>
<StackPane fx:id="drawerWrapper">
<JFXDrawer fx:id="drawer" defaultDrawerSize="200" direction="LEFT">
<styleClass>
<String fx:value="body"/>
</styleClass>
<sidePane>
<AdvancedListBox fx:id="sidePane" />
</sidePane>
<BorderPane>
<left>
<StackPane minWidth="200" maxWidth="200" styleClass="jfx-decorator-content-container">
<BorderPane fx:id="leftRootPane">
<center>
<BorderPane>
<center>
<AdvancedListBox fx:id="leftPane" />
</center>
</BorderPane>
</center>
<right>
<Rectangle height="${leftRootPane.height}" width="1" fill="gray"/>
</right>
</BorderPane>
<JFXDialog fx:id="dialog" overlayClose="false" />
<BorderPane>
<left>
<StackPane minWidth="200" maxWidth="200" styleClass="jfx-decorator-content-container">
<BorderPane fx:id="leftRootPane">
<center>
<BorderPane>
<center>
<AdvancedListBox fx:id="leftPane"/>
</center>
</BorderPane>
</center>
<right>
<Rectangle height="${leftRootPane.height}" width="1" fill="gray"/>
</right>
</BorderPane>
</StackPane>
</left>
<center>
<StackPane fx:id="contentPlaceHolderRoot" styleClass="jfx-decorator-content-container"
VBox.vgrow="ALWAYS">
<StackPane fx:id="contentPlaceHolder" styleClass="jfx-decorator-content-container">
<styleClass>
<String fx:value="jfx-decorator-content-container"/>
</styleClass>
<!-- Node -->
</StackPane>
</left>
<center>
<StackPane fx:id="contentPlaceHolderRoot" styleClass="jfx-decorator-content-container" VBox.vgrow="ALWAYS">
<StackPane fx:id="contentPlaceHolder" styleClass="jfx-decorator-content-container">
<styleClass>
<String fx:value="jfx-decorator-content-container"/>
</styleClass>
<!-- Node -->
</StackPane>
</StackPane>
</center>
</BorderPane>
</JFXDrawer>
</StackPane>
</center>
</BorderPane>
</StackPane>
</center>
<top>
@ -65,18 +59,8 @@
<left>
<BorderPane minWidth="200" maxWidth="200" fx:id="titleWrapper">
<center>
<HBox>
<JFXRippler maskType="CIRCLE" style="-fx-ripple-color:WHITE;">
<StackPane fx:id="titleBurgerContainer" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30">
<JFXHamburger fx:id="titleBurger">
<HamburgerBackArrowBasicTransition/>
</JFXHamburger>
</StackPane>
</JFXRippler>
<Label fx:id="lblTitle" BorderPane.alignment="CENTER"
mouseTransparent="true"
style="-fx-background-color: transparent; -fx-text-fill: white; -fx-font-size: 15px;"/>
</HBox>
<Label fx:id="lblTitle" BorderPane.alignment="CENTER" mouseTransparent="true"
style="-fx-background-color: transparent; -fx-text-fill: white; -fx-font-size: 15px;"/>
</center>
<right>
<Rectangle height="${navBar.height}" width="1" fill="gray"/>
@ -87,7 +71,8 @@
<BorderPane fx:id="navBar">
<left>
<HBox fx:id="navLeft" alignment="CENTER_LEFT" style="-fx-padding: 0 5 0 5;">
<JFXButton fx:id="backNavButton" onMouseClicked="#onBack" styleClass="jfx-decorator-button" ripplerFill="white">
<JFXButton fx:id="backNavButton" onMouseClicked="#onBack"
styleClass="jfx-decorator-button" ripplerFill="white">
<graphic>
<fx:include source="/assets/svg/arrow-left.fxml"/>
</graphic>
@ -97,12 +82,14 @@
</left>
<right>
<HBox fx:id="navRight" alignment="CENTER_LEFT">
<JFXButton fx:id="refreshNavButton" onMouseClicked="#onRefresh" styleClass="jfx-decorator-button" ripplerFill="white">
<JFXButton fx:id="refreshNavButton" onMouseClicked="#onRefresh"
styleClass="jfx-decorator-button" ripplerFill="white">
<graphic>
<fx:include source="/assets/svg/refresh.fxml"/>
</graphic>
</JFXButton>
<JFXButton fx:id="closeNavButton" onMouseClicked="#onCloseNav" styleClass="jfx-decorator-button" ripplerFill="white">
<JFXButton fx:id="closeNavButton" onMouseClicked="#onCloseNav"
styleClass="jfx-decorator-button" ripplerFill="white">
<graphic>
<fx:include source="/assets/svg/close.fxml"/>
</graphic>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.*?>
<?import com.jfoenix.controls.JFXProgressBar?>
<?import javafx.scene.control.Label?>
<fx:root xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
type="StackPane">
<JFXProgressBar fx:id="pgsTasks" StackPane.alignment="TOP_CENTER" />
<VBox alignment="CENTER">
<Label fx:id="lblCurrentState" style="-fx-font-size: 20px;" />
<Label fx:id="lblSteps" style="-fx-font-size: 14px;" />
</VBox>
</fx:root>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.*?>
<?import com.jfoenix.validation.RequiredFieldValidator?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.*?>
<fx:root xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
type="StackPane">
<JFXDialogLayout>
<heading>
<Label text="%ui.message.enter_password" />
</heading>
<body>
<VBox spacing="15" style="-fx-padding: 15 0 0 0;">
<Label fx:id="lblUsername" />
<JFXPasswordField fx:id="txtPassword" promptText="%ui.label.password" labelFloat="true">
<validators>
<RequiredFieldValidator message="Input Required!">
</RequiredFieldValidator>
</validators>
</JFXPasswordField>
</VBox>
</body>
<actions>
<Label fx:id="lblCreationWarning" />
<JFXButton onMouseClicked="#onAccept" text="%button.ok" styleClass="dialog-accept"/>
<JFXButton onMouseClicked="#onCancel" text="%button.cancel" styleClass="dialog-cancel"/>
</actions>
</JFXDialogLayout>
<JFXProgressBar fx:id="progressBar" visible="false" StackPane.alignment="TOP_CENTER"/>
</fx:root>

View File

@ -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嗎

View File

@ -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吗

View File

@ -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.")
}

View File

@ -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<Version>(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<Version>(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<String>) -> 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<Task> = LinkedList()
override val dependencies: MutableCollection<Task> = 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)
}
}

View File

@ -38,11 +38,11 @@ class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager,
lateinit var remote: RemoteVersion<*>
override val dependents = mutableListOf<Task>()
override val dependencies = mutableListOf<Task>()
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"
}
}

View File

@ -37,11 +37,11 @@ class LiteLoaderInstallTask(private val dependencyManager: DefaultDependencyMana
lateinit var remote: RemoteVersion<LiteLoaderRemoteVersionTag>
override val dependents = mutableListOf<Task>()
override val dependencies = mutableListOf<Task>()
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"
}
}

View File

@ -34,7 +34,7 @@ class OptiFineInstallTask(private val dependencyManager: DefaultDependencyManage
lateinit var remote: RemoteVersion<*>
override val dependents = mutableListOf<Task>()
override val dependencies = mutableListOf<Task>()
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"
}
}

View File

@ -17,14 +17,16 @@
*/
package org.jackhuang.hmcl.task
internal class CoupleTask<P: Task>(private val pred: P, private val succ: Task.(P) -> Task?, override val reliant: Boolean) : Task() {
import org.jackhuang.hmcl.util.AutoTypingMap
internal class CoupleTask<P: Task>(pred: P, private val succ: (AutoTypingMap<String>) -> Task?, override val reliant: Boolean) : Task() {
override val hidden: Boolean = true
override val dependents: Collection<Task> = listOf(pred)
override val dependencies: MutableCollection<Task> = 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<P: Task>(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: Task> T.then(b: Task.(T) -> Task?): Task = CoupleTask(this, b, true)
infix fun <T: Task> T.then(b: (AutoTypingMap<String>) -> 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: Task> T.with(b: Task.(T) -> Task?): Task = CoupleTask(this, b, false)
infix fun <T: Task> T.with(b: (AutoTypingMap<String>) -> Task?): Task = CoupleTask(this, b, false)

View File

@ -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<String>() {
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<String>() {
override val scheduler: Scheduler = Scheduler.IO
override val id = ID
override fun execute() {
var exception: IOException? = null

View File

@ -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<String>()
private val messagePropertyImpl = ReadOnlyStringWrapper(this, "message", null)
val messageProperty: ReadOnlyStringProperty = messagePropertyImpl.readOnlyProperty

View File

@ -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()
})
}
/**

View File

@ -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() {}
}

View File

@ -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
})
}