diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/VersionSetting.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/VersionSetting.kt index d9c2dab87..0236d92b4 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/VersionSetting.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/VersionSetting.kt @@ -47,8 +47,8 @@ class VersionSetting() { /** * Java version or null if user customizes java directory. */ - val javaProperty = ImmediateNullableStringProperty(this, "java", null) - var java: String? by javaProperty + val javaProperty = ImmediateStringProperty(this, "java", "") + var java: String by javaProperty /** * User customized java directory or null if user uses system Java. @@ -169,6 +169,27 @@ class VersionSetting() { val launcherVisibilityProperty = ImmediateObjectProperty(this, "launcherVisibility", LauncherVisibility.HIDE) var launcherVisibility: LauncherVisibility by launcherVisibilityProperty + val javaVersion: JavaVersion? get() { + // TODO: lazy initialization may result in UI suspension. + if (java.isBlank()) + java = if (javaDir.isBlank()) "Default" else "Custom" + if (java == "Default") return JavaVersion.fromCurrentEnvironment() + else if (java == "Custom") { + try { + return JavaVersion.fromExecutable(File(javaDir)) + } catch (e: IOException) { + return null // Custom Java Directory not found, + } + } else if (java.isNotBlank()) { + val c = JavaVersion.JAVAS[java] + if (c == null) { + java = "Default" + return JavaVersion.fromCurrentEnvironment() + } else + return c + } else throw Error() + } + fun addPropertyChangedListener(listener: InvalidationListener) { usesGlobalProperty.addListener(listener) javaProperty.addListener(listener) @@ -194,8 +215,7 @@ class VersionSetting() { fun toLaunchOptions(gameDir: File): LaunchOptions { return LaunchOptions( gameDir = gameDir, - java = if (java == null) JavaVersion.fromCurrentEnvironment() - else JavaVersion.fromExecutable(File(java)), + java = javaVersion ?: JavaVersion.fromCurrentEnvironment(), versionName = Main.TITLE, profileName = Main.TITLE, minecraftArgs = minecraftArgs, @@ -261,7 +281,7 @@ class VersionSetting() { javaDir = json["javaDir"]?.asString ?: "" precalledCommand = json["precalledCommand"]?.asString ?: "" serverIp = json["serverIp"]?.asString ?: "" - java = json["java"]?.asString + java = json["java"]?.asString ?: "" wrapper = json["wrapper"]?.asString ?: "" fullscreen = json["fullscreen"]?.asBoolean ?: false noJVMArgs = json["noJVMArgs"]?.asBoolean ?: false diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountsPage.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountsPage.kt index 5ebf1f9e0..017d8cf21 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountsPage.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountsPage.kt @@ -34,10 +34,8 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.task.Scheduler -import org.jackhuang.hmcl.task.Task -import org.jackhuang.hmcl.task.task +import org.jackhuang.hmcl.task.taskResult import org.jackhuang.hmcl.ui.wizard.DecoratorPage -import java.util.concurrent.Callable class AccountsPage() : StackPane(), DecoratorPage { override val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Accounts") @@ -136,7 +134,7 @@ class AccountsPage() : StackPane(), DecoratorPage { val username = txtUsername.text val password = txtPassword.text progressBar.isVisible = true - val task = task(Callable { + taskResult("create_account") { try { val account = when (type) { 0 -> OfflineAccount.fromUsername(username) @@ -149,9 +147,8 @@ class AccountsPage() : StackPane(), DecoratorPage { } catch (e: Exception) { e } - }) - task.subscribe(Scheduler.JAVAFX) { - val account = task.result + }.subscribe(Scheduler.JAVAFX) { + val account: Any = it["create_account"] if (account is Account) { Settings.addAccount(account) dialog.close() diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ModController.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ModController.kt index b44017f45..c596ef6ce 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ModController.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ModController.kt @@ -19,14 +19,11 @@ package org.jackhuang.hmcl.ui import com.jfoenix.effects.JFXDepthManager import javafx.fxml.FXML -import javafx.scene.Node import javafx.scene.control.ScrollPane import javafx.scene.layout.VBox import org.jackhuang.hmcl.mod.ModManager import org.jackhuang.hmcl.task.Scheduler -import org.jackhuang.hmcl.task.Task import org.jackhuang.hmcl.task.task -import java.util.concurrent.Callable class ModController { @FXML lateinit var scrollPane: ScrollPane diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/VersionSettingsController.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/VersionSettingsController.kt index b46779614..892e70540 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/VersionSettingsController.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/VersionSettingsController.kt @@ -19,23 +19,27 @@ package org.jackhuang.hmcl.ui import com.jfoenix.controls.* import javafx.beans.InvalidationListener +import javafx.beans.binding.Bindings +import javafx.beans.value.ChangeListener import javafx.fxml.FXML +import javafx.geometry.Pos import javafx.scene.control.Label import javafx.scene.control.ScrollPane -import javafx.scene.layout.GridPane -import javafx.scene.layout.VBox +import javafx.scene.control.Toggle +import javafx.scene.control.ToggleGroup +import javafx.scene.layout.* import javafx.stage.DirectoryChooser import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.setting.VersionSetting import org.jackhuang.hmcl.ui.construct.ComponentList import org.jackhuang.hmcl.ui.construct.NumberValidator +import org.jackhuang.hmcl.util.JavaVersion import org.jackhuang.hmcl.util.OS class VersionSettingsController { 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 @@ -45,7 +49,7 @@ class VersionSettingsController { @FXML lateinit var txtWrapper: JFXTextField @FXML lateinit var txtPrecallingCommand: JFXTextField @FXML lateinit var txtServerIP: JFXTextField - @FXML lateinit var txtGameDir: JFXTextField + @FXML lateinit var txtJavaDir: JFXTextField @FXML lateinit var advancedSettingsPane: ComponentList @FXML lateinit var cboLauncherVisibility: JFXComboBox<*> @FXML lateinit var cboRunDirectory: JFXComboBox<*> @@ -54,6 +58,13 @@ class VersionSettingsController { @FXML lateinit var chkNoJVMArgs: JFXToggleButton @FXML lateinit var chkNoCommon: JFXToggleButton @FXML lateinit var chkNoGameCheck: JFXToggleButton + @FXML lateinit var componentJava: ComponentList + @FXML lateinit var javaPane: VBox + @FXML lateinit var javaPaneCustom: HBox + @FXML lateinit var radioCustom: JFXRadioButton + @FXML lateinit var btnJavaSelect: JFXButton + + val javaGroup = ToggleGroup() fun initialize() { lblPhysicalMemory.text = i18n("settings.physical_memory") + ": ${OS.TOTAL_MEMORY}MB" @@ -61,7 +72,7 @@ class VersionSettingsController { scroll.smoothScrolling() val limit = 300.0 - txtGameDir.limitWidth(limit) + //txtJavaDir.limitWidth(limit) txtMaxMemory.limitWidth(limit) cboLauncherVisibility.limitWidth(limit) cboRunDirectory.limitWidth(limit) @@ -82,6 +93,32 @@ class VersionSettingsController { txtMaxMemory.textProperty().addListener(validation(txtMaxMemory)) txtMetaspace.setValidators(validator(true)) txtMetaspace.textProperty().addListener(validation(txtMetaspace)) + + javaPane.children.clear() + javaPane.children += createJavaPane(JavaVersion.fromCurrentEnvironment(), javaGroup) + JavaVersion.JAVAS.values.forEach { javaVersion -> + javaPane.children += createJavaPane(javaVersion, javaGroup) + } + javaPane.children += javaPaneCustom + radioCustom.toggleGroup = javaGroup + txtJavaDir.disableProperty().bind(radioCustom.selectedProperty().not()) + btnJavaSelect.disableProperty().bind(radioCustom.selectedProperty().not()) + } + + private fun createJavaPane(java: JavaVersion, group: ToggleGroup): Pane { + return HBox().apply { + style = "-fx-padding: 3;" + spacing = 8.0 + alignment = Pos.CENTER_LEFT + children += JFXRadioButton(java.longVersion).apply { + toggleGroup = group + userData = java + } + children += Label(java.binary.absolutePath).apply { + styleClass += "subtitle-label" + style += "-fx-font-size: 10;" + } + } } fun loadVersionSetting(version: VersionSetting) { @@ -100,6 +137,7 @@ class VersionSettingsController { fullscreenProperty.unbind() notCheckGameProperty.unbind() noCommonProperty.unbind() + javaDirProperty.unbind() unbindEnum(cboLauncherVisibility) unbindEnum(cboRunDirectory) } @@ -107,6 +145,7 @@ class VersionSettingsController { bindInt(txtWidth, version.widthProperty) bindInt(txtHeight, version.heightProperty) bindInt(txtMaxMemory, version.maxMemoryProperty) + bindString(txtJavaDir, version.javaDirProperty) bindString(txtJVMArgs, version.javaArgsProperty) bindString(txtGameArgs, version.minecraftArgsProperty) bindString(txtMetaspace, version.permSizeProperty) @@ -119,9 +158,47 @@ class VersionSettingsController { bindBoolean(chkNoGameCheck, version.notCheckGameProperty) bindBoolean(chkNoCommon, version.noCommonProperty) + val javaGroupKey = "java_group.listener" + @Suppress("UNCHECKED_CAST") + if (javaGroup.properties.containsKey(javaGroupKey)) + javaGroup.selectedToggleProperty().removeListener(javaGroup.properties[javaGroupKey] as ChangeListener) + + var flag = false + var defaultToggle: JFXRadioButton? = null + for (toggle in javaGroup.toggles) + if (toggle is JFXRadioButton) + if (toggle.userData == version.javaVersion) { + toggle.isSelected = true + flag = true + } else if (toggle.userData == JavaVersion.fromCurrentEnvironment()) { + defaultToggle = toggle + } + + val listener = ChangeListener { _, _, newValue -> + if (newValue == radioCustom) { // Custom + version.java = "Custom" + } else { + version.java = ((newValue as JFXRadioButton).userData as JavaVersion).longVersion + } + } + javaGroup.properties[javaGroupKey] = listener + javaGroup.selectedToggleProperty().addListener(listener) + + if (!flag) { + defaultToggle?.isSelected = true + } + + version.javaDirProperty.setChangedListener { initJavaSubtitle(version) } + version.javaProperty.setChangedListener { initJavaSubtitle(version)} + initJavaSubtitle(version) + lastVersionSetting = version } + private fun initJavaSubtitle(version: VersionSetting) { + componentJava.subtitle = version.javaVersion?.binary?.absolutePath ?: "Invalid Java Directory" + } + fun onShowAdvanced() { if (!rootPane.children.contains(advancedSettingsPane)) rootPane.children += advancedSettingsPane @@ -134,6 +211,6 @@ class VersionSettingsController { chooser.title = i18n("settings.choose_javapath") val selectedDir = chooser.showDialog(Controllers.stage) if (selectedDir != null) - txtGameDir.text = selectedDir.absolutePath + txtJavaDir.text = selectedDir.absolutePath } } \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/construct/ComponentList.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/construct/ComponentList.kt index cadeabb64..3dcc44e7f 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/construct/ComponentList.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/construct/ComponentList.kt @@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui.construct import javafx.beans.DefaultProperty import javafx.beans.property.SimpleIntegerProperty import javafx.beans.property.SimpleObjectProperty +import javafx.beans.property.SimpleStringProperty import javafx.collections.FXCollections import javafx.collections.ListChangeListener import javafx.collections.ObservableList @@ -52,10 +53,12 @@ class ComponentList: StackPane() { } fun addChildren(node: Node) { - if (node is ComponentList) + if (node is ComponentList) { node.properties["title"] = node.title + node.properties["subtitle"] = node.subtitle + } vbox.children += StackPane().apply { - children += ComponentListCell(this@ComponentList, node) + children += ComponentListCell(node) if (vbox.children.isEmpty()) styleClass += "options-list-item-ahead" else { @@ -64,9 +67,14 @@ class ComponentList: StackPane() { } } - val titleProperty = SimpleObjectProperty(this, "title", "Group") + val titleProperty = SimpleStringProperty(this, "title", "Group") var title: String by titleProperty + val subtitleProperty = SimpleStringProperty(this, "subtitle", "") + var subtitle: String by subtitleProperty + + var hasSubtitle: Boolean = false + val depthProperty = SimpleIntegerProperty(this, "depth", 0) var depth: Int by depthProperty } \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/construct/ComponentListCell.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/construct/ComponentListCell.kt index 571041dd8..d51e4f90a 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/construct/ComponentListCell.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/construct/ComponentListCell.kt @@ -35,7 +35,7 @@ import org.jackhuang.hmcl.ui.SVG import org.jackhuang.hmcl.ui.limitHeight import org.jackhuang.hmcl.util.* -class ComponentListCell(private val superList: ComponentList, private val content: Node) : StackPane() { +class ComponentListCell(private val content: Node) : StackPane() { var expandAnimation: Animation? = null private var clipRect: Rectangle? = null @@ -67,9 +67,7 @@ class ComponentListCell(private val superList: ComponentList, private val conten } private fun updateLayout() { - val isSubList = content is ComponentList - - if (isSubList) { + if (content is ComponentList) { content.styleClass -= "options-list" content.styleClass += "options-sublist" @@ -82,12 +80,33 @@ class ComponentListCell(private val superList: ComponentList, private val conten expandButton.styleClass += "options-list-item-expand-button" StackPane.setAlignment(expandButton, Pos.CENTER_RIGHT) + val labelVBox = VBox() + Label().apply { + textProperty().bind(content.titleProperty) + isMouseTransparent = true + labelVBox.children += this + } + + if (content.hasSubtitle) + Label().apply { + textProperty().bind(content.subtitleProperty) + isMouseTransparent = true + styleClass += "subtitle-label" + labelVBox.children += this + } + + StackPane.setAlignment(labelVBox, Pos.CENTER_LEFT) groupNode.children.setAll( - Label(content.properties["title"]?.toString() ?: "Group").apply { isMouseTransparent = true; StackPane.setAlignment(this, Pos.CENTER_LEFT) }, + labelVBox, expandButton) val container = VBox().apply { + style += "-fx-padding: 8 0 0 0;" limitHeight(0.0) + val clipRect = Rectangle() + clipRect.widthProperty().bind(widthProperty()) + clipRect.heightProperty().bind(heightProperty()) + clip = clipRect children.setAll(content) } diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/construct/FileItem.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/construct/FileItem.kt index b47d3f7e7..5dcc15011 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/construct/FileItem.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/construct/FileItem.kt @@ -45,7 +45,7 @@ class FileItem : BorderPane() { init { left = VBox().apply { children += Label().apply { textProperty().bind(nameProperty) } - children += x.apply { style += "-fx-text-fill: gray;" } + children += x.apply { styleClass += "subtitle-label" } } right = JFXButton().apply { diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/DownloadWizardProvider.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/DownloadWizardProvider.kt index b0e7cc2c7..281fea703 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/DownloadWizardProvider.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/DownloadWizardProvider.kt @@ -18,9 +18,7 @@ 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.mod.CurseForgeModpackCompletionTask import org.jackhuang.hmcl.mod.CurseForgeModpackInstallTask import org.jackhuang.hmcl.mod.CurseForgeModpackManifest import org.jackhuang.hmcl.setting.EnumGameDirectory 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 35903da0b..f0dc95ffc 100644 --- a/HMCL/src/main/resources/assets/css/jfoenix-main-demo.css +++ b/HMCL/src/main/resources/assets/css/jfoenix-main-demo.css @@ -23,6 +23,10 @@ -fx-text-fill: rgba(0.0, 0.0, 0.0, 0.87); } +.subtitle-label { + -fx-text-fill: gray; +} + .radio-button-title-label { -fx-font-size: 16.0px; -fx-padding: 14.0 0.0 -20.0 0.0; @@ -579,7 +583,11 @@ .options-list { -fx-background-color: transparent; - -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2); + -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 5, 0.06, -0.5, 1); +} + +.card { + -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 5, 0.06, -0.5, 1); } .options-sublist { @@ -602,7 +610,7 @@ } .options-list-item-expand-button { - -fx-toggle-icon4-size: 15px; + -fx-toggle-icon4-size: 20px; -fx-pref-width: -fx-toggle-icon4-size; -fx-max-width: -fx-toggle-icon4-size; -fx-min-width: -fx-toggle-icon4-size; diff --git a/HMCL/src/main/resources/assets/fxml/version-settings.fxml b/HMCL/src/main/resources/assets/fxml/version-settings.fxml index bc4be9362..61d90f282 100644 --- a/HMCL/src/main/resources/assets/fxml/version-settings.fxml +++ b/HMCL/src/main/resources/assets/fxml/version-settings.fxml @@ -1,8 +1,5 @@ - - - @@ -12,9 +9,9 @@ - + @@ -24,19 +21,22 @@ - - - - - - - - + + + + + + + + + + + - + @@ -73,16 +73,16 @@ - + - + - + @@ -101,11 +101,14 @@ - - - - - + + + + + + + + diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/ForgeInstallTask.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/ForgeInstallTask.kt index c3df6f584..192cfc552 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/ForgeInstallTask.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/ForgeInstallTask.kt @@ -36,8 +36,9 @@ class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager, private val forgeVersionList = dependencyManager.getVersionList("forge") private val installer: File = File("forge-installer.jar").absoluteFile lateinit var remote: RemoteVersion<*> - override val dependents: MutableCollection = mutableListOf() - override val dependencies: MutableCollection = mutableListOf() + override val dependents = mutableListOf() + override val dependencies = mutableListOf() + override val id = ID init { if (!forgeVersionList.loaded) @@ -76,5 +77,7 @@ class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager, check(installer.delete(), { "Unable to delete installer file $installer" }) } - + companion object { + const val ID = "forge_install_task" + } } \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/GameDownloadTasks.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/GameDownloadTasks.kt index 52ec9367c..e8fe34365 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/GameDownloadTasks.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/GameDownloadTasks.kt @@ -37,7 +37,7 @@ import java.util.logging.Level * @param resolvedVersion the resolved version */ class GameLibrariesTask(private val dependencyManager: DefaultDependencyManager, private val resolvedVersion: Version): Task() { - override val dependencies: MutableCollection = LinkedList() + override val dependencies = LinkedList() override fun execute() { for (library in resolvedVersion.libraries) if (library.appliesToCurrentEnvironment) { @@ -50,7 +50,7 @@ class GameLibrariesTask(private val dependencyManager: DefaultDependencyManager, } class GameLoggingDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() { - override val dependencies: MutableCollection = LinkedList() + override val dependencies = LinkedList() override fun execute() { val logging = version.logging?.get(DownloadType.CLIENT) ?: return val file = dependencyManager.repository.getLoggingObject(version.id, version.actualAssetIndex.id, logging) @@ -60,7 +60,7 @@ class GameLoggingDownloadTask(private val dependencyManager: DefaultDependencyMa } class GameAssetIndexDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() { - override val dependencies: MutableCollection = LinkedList() + override val dependencies = LinkedList() override fun execute() { val assetIndexInfo = version.actualAssetIndex val assetDir = dependencyManager.repository.getAssetDirectory(version.id, assetIndexInfo.id) @@ -76,7 +76,8 @@ class GameAssetRefreshTask(private val dependencyManager: DefaultDependencyManag private val assetIndexTask = GameAssetIndexDownloadTask(dependencyManager, version) private val assetIndexInfo = version.actualAssetIndex private val assetIndexFile = dependencyManager.repository.getIndexFile(version.id, assetIndexInfo.id) - override val dependents: MutableCollection = LinkedList() + override val dependents = LinkedList() + override val id = ID init { if (!assetIndexFile.exists()) @@ -93,12 +94,16 @@ class GameAssetRefreshTask(private val dependencyManager: DefaultDependencyManag } result = res } + + companion object { + val ID = "game_asset_refresh_task" + } } class GameAssetDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() { private val refreshTask = GameAssetRefreshTask(dependencyManager, version) - override val dependents: Collection = listOf(refreshTask) - override val dependencies: MutableCollection = LinkedList() + override val dependents = listOf(refreshTask) + override val dependencies = LinkedList() override fun execute() { val size = refreshTask.result?.size ?: 0 refreshTask.result?.forEach single@{ (file, assetObject) -> diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/LiteLoaderInstallTask.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/LiteLoaderInstallTask.kt index f4b7b0287..7c1a18991 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/LiteLoaderInstallTask.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/LiteLoaderInstallTask.kt @@ -35,8 +35,9 @@ class LiteLoaderInstallTask(private val dependencyManager: DefaultDependencyMana private val remoteVersion: String): TaskResult() { private val liteLoaderVersionList = dependencyManager.getVersionList("liteloader") as LiteLoaderVersionList lateinit var remote: RemoteVersion - override val dependents: MutableCollection = mutableListOf() - override val dependencies: MutableCollection = mutableListOf() + override val dependents = mutableListOf() + override val dependencies = mutableListOf() + override val id = ID init { if (!liteLoaderVersionList.loaded) @@ -70,4 +71,7 @@ class LiteLoaderInstallTask(private val dependencyManager: DefaultDependencyMana dependencies += GameLibrariesTask(dependencyManager, tempVersion) } + companion object { + const val ID = "lite_loader_install_task" + } } \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/OptiFineInstallTask.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/OptiFineInstallTask.kt index ab43bd281..112aaa6de 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/OptiFineInstallTask.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/OptiFineInstallTask.kt @@ -32,8 +32,9 @@ class OptiFineInstallTask(private val dependencyManager: DefaultDependencyManage private val remoteVersion: String): TaskResult() { private val optiFineVersionList = dependencyManager.getVersionList("optifine") lateinit var remote: RemoteVersion<*> - override val dependents: MutableCollection = mutableListOf() - override val dependencies: MutableCollection = mutableListOf() + override val dependents = mutableListOf() + override val dependencies = mutableListOf() + override val id = ID init { if (!optiFineVersionList.loaded) @@ -45,6 +46,7 @@ class OptiFineInstallTask(private val dependencyManager: DefaultDependencyManage remote = optiFineVersionList.getVersion(gameVersion, remoteVersion) ?: throw IllegalArgumentException("Remote OptiFine version $gameVersion-$remoteVersion not found") } } + override fun execute() { val library = Library( groupId = "net.optifine", @@ -73,4 +75,8 @@ class OptiFineInstallTask(private val dependencyManager: DefaultDependencyManage result = version.copy(libraries = merge(version.libraries, libraries), mainClass = mainClass, minecraftArguments = arg) dependencies += GameLibrariesTask(dependencyManager, version.copy(libraries = libraries)) } + + companion object { + const val ID = "optifine_install_task" + } } \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/DefaultLauncher.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/DefaultLauncher.kt index 69369cbd9..6f66fe780 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/DefaultLauncher.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/DefaultLauncher.kt @@ -68,7 +68,7 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun if (OS.CURRENT_OS == OS.OSX) { res.add("-Xdock:name=Minecraft ${version.id}") - res.add("-Xdock:icon=" + repository.getAssetObject(version.id, version.actualAssetIndex.id, "icons/minecraft.icns").absolutePath); + res.add("-Xdock:icon=" + repository.getAssetObject(version.id, version.actualAssetIndex.id, "icons/minecraft.icns").absolutePath) } val logging = version.logging @@ -107,8 +107,8 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun if (options.minMemory != null && options.minMemory > 0) res.add("-Xms${options.minMemory}m") - res.add("-Dfml.ignoreInvalidMinecraftCertificates=true"); - res.add("-Dfml.ignorePatchDiscrepancies=true"); + res.add("-Dfml.ignoreInvalidMinecraftCertificates=true") + res.add("-Dfml.ignorePatchDiscrepancies=true") } // Classpath @@ -161,9 +161,9 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun // Optional Minecraft arguments if (options.height != null && options.width != null) { - res.add("--height"); + res.add("--height") res.add(options.height.toString()) - res.add("--width"); + res.add("--width") res.add(options.width.toString()) } @@ -180,16 +180,16 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun if (options.proxyHost != null && options.proxyHost.isNotBlank() && options.proxyPort != null && options.proxyPort.isNotBlank()) { - res.add("--proxyHost"); + res.add("--proxyHost") res.add(options.proxyHost) - res.add("--proxyPort"); + res.add("--proxyPort") res.add(options.proxyPort) if (options.proxyUser != null && options.proxyUser.isNotBlank() && options.proxyPass != null && options.proxyPass.isNotBlank()) { - res.add("--proxyUser"); - res.add(options.proxyUser); - res.add("--proxyPass"); - res.add(options.proxyPass); + res.add("--proxyUser") + res.add(options.proxyUser) + res.add("--proxyPass") + res.add(options.proxyPass) } } @@ -246,6 +246,7 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun fun launchAsync(): TaskResult { return object : TaskResult() { + override val id = LAUNCH_ASYNC_ID override fun execute() { result = launch() } @@ -259,11 +260,11 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun throw IOException("Script file: $scriptFile cannot be created.") scriptFile.bufferedWriter().use { writer -> if (isWindows) { - writer.write("@echo off"); + writer.write("@echo off") writer.newLine() - writer.write("set APPDATA=" + options.gameDir.parent); + writer.write("set APPDATA=" + options.gameDir.parent) writer.newLine() - writer.write("cd /D %APPDATA%"); + writer.write("cd /D %APPDATA%") writer.newLine() } if (options.precalledCommand != null && options.precalledCommand.isNotBlank()) { @@ -287,4 +288,8 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun thread(name = "stderr-pump", isDaemon = isDaemon, block = StreamPump(javaProcess.process.errorStream, processListener::onErrorLog)::run) thread(name = "exit-waiter", isDaemon = isDaemon, block = ExitWaiter(javaProcess.process, processListener::onExit)::run) } + + companion object { + const val LAUNCH_ASYNC_ID = "process" + } } \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/GetTask.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/GetTask.kt index fa9cf9d5d..71d9ff184 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/GetTask.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/GetTask.kt @@ -27,6 +27,7 @@ import java.nio.charset.Charset class GetTask @JvmOverloads constructor(val url: URL, val encoding: Charset = Charsets.UTF_8, private val retry: Int = 5, private val proxy: Proxy = Proxy.NO_PROXY): TaskResult() { override val scheduler: Scheduler = Scheduler.IO_THREAD + override val id = ID override fun execute() { var exception: IOException? = null @@ -63,4 +64,8 @@ class GetTask @JvmOverloads constructor(val url: URL, val encoding: Charset = Ch if (exception != null) throw exception } + + companion object { + const val ID = "http_get" + } } \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/SimpleTask.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/SimpleTask.kt index e17ed1743..b55a8db5c 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/SimpleTask.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/SimpleTask.kt @@ -17,8 +17,10 @@ */ package org.jackhuang.hmcl.task -internal class SimpleTask @JvmOverloads constructor(private val runnable: () -> Unit, override val scheduler: Scheduler = Scheduler.DEFAULT) : Task() { +import org.jackhuang.hmcl.util.AutoTypingMap + +internal class SimpleTask @JvmOverloads constructor(private val runnable: (AutoTypingMap) -> Unit, override val scheduler: Scheduler = Scheduler.DEFAULT) : Task() { override fun execute() { - runnable() + runnable(variables!!) } } \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/Task.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/Task.kt index 161a64be0..18671a7a7 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/Task.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/Task.kt @@ -49,6 +49,8 @@ abstract class Task { var title: String = this.javaClass.toString() + var variables: AutoTypingMap? = null + /** * @see Thread.isInterrupted * @throws InterruptedException if current thread is interrupted @@ -119,12 +121,13 @@ abstract class Task { submit(subscriber).start() } - fun subscribe(scheduler: Scheduler = Scheduler.DEFAULT, closure: () -> Unit) = subscribe(task(scheduler, closure)) + fun subscribe(scheduler: Scheduler = Scheduler.DEFAULT, closure: (AutoTypingMap) -> Unit) = subscribe(task(scheduler, closure)) override fun toString(): String { return title } } -fun task(scheduler: Scheduler = Scheduler.DEFAULT, closure: () -> Unit): Task = SimpleTask(closure, scheduler) -fun task(callable: Callable): TaskResult = TaskCallable(callable) \ No newline at end of file +fun task(scheduler: Scheduler = Scheduler.DEFAULT, closure: (AutoTypingMap) -> Unit): Task = SimpleTask(closure, scheduler) +fun taskResult(id: String, callable: Callable): TaskResult = TaskCallable(id, callable) +fun taskResult(id: String, callable: (AutoTypingMap) -> V): TaskResult = TaskCallable2(id, callable) \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskCallable.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskCallable.kt index e0b7bf4b2..ceada420e 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskCallable.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskCallable.kt @@ -17,10 +17,17 @@ */ package org.jackhuang.hmcl.task +import org.jackhuang.hmcl.util.AutoTypingMap import java.util.concurrent.Callable -internal class TaskCallable(private val callable: Callable) : TaskResult() { +internal class TaskCallable(override val id: String, private val callable: Callable) : TaskResult() { override fun execute() { result = callable.call() } +} + +internal class TaskCallable2(override val id: String, private val callable: (AutoTypingMap) -> V) : TaskResult() { + override fun execute() { + result = callable(variables!!) + } } \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskExecutor.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskExecutor.kt index 89a75c0cf..02d4b7769 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskExecutor.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskExecutor.kt @@ -17,6 +17,7 @@ */ package org.jackhuang.hmcl.task +import org.jackhuang.hmcl.util.AutoTypingMap import org.jackhuang.hmcl.util.LOG import java.util.concurrent.* import java.util.concurrent.atomic.AtomicBoolean @@ -30,6 +31,7 @@ class TaskExecutor() { var canceled = false private set val totTask = AtomicInteger(0) + val variables = AutoTypingMap(mutableMapOf()) private val taskQueue = ConcurrentLinkedQueue() private val workerQueue = ConcurrentLinkedQueue>() @@ -119,7 +121,10 @@ class TaskExecutor() { if (!doDependentsSucceeded && t.reliant || canceled) throw SilentException() + t.variables = variables t.execute() + if (t is TaskResult<*>) + variables[t.id] = t.result flag = true if (!t.hidden) LOG.finer("Task finished: ${t.title}") @@ -142,6 +147,8 @@ class TaskExecutor() { t.onDone(TaskEvent(source = this, task = t, failed = true)) taskListener?.onFailed(t) } + } finally { + t.variables = null } return flag } diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskResult.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskResult.kt index 6b77caeb5..c4ed957c4 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskResult.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/task/TaskResult.kt @@ -19,4 +19,9 @@ package org.jackhuang.hmcl.task abstract class TaskResult : Task() { open var result: V? = null + + /** + * The task id, will be stored as key of [variables] + */ + abstract val id: String } \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/AutoTypingMap.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/AutoTypingMap.kt new file mode 100644 index 000000000..04ae0d505 --- /dev/null +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/AutoTypingMap.kt @@ -0,0 +1,35 @@ +/* + * 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.util + +class AutoTypingMap(private val impl: MutableMap) { + + fun clear() = impl.clear() + + @Suppress("UNCHECKED_CAST") + operator fun get(key: K): V = impl[key] as V + operator fun set(key: K, value: Any?) { + if (value != null) + impl.set(key, value) + } + val values get() = impl.values + val keys get() = impl.keys + + fun containsKey(key: K) = impl.containsKey(key) + fun remove(key: K) = impl.remove(key) +} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/JavaVersion.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/JavaVersion.kt index d7f9141cb..8a899c35f 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/JavaVersion.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/JavaVersion.kt @@ -21,17 +21,22 @@ import com.google.gson.annotations.SerializedName import java.io.File import java.io.IOException import java.io.Serializable +import java.util.* import java.util.regex.Pattern data class JavaVersion internal constructor( @SerializedName("location") val binary: File, - val version: Int, + val longVersion: String, val platform: Platform) : Serializable { + val version = parseVersion(longVersion) + companion object { private val regex = Pattern.compile("java version \"(?[1-9]*\\.[1-9]*\\.[0-9]*(.*?))\"") + val JAVAS: Map + val UNKNOWN: Int = -1 val JAVA_5: Int = 50 val JAVA_6: Int = 60 @@ -54,12 +59,15 @@ data class JavaVersion internal constructor( @Throws(IOException::class) fun fromExecutable(file: File): JavaVersion { + var actualFile = file var platform = Platform.BIT_32 var version: String? = null + if (actualFile.nameWithoutExtension == "javaw") // javaw will not output version information + actualFile = actualFile.absoluteFile.parentFile.resolve("java") try { - val process = ProcessBuilder(file.absolutePath, "-version").start() + val process = ProcessBuilder(actualFile.absolutePath, "-version").start() process.waitFor() - process.inputStream.bufferedReader().forEachLine { line -> + process.errorStream.bufferedReader().forEachLine { line -> val m = regex.matcher(line) if (m.find()) version = m.group("version") @@ -74,10 +82,26 @@ data class JavaVersion internal constructor( val parsedVersion = parseVersion(thisVersion) if (parsedVersion == UNKNOWN) throw IOException("Java version '$thisVersion' can not be recognized") - return JavaVersion(file.parentFile, parsedVersion, platform) + return JavaVersion(file.parentFile, thisVersion, platform) } - fun getJavaFile(home: File): File { + private fun fromExecutable(file: File, version: String) = + JavaVersion ( + binary = file, + longVersion = version, + platform = Platform.UNKNOWN + ) + + @Throws(IOException::class) + fun fromJavaHome(home: File): JavaVersion { + return fromExecutable(getJavaFile(home)) + } + + private fun fromJavaHome(home: File, version: String): JavaVersion { + return fromExecutable(getJavaFile(home), version) + } + + private fun getJavaFile(home: File): File { val path = home.resolve("bin") val javaw = path.resolve("javaw.exe") if (OS.CURRENT_OS === OS.WINDOWS && javaw.isFile) @@ -86,11 +110,80 @@ data class JavaVersion internal constructor( return path.resolve("java") } - fun fromCurrentEnvironment(): JavaVersion { - return JavaVersion( - binary = getJavaFile(File(System.getProperty("java.home"))), - version = parseVersion(System.getProperty("java.version")), - platform = Platform.PLATFORM) + private val currentJava: JavaVersion = JavaVersion( + binary = getJavaFile(File(System.getProperty("java.home"))), + longVersion = System.getProperty("java.version"), + platform = Platform.PLATFORM) + fun fromCurrentEnvironment() = currentJava + + init { + val temp = mutableMapOf() + (when (OS.CURRENT_OS) { + OS.WINDOWS -> queryWindows() + OS.OSX -> queryMacintosh() + else -> emptyList() /* Cannot detect Java in linux. */ + }).forEach { javaVersion -> + temp.put(javaVersion.longVersion, javaVersion) + } + JAVAS = temp + } + + private fun queryMacintosh() = LinkedList().apply { + val currentJRE = File("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home") + if (currentJRE.exists()) + this += fromJavaHome(currentJRE) + File("/Library/Java/JavaVirtualMachines/").listFiles()?.forEach { file -> + this += fromJavaHome(file.resolve("Contents/Home")) + } + } + + private fun queryWindows() = LinkedList().apply { + ignoreException { this += queryRegisterKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Runtime Environment\\") } + ignoreException { this += queryRegisterKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\") } + } + + private fun queryRegisterKey(location: String) = LinkedList().apply { + querySubFolders(location).forEach { java -> + val s = java.count { it == '.' } + if (s > 1) { + val home = queryRegisterValue(java, "JavaHome") + if (home != null) + this += fromJavaHome(File(home)) + } + } + } + + private fun querySubFolders(location: String) = LinkedList().apply { + val cmd = arrayOf("cmd", "/c", "reg", "query", location) + val process = Runtime.getRuntime().exec(cmd) + process.waitFor() + process.inputStream.bufferedReader().readLines().forEach { s -> + if (s.startsWith(location) && s != location) + this += s + } + } + + private fun queryRegisterValue(location: String, name: String): String? { + val cmd = arrayOf("cmd", "/c", "reg", "query", location, "/v", name) + var last = false + val process = Runtime.getRuntime().exec(cmd) + process.waitFor() + process.inputStream.bufferedReader().readLines().forEach { s -> + if (s.isNotBlank()) { + if (last && s.trim().startsWith(name)) { + var begins = s.indexOf(name) + if (begins > 0) { + val s2 = s.substring(begins + name.length) + begins = s2.indexOf("REG_SZ") + if (begins > 0) + return s2.substring(begins + "REG_SZ".length).trim() + } + } + if (s.trim() == location) + last = true + } + } + return null } } } \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/property/ImmediateStringProperty.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/property/ImmediateStringProperty.kt index 547aeaa74..984884828 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/property/ImmediateStringProperty.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/property/ImmediateStringProperty.kt @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.util.property import javafx.beans.property.* +import javafx.beans.value.ChangeListener import javafx.beans.value.ObservableValue open class ImmediateStringProperty(bean: Any, name: String, initialValue: String): SimpleStringProperty(bean, name, initialValue) { @@ -27,10 +28,6 @@ open class ImmediateStringProperty(bean: Any, name: String, initialValue: String super.set(newValue) } - protected fun superSet(newValue: String) { - super.set(newValue) - } - override fun bind(newObservable: ObservableValue) { super.get() super.bind(newObservable) @@ -41,30 +38,17 @@ open class ImmediateStringProperty(bean: Any, name: String, initialValue: String super.unbind() } - public override fun fireValueChangedEvent() { - super.fireValueChangedEvent() - } -} - -open class ImmediateNullableStringProperty(bean: Any, name: String, initialValue: String?): SimpleStringProperty(bean, name, initialValue) { - - override fun set(newValue: String?) { - super.get() - super.set(newValue) + private var myListener: (String) -> Unit = {} + private val changeListener = ChangeListener { _, _, newValue -> + myListener(newValue) } - protected fun superSet(newValue: String?) { - super.set(newValue) + fun setChangedListener(listener: (String) -> Unit) { + myListener = listener } - override fun bind(newObservable: ObservableValue) { - super.get() - super.bind(newObservable) - } - - override fun unbind() { - super.get() - super.unbind() + init { + addListener(changeListener) } public override fun fireValueChangedEvent() { @@ -79,10 +63,6 @@ open class ImmediateBooleanProperty(bean: Any, name: String, initialValue: Boole super.set(newValue) } - protected fun superSet(newValue: Boolean) { - super.set(newValue) - } - override fun bind(rawObservable: ObservableValue?) { super.get() super.bind(rawObservable) @@ -93,6 +73,19 @@ open class ImmediateBooleanProperty(bean: Any, name: String, initialValue: Boole super.unbind() } + private var myListener: (Boolean) -> Unit = {} + private val changeListener = ChangeListener { _, _, newValue -> + myListener(newValue) + } + + fun setChangedListener(listener: (Boolean) -> Unit) { + myListener = listener + } + + init { + addListener(changeListener) + } + public override fun fireValueChangedEvent() { super.fireValueChangedEvent() } @@ -105,10 +98,6 @@ open class ImmediateIntegerProperty(bean: Any, name: String, initialValue: Int): super.set(newValue) } - protected fun superSet(newValue: Int) { - super.set(newValue) - } - override fun bind(rawObservable: ObservableValue) { super.get() super.bind(rawObservable) @@ -119,6 +108,19 @@ open class ImmediateIntegerProperty(bean: Any, name: String, initialValue: Int): super.unbind() } + private var myListener: (Int) -> Unit = {} + private val changeListener = ChangeListener { _, _, newValue -> + myListener(newValue.toInt()) + } + + fun setChangedListener(listener: (Int) -> Unit) { + myListener = listener + } + + init { + addListener(changeListener) + } + public override fun fireValueChangedEvent() { super.fireValueChangedEvent() } @@ -131,10 +133,6 @@ open class ImmediateDoubleProperty(bean: Any, name: String, initialValue: Double super.set(newValue) } - protected fun superSet(newValue: Double) { - super.set(newValue) - } - override fun bind(rawObservable: ObservableValue) { super.get() super.bind(rawObservable) @@ -145,6 +143,19 @@ open class ImmediateDoubleProperty(bean: Any, name: String, initialValue: Double super.unbind() } + private var myListener: (Double) -> Unit = {} + private val changeListener = ChangeListener { _, _, newValue -> + myListener(newValue.toDouble()) + } + + fun setChangedListener(listener: (Double) -> Unit) { + myListener = listener + } + + init { + addListener(changeListener) + } + public override fun fireValueChangedEvent() { super.fireValueChangedEvent() } @@ -167,6 +178,19 @@ open class ImmediateObjectProperty(bean: Any, name: String, initialValue: T): super.unbind() } + private var myListener: (T) -> Unit = {} + private val changeListener = ChangeListener { _, _, newValue -> + myListener(newValue) + } + + fun setChangedListener(listener: (T) -> Unit) { + myListener = listener + } + + init { + addListener(changeListener) + } + public override fun fireValueChangedEvent() { super.fireValueChangedEvent() }