diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Main.kt b/HMCL/src/main/java/org/jackhuang/hmcl/MainApplication.kt similarity index 86% rename from HMCL/src/main/java/org/jackhuang/hmcl/Main.kt rename to HMCL/src/main/java/org/jackhuang/hmcl/MainApplication.kt index e6c38d2dd..a63a6d4c8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Main.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/MainApplication.kt @@ -19,6 +19,7 @@ package org.jackhuang.hmcl import javafx.application.Application import javafx.stage.Stage +import org.jackhuang.hmcl.task.Scheduler import org.jackhuang.hmcl.ui.Controllers import org.jackhuang.hmcl.util.DEFAULT_USER_AGENT import org.jackhuang.hmcl.util.OS @@ -27,6 +28,7 @@ import java.io.File class MainApplication : Application() { override fun start(stage: Stage) { + PRIMARY_STAGE = stage Controllers.initialize(stage) stage.isResizable = false @@ -36,6 +38,10 @@ class MainApplication : Application() { companion object { + val VERSION = "@HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@" + val TITLE = "HMCL $VERSION" + lateinit var PRIMARY_STAGE: Stage + @JvmStatic fun main(args: Array) { DEFAULT_USER_AGENT = "Hello Minecraft! Launcher" @@ -57,5 +63,10 @@ class MainApplication : Application() { } fun getMinecraftDirectory(): File = getWorkingDirectory("minecraft") + + fun stop() { + PRIMARY_STAGE.close() + Scheduler.shutdown() + } } } \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.kt b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.kt new file mode 100644 index 000000000..adbca095c --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.kt @@ -0,0 +1,41 @@ +/* + * 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.game + +import org.jackhuang.hmcl.launch.DefaultLauncher +import org.jackhuang.hmcl.setting.Settings +import org.jackhuang.hmcl.task.Scheduler + +object LauncherHelper { + fun launch() { + val profile = Settings.selectedProfile + val repository = profile.repository + val account = Settings.selectedAccount ?: throw IllegalStateException("No account here") + val version = repository.getVersion(profile.selectedVersion) + val launcher = DefaultLauncher( + repository = repository, + versionId = profile.selectedVersion, + options = profile.getVersionSetting(profile.selectedVersion).toLaunchOptions(profile.gameDir), + account = account.logIn(Settings.PROXY) + ) + + profile.dependency.checkGameCompletionAsync(version) + .then(launcher.launchAsync()) + .subscribe(Scheduler.JAVAFX) { println("lalala") } + } +} \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.kt b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.kt index 8a7a9110a..901d7e39f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.kt @@ -25,7 +25,7 @@ import java.util.TreeMap class Config { @SerializedName("last") - var last: String = "" + var selectedProfile: String = "" set(value) { field = value Settings.save() @@ -97,7 +97,13 @@ class Config { Settings.save() } @SerializedName("accounts") - var accounts: MutableMap> = TreeMap() + var accounts: MutableMap> = TreeMap() + set(value) { + field = value + Settings.save() + } + @SerializedName("selectedAccount") + var selectedAccount: String = "" set(value) { field = value Settings.save() diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.kt b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.kt index d1e7ef07e..3a92a045b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.kt @@ -20,6 +20,9 @@ package org.jackhuang.hmcl.setting import com.google.gson.* import javafx.beans.InvalidationListener import javafx.beans.property.* +import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider +import org.jackhuang.hmcl.download.DefaultDependencyManager +import org.jackhuang.hmcl.download.DependencyManager import org.jackhuang.hmcl.game.HMCLGameRepository import org.jackhuang.hmcl.util.* import java.io.File @@ -39,6 +42,7 @@ class Profile(var name: String = "Default", gameDir: File = File(".minecraft")) var noCommon: Boolean by noCommonProperty var repository = HMCLGameRepository(gameDir) + var dependency = DefaultDependencyManager(repository, BMCLAPIDownloadProvider) init { gameDirProperty.addListener { _, _, newValue -> diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.kt b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.kt index 524e7354c..2cc07e93d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.kt @@ -30,8 +30,15 @@ import java.io.File import java.util.logging.Level import org.jackhuang.hmcl.ProfileLoadingEvent import org.jackhuang.hmcl.ProfileChangedEvent +import org.jackhuang.hmcl.auth.Account +import org.jackhuang.hmcl.auth.Accounts +import org.jackhuang.hmcl.auth.OfflineAccount +import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount import org.jackhuang.hmcl.event.EVENT_BUS import org.jackhuang.hmcl.util.FileTypeAdapter +import org.jackhuang.hmcl.util.ignoreException +import java.net.Proxy +import java.util.* object Settings { @@ -48,8 +55,31 @@ object Settings { val SETTINGS: Config + private val ACCOUNTS = mutableMapOf() + init { SETTINGS = initSettings(); + + loop@for ((name, settings) in SETTINGS.accounts) { + val factory = when(settings["type"]) { + "yggdrasil" -> YggdrasilAccount + "offline" -> OfflineAccount + else -> { + SETTINGS.accounts.remove(name) + continue@loop + } + } + + val account = factory.fromStorage(settings) + + if (account.username != name) { + SETTINGS.accounts.remove(name) + continue + } + + ACCOUNTS[name] = account + } + save() if (!getProfiles().containsKey(DEFAULT_PROFILE)) @@ -59,6 +89,10 @@ object Settings { profile.name = name profile.addPropertyChangedListener(InvalidationListener { save() }) } + + ignoreException { + Runtime.getRuntime().addShutdownHook(Thread(this::save)) + } } fun getDownloadProvider(): DownloadProvider = when (SETTINGS.downloadtype) { @@ -93,16 +127,62 @@ object Settings { fun save() { try { + SETTINGS.accounts.clear() + for ((name, account) in ACCOUNTS) { + val storage = account.toStorage() + storage["type"] = when(account) { + is OfflineAccount -> "offline" + is YggdrasilAccount -> "yggdrasil" + else -> "" + } + SETTINGS.accounts[name] = storage + } + SETTINGS_FILE.writeText(GSON.toJson(SETTINGS)) } catch (ex: IOException) { LOG.log(Level.SEVERE, "Failed to save config", ex) } } - fun getLastProfile(): Profile { - if (!hasProfile(SETTINGS.last)) - SETTINGS.last = DEFAULT_PROFILE - return getProfile(SETTINGS.last) + val selectedProfile: Profile + get() { + if (!hasProfile(SETTINGS.selectedProfile)) + SETTINGS.selectedProfile = DEFAULT_PROFILE + return getProfile(SETTINGS.selectedProfile) + } + + val selectedAccount: Account? + get() { + val a = getAccount(SETTINGS.selectedAccount) + if (a == null && ACCOUNTS.isNotEmpty()) { + val (key, acc) = ACCOUNTS.entries.first() + SETTINGS.selectedAccount = key + return acc + } + return a + } + + fun setSelectedAccount(name: String) { + if (ACCOUNTS.containsKey(name)) + SETTINGS.selectedAccount = name + } + + val PROXY: Proxy = Proxy.NO_PROXY + + fun addAccount(account: Account) { + ACCOUNTS[account.username] = account + } + + fun getAccount(name: String): Account? { + return ACCOUNTS[name] + } + + fun getAccounts(): Map { + return Collections.unmodifiableMap(ACCOUNTS) + } + + fun deleteAccount(name: String) { + ACCOUNTS.remove(name) } fun getProfile(name: String?): Profile { @@ -136,16 +216,16 @@ object Settings { return true } - fun delProfile(ver: Profile): Boolean { - return delProfile(ver.name) + fun deleteProfile(ver: Profile): Boolean { + return deleteProfile(ver.name) } - fun delProfile(ver: String): Boolean { + fun deleteProfile(ver: String): Boolean { if (DEFAULT_PROFILE == ver) { return false } var notify = false - if (getLastProfile().name == ver) + if (selectedProfile.name == ver) notify = true val flag = getProfiles().remove(ver) != null if (notify && flag) @@ -154,9 +234,8 @@ object Settings { } internal fun onProfileChanged() { - val p = getLastProfile() - EVENT_BUS.fireEvent(ProfileChangedEvent(SETTINGS, p)) - p.repository.refreshVersions() + EVENT_BUS.fireEvent(ProfileChangedEvent(SETTINGS, selectedProfile)) + selectedProfile.repository.refreshVersions() } /** diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.kt b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.kt index 2676d9a9e..80cb61124 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.kt @@ -20,7 +20,11 @@ package org.jackhuang.hmcl.setting import com.google.gson.* import javafx.beans.InvalidationListener import javafx.beans.property.* +import org.jackhuang.hmcl.MainApplication +import org.jackhuang.hmcl.game.LaunchOptions import org.jackhuang.hmcl.util.* +import java.io.File +import java.io.IOException import java.lang.reflect.Type class VersionSetting() { @@ -61,14 +65,14 @@ class VersionSetting() { /** * The permanent generation size of JVM garbage collection. */ - val permSizeProperty = SimpleIntegerProperty(this, "permSize", 0) - var permSize: Int by permSizeProperty + val permSizeProperty = SimpleStringProperty(this, "permSize", "") + var permSize: String by permSizeProperty /** * The maximum memory that JVM can allocate. * The size of JVM heap. */ - val maxMemoryProperty = SimpleIntegerProperty(this, "maxMemory", 0) + val maxMemoryProperty = SimpleIntegerProperty(this, "maxMemory", OS.SUGGESTED_MEMORY) var maxMemory: Int by maxMemoryProperty /** @@ -127,7 +131,7 @@ class VersionSetting() { * String type prevents unexpected value from causing JsonSyntaxException. * We can only reset this field instead of recreating the whole setting file. */ - val widthProperty = SimpleIntegerProperty(this, "width", 0) + val widthProperty = SimpleIntegerProperty(this, "width", 854) var width: Int by widthProperty @@ -138,7 +142,7 @@ class VersionSetting() { * String type prevents unexpected value from causing JsonSyntaxException. * We can only reset this field instead of recreating the whole setting file. */ - val heightProperty = SimpleIntegerProperty(this, "height", 0) + val heightProperty = SimpleIntegerProperty(this, "height", 480) var height: Int by heightProperty @@ -179,6 +183,32 @@ class VersionSetting() { launcherVisibilityProperty.addListener(listener) } + @Throws(IOException::class) + fun toLaunchOptions(gameDir: File): LaunchOptions { + return LaunchOptions( + gameDir = gameDir, + java = if (java == null) JavaVersion.fromCurrentEnvironment() + else JavaVersion.fromExecutable(File(java)), + versionName = MainApplication.TITLE, + profileName = MainApplication.TITLE, + minecraftArgs = minecraftArgs, + javaArgs = javaArgs, + maxMemory = maxMemory, + metaspace = permSize.toIntOrNull(), + width = width, + height = height, + fullscreen = fullscreen, + serverIp = serverIp, + wrapper = wrapper, + proxyHost = Settings.SETTINGS.proxyHost, + proxyPort = Settings.SETTINGS.proxyPort, + proxyUser = Settings.SETTINGS.proxyUserName, + proxyPass = Settings.SETTINGS.proxyPassword, + precalledCommand = precalledCommand, + noGeneratedJVMArgs = noJVMArgs + ) + } + companion object Serializer: JsonSerializer, JsonDeserializer { override fun serialize(src: VersionSetting?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { if (src == null) return JsonNull.INSTANCE @@ -214,7 +244,7 @@ class VersionSetting() { javaArgs = json["javaArgs"]?.asString ?: "" minecraftArgs = json["minecraftArgs"]?.asString ?: "" maxMemory = parseJsonPrimitive(json["maxMemory"]?.asJsonPrimitive) - permSize = parseJsonPrimitive(json["permSize"]?.asJsonPrimitive) + permSize = json["permSize"]?.asString ?: "" width = parseJsonPrimitive(json["width"]?.asJsonPrimitive) height = parseJsonPrimitive(json["height"]?.asJsonPrimitive) javaDir = json["javaDir"]?.asString ?: "" diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountItem.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountItem.kt new file mode 100644 index 000000000..d5d0a56c1 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountItem.kt @@ -0,0 +1,83 @@ +/* + * 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.effects.JFXDepthManager +import javafx.beans.binding.Bindings +import javafx.fxml.FXML +import javafx.scene.control.Label +import javafx.scene.layout.Pane +import javafx.scene.layout.StackPane +import javafx.scene.layout.VBox +import java.util.concurrent.Callable + +class AccountItem(i: Int, width: Double, height: Double) : StackPane() { + @FXML lateinit var icon: Pane + @FXML lateinit var content: VBox + @FXML lateinit var header: StackPane + @FXML lateinit var body: StackPane + @FXML lateinit var btnDelete: JFXButton + @FXML lateinit var btnEdit: JFXButton + @FXML lateinit var lblUser: Label + @FXML lateinit var lblType: Label + + init { + loadFXML("/assets/fxml/account-item.fxml") + + minWidth = width + maxWidth = width + prefWidth = width + + minHeight = height + maxHeight = height + prefHeight = height + + JFXDepthManager.setDepth(this, 1) + + // create content + val headerColor = getDefaultColor(i % 12) + header.style = "-fx-background-radius: 5 5 0 0; -fx-background-color: " + headerColor + body.minHeight = Math.random() * 20 + 50 + + // create image view + icon.translateYProperty().bind(Bindings.createDoubleBinding(Callable { header.boundsInParent.height - icon.height / 2 }, header.boundsInParentProperty(), icon.heightProperty())) + } + + private fun getDefaultColor(i: Int): String { + var color = "#FFFFFF" + when (i) { + 0 -> color = "#8F3F7E" + 1 -> color = "#B5305F" + 2 -> color = "#CE584A" + 3 -> color = "#DB8D5C" + 4 -> color = "#DA854E" + 5 -> color = "#E9AB44" + 6 -> color = "#FEE435" + 7 -> color = "#99C286" + 8 -> color = "#01A05E" + 9 -> color = "#4A8895" + 10 -> color = "#16669B" + 11 -> color = "#2F65A5" + 12 -> color = "#4E6A9C" + else -> { + } + } + return color + } +} \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountsPage.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountsPage.kt new file mode 100644 index 000000000..8a3f35270 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountsPage.kt @@ -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.* +import javafx.application.Platform +import javafx.fxml.FXML +import javafx.scene.Node +import javafx.scene.control.ScrollPane +import javafx.scene.layout.StackPane +import javafx.beans.property.SimpleStringProperty +import javafx.beans.property.StringProperty +import javafx.scene.control.Label +import org.jackhuang.hmcl.auth.Account +import org.jackhuang.hmcl.auth.OfflineAccount +import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount +import org.jackhuang.hmcl.setting.Settings +import org.jackhuang.hmcl.task.Scheduler +import org.jackhuang.hmcl.task.with +import org.jackhuang.hmcl.task.Task +import org.jackhuang.hmcl.ui.wizard.HasTitle +import java.util.concurrent.Callable + +class AccountsPage : StackPane(), HasTitle { + override val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Accounts") + + @FXML lateinit var scrollPane: ScrollPane + @FXML lateinit var masonryPane: JFXMasonryPane + @FXML lateinit var dialog: JFXDialog + @FXML lateinit var txtUsername: JFXTextField + @FXML lateinit var txtPassword: JFXPasswordField + @FXML lateinit var lblPassword: Label + @FXML lateinit var lblCreationWarning: Label + @FXML lateinit var cboType: JFXComboBox + + init { + loadFXML("/assets/fxml/account.fxml") + children.remove(dialog) + dialog.dialogContainer = this + + JFXScrollPane.smoothScrolling(scrollPane) + + cboType.selectionModel.selectedIndexProperty().addListener { _, _, newValue -> + val visible = newValue != 0 + lblPassword.isVisible = visible + txtPassword.isVisible = visible + } + cboType.selectionModel.select(0) + + loadAccounts() + } + + fun loadAccounts() { + val children = mutableListOf() + var i = 0 + for ((_, account) in Settings.getAccounts()) { + children += buildNode(++i, account) + } + masonryPane.children.setAll(children) + Platform.runLater { scrollPane.requestLayout() } + } + + private fun buildNode(i: Int, account: Account): Node { + return AccountItem(i, Math.random() * 100 + 100, Math.random() * 100 + 100).apply { + lblUser.text = account.username + lblType.text = when(account) { + is OfflineAccount -> "Offline Account" + is YggdrasilAccount -> "Yggdrasil Account" + else -> throw Error("Unsupported account: $account") + } + btnDelete.setOnMouseClicked { + Settings.deleteAccount(account.username) + Platform.runLater(this@AccountsPage::loadAccounts) + } + } + } + + private fun getDefaultColor(i: Int): String { + var color = "#FFFFFF" + when (i) { + 0 -> color = "#8F3F7E" + 1 -> color = "#B5305F" + 2 -> color = "#CE584A" + 3 -> color = "#DB8D5C" + 4 -> color = "#DA854E" + 5 -> color = "#E9AB44" + 6 -> color = "#FEE435" + 7 -> color = "#99C286" + 8 -> color = "#01A05E" + 9 -> color = "#4A8895" + 10 -> color = "#16669B" + 11 -> color = "#2F65A5" + 12 -> color = "#4E6A9C" + else -> { + } + } + return color + } + + fun addNewAccount() { + dialog.show() + } + + fun onCreationAccept() { + val type = cboType.selectionModel.selectedIndex + val username = txtUsername.text + val password = txtPassword.text + val task = Task.of(Callable { + try { + val account = when (type) { + 0 -> OfflineAccount.fromUsername(username) + 1 -> YggdrasilAccount.fromUsername(username, password) + else -> throw UnsupportedOperationException() + } + + account.logIn(Settings.PROXY) + account + } catch (e: Exception) { + e + } + }) + task.subscribe(Scheduler.JAVAFX) { + val account = task.result + if (account is Account) { + Settings.addAccount(account) + dialog.close() + loadAccounts() + } else if (account is Exception) { + lblCreationWarning.text = account.localizedMessage + } + } + } + + fun onCreationCancel() { + dialog.close() + } +} \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ClassTitle.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ClassTitle.kt new file mode 100644 index 000000000..f205fbe7d --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ClassTitle.kt @@ -0,0 +1,40 @@ +/* + * 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 javafx.scene.layout.StackPane +import javafx.scene.layout.VBox +import javafx.scene.paint.Color +import javafx.scene.shape.Rectangle +import javafx.scene.text.Text + +class ClassTitle(val text: String) : StackPane() { + + init { + val vbox = VBox() + vbox.children += Text(text).apply { + } + vbox.children += Rectangle().apply { + widthProperty().bind(vbox.widthProperty()) + height = 1.0 + fill = Color.GRAY + } + children.setAll(vbox) + styleClass += "class-title" + } +} \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.kt index bc8d5015f..277ebbf03 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.kt @@ -20,7 +20,6 @@ package org.jackhuang.hmcl.ui import javafx.fxml.FXMLLoader import javafx.scene.Node import javafx.scene.Scene -import javafx.scene.layout.Pane import javafx.scene.layout.StackPane import javafx.stage.Stage @@ -28,18 +27,22 @@ object Controllers { lateinit var scene: Scene private set lateinit var stage: Stage private set - lateinit var mainController: MainController - private val mainPane: Pane = loadPane("main") + val mainPane = MainPage() - lateinit var versionController: VersionController - val versionPane: Pane = loadPane("version") + val versionPane = VersionPage() + + lateinit var leftPaneController: LeftPaneController lateinit var decorator: Decorator fun initialize(stage: Stage) { this.stage = stage - val decorator = Decorator(stage, mainPane, max = false) + decorator = Decorator(stage, max = false) + decorator.mainPage = mainPane + decorator.showPage(null) + leftPaneController = LeftPaneController(decorator.leftPane) + // Let root pane fix window size. with(mainPane.parent as StackPane) { mainPane.prefWidthProperty().bind(widthProperty()) @@ -56,7 +59,7 @@ object Controllers { } fun navigate(node: Node?) { - //mainController.setContentPage(node) + decorator.showPage(node) } private fun loadPane(s: String): T = FXMLLoader(Controllers::class.java.getResource("/assets/fxml/$s.fxml")).load() diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Decorator.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Decorator.kt index 2a3979f33..b8b52f708 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Decorator.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Decorator.kt @@ -43,9 +43,18 @@ import javafx.stage.Stage import javafx.stage.StageStyle import javafx.scene.layout.BorderStrokeStyle import javafx.scene.layout.BorderStroke +import org.jackhuang.hmcl.MainApplication +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.wizard.* import org.jackhuang.hmcl.util.* +import java.util.* +import java.util.concurrent.ConcurrentLinkedQueue + +class Decorator @JvmOverloads constructor(private val primaryStage: Stage, private val max: Boolean = true, min: Boolean = true) : GridPane(), AbstractWizardDisplayer { + override val wizardController: WizardController = WizardController(this) -class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node: Node, private val max: Boolean = true, min: Boolean = true) : GridPane() { private var xOffset: Double = 0.0 private var yOffset: Double = 0.0 private var newX: Double = 0.0 @@ -66,7 +75,7 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node: @FXML lateinit var titleLabel: Label @FXML lateinit var leftPane: VBox - private val onCloseButtonActionProperty: ObjectProperty = SimpleObjectProperty(Runnable { this.primaryStage.close() }) + private val onCloseButtonActionProperty: ObjectProperty = SimpleObjectProperty(Runnable { MainApplication.stop() }) @JvmName("onCloseButtonActionProperty") get var onCloseButtonAction: Runnable by onCloseButtonActionProperty @@ -89,6 +98,9 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node: private val close = 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) .apply { setPrefSize(12.0, 12.0); setSize(12.0, 12.0) } + val animationHandler: TransitionHandler + override val cancelQueue: Queue = ConcurrentLinkedQueue() + init { loadFXML("/assets/fxml/decorator.fxml") @@ -111,13 +123,9 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node: titleContainer.addEventHandler(MouseEvent.MOUSE_ENTERED) { this.allowMove = true } titleContainer.addEventHandler(MouseEvent.MOUSE_EXITED) { if (!this.isDragging) this.allowMove = false } - this.contentPlaceHolder.children.add(node) - (node as Region).setMinSize(0.0, 0.0) - this.border = Border(BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderWidths(0.0, 4.0, 4.0, 4.0))) - val clip = Rectangle() - clip.widthProperty().bind(node.widthProperty()) - clip.heightProperty().bind(node.heightProperty()) - node.setClip(clip) + animationHandler = TransitionHandler(contentPlaceHolder) + + setOverflowHidden(lookup("#contentPlaceHolderRoot") as Pane) } fun onMouseMoved(mouseEvent: MouseEvent) { @@ -285,18 +293,22 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node: this.yOffset = mouseEvent.sceneY } + @Suppress("UNUSED_PARAMETER") private fun isRightEdge(x: Double, y: Double, boundsInParent: Bounds): Boolean { return x < this.width && x > this.width - this.contentPlaceHolder.snappedLeftInset() } + @Suppress("UNUSED_PARAMETER") private fun isTopEdge(x: Double, y: Double, boundsInParent: Bounds): Boolean { return y >= 0.0 && y < this.contentPlaceHolder.snappedLeftInset() } + @Suppress("UNUSED_PARAMETER") private fun isBottomEdge(x: Double, y: Double, boundsInParent: Bounds): Boolean { return y < this.height && y > this.height - this.contentPlaceHolder.snappedLeftInset() } + @Suppress("UNUSED_PARAMETER") private fun isLeftEdge(x: Double, y: Double, boundsInParent: Bounds): Boolean { return x >= 0.0 && x < this.contentPlaceHolder.snappedLeftInset() } @@ -335,7 +347,74 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node: } } - fun setContent(content: Node) { - this.contentPlaceHolder.children.setAll(content) + private fun setContent(content: Node, animation: AnimationProducer) { + animationHandler.setContent(content, animation) + + if (content is Region) { + content.setMinSize(0.0, 0.0) + setOverflowHidden(content) + } + + backNavButton.isDisable = !wizardController.canPrev() + + if (content is Refreshable) + refreshNavButton.isVisible = true + + if (content != mainPage) + closeNavButton.isVisible = true + + val prefix = if (category == null) "" else category + " - " + + titleLabel.textProperty().unbind() + + if (content is WizardPage) + titleLabel.text = prefix + content.title + + if (content is HasTitle) + titleLabel.textProperty().bind(content.titleProperty) + } + + lateinit var mainPage: Node + var category: String? = null + + fun showPage(content: Node?) { + onEnd() + setContent(content ?: mainPage, ContainerAnimations.FADE.animationProducer) + } + + fun startWizard(wizardProvider: WizardProvider, category: String? = null) { + this.category = category + wizardController.provider = wizardProvider + wizardController.onStart() + } + + override fun onStart() { + backNavButton.isVisible = true + backNavButton.isDisable = false + closeNavButton.isVisible = true + refreshNavButton.isVisible = false + } + + override fun onEnd() { + backNavButton.isVisible = false + closeNavButton.isVisible = false + refreshNavButton.isVisible = false + } + + override fun navigateTo(page: Node, nav: Navigation.NavigationDirection) { + setContent(page, nav.animation.animationProducer) + } + + fun onRefresh() { + (contentPlaceHolder.children.single() as Refreshable).refresh() + } + + fun onCloseNav() { + wizardController.onCancel() + showPage(null) + } + + fun onBack() { + wizardController.onPrev(true) } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.kt index 7c0259d4c..a83d2d9e5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.kt @@ -33,6 +33,7 @@ import javafx.scene.image.WritableImage import javafx.scene.input.MouseEvent import javafx.scene.input.ScrollEvent import javafx.scene.layout.Pane +import javafx.scene.layout.Region import javafx.scene.shape.Rectangle import javafx.util.Duration @@ -99,7 +100,7 @@ fun takeSnapshot(node: Parent, width: Double, height: Double): WritableImage { return scene.snapshot(null) } -fun setOverflowHidden(node: Pane) { +fun setOverflowHidden(node: Region) { val rectangle = Rectangle() rectangle.widthProperty().bind(node.widthProperty()) rectangle.heightProperty().bind(node.heightProperty()) @@ -109,5 +110,5 @@ fun setOverflowHidden(node: Pane) { val stylesheets = arrayOf( Controllers::class.java.getResource("/css/jfoenix-fonts.css").toExternalForm(), Controllers::class.java.getResource("/css/jfoenix-design.css").toExternalForm(), -Controllers::class.java.getResource("/assets/css/jfoenix-components.css").toExternalForm(), +//Controllers::class.java.getResource("/assets/css/jfoenix-components.css").toExternalForm(), Controllers::class.java.getResource("/assets/css/jfoenix-main-demo.css").toExternalForm()) \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/IconedItem.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/IconedItem.kt new file mode 100644 index 000000000..90b5debf6 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/IconedItem.kt @@ -0,0 +1,29 @@ +/* + * 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 javafx.geometry.Pos +import javafx.scene.Node +import javafx.scene.control.Label +import javafx.scene.layout.HBox + +class IconedItem(val icon: Node, val text: String) + : RipplerContainer(HBox().apply { + children += icon.apply { isMouseTransparent = true } + children += Label(text).apply { alignment = Pos.CENTER; isMouseTransparent = true } +}) \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.kt new file mode 100644 index 000000000..e4117b1df --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.kt @@ -0,0 +1,120 @@ +/* + * 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.JFXComboBox +import javafx.scene.Node +import javafx.scene.layout.* +import javafx.scene.paint.Paint +import org.jackhuang.hmcl.ProfileChangedEvent +import org.jackhuang.hmcl.ProfileLoadingEvent +import org.jackhuang.hmcl.event.EVENT_BUS +import org.jackhuang.hmcl.event.RefreshedVersionsEvent +import org.jackhuang.hmcl.game.LauncherHelper +import org.jackhuang.hmcl.game.minecraftVersion +import org.jackhuang.hmcl.setting.Settings +import org.jackhuang.hmcl.ui.download.DownloadWizardProvider + +class LeftPaneController(val leftPane: VBox) { + val versionsPane = VBox() + val cboProfiles = JFXComboBox().apply { items.add("Default"); prefWidthProperty().bind(leftPane.widthProperty()) } + val accountItem = VersionListItem("mojang@mojang.com", "Yggdrasil") + + init { + addChildren(ClassTitle("ACCOUNTS")) + addChildren(RipplerContainer(accountItem).apply { + accountItem.onSettingsButtonClicked { + Controllers.navigate(AccountsPage()) + } + }) + addChildren(ClassTitle("LAUNCHER")) + addChildren(IconedItem(SVG.gear("black"), "Settings").apply { prefWidthProperty().bind(leftPane.widthProperty()) }) + addChildren(ClassTitle("PROFILES")) + addChildren(cboProfiles) + addChildren(ClassTitle("VERSIONS")) + addChildren(versionsPane) + + EVENT_BUS.channel() += this::loadVersions + EVENT_BUS.channel() += this::onProfilesLoading + EVENT_BUS.channel() += this::onProfileChanged + + Settings.onProfileLoading() + + Controllers.decorator.addMenuButton.setOnMouseClicked { + Controllers.decorator.startWizard(DownloadWizardProvider(), "Install New Game") + } + Controllers.decorator.refreshMenuButton.setOnMouseClicked { + Settings.selectedProfile.repository.refreshVersions() + } + Controllers.mainPane.buttonLaunch.setOnMouseClicked { LauncherHelper.launch() } + } + + private fun addChildren(content: Node) { + if (content is Pane) { + leftPane.children += content + } else { + val pane = StackPane() + pane.styleClass += "left-pane-item" + pane.children.setAll(content) + leftPane.children += pane + } + } + + fun onProfilesLoading() { + // TODO: Profiles + } + + fun onProfileChanged(event: ProfileChangedEvent) { + val profile = event.value + profile.selectedVersionProperty.addListener { _, _, newValue -> + versionChanged(newValue) + } + } + + private fun loadAccounts() { + + } + + private fun loadVersions() { + val profile = Settings.selectedProfile + versionsPane.children.clear() + profile.repository.getVersions().forEach { version -> + val item = VersionListItem(version.id, minecraftVersion(profile.repository.getVersionJar(version.id)) ?: "Unknown") + val ripplerContainer = RipplerContainer(item) + item.onSettingsButtonClicked { + Controllers.decorator.showPage(Controllers.versionPane) + Controllers.versionPane.loadVersionSetting(item.versionName, profile.getVersionSetting(item.versionName)) + } + ripplerContainer.ripplerFill = Paint.valueOf("#89E1F9") + ripplerContainer.setOnMouseClicked { + // clean selected property + versionsPane.children.forEach { if (it is RipplerContainer) it.selected = false } + ripplerContainer.selected = true + profile.selectedVersion = version.id + } + ripplerContainer.userData = version.id to item + versionsPane.children += ripplerContainer + } + } + + fun versionChanged(selectedVersion: String) { + versionsPane.children + .filter { it is RipplerContainer && it.userData is Pair<*, *> } + .forEach { (it as RipplerContainer).selected = (it.userData as Pair).first == selectedVersion } + } +} \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/MainController.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/MainPage.kt similarity index 79% rename from HMCL/src/main/java/org/jackhuang/hmcl/ui/MainController.kt rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/MainPage.kt index ae0442440..d4b2c11fb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/MainController.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/MainPage.kt @@ -21,9 +21,12 @@ import com.jfoenix.controls.JFXButton import com.jfoenix.controls.JFXComboBox import com.jfoenix.controls.JFXListCell import com.jfoenix.controls.JFXListView +import javafx.beans.property.SimpleStringProperty +import javafx.beans.property.StringProperty import javafx.collections.FXCollections import javafx.fxml.FXML import javafx.scene.Node +import javafx.scene.layout.BorderPane import javafx.scene.layout.Pane import javafx.scene.layout.StackPane import org.jackhuang.hmcl.ProfileChangedEvent @@ -36,11 +39,19 @@ import org.jackhuang.hmcl.setting.VersionSetting import org.jackhuang.hmcl.ui.animation.ContainerAnimations import org.jackhuang.hmcl.ui.download.DownloadWizardProvider import org.jackhuang.hmcl.ui.animation.TransitionHandler +import org.jackhuang.hmcl.ui.wizard.HasTitle import org.jackhuang.hmcl.ui.wizard.Wizard /** * @see /assets/fxml/main.fxml */ -class MainController { +class MainPage : BorderPane(), HasTitle { + override val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Main Page") + + @FXML lateinit var buttonLaunch: JFXButton + + init { + loadFXML("/assets/fxml/main.fxml") + } } \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/NumberValidator.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/NumberValidator.kt new file mode 100644 index 000000000..225db0b04 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/NumberValidator.kt @@ -0,0 +1,46 @@ +/* + * 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.validation.base.ValidatorBase +import javafx.scene.control.TextInputControl + +class NumberValidator @JvmOverloads constructor(val nullable: Boolean = false) : ValidatorBase() { + + override fun eval() { + if (this.srcControl.get() is TextInputControl) { + this.evalTextInputField() + } + + } + + private fun evalTextInputField() { + val textField = this.srcControl.get() as TextInputControl + + if (textField.text.isBlank()) + hasErrors.set(false) + else + try { + Integer.parseInt(textField.text) + this.hasErrors.set(false) + } catch (var3: Exception) { + this.hasErrors.set(true) + } + + } +} \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/RipplerContainer.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/RipplerContainer.kt new file mode 100644 index 000000000..c3018b7af --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/RipplerContainer.kt @@ -0,0 +1,230 @@ +/* + * 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.JFXRippler +import javafx.animation.Transition +import javafx.beans.DefaultProperty +import javafx.beans.NamedArg +import javafx.beans.Observable +import javafx.beans.binding.Bindings +import javafx.beans.property.SimpleBooleanProperty +import javafx.beans.property.SimpleObjectProperty +import javafx.geometry.Insets +import javafx.geometry.Pos +import javafx.scene.Node +import javafx.scene.control.Label +import javafx.scene.layout.* +import javafx.scene.paint.Color +import javafx.scene.paint.Paint +import javafx.scene.shape.Rectangle +import javafx.scene.shape.Shape +import org.jackhuang.hmcl.util.getValue +import org.jackhuang.hmcl.util.setValue +import java.util.concurrent.Callable + +@DefaultProperty("container") +open class RipplerContainer(@NamedArg("container") container: Node): StackPane() { + val containerProperty = SimpleObjectProperty(this, "container", null) + @JvmName("containerProperty") get + var container: Node by containerProperty + + val ripplerFillProperty = SimpleObjectProperty(this, "ripplerFill", null) + @JvmName("ripplerFillProperty") get + var ripplerFill: Paint? by ripplerFillProperty + + val selectedProperty = SimpleBooleanProperty(this, "selected", false) + @JvmName("selectedProperty") get + var selected: Boolean by selectedProperty + + private val buttonContainer = StackPane() + private val buttonRippler = object : JFXRippler(StackPane()) { + override fun getMask(): Node { + val mask = StackPane() + mask.shapeProperty().bind(buttonContainer.shapeProperty()) + mask.backgroundProperty().bind(Bindings.createObjectBinding(Callable { Background(BackgroundFill(Color.WHITE, if (buttonContainer.backgroundProperty().get() != null && buttonContainer.getBackground().getFills().size > 0) buttonContainer.background.fills[0].radii else defaultRadii, if (buttonContainer.backgroundProperty().get() != null && buttonContainer.background.fills.size > 0) buttonContainer.background.fills[0].insets else Insets.EMPTY)) }, buttonContainer.backgroundProperty())) + mask.resize(buttonContainer.width - buttonContainer.snappedRightInset() - buttonContainer.snappedLeftInset(), buttonContainer.height - buttonContainer.snappedBottomInset() - buttonContainer.snappedTopInset()) + return mask + } + + override fun initListeners() { + this.ripplerPane.setOnMousePressed { event -> + if (releaseManualRippler != null) { + releaseManualRippler!!.run() + } + + releaseManualRippler = null + this.createRipple(event.x, event.y) + } + } + } + private var clickedAnimation: Transition? = null + private val defaultRadii = CornerRadii(3.0) + private var invalid = true + private var releaseManualRippler: Runnable? = null + + init { + styleClass += "rippler-container" + this.container = container + /* + armedProperty().addListener { o, oldVal, newVal -> + if (newVal!!.booleanValue()) { + this.releaseManualRippler = this.buttonRippler.createManualRipple() + if (this.clickedAnimation != null) { + this.clickedAnimation!!.rate = 1.0 + this.clickedAnimation!!.play() + } + } else { + if (this.releaseManualRippler != null) { + this.releaseManualRippler!!.run() + } + + if (this.clickedAnimation != null) { + this.clickedAnimation!!.rate = -1.0 + this.clickedAnimation!!.play() + } + } + + }*/ + this.buttonContainer.children.add(this.buttonRippler) + setOnMousePressed { e -> + if (this.clickedAnimation != null) { + this.clickedAnimation!!.rate = 1.0 + this.clickedAnimation!!.play() + } + + } + setOnMouseReleased { e -> + if (this.clickedAnimation != null) { + this.clickedAnimation!!.rate = -1.0 + this.clickedAnimation!!.play() + } + + } + focusedProperty().addListener { o, oldVal, newVal -> + if (newVal) { + if (!isPressed) { + this.buttonRippler.showOverlay() + } + } else { + this.buttonRippler.hideOverlay() + } + + } + pressedProperty().addListener { _, _, _ -> this.buttonRippler.hideOverlay() } + isPickOnBounds = false + this.buttonContainer.isPickOnBounds = false + this.buttonContainer.shapeProperty().bind(shapeProperty()) + this.buttonContainer.borderProperty().bind(borderProperty()) + this.buttonContainer.backgroundProperty().bind(Bindings.createObjectBinding(Callable { + if (background == null || this.isJavaDefaultBackground(background) || this.isJavaDefaultClickedBackground(background)) { + background = Background(BackgroundFill(Color.TRANSPARENT, defaultRadii, null)) + } + + try { + return@Callable( + if (background != null && (background.fills[0] as BackgroundFill).insets == Insets(-0.2, -0.2, -0.2, -0.2)) + Background(BackgroundFill((if (background != null) (background.fills[0] as BackgroundFill).fill else Color.TRANSPARENT) as Paint, + if (backgroundProperty().get() != null) background.fills[0].radii else defaultRadii, Insets.EMPTY)) + else + Background(BackgroundFill((if (background != null) background.fills[0].fill else Color.TRANSPARENT) as Paint, + if (background != null) background.fills[0].radii else defaultRadii, Insets.EMPTY)) + ) + } catch (var3: Exception) { + return@Callable background + } + }, backgroundProperty())) + ripplerFillProperty.addListener { o, oldVal, newVal -> this.buttonRippler.ripplerFill = newVal } + if (background == null || this.isJavaDefaultBackground(background)) { + background = Background(BackgroundFill(Color.TRANSPARENT, this.defaultRadii, null)) + } + + this.updateChildren() + + containerProperty.addListener { _ -> updateChildren() } + selectedProperty.addListener { _, _, newValue -> + if (newValue) background = Background(BackgroundFill(ripplerFill, defaultRadii, null)) + else background = Background(BackgroundFill(Color.TRANSPARENT, defaultRadii, null)) + } + + shape = Rectangle().apply { + widthProperty().bind(this@RipplerContainer.widthProperty()) + heightProperty().bind(this@RipplerContainer.heightProperty()) + } + } + + protected fun updateChildren() { + children.add(container) + + if (this.buttonContainer != null) { + this.children.add(0, this.buttonContainer) + } + + for (i in 1..this.children.size - 1) { + this.children[i].isPickOnBounds = false + } + + } + + fun layoutChildren(x: Double, y: Double, w: Double, h: Double) { + if (this.invalid) { + if (ripplerFill == null) { + for (i in this.children.size - 1 downTo 1) { + if (this.children[i] is Shape) { + this.buttonRippler.ripplerFill = (this.children[i] as Shape).fill + (this.children[i] as Shape).fillProperty().addListener { o, oldVal, newVal -> this.buttonRippler.ripplerFill = newVal } + break + } + + if (this.children[i] is Label) { + this.buttonRippler.ripplerFill = (this.children[i] as Label).textFill + (this.children[i] as Label).textFillProperty().addListener { o, oldVal, newVal -> this.buttonRippler.ripplerFill = newVal } + break + } + } + } else { + this.buttonRippler.ripplerFill = ripplerFill + } + + this.invalid = false + } + + val shift = 1.0 + this.buttonContainer.resizeRelocate(layoutBounds.minX - shift, layoutBounds.minY - shift, width + 2.0 * shift, height + 2.0 * shift) + //this.layoutLabelInArea(x, y, w, h) + } + + private fun isJavaDefaultBackground(background: Background): Boolean { + try { + val firstFill = (background.fills[0] as BackgroundFill).fill.toString() + return "0xffffffba" == firstFill || "0xffffffbf" == firstFill || "0xffffffbd" == firstFill + } catch (var3: Exception) { + return false + } + + } + + private fun isJavaDefaultClickedBackground(background: Background): Boolean { + try { + return "0x039ed3ff" == (background.fills[0] as BackgroundFill).fill.toString() + } catch (var3: Exception) { + return false + } + + } +} \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.kt index 687a4039c..ae22d4c69 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.kt @@ -50,8 +50,10 @@ object SVG { return svg } - fun gear(): Node = createSVGPath("M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z") - fun back(fill: String = "white"): Node = createSVGPath("M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z", fill) - fun close(fill: String = "white"): Node = createSVGPath("M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z", fill) - fun dotsVertical(fill: String = "white"): Node = createSVGPath("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z", fill) + fun gear(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z", fill, width, height) + fun back(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z", fill, width, height) + fun close(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z", fill, width, height) + fun dotsVertical(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z", fill, width, height) + fun delete(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z", fill, width, height) + fun accountEdit(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M21.7,13.35L20.7,14.35L18.65,12.3L19.65,11.3C19.86,11.09 20.21,11.09 20.42,11.3L21.7,12.58C21.91,12.79 21.91,13.14 21.7,13.35M12,18.94L18.06,12.88L20.11,14.93L14.06,21H12V18.94M12,14C7.58,14 4,15.79 4,18V20H10V18.11L14,14.11C13.34,14.03 12.67,14 12,14M12,4A4,4 0 0,0 8,8A4,4 0 0,0 12,12A4,4 0 0,0 16,8A4,4 0 0,0 12,4Z", fill, width, height) } \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SafeIntStringConverter.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SafeIntStringConverter.kt new file mode 100644 index 000000000..a0f14a990 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SafeIntStringConverter.kt @@ -0,0 +1,28 @@ +/* + * 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 javafx.util.StringConverter + +class SafeIntStringConverter : StringConverter() { + /** {@inheritDoc} */ + override fun fromString(value: String?) = value?.toIntOrNull() + + /** {@inheritDoc} */ + override fun toString(value: Int?) = value?.toString() ?: "" +} \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionController.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionController.kt deleted file mode 100644 index 48dc5224f..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionController.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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.JFXScrollPane -import com.jfoenix.controls.JFXTextField -import javafx.fxml.FXML -import javafx.scene.control.Label -import javafx.scene.control.ScrollPane -import javafx.scene.layout.ColumnConstraints -import javafx.scene.layout.GridPane -import javafx.scene.layout.Priority -import javafx.scene.paint.Color -import javafx.stage.DirectoryChooser -import org.jackhuang.hmcl.setting.VersionSetting - -class VersionController { - @FXML - lateinit var titleLabel: Label - @FXML - lateinit var backButton: JFXButton - @FXML - lateinit var scroll: ScrollPane - @FXML - lateinit var settingsPane: GridPane - @FXML - lateinit var txtGameDir: JFXTextField - - fun initialize() { - Controllers.versionController = this - - settingsPane.columnConstraints.addAll( - ColumnConstraints(), - ColumnConstraints().apply { hgrow = Priority.ALWAYS }, - ColumnConstraints() - ) - - backButton.ripplerFill = Color.WHITE - backButton.setOnMouseClicked { - Controllers.navigate(null) - } - - JFXScrollPane.smoothScrolling(scroll) - } - - fun loadVersionSetting(id: String, version: VersionSetting) { - titleLabel.text = id - } - - fun onExploreJavaDir() { - val chooser = DirectoryChooser() - chooser.title = "Selecting Java Directory" - val selectedDir = chooser.showDialog(Controllers.stage) - if (selectedDir != null) - txtGameDir.text = selectedDir.absolutePath - } -} \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionListItem.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionListItem.kt index ce77712d1..47ca70a7b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionListItem.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionListItem.kt @@ -39,10 +39,6 @@ class VersionListItem(val versionName: String, val gameVersion: String) : Border handler() } - fun onLaunch() { - - } - fun onSettingsButtonClicked(handler: () -> Unit) { this.handler = handler } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionPage.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionPage.kt new file mode 100644 index 000000000..d61771070 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionPage.kt @@ -0,0 +1,123 @@ +/* + * 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.JFXScrollPane +import com.jfoenix.controls.JFXTextField +import javafx.beans.InvalidationListener +import javafx.beans.property.Property +import javafx.beans.property.SimpleStringProperty +import javafx.beans.property.StringProperty +import javafx.fxml.FXML +import javafx.scene.control.ScrollPane +import javafx.scene.layout.* +import javafx.stage.DirectoryChooser +import org.jackhuang.hmcl.setting.VersionSetting +import org.jackhuang.hmcl.ui.wizard.HasTitle + +class VersionPage : StackPane(), HasTitle { + override val titleProperty: StringProperty = SimpleStringProperty(this, "title", null) + var lastVersionSetting: VersionSetting? = null + + @FXML lateinit var rootPane: VBox + @FXML lateinit var scroll: ScrollPane + @FXML lateinit var settingsPane: GridPane + @FXML lateinit var txtWidth: JFXTextField + @FXML lateinit var txtHeight: JFXTextField + @FXML lateinit var txtMaxMemory: JFXTextField + @FXML lateinit var txtJVMArgs: JFXTextField + @FXML lateinit var txtGameArgs: JFXTextField + @FXML lateinit var txtMetaspace: JFXTextField + @FXML lateinit var txtWrapper: JFXTextField + @FXML lateinit var txtPrecallingCommand: JFXTextField + @FXML lateinit var txtServerIP: JFXTextField + @FXML lateinit var txtGameDir: JFXTextField + @FXML lateinit var advancedSettingsPane: VBox + + init { + loadFXML("/assets/fxml/version.fxml") + } + + fun initialize() { + JFXScrollPane.smoothScrolling(scroll) + + fun validation(field: JFXTextField) = InvalidationListener { field.validate() } + fun validator(nullable: Boolean = false) = NumberValidator(nullable).apply { message = "Must be a number." } + + txtWidth.setValidators(validator()) + txtWidth.textProperty().addListener(validation(txtWidth)) + txtHeight.setValidators(validator()) + txtHeight.textProperty().addListener(validation(txtHeight)) + txtMaxMemory.setValidators(validator()) + txtMaxMemory.textProperty().addListener(validation(txtMaxMemory)) + txtMetaspace.setValidators(validator(true)) + txtMetaspace.textProperty().addListener(validation(txtMetaspace)) + } + + fun loadVersionSetting(id: String, version: VersionSetting) { + titleProperty.set("Version settings - " + id) + rootPane.children -= advancedSettingsPane + + lastVersionSetting?.apply { + widthProperty.unbind() + heightProperty.unbind() + maxMemoryProperty.unbind() + javaArgsProperty.unbind() + minecraftArgsProperty.unbind() + permSizeProperty.unbind() + wrapperProperty.unbind() + precalledCommandProperty.unbind() + serverIpProperty.unbind() + } + + bindInt(txtWidth, version.widthProperty) + bindInt(txtHeight, version.heightProperty) + bindInt(txtMaxMemory, version.maxMemoryProperty) + bindString(txtJVMArgs, version.javaArgsProperty) + bindString(txtGameArgs, version.minecraftArgsProperty) + bindString(txtMetaspace, version.permSizeProperty) + bindString(txtWrapper, version.wrapperProperty) + bindString(txtPrecallingCommand, version.precalledCommandProperty) + bindString(txtServerIP, version.serverIpProperty) + + lastVersionSetting = version + } + + private fun bindInt(textField: JFXTextField, property: Property<*>) { + textField.textProperty().unbind() + textField.textProperty().bindBidirectional(property as Property, SafeIntStringConverter()) + } + + private fun bindString(textField: JFXTextField, property: Property) { + textField.textProperty().unbind() + textField.textProperty().bindBidirectional(property) + } + + fun onShowAdvanced() { + if (!rootPane.children.contains(advancedSettingsPane)) + rootPane.children += advancedSettingsPane + } + + fun onExploreJavaDir() { + val chooser = DirectoryChooser() + chooser.title = "Selecting Java Directory" + val selectedDir = chooser.showDialog(Controllers.stage) + if (selectedDir != null) + txtGameDir.text = selectedDir.absolutePath + } +} \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/ContainerAnimations.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/ContainerAnimations.kt index a5ac3ca0c..b2c8e4f19 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/ContainerAnimations.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/ContainerAnimations.kt @@ -22,7 +22,9 @@ import javafx.animation.KeyFrame import javafx.animation.KeyValue import javafx.util.Duration -enum class ContainerAnimations(val animationProducer: (AnimationHandler) -> List) { +typealias AnimationProducer = (AnimationHandler) -> List + +enum class ContainerAnimations(val animationProducer: AnimationProducer) { /** * None */ diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/TransitionHandler.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/TransitionHandler.kt index ac85d541e..48bca6ce8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/TransitionHandler.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/animation/TransitionHandler.kt @@ -26,7 +26,9 @@ import javafx.scene.SnapshotParameters import javafx.scene.image.ImageView import javafx.scene.image.WritableImage import javafx.scene.layout.StackPane +import javafx.scene.shape.Rectangle import javafx.util.Duration +import org.jackhuang.hmcl.ui.setOverflowHidden import org.jackhuang.hmcl.ui.takeSnapshot /** @@ -43,7 +45,7 @@ class TransitionHandler(override val view: StackPane): AnimationHandler { override lateinit var duration: Duration private set - fun setContent(newView: Node, transition: (TransitionHandler) -> List, duration: Duration = Duration.millis(320.0)) { + fun setContent(newView: Node, transition: AnimationProducer, duration: Duration = Duration.millis(320.0)) { this.duration = duration val prevAnimation = animation @@ -82,8 +84,7 @@ class TransitionHandler(override val view: StackPane): AnimationHandler { snapshot.isVisible = true snapshot.opacity = 1.0 - view.children.setAll(snapshot) - view.children.add(newView) + view.children.setAll(snapshot, newView) snapshot.toFront() } } \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadWizardProvider.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadWizardProvider.kt index 011ac03af..55ad8c314 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadWizardProvider.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadWizardProvider.kt @@ -20,14 +20,28 @@ package org.jackhuang.hmcl.ui.download import javafx.scene.Node import javafx.scene.layout.Pane import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider +import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.ui.wizard.WizardController import org.jackhuang.hmcl.ui.wizard.WizardProvider class DownloadWizardProvider(): WizardProvider() { override fun finish(settings: Map): Any? { - println(settings) - return null + val builder = Settings.selectedProfile.dependency.gameBuilder() + + builder.name(settings["name"] as String) + builder.gameVersion(settings["game"] as String) + + if (settings.containsKey("forge")) + builder.version("forge", settings["forge"] as String) + + if (settings.containsKey("liteloader")) + builder.version("liteloader", settings["liteloader"] as String) + + if (settings.containsKey("optifine")) + builder.version("optifine", settings["optifine"] as String) + + return builder.buildAsync() } override fun createPage(controller: WizardController, step: Int, settings: Map): Node { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.kt index bd9870522..890c2dd05 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.kt @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.ui.download import com.jfoenix.controls.JFXListView +import com.jfoenix.controls.JFXTextField import javafx.fxml.FXML import javafx.scene.control.Label import javafx.scene.layout.StackPane @@ -33,15 +34,20 @@ class InstallersPage(private val controller: WizardController, private val downl @FXML lateinit var lblForge: Label @FXML lateinit var lblLiteLoader: Label @FXML lateinit var lblOptiFine: Label + @FXML lateinit var txtName: JFXTextField init { loadFXML("/assets/fxml/download/installers.fxml") + + val gameVersion = controller.settings["game"] as String + txtName.text = gameVersion + list.selectionModel.selectedIndexProperty().addListener { _, _, newValue -> controller.settings[INSTALLER_TYPE] = newValue controller.onNext(when (newValue){ - 0 -> VersionsPage(controller, controller.settings["game"] as String, downloadProvider, "forge") { controller.onPrev(false) } - 1 -> VersionsPage(controller, controller.settings["game"] as String, downloadProvider, "liteloader") { controller.onPrev(false) } - 2 -> VersionsPage(controller, controller.settings["game"] as String, downloadProvider, "optifine") { controller.onPrev(false) } + 0 -> VersionsPage(controller, gameVersion, downloadProvider, "forge") { controller.onPrev(false) } + 1 -> VersionsPage(controller, gameVersion, downloadProvider, "liteloader") { controller.onPrev(false) } + 2 -> VersionsPage(controller, gameVersion, downloadProvider, "optifine") { controller.onPrev(false) } else -> throw IllegalStateException() }) } @@ -72,6 +78,11 @@ class InstallersPage(private val controller: WizardController, private val downl settings.remove(INSTALLER_TYPE) } + fun onInstall() { + controller.settings["name"] = txtName.text + controller.onFinish() + } + companion object { val INSTALLER_TYPE = "INSTALLER_TYPE" } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.kt index f1ea0ff86..490f5a66f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.kt @@ -24,6 +24,7 @@ import javafx.scene.layout.StackPane import org.jackhuang.hmcl.download.RemoteVersion import org.jackhuang.hmcl.download.DownloadProvider import org.jackhuang.hmcl.task.Scheduler +import org.jackhuang.hmcl.task.TaskExecutor import org.jackhuang.hmcl.ui.animation.ContainerAnimations import org.jackhuang.hmcl.ui.animation.TransitionHandler import org.jackhuang.hmcl.ui.loadFXML @@ -37,6 +38,7 @@ class VersionsPage(private val controller: WizardController, private val gameVer val transitionHandler = TransitionHandler(this) private val versionList = downloadProvider.getVersionListById(libraryId) + private var executor: TaskExecutor? = null init { loadFXML("/assets/fxml/download/versions.fxml") @@ -45,7 +47,7 @@ class VersionsPage(private val controller: WizardController, private val gameVer controller.settings[libraryId] = newValue.remoteVersion.selfVersion callback() } - versionList.refreshAsync(downloadProvider).subscribe(Scheduler.JAVAFX) { + executor = versionList.refreshAsync(downloadProvider).subscribe(Scheduler.JAVAFX) { val versions = ArrayList(versionList.getVersions(gameVersion)) versions.sortWith(RemoteVersion) for (version in versions) { @@ -61,5 +63,6 @@ class VersionsPage(private val controller: WizardController, private val gameVer override fun cleanup(settings: MutableMap) { settings.remove(libraryId) + executor?.cancel() } } \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPageItem.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPageItem.kt index f5e13b1dc..845cfeeb0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPageItem.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPageItem.kt @@ -20,10 +20,11 @@ package org.jackhuang.hmcl.ui.download import javafx.fxml.FXML import javafx.scene.control.Label import javafx.scene.layout.BorderPane +import javafx.scene.layout.StackPane import org.jackhuang.hmcl.download.RemoteVersion import org.jackhuang.hmcl.ui.loadFXML -class VersionsPageItem(val remoteVersion: RemoteVersion<*>) : BorderPane() { +class VersionsPageItem(val remoteVersion: RemoteVersion<*>) : StackPane() { @FXML lateinit var lblSelfVersion: Label @FXML lateinit var lblGameVersion: Label diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/AbstractWizardDisplayer.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/AbstractWizardDisplayer.kt new file mode 100644 index 000000000..640e605cf --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/AbstractWizardDisplayer.kt @@ -0,0 +1,143 @@ +/* + * 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.wizard + +import com.jfoenix.concurrency.JFXUtilities +import com.jfoenix.controls.JFXProgressBar +import javafx.application.Platform +import javafx.scene.Node +import javafx.scene.control.Label +import javafx.scene.layout.StackPane +import javafx.scene.layout.VBox +import org.jackhuang.hmcl.task.Scheduler +import org.jackhuang.hmcl.task.Task +import org.jackhuang.hmcl.task.TaskExecutor +import org.jackhuang.hmcl.task.TaskListener +import java.util.* +import kotlin.concurrent.thread + +interface AbstractWizardDisplayer : WizardDisplayer { + val wizardController: WizardController + val cancelQueue: Queue + + override fun handleDeferredWizardResult(settings: Map, deferredResult: DeferredWizardResult) { + val vbox = VBox() + val progressBar = JFXProgressBar() + val label = Label() + progressBar.maxHeight = 10.0 + vbox.children += progressBar + vbox.children += label + + navigateTo(StackPane().apply { children += vbox }, Navigation.NavigationDirection.FINISH) + + cancelQueue.add(thread { + deferredResult.start(settings, object : ResultProgressHandle { + private var running = true + + override fun setProgress(currentStep: Int, totalSteps: Int) { + progressBar.progress = 1.0 * currentStep / totalSteps + } + + override fun setProgress(description: String, currentStep: Int, totalSteps: Int) { + label.text = description + progressBar.progress = 1.0 * currentStep / totalSteps + } + + override fun setBusy(description: String) { + progressBar.progress = JFXProgressBar.INDETERMINATE_PROGRESS + } + + override fun finished(result: Any) { + running = false + } + + override fun failed(message: String, canNavigateBack: Boolean) { + label.text = message + running = false + } + + override val isRunning: Boolean + get() = running + + }) + + if (!Thread.currentThread().isInterrupted) + JFXUtilities.runInFX { + navigateTo(Label("Successful"), Navigation.NavigationDirection.FINISH) + } + }) + } + + override fun handleTask(settings: Map, task: Task) { + val vbox = VBox() + val tasksBar = JFXProgressBar() + val label = Label() + tasksBar.maxHeight = 10.0 + vbox.children += tasksBar + vbox.children += label + + var finishedTasks = 0 + + navigateTo(StackPane().apply { children += vbox }, Navigation.NavigationDirection.FINISH) + + task.executor().let { executor -> + executor.taskListener = object : TaskListener { + override fun onReady(task: Task) { + Platform.runLater { tasksBar.progressProperty().set(finishedTasks * 1.0 / executor.totTask.get()) } + } + + override fun onFinished(task: Task) { + Platform.runLater { + label.text = task.title + ++finishedTasks + tasksBar.progressProperty().set(finishedTasks * 1.0 / executor.totTask.get()) + } + } + + override fun onFailed(task: Task) { + Platform.runLater { + label.text = task.title + ++finishedTasks + tasksBar.progressProperty().set(finishedTasks * 1.0 / executor.totTask.get()) + } + } + + override fun onTerminate() { + Platform.runLater { navigateTo(Label("Successful"), Navigation.NavigationDirection.FINISH) } + } + + } + + cancelQueue.add(executor) + + executor.submit(Task.of(Scheduler.JAVAFX) { + navigateTo(Label("Successful"), Navigation.NavigationDirection.FINISH) + }) + }.start() + } + + override fun onCancel() { + while (cancelQueue.isNotEmpty()) { + val x = cancelQueue.poll() + when (x) { + is TaskExecutor -> x.cancel() + is Thread -> x.interrupt() + } + } + } +} \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/DefaultWizardDisplayer.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/DefaultWizardDisplayer.kt index 72b59843c..869f2aead 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/DefaultWizardDisplayer.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/DefaultWizardDisplayer.kt @@ -17,29 +17,26 @@ */ package org.jackhuang.hmcl.ui.wizard -import com.jfoenix.concurrency.JFXUtilities import com.jfoenix.controls.JFXButton -import com.jfoenix.controls.JFXProgressBar import com.jfoenix.controls.JFXToolbar -import com.jfoenix.effects.JFXDepthManager import javafx.fxml.FXML import javafx.scene.Node import javafx.scene.control.Label import javafx.scene.layout.StackPane -import javafx.scene.layout.VBox import org.jackhuang.hmcl.ui.Controllers import org.jackhuang.hmcl.ui.animation.TransitionHandler import org.jackhuang.hmcl.ui.loadFXML -import kotlin.concurrent.thread +import java.util.* +import java.util.concurrent.ConcurrentLinkedQueue -internal class DefaultWizardDisplayer(private val prefix: String, wizardProvider: WizardProvider) : StackPane(), WizardDisplayer { +internal class DefaultWizardDisplayer(private val prefix: String, wizardProvider: WizardProvider) : StackPane(), AbstractWizardDisplayer { - val wizardController = WizardController(this, wizardProvider) + override val wizardController = WizardController(this).apply { provider = wizardProvider } + override val cancelQueue: Queue = ConcurrentLinkedQueue() lateinit var transitionHandler: TransitionHandler @FXML lateinit var root: StackPane - @FXML lateinit var closeButton: JFXButton @FXML lateinit var backButton: JFXButton @FXML lateinit var toolbar: JFXToolbar /** @@ -48,6 +45,8 @@ internal class DefaultWizardDisplayer(private val prefix: String, wizardProvider @FXML lateinit var refreshButton: JFXButton @FXML lateinit var titleLabel: Label + lateinit var nowPage: Node + init { loadFXML("/assets/fxml/wizard.fxml") toolbar.effect = null @@ -59,6 +58,16 @@ internal class DefaultWizardDisplayer(private val prefix: String, wizardProvider wizardController.onStart() } + override fun onStart() { + } + + override fun onEnd() { + } + + override fun onCancel() { + + } + fun back() { wizardController.onPrev(true) } @@ -68,58 +77,17 @@ internal class DefaultWizardDisplayer(private val prefix: String, wizardProvider Controllers.navigate(null) } + fun refresh() { + (nowPage as Refreshable).refresh() + } + override fun navigateTo(page: Node, nav: Navigation.NavigationDirection) { backButton.isDisable = !wizardController.canPrev() transitionHandler.setContent(page, nav.animation.animationProducer) val title = if (prefix.isEmpty()) "" else "$prefix - " if (page is WizardPage) titleLabel.text = title + page.title - } - - override fun handleDeferredWizardResult(settings: Map, deferredResult: DeferredWizardResult) { - val vbox = VBox() - val progressBar = JFXProgressBar() - val label = Label() - progressBar.maxHeight = 10.0 - vbox.children += progressBar - vbox.children += label - - root.children.setAll(progressBar) - - thread { - deferredResult.start(settings, object : ResultProgressHandle { - private var running = true - - override fun setProgress(currentStep: Int, totalSteps: Int) { - progressBar.progress = 1.0 * currentStep / totalSteps - } - - override fun setProgress(description: String, currentStep: Int, totalSteps: Int) { - label.text = description - progressBar.progress = 1.0 * currentStep / totalSteps - } - - override fun setBusy(description: String) { - progressBar.progress = JFXProgressBar.INDETERMINATE_PROGRESS - } - - override fun finished(result: Any) { - running = false - } - - override fun failed(message: String, canNavigateBack: Boolean) { - label.text = message - running = false - } - - override val isRunning: Boolean - get() = running - - }) - - JFXUtilities.runInFX { - navigateTo(Label("Successful"), Navigation.NavigationDirection.FINISH) - } - } + refreshButton.isVisible = page is Refreshable + nowPage = page } } \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/HasTitle.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/HasTitle.kt new file mode 100644 index 000000000..92dde71a2 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/HasTitle.kt @@ -0,0 +1,24 @@ +/* + * 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.wizard + +import javafx.beans.property.StringProperty + +interface HasTitle { + val titleProperty: StringProperty +} \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/Navigation.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/Navigation.kt index e9327513b..3cdb53778 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/Navigation.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/Navigation.kt @@ -25,6 +25,7 @@ interface Navigation { fun onPrev(cleanUp: Boolean) fun canPrev(): Boolean fun onFinish() + fun onEnd() fun onCancel() enum class NavigationDirection(val animation: ContainerAnimations) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardObserver.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/Refreshable.kt similarity index 92% rename from HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardObserver.kt rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/Refreshable.kt index 84be351ed..ffc6dff6e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardObserver.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/Refreshable.kt @@ -17,7 +17,6 @@ */ package org.jackhuang.hmcl.ui.wizard -interface WizardObserver { - - fun stepsChanged(wizard: Wizard) +interface Refreshable { + fun refresh() } \ No newline at end of file diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardController.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardController.kt index 6162f51ec..74d7d96ed 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardController.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardController.kt @@ -18,15 +18,20 @@ package org.jackhuang.hmcl.ui.wizard import javafx.scene.Node +import org.jackhuang.hmcl.task.Task import java.util.* -class WizardController(protected val displayer: WizardDisplayer, protected val provider: WizardProvider) : Navigation { +class WizardController(protected val displayer: WizardDisplayer) : Navigation { + lateinit var provider: WizardProvider val settings = mutableMapOf() val pages = Stack() override fun onStart() { + settings.clear() + pages.clear() val page = navigatingTo(0) pages.push(page) + displayer.onStart() displayer.navigateTo(page, Navigation.NavigationDirection.START) } @@ -62,11 +67,19 @@ class WizardController(protected val displayer: WizardDisplayer, protected val p when (result) { is DeferredWizardResult -> displayer.handleDeferredWizardResult(settings, result) is Summary -> displayer.navigateTo(result.component, Navigation.NavigationDirection.NEXT) + is Task -> displayer.handleTask(settings, result) } } - override fun onCancel() { + override fun onEnd() { + settings.clear() + pages.clear() + displayer.onEnd() + } + override fun onCancel() { + displayer.onCancel() + onEnd() } fun navigatingTo(step: Int): Node { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardDisplayer.kt b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardDisplayer.kt index f30785508..5fc69bd46 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardDisplayer.kt +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardDisplayer.kt @@ -18,9 +18,16 @@ package org.jackhuang.hmcl.ui.wizard import javafx.scene.Node +import org.jackhuang.hmcl.task.Task interface WizardDisplayer { + fun onStart() + fun onEnd() + fun onCancel() + fun navigateTo(page: Node, nav: Navigation.NavigationDirection) fun handleDeferredWizardResult(settings: Map, deferredResult: DeferredWizardResult) + + fun handleTask(settings: Map, task: Task) } \ No newline at end of file diff --git a/HMCL/src/main/resources/assets/css/jfoenix-components.css b/HMCL/src/main/resources/assets/css/jfoenix-components.css deleted file mode 100644 index 0f6d78e29..000000000 --- a/HMCL/src/main/resources/assets/css/jfoenix-components.css +++ /dev/null @@ -1,394 +0,0 @@ -.root { - -fx-font-family: Roboto; - src: "/resources/roboto/Roboto-Regular.ttf"; -} - -/* Burgers Demo */ - -.jfx-hamburger { - -fx-spacing: 5; - -fx-cursor: hand; -} - -.jfx-hamburger StackPane { - -fx-pref-width: 40px; - -fx-pref-height: 7px; - -fx-background-color: #D63333; - -fx-background-radius: 5px; -} - -/* Input Demo */ - -.text-field { - -fx-max-width: 300; -} - -.jfx-text-field, .jfx-password-field { - -fx-background-color: WHITE; - -fx-font-weight: BOLD; - -fx-prompt-text-fill: #808080; - -fx-alignment: top-left; - -jfx-focus-color: #4059A9; - -jfx-unfocus-color: #4d4d4d; - -fx-max-width: 300; -} - -.jfx-decorator { - -fx-decorator-color: RED; -} - -.jfx-decorator .jfx-decorator-buttons-container { - -fx-background-color: -fx-decorator-color; -} - -.jfx-decorator .resize-border { - -fx-border-color: -fx-decorator-color; - -fx-border-width: 0 1 1 1; -} - -.jfx-text-area, .text-area { - -fx-font-weight: BOLD; -} - -.jfx-text-field:error, .jfx-password-field:error, .jfx-text-area:error { - -jfx-focus-color: #D34336; - -jfx-unfocus-color: #D34336; -} - -.jfx-text-field .error-label, .jfx-password-field .error-label, .jfx-text-area .error-label { - -fx-text-fill: #D34336; - -fx-font-size: 0.75em; -} - -.jfx-text-field .error-icon, .jfx-password-field .error-icon, .jfx-text-area .error-icon { - -fx-text-fill: #D34336; - -fx-font-size: 1em; -} - -/* Progress Bar Demo */ - -.progress-bar > .bar { - -fx-min-width: 500; -} - -.jfx-progress-bar > .bar { - -fx-min-width: 500; -} - -.jfx-progress-bar { - -fx-progress-color: #0F9D58; - -fx-stroke-width: 3; -} - -/* Icons Demo */ -.icon { - -fx-text-fill: #FE774D; - -fx-padding: 10; - -fx-cursor: hand; -} - -.icons-rippler { - -jfx-rippler-fill: BLUE; - -jfx-mask-type: CIRCLE; -} - -.icons-rippler:hover { - -fx-cursor: hand; -} - -.jfx-check-box { - -fx-font-weight: BOLD; -} - -.custom-jfx-check-box { - -jfx-checked-color: RED; - -jfx-unchecked-color: BLACK; -} - -/* Button */ -.button { - -fx-padding: 0.7em 0.57em; - -fx-font-size: 14px; -} - -.button-raised { - -fx-padding: 0.7em 0.57em; - -fx-font-size: 14px; - -jfx-button-type: RAISED; - -fx-background-color: rgb(77, 102, 204); - -fx-pref-width: 200; - -fx-text-fill: WHITE; -} - -/* The main scrollbar **track** CSS class */ -.mylistview .scroll-bar:horizontal .track, -.mylistview .scroll-bar:vertical .track { - -fx-background-color: transparent; - -fx-border-color: transparent; - -fx-background-radius: 0em; - -fx-border-radius: 2em; -} - -/* The increment and decrement button CSS class of scrollbar */ -.mylistview .scroll-bar:horizontal .increment-button, -.mylistview .scroll-bar:horizontal .decrement-button { - -fx-background-color: transparent; - -fx-background-radius: 0em; - -fx-padding: 0 0 10 0; -} - -/* The increment and decrement button CSS class of scrollbar */ - -.mylistview .scroll-bar:vertical .increment-button, -.mylistview .scroll-bar:vertical .decrement-button { - -fx-background-color: transparent; - -fx-background-radius: 0em; - -fx-padding: 0 10 0 0; - -} - -.mylistview .scroll-bar .increment-arrow, -.mylistview .scroll-bar .decrement-arrow { - -fx-shape: " "; - -fx-padding: 0; -} - -/* The main scrollbar **thumb** CSS class which we drag every time (movable) */ -.mylistview .scroll-bar:horizontal .thumb, -.mylistview .scroll-bar:vertical .thumb { - -fx-background-color: derive(black, 90%); - -fx-background-insets: 2, 0, 0; - -fx-background-radius: 2em; -} - -.jfx-list-cell-container { - -fx-alignment: center-left; -} - -.jfx-list-cell-container > .label { - -fx-text-fill: BLACK; -} - -.jfx-list-cell:odd:selected > .jfx-rippler > StackPane, .jfx-list-cell:even:selected > .jfx-rippler > StackPane { - -fx-background-color: rgba(0, 0, 255, 0.2); -} - -.jfx-list-cell { - -fx-background-insets: 0.0; - -fx-text-fill: BLACK; -} - -.jfx-list-cell:odd, .jfx-list-cell:even { - -fx-background-color: WHITE; -} - -.jfx-list-cell:filled:hover { - -fx-text-fill: black; -} - -.jfx-list-cell .jfx-rippler { - -jfx-rippler-fill: BLUE; -} - -.jfx-list-view { - -fx-background-insets: 0; - -jfx-cell-horizontal-margin: 0.0; - -jfx-cell-vertical-margin: 5.0; - -jfx-vertical-gap: 10; - -jfx-expanded: false; - -fx-pref-width: 200; -} - -.jfx-toggle-button { - -jfx-toggle-color: RED; -} - -.jfx-tool-bar { - -fx-font-size: 15; - -fx-background-color: #5264AE; - -fx-pref-width: 100%; - -fx-pref-height: 64px; -} - -.jfx-tool-bar HBox { - -fx-alignment: center; -/* -fx-spacing: 25;*/ - -fx-padding: 0 10; -} - -.jfx-tool-bar Label { - -fx-text-fill: WHITE; -} - -.jfx-popup-container { - -fx-background-color: WHITE; -} - -.jfx-snackbar-content { - -fx-background-color: #323232; - -fx-padding: 5; - -fx-spacing: 5; -} - -.jfx-snackbar-toast { - -fx-text-fill: WHITE; -} - -.jfx-snackbar-action { - -fx-text-fill: #ff4081; -} - -.jfx-list-cell-content-container { - -fx-alignment: center-left; -} - -.jfx-list-cell-container .label { - -fx-text-fill: BLACK; -} - -.combo-box-popup .list-view .jfx-list-cell:odd:selected .jfx-list-cell-container, -.combo-box-popup .list-view .jfx-list-cell:even:selected .jfx-list-cell-container { - -fx-background-color: rgba(0.0, 0.0, 255.0, 0.2); -} - -.combo-box-popup .list-view .jfx-list-cell { - -fx-background-insets: 0.0; - -fx-text-fill: BLACK; -} - -.combo-box-popup .list-view .jfx-list-cell:odd, -.combo-box-popup .list-view .jfx-list-cell:even { - -fx-background-color: WHITE; -} - -.combo-box-popup .list-view .jfx-list-cell:filled:hover { - -fx-text-fill: black; -} - -.combo-box-popup .list-view .jfx-list-cell .jfx-rippler { - -fx-rippler-fill: #5264AE; -} - -/*.combo-box .combo-box-button-container{ - -fx-border-color:BLACK;-fx-border-width: 0 0 1 0; -} -.combo-box .combo-box-selected-value-container{ - -fx-border-color:BLACK; -} */ - -/* - * TREE TABLE CSS - */ - -.tree-table-view { - -fx-tree-table-color: rgba(255, 0, 0, 0.2); - -fx-tree-table-rippler-color: rgba(255, 0, 0, 0.4); -} - -.tree-table-view:focused .tree-table-row-cell:selected { - -fx-background-color: -fx-tree-table-color; - -fx-table-cell-border-color: -fx-tree-table-color; - -fx-text-fill: BLACK; -} - -.tree-table-view:focused .tree-table-row-cell:selected .tree-table-cell { - -fx-text-fill: BLACK; -} - -.tree-table-view .jfx-rippler { - -jfx-rippler-fill: -fx-tree-table-rippler-color; -} - -.tree-table-view .column-header, -.tree-table-view .column-header-background, -.tree-table-view .column-header-background .filler { - -fx-background-color: TRANSPARENT; -} - -.tree-table-view .column-header { - -fx-border-width: 0 1 0 1; - -fx-border-color: #F3F3F3; -} - -.tree-table-view .column-header .label { - -fx-text-fill: #949494; - -fx-padding: 16 0 16 0; -} - -.tree-table-view .column-header .arrow, .tree-table-view .column-header .sort-order-dot { - -fx-background-color: #949494; -} - -.tree-table-view .column-header:last-visible { - -fx-border-width: 0 2 0 1; -} - -.tree-table-view .column-header-background { - -fx-border-width: 0 0.0 1 0; - -fx-border-color: #F3F3F3; -} - -.tree-table-view .tree-table-cell { - -fx-border-width: 0 0 0 0; - -fx-padding: 16 0 16 0; -} - -.tree-table-view .column-overlay { - -fx-background-color: -fx-tree-table-color; -} - -.tree-table-view .column-resize-line, .tree-table-view .column-drag-header { - -fx-background-color: -fx-tree-table-rippler-color; -} - -.tree-table-view:focused { - -fx-background-color: -fx-tree-table-color, -fx-box-border, -fx-control-inner-background; - -fx-background-insets: -1.4, 0, 1; - -fx-background-radius: 1.4, 0, 0; - /*....*/ - -fx-padding: 1; /* 0.083333em; */ -} - -.tree-table-row-cell > .tree-disclosure-node > .arrow { - -fx-background-color: -fx-text-fill; - -fx-padding: 0.333333em 0.229em 0.333333em 0.229em; /* 4 */ - -fx-shape: "M 0 -3.5 L 4 0 L 0 3.5 z"; -} - -.tree-table-row-cell .jfx-text-field { - -fx-focus-color: rgba(240, 40, 40); -} - -.tree-table-row-group { - -fx-background-color: rgba(230, 230, 230); -} - -.animated-option-button { - -fx-pref-width: 50px; - -fx-background-color: #44B449; - -fx-background-radius: 50px; - -fx-pref-height: 50px; - -fx-text-fill: white; - -fx-border-color: WHITE; - -fx-border-radius: 50px; - -fx-border-width: 4px; -} - -.animated-option-sub-button { - -fx-background-color: #43609C; -} - -.animated-option-sub-button2 { - -fx-background-color: rgb(203, 104, 96); -} - -.tree-table-view .menu-item:focused { - -fx-background-color: -fx-tree-table-color; - -} - -.tree-table-view .menu-item .label { - -fx-padding: 5 0 5 0; -} - - diff --git a/HMCL/src/main/resources/assets/css/jfoenix-main-demo.css b/HMCL/src/main/resources/assets/css/jfoenix-main-demo.css index 5f6cf082e..1cd9db15c 100644 --- a/HMCL/src/main/resources/assets/css/jfoenix-main-demo.css +++ b/HMCL/src/main/resources/assets/css/jfoenix-main-demo.css @@ -25,6 +25,25 @@ -fx-text-fill: rgba(0.0, 0.0, 0.0, 0.87); } +.class-title { + -fx-font-size: 12px; + -fx-padding: 0 16 0 16; +} + +.rippler-container HBox { + -fx-font-size: 14px; + -fx-padding: 10 16 10 16; + -fx-spacing: 10; +} + +.left-pane-item { + -fx-padding: 10 16 10 16; +} + +.jfx-decorator-left-pane { + -fx-padding: 20 0 20 0; +} + /******************************************************************************* * * * JFX Drawer * @@ -84,6 +103,11 @@ -fx-padding: 0.7em 0.8em; } +.dialog-cancel { + -fx-font-weight: BOLD; + -fx-padding: 0.7em 0.8em; +} + /******************************************************************************* * * * JFX Pop Up * @@ -453,7 +477,6 @@ -fx-font-weight: BOLD; -fx-prompt-text-fill: #808080; -fx-alignment: top-left; - -fx-max-width: 300.0; -fx-pref-width: 300.0; -jfx-focus-color: #4059A9; -jfx-unfocus-color: #4d4d4d; @@ -658,9 +681,9 @@ } .toggle-icon3 { - -fx-pref-width: 40px; + -fx-pref-width: 5px; -fx-background-radius: 50px; - -fx-pref-height: 30px; + -fx-pref-height: 5px; -fx-background-color: transparent; -jfx-toggle-color: rgba(128, 128, 255, 0.2); -jfx-untoggle-color: transparent; @@ -672,10 +695,33 @@ } .toggle-icon3 .jfx-rippler { + -jfx-rippler-fill: white; + -jfx-mask-type: CIRCLE; +} + +.toggle-icon4 { + -fx-pref-width: 5px; + -fx-background-radius: 50px; + -fx-pref-height: 5px; + -fx-background-color: transparent; + -jfx-toggle-color: rgba(128, 128, 255, 0.2); + -jfx-untoggle-color: transparent; +} + +.toggle-icon4 .icon { + -fx-fill: rgb(204.0, 204.0, 51.0); + -fx-padding: 10.0; +} + +.toggle-icon4 .jfx-rippler { -jfx-rippler-fill: #0F9D58; -jfx-mask-type: CIRCLE; } +.transparent { + -fx-background-color: null; +} + /******************************************************************************* * * * JFX Spinner * diff --git a/HMCL/src/main/resources/assets/fxml/account-item.fxml b/HMCL/src/main/resources/assets/fxml/account-item.fxml new file mode 100644 index 000000000..a2875c6b5 --- /dev/null +++ b/HMCL/src/main/resources/assets/fxml/account-item.fxml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+ +
+ + + + + + + + +
diff --git a/HMCL/src/main/resources/assets/fxml/account.fxml b/HMCL/src/main/resources/assets/fxml/account.fxml new file mode 100644 index 000000000..8c26e5720 --- /dev/null +++ b/HMCL/src/main/resources/assets/fxml/account.fxml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HMCL/src/main/resources/assets/fxml/decorator.fxml b/HMCL/src/main/resources/assets/fxml/decorator.fxml index f595d8eac..26ef8443d 100644 --- a/HMCL/src/main/resources/assets/fxml/decorator.fxml +++ b/HMCL/src/main/resources/assets/fxml/decorator.fxml @@ -7,9 +7,6 @@ - - - - + @@ -30,49 +27,62 @@ - +
- - - - -
- - - - - - - - - - - - - - + +
+ + + + +
+ + + + + + + + + + + + + + + +
-
+ + + +
- - - - - + + + + + + + - + - - + +
+
+ + + +
- - + @@ -82,7 +92,7 @@ - + @@ -90,7 +100,7 @@ - + diff --git a/HMCL/src/main/resources/assets/fxml/download/installers.fxml b/HMCL/src/main/resources/assets/fxml/download/installers.fxml index e81963ce1..6b69a5648 100644 --- a/HMCL/src/main/resources/assets/fxml/download/installers.fxml +++ b/HMCL/src/main/resources/assets/fxml/download/installers.fxml @@ -7,14 +7,16 @@ + - + + +
@@ -46,7 +48,7 @@
- diff --git a/HMCL/src/main/resources/assets/fxml/download/versions-list-item.fxml b/HMCL/src/main/resources/assets/fxml/download/versions-list-item.fxml index c39c1fd4a..749d40b69 100644 --- a/HMCL/src/main/resources/assets/fxml/download/versions-list-item.fxml +++ b/HMCL/src/main/resources/assets/fxml/download/versions-list-item.fxml @@ -5,18 +5,18 @@ - - - - -
- - -
- - - + type="StackPane" mouseTransparent="true"> + + + + + + + + + + +
diff --git a/HMCL/src/main/resources/assets/fxml/main.fxml b/HMCL/src/main/resources/assets/fxml/main.fxml index 9fde666a3..46d77a903 100644 --- a/HMCL/src/main/resources/assets/fxml/main.fxml +++ b/HMCL/src/main/resources/assets/fxml/main.fxml @@ -2,10 +2,9 @@ -
@@ -23,4 +22,4 @@ - + diff --git a/HMCL/src/main/resources/assets/fxml/version-list-item.fxml b/HMCL/src/main/resources/assets/fxml/version-list-item.fxml index 682f54fa8..26c301af1 100644 --- a/HMCL/src/main/resources/assets/fxml/version-list-item.fxml +++ b/HMCL/src/main/resources/assets/fxml/version-list-item.fxml @@ -3,29 +3,31 @@ + + - - + + + + + + + - - - - - - - - - - - + + + + + + diff --git a/HMCL/src/main/resources/assets/fxml/version.fxml b/HMCL/src/main/resources/assets/fxml/version.fxml index a3620092e..c62fdfe82 100644 --- a/HMCL/src/main/resources/assets/fxml/version.fxml +++ b/HMCL/src/main/resources/assets/fxml/version.fxml @@ -5,16 +5,19 @@ - - -
- + - - + + + + + + + @@ -59,9 +62,9 @@ - + - + @@ -75,36 +78,17 @@ - - - - - - - + + + + + + + + + + - - -
- - - - - - - - - - - - - - - - - -
-
+ + + diff --git a/HMCL/src/main/resources/assets/fxml/wizard.fxml b/HMCL/src/main/resources/assets/fxml/wizard.fxml index a9001e59e..b86285a28 100644 --- a/HMCL/src/main/resources/assets/fxml/wizard.fxml +++ b/HMCL/src/main/resources/assets/fxml/wizard.fxml @@ -11,7 +11,7 @@ - @@ -31,7 +31,7 @@ + StackPane.alignment="CENTER_RIGHT" onMouseClicked="#refrseh"> diff --git a/HMCL/src/main/resources/assets/img/icon.png b/HMCL/src/main/resources/assets/img/icon.png new file mode 100644 index 000000000..25f3cf6c8 Binary files /dev/null and b/HMCL/src/main/resources/assets/img/icon.png differ diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.kt index 3d6a7f484..2f76287eb 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.kt @@ -24,5 +24,5 @@ abstract class Account() { @Throws(AuthenticationException::class) abstract fun logIn(proxy: Proxy = Proxy.NO_PROXY): AuthInfo abstract fun logOut() - abstract fun toStorage(): Map + abstract fun toStorage(): MutableMap } \ No newline at end of file diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Accounts.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Accounts.kt new file mode 100644 index 000000000..de058fec9 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Accounts.kt @@ -0,0 +1,27 @@ +/* + * 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.auth + +import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount + +object Accounts { + val ACCOUNTS = mapOf( + "offline" to OfflineAccount, + "yggdrasil" to YggdrasilAccount + ) +} \ No newline at end of file diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccount.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccount.kt index f17688ebd..a5b625eb4 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccount.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccount.kt @@ -35,13 +35,15 @@ class OfflineAccount private constructor(val uuid: String, override val username // Offline account need not log out. } - override fun toStorage(): Map { - return mapOf( + override fun toStorage(): MutableMap { + return mutableMapOf( "uuid" to uuid, "username" to username ) } + override fun toString() = "OfflineAccount[username=$username,uuid=$uuid]" + companion object OfflineAccountFactory : AccountFactory { override fun fromUsername(username: String, password: String): OfflineAccount { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.kt index ee611f139..45ac93a79 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.kt @@ -135,8 +135,8 @@ class YggdrasilAccount private constructor(override val username: String): Accou selectedProfile = null } - override fun toStorage(): Map { - val result = HashMap() + override fun toStorage(): MutableMap { + val result = HashMap() result[STORAGE_KEY_USER_NAME] = username if (userId != null) @@ -189,6 +189,8 @@ class YggdrasilAccount private constructor(override val username: String): Accou } } + override fun toString() = "YggdrasilAccount[username=$username]" + companion object YggdrasilAccountFactory : AccountFactory { private val GSON = GsonBuilder() .registerTypeAdapter(GameProfile::class.java, GameProfile) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.kt index 394a0ca7c..d2b3cd2c8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.kt @@ -19,6 +19,7 @@ package org.jackhuang.hmcl.launch import org.jackhuang.hmcl.auth.AuthInfo import org.jackhuang.hmcl.game.* +import org.jackhuang.hmcl.task.TaskResult import org.jackhuang.hmcl.util.* import java.io.File import java.io.IOException @@ -26,12 +27,12 @@ import java.util.* import kotlin.concurrent.thread /** - * @param version A resolved version(calling [Version.resolve]) + * @param versionId The version to be launched. * @param account The user account * @param options The launching configuration */ -open class DefaultLauncher(repository: GameRepository, version: Version, account: AuthInfo, options: LaunchOptions, listener: ProcessListener? = null, isDaemon: Boolean = true) - : Launcher(repository, version, account, options, listener, isDaemon) { +open class DefaultLauncher(repository: GameRepository, versionId: String, account: AuthInfo, options: LaunchOptions, listener: ProcessListener? = null, isDaemon: Boolean = true) + : Launcher(repository, versionId, account, options, listener, isDaemon) { protected val native: File by lazy { repository.getNativeDirectory(version.id) } @@ -234,7 +235,7 @@ open class DefaultLauncher(repository: GameRepository, version: Version, account } builder.directory(repository.getRunDirectory(version.id)) - .environment().put("APPDATA", options.gameDir.parent) + .environment().put("APPDATA", options.gameDir.absoluteFile.parent) val p = JavaProcess(builder.start(), rawCommandLine) if (listener == null) startMonitors(p) @@ -243,6 +244,14 @@ open class DefaultLauncher(repository: GameRepository, version: Version, account return p } + fun launchAsync(): TaskResult { + return object : TaskResult() { + override fun execute() { + result = launch() + } + } + } + override fun makeLaunchScript(file: String): File { val isWindows = OS.WINDOWS == OS.CURRENT_OS val scriptFile = File(file + (if (isWindows) ".bat" else ".sh")) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/Launcher.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/Launcher.kt index 607faf1a4..8907219f2 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/Launcher.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/Launcher.kt @@ -26,12 +26,13 @@ import java.io.File abstract class Launcher( protected val repository: GameRepository, - protected val version: Version, + protected val versionId: String, protected val account: AuthInfo, protected val options: LaunchOptions, protected val listener: ProcessListener? = null, protected val isDaemon: Boolean = true) { + val version: Version = repository.getVersion(versionId).resolve(repository) abstract val rawCommandLine: List abstract fun launch(): JavaProcess diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/CoupleTask.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/CoupleTask.kt index 662e7f5a9..6141f8e31 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/CoupleTask.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/CoupleTask.kt @@ -17,7 +17,7 @@ */ package org.jackhuang.hmcl.task -private class CoupleTask(private val pred: P, private val succ: Task.(P) -> Task?, override val reliant: Boolean) : Task() { +internal class CoupleTask(private val pred: P, private val succ: Task.(P) -> Task?, override val reliant: Boolean) : Task() { override val hidden: Boolean = true override val dependents: Collection = listOf(pred) @@ -30,11 +30,12 @@ private class CoupleTask(private val pred: P, private val succ: Task.(P } } -infix fun Task.then(b: 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.then(b: Task.(T) -> Task?): Task = CoupleTask(this, b, true) -infix fun Task.with(b: Task): Task = CoupleTask(this, { b }, false) \ No newline at end of file +/** + * @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 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Scheduler.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Scheduler.kt index 17e0640d3..43daea8b7 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Scheduler.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Scheduler.kt @@ -23,16 +23,17 @@ import java.util.concurrent.atomic.AtomicReference import javax.swing.SwingUtilities interface Scheduler { + fun schedule(block: () -> Unit): Future<*>? = schedule(Callable { block() }) fun schedule(block: Callable): Future<*>? companion object Schedulers { val JAVAFX: Scheduler = SchedulerImpl(Platform::runLater) val SWING: Scheduler = SchedulerImpl(SwingUtilities::invokeLater) - private class SchedulerImpl(val executor: (() -> Unit) -> Unit) : Scheduler { + private class SchedulerImpl(val executor: (Runnable) -> Unit) : Scheduler { override fun schedule(block: Callable): Future<*>? { val latch = CountDownLatch(1) val wrapper = AtomicReference() - executor { + executor.invoke(Runnable { try { block.call() } catch (e: Exception) { @@ -40,7 +41,7 @@ interface Scheduler { } finally { latch.countDown() } - } + }) return object : Future { override fun get(timeout: Long, unit: TimeUnit) { latch.await(timeout, unit) @@ -66,7 +67,9 @@ interface Scheduler { } val DEFAULT = NEW_THREAD private val CACHED_EXECUTOR: ExecutorService by lazy { - Executors.newCachedThreadPool() + ThreadPoolExecutor(0, Integer.MAX_VALUE, + 60L, TimeUnit.SECONDS, + SynchronousQueue()); } private val IO_EXECUTOR: ExecutorService by lazy { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.kt index f6da3601b..4b7ee2d7d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.kt @@ -57,6 +57,8 @@ abstract class Task { abstract fun execute() infix fun parallel(couple: Task): Task = ParallelTask(this, couple) + infix fun then(b: Task): Task = CoupleTask(this, { b }, true) + infix fun with(b: Task): Task = CoupleTask(this, { b }, false) /** * The collection of sub-tasks that should execute before this task running. @@ -112,18 +114,18 @@ abstract class Task { fun executor() = TaskExecutor().submit(this) - fun subscribe(subscriber: Task) { - executor().submit(subscriber).start() + fun subscribe(subscriber: Task) = executor().apply { + submit(subscriber).start() } - fun subscribe(scheduler: Scheduler = Scheduler.DEFAULT, closure: () -> Unit) = subscribe(Task.of(closure, scheduler)) + fun subscribe(scheduler: Scheduler = Scheduler.DEFAULT, closure: () -> Unit) = subscribe(Task.of(scheduler, closure)) override fun toString(): String { return title } companion object { - fun of(closure: () -> Unit, scheduler: Scheduler = Scheduler.DEFAULT): Task = SimpleTask(closure, scheduler) + fun of(scheduler: Scheduler = Scheduler.DEFAULT, closure: () -> Unit): Task = SimpleTask(closure, scheduler) fun of(callable: Callable): TaskResult = TaskCallable(callable) } } \ No newline at end of file diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.kt index 73dc45ef0..89a75c0cf 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.kt @@ -29,7 +29,7 @@ class TaskExecutor() { var canceled = false private set - private val totTask = AtomicInteger(0) + val totTask = AtomicInteger(0) private val taskQueue = ConcurrentLinkedQueue() private val workerQueue = ConcurrentLinkedQueue>() @@ -47,9 +47,10 @@ class TaskExecutor() { * Start the subscription and run all registered tasks asynchronously. */ fun start() { - thread { + workerQueue.add(Scheduler.Schedulers.NEW_THREAD.schedule(Callable { totTask.addAndGet(taskQueue.size) - while (!taskQueue.isEmpty() && !canceled) { + while (!taskQueue.isEmpty()) { + if (canceled) break val task = taskQueue.poll() if (task != null) { val future = task.scheduler.schedule(Callable { executeTask(task); Unit }) @@ -61,9 +62,9 @@ class TaskExecutor() { } } } - if (!canceled) + if (canceled || Thread.interrupted()) taskListener?.onTerminate() - } + })) } /** @@ -91,9 +92,11 @@ class TaskExecutor() { if (future != null) workerQueue.add(future) } + if (canceled) + return false try { counter.await() - return success.get() + return success.get() && !canceled } catch (e: InterruptedException) { Thread.currentThread().interrupt() // Once interrupted, we are aborting the subscription. @@ -110,9 +113,10 @@ class TaskExecutor() { LOG.fine("Executing task: ${t.title}") taskListener?.onReady(t) val doDependentsSucceeded = executeTasks(t.dependents) + var flag = false try { - if (!doDependentsSucceeded && t.reliant) + if (!doDependentsSucceeded && t.reliant || canceled) throw SilentException() t.execute() diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/JavaVersion.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/JavaVersion.kt index 3335ebea4..d7f9141cb 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/JavaVersion.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/JavaVersion.kt @@ -52,6 +52,7 @@ data class JavaVersion internal constructor( } } + @Throws(IOException::class) fun fromExecutable(file: File): JavaVersion { var platform = Platform.BIT_32 var version: String? = null @@ -66,6 +67,7 @@ data class JavaVersion internal constructor( platform = Platform.BIT_64 } } catch (e: InterruptedException) { + Thread.currentThread().interrupt() throw IOException("Java process is interrupted", e) } val thisVersion = version ?: throw IOException("Java version not matched") diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/OS.kt b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/OS.kt index 44af16c36..d77ef5c9f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/OS.kt +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/OS.kt @@ -49,9 +49,9 @@ enum class OS { ReflectionHelper.get(ManagementFactory.getOperatingSystemMXBean(), "getTotalPhysicalMemorySize") ?: 1024L } - val SUGGESTED_MEMORY: Long by lazy { + val SUGGESTED_MEMORY: Int by lazy { val memory = TOTAL_MEMORY / 1024 / 1024 / 4 - Math.round(1.0 * memory / 128.0) * 128 + (Math.round(1.0 * memory / 128.0) * 128).toInt() } val PATH_SEPARATOR: String = File.pathSeparator diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/Test.kt b/HMCLCore/src/test/java/org/jackhuang/hmcl/Test.kt index 45076467f..fb5eff90e 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/Test.kt +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/Test.kt @@ -19,7 +19,7 @@ package org.jackhuang.hmcl import org.jackhuang.hmcl.auth.OfflineAccount import org.jackhuang.hmcl.download.DefaultDependencyManager -import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList +import org.jackhuang.hmcl.download.LiteLoaderVersionList import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider import org.jackhuang.hmcl.download.MojangDownloadProvider import org.jackhuang.hmcl.game.DefaultGameRepository @@ -50,7 +50,7 @@ class Test { fun launch() { val launcher = DefaultLauncher( repository = repository, - version = repository.getVersion("test"), + versionId = "test", account = OfflineAccount.fromUsername("player007").logIn(), options = LaunchOptions(gameDir = repository.baseDirectory), listener = object : ProcessListener { diff --git a/LICENSE b/LICENSE index 074b814ad..711fa7b61 100644 --- a/LICENSE +++ b/LICENSE @@ -576,8 +576,8 @@ Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's + If the Program specifies that a PROXY can decide which future +versions of the GNU General Public License can be used, that PROXY's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. diff --git a/lib/JFoenix.jar b/lib/JFoenix.jar index d837cecc4..bb0b105a8 100644 Binary files a/lib/JFoenix.jar and b/lib/JFoenix.jar differ