diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLGameRepository.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLGameRepository.kt index 586cdb8b8..a4e963ea0 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLGameRepository.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLGameRepository.kt @@ -87,6 +87,14 @@ class HMCLGameRepository(val profile: Profile, baseDirectory: File) } + fun changeDirectory(newDir: File) { + baseDirectory = newDir + + + + refreshVersions() + } + private fun checkModpack() {} private fun getVersionSettingFile(id: String) = getVersionRoot(id).resolve("hmclversion.cfg") diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/LauncherHelper.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/LauncherHelper.kt index d3a3386fb..bcc51226c 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/LauncherHelper.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/LauncherHelper.kt @@ -41,11 +41,12 @@ object LauncherHelper { fun launch() { val profile = Settings.selectedProfile + val selectedVersion = profile.selectedVersion ?: throw IllegalStateException("No version here") val repository = profile.repository val dependency = profile.dependency val account = Settings.selectedAccount ?: throw IllegalStateException("No account here") - val version = repository.getVersion(profile.selectedVersion) - val setting = profile.getVersionSetting(profile.selectedVersion) + val version = repository.getVersion(selectedVersion) + val setting = profile.getVersionSetting(selectedVersion) var finished = 0 Controllers.dialog(launchingStepsPane) @@ -53,7 +54,7 @@ object LauncherHelper { .then(dependency.checkGameCompletionAsync(version)) .then(task(Scheduler.JAVAFX) { emitStatus(LoadingState.MODS) }) - .then(CurseForgeModpackCompletionTask(dependency, profile.selectedVersion)) + .then(CurseForgeModpackCompletionTask(dependency, selectedVersion)) .then(task(Scheduler.JAVAFX) { emitStatus(LoadingState.LOGIN) }) .then(task { @@ -69,7 +70,7 @@ object LauncherHelper { .then(task { it["launcher"] = HMCLGameLauncher( repository = repository, - versionId = profile.selectedVersion, + versionId = selectedVersion, options = setting.toLaunchOptions(profile.gameDir), listener = HMCLProcessListener(it["account"], setting), account = it["account"] diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Profile.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Profile.kt index 1c3e395eb..76151aaf5 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Profile.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Profile.kt @@ -20,21 +20,24 @@ package org.jackhuang.hmcl.setting import com.google.gson.* import javafx.beans.InvalidationListener import org.jackhuang.hmcl.download.DefaultDependencyManager +import org.jackhuang.hmcl.event.EVENT_BUS +import org.jackhuang.hmcl.event.RefreshedVersionsEvent import org.jackhuang.hmcl.game.HMCLGameRepository import org.jackhuang.hmcl.mod.ModManager -import org.jackhuang.hmcl.util.ImmediateObjectProperty -import org.jackhuang.hmcl.util.ImmediateStringProperty -import org.jackhuang.hmcl.util.getValue -import org.jackhuang.hmcl.util.setValue +import org.jackhuang.hmcl.ui.runOnUiThread +import org.jackhuang.hmcl.util.* import java.io.File import java.lang.reflect.Type -class Profile(var name: String = "Default", initialGameDir: File = File(".minecraft"), initialSelectedVersion: String = "") { +class Profile(name: String = "Default", initialGameDir: File = File(".minecraft"), initialSelectedVersion: String = "") { + val nameProperty = ImmediateStringProperty(this, "name", name) + var name: String by nameProperty + val globalProperty = ImmediateObjectProperty(this, "global", VersionSetting()) var global: VersionSetting by globalProperty - val selectedVersionProperty = ImmediateStringProperty(this, "selectedVersion", initialSelectedVersion) - var selectedVersion: String by selectedVersionProperty + val selectedVersionProperty = ImmediateObjectProperty(this, "selectedVersion", initialSelectedVersion) + var selectedVersion: String? by selectedVersionProperty val gameDirProperty = ImmediateObjectProperty(this, "gameDir", initialGameDir) var gameDir: File by gameDirProperty @@ -44,17 +47,21 @@ class Profile(var name: String = "Default", initialGameDir: File = File(".minecr var modManager = ModManager(repository) init { - gameDirProperty.addListener { _ -> - repository.baseDirectory = gameDir + gameDirProperty.onChange { newGameDir -> + repository.baseDirectory = newGameDir!! repository.refreshVersions() } - selectedVersionProperty.addListener { _ -> - if (selectedVersion.isNotBlank() && !repository.hasVersion(selectedVersion)) { - val newVersion = repository.getVersions().firstOrNull() - // will cause anthor change event, we must ensure that there will not be dead recursion. - selectedVersion = newVersion?.id ?: "" - } + selectedVersionProperty.addListener { _ -> verifySelectedVersion() } + EVENT_BUS.channel() += { event -> if (event.source == repository) verifySelectedVersion() } + } + + private fun verifySelectedVersion() = runOnUiThread { + // To prevent not loaded profile's selectedVersion being changed. + if (repository.isLoaded && ((selectedVersion == null && repository.getVersions().isNotEmpty()) || (selectedVersion != null && !repository.hasVersion(selectedVersion!!)))) { + val newVersion = repository.getVersions().firstOrNull() + // will cause anthor change event, we must ensure that there will not be dead recursion. + selectedVersion = newVersion?.id } } @@ -86,10 +93,10 @@ class Profile(var name: String = "Default", initialGameDir: File = File(".minecr return vs } - fun getSelectedVersionSetting(): VersionSetting = - getVersionSetting(selectedVersion) + fun getSelectedVersionSetting(): VersionSetting? = if (selectedVersion == null) null else getVersionSetting(selectedVersion!!) fun addPropertyChangedListener(listener: InvalidationListener) { + nameProperty.addListener(listener) globalProperty.addListener(listener) selectedVersionProperty.addListener(listener) gameDirProperty.addListener(listener) diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Settings.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Settings.kt index 2fe05239f..c9ac41b27 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Settings.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Settings.kt @@ -28,6 +28,7 @@ import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider import org.jackhuang.hmcl.download.DownloadProvider import org.jackhuang.hmcl.download.MojangDownloadProvider import org.jackhuang.hmcl.event.EVENT_BUS +import org.jackhuang.hmcl.task.Scheduler import org.jackhuang.hmcl.util.* import java.io.File import java.io.IOException @@ -270,12 +271,20 @@ object Settings { * PROFILES * ****************************************/ - val selectedProfile: Profile + var selectedProfile: Profile get() { - if (!hasProfile(SETTINGS.selectedProfile)) + if (!hasProfile(SETTINGS.selectedProfile)) { SETTINGS.selectedProfile = DEFAULT_PROFILE + Scheduler.COMPUTATION.schedule { onProfileChanged() } + } return getProfile(SETTINGS.selectedProfile) } + set(value) { + if (hasProfile(value.name) && value.name != SETTINGS.selectedProfile) { + SETTINGS.selectedProfile = value.name + Scheduler.COMPUTATION.schedule { onProfileChanged() } + } + } fun getProfile(name: String?): Profile { var p: Profile? = getProfileMap()[name ?: DEFAULT_PROFILE] @@ -316,12 +325,10 @@ object Settings { if (DEFAULT_PROFILE == ver) { return false } - var notify = false - if (selectedProfile.name == ver) - notify = true val flag = getProfileMap().remove(ver) != null - if (notify && flag) - onProfileChanged() + if (flag) + Scheduler.COMPUTATION.schedule { onProfileLoading() } + return flag } diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountItem.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountItem.kt index 27ae0dd44..af2bbb7da 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountItem.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountItem.kt @@ -72,7 +72,7 @@ class AccountItem(i: Int, val account: Account, group: ToggleGroup) : StackPane( header.style = "-fx-background-radius: 2 2 0 0; -fx-background-color: " + headerColor // create image view - icon.translateYProperty().bind(Bindings.createDoubleBinding(Callable { header.boundsInParent.height - icon.height / 2 }, header.boundsInParentProperty(), icon.heightProperty())) + icon.translateYProperty().bind(Bindings.createDoubleBinding(Callable { header.boundsInParent.height - icon.height / 2 - 32.0 }, header.boundsInParentProperty(), icon.heightProperty())) chkSelected.properties["account"] = account chkSelected.isSelected = Settings.selectedAccount == account diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Controllers.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Controllers.kt index 3061c5694..002b4bee2 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Controllers.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/Controllers.kt @@ -24,6 +24,7 @@ import javafx.scene.image.Image import javafx.scene.layout.Region import javafx.stage.Stage import org.jackhuang.hmcl.Main +import org.jackhuang.hmcl.setting.Settings object Controllers { lateinit var scene: Scene private set @@ -44,6 +45,8 @@ object Controllers { decorator.showPage(null) leftPaneController = LeftPaneController(decorator.leftPane) + Settings.onProfileLoading() + decorator.isCustomMaximize = false scene = Scene(decorator, 800.0, 480.0) diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LeftPaneController.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LeftPaneController.kt index dab3e2e53..694fb38cd 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LeftPaneController.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LeftPaneController.kt @@ -17,54 +17,51 @@ */ package org.jackhuang.hmcl.ui -import com.jfoenix.controls.JFXComboBox import javafx.scene.layout.VBox +import javafx.scene.paint.Paint +import org.jackhuang.hmcl.ProfileChangedEvent +import org.jackhuang.hmcl.ProfileLoadingEvent import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount +import org.jackhuang.hmcl.event.EVENT_BUS import org.jackhuang.hmcl.game.AccountHelper import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.ui.construct.IconedItem import org.jackhuang.hmcl.ui.construct.RipplerContainer import org.jackhuang.hmcl.util.onChangeAndOperate +import java.util.* class LeftPaneController(private val leftPane: AdvancedListBox) { - val versionsPane = VBox() - val cboProfiles = JFXComboBox().apply { items.add("Default"); prefWidthProperty().bind(leftPane.widthProperty()) } + val profilePane = VBox() val accountItem = VersionListItem("No Account", "unknown") init { leftPane .startCategory("ACCOUNTS") .add(RipplerContainer(accountItem).apply { + setOnMouseClicked { + Controllers.navigate(AccountsPage()) + } accountItem.onSettingsButtonClicked { Controllers.navigate(AccountsPage()) } }) - .startCategory(i18n("ui.label.profile")) - .add(cboProfiles) .startCategory("LAUNCHER") - .add(IconedItem(SVG.gear("black"), "Settings").apply { + .add(IconedItem(SVG.gear("black"), i18n("launcher.title.launcher")).apply { prefWidthProperty().bind(leftPane.widthProperty()) setOnMouseClicked { Controllers.navigate(Controllers.settingsPane) } }) -/* .startCategory(i18n("ui.label.version")) - .add(versionsPane) + .startCategory(i18n("ui.label.profile")) + .add(profilePane) - 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.showPage(ProfilePage(null)) } - Controllers.decorator.refreshMenuButton.setOnMouseClicked { - Settings.selectedProfile.repository.refreshVersions() - } - Controllers.mainPane.buttonLaunch.setOnMouseClicked { LauncherHelper.launch() }*/ Settings.selectedAccountProperty.onChangeAndOperate { if (it == null) { @@ -86,46 +83,36 @@ class LeftPaneController(private val leftPane: AdvancedListBox) { if (Settings.getAccounts().isEmpty()) Controllers.navigate(AccountsPage()) } -/* - fun onProfilesLoading() { - // TODO: Profiles - } fun onProfileChanged(event: ProfileChangedEvent) { val profile = event.value - profile.selectedVersionProperty.addListener { _ -> - versionChanged(profile.selectedVersion) - } - profile.selectedVersionProperty.fireValueChangedEvent() + + profilePane.children + .filter { it is RipplerContainer && it.properties["profile"] is Pair<*, *> } + .forEach { (it as RipplerContainer).selected = (it.properties["profile"] as Pair).first == profile.name } } - 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") + fun onProfilesLoading() { + val list = LinkedList() + Settings.getProfiles().forEach { profile -> + val item = VersionListItem(profile.name).apply { + lblGameVersion.textProperty().bind(profile.selectedVersionProperty) + } val ripplerContainer = RipplerContainer(item) item.onSettingsButtonClicked { - Controllers.decorator.showPage(Controllers.versionPane) - Controllers.versionPane.load(item.versionName, profile) + Controllers.decorator.showPage(ProfilePage(profile)) } ripplerContainer.ripplerFill = Paint.valueOf("#89E1F9") ripplerContainer.setOnMouseClicked { // clean selected property - versionsPane.children.forEach { if (it is RipplerContainer) it.selected = false } + profilePane.children.forEach { if (it is RipplerContainer) it.selected = false } ripplerContainer.selected = true - profile.selectedVersion = version.id + Settings.selectedProfile = profile } - ripplerContainer.properties["version"] = version.id to item + ripplerContainer.properties["profile"] = profile.name to item ripplerContainer.maxWidthProperty().bind(leftPane.widthProperty()) - versionsPane.children += ripplerContainer + list += ripplerContainer } + runOnUiThread { profilePane.children.setAll(list) } } - - @Suppress("UNCHECKED_CAST") - fun versionChanged(selectedVersion: String) { - versionsPane.children - .filter { it is RipplerContainer && it.properties["version"] is Pair<*, *> } - .forEach { (it as RipplerContainer).selected = (it.properties["version"] as Pair).first == selectedVersion } - }*/ } \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MainPage.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MainPage.kt index 9b225d2e0..38c0a6886 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MainPage.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MainPage.kt @@ -45,7 +45,7 @@ import org.jackhuang.hmcl.util.onChange * @see /assets/fxml/main.fxml */ class MainPage : StackPane(), DecoratorPage { - override val titleProperty: StringProperty = SimpleStringProperty(this, "title", i18n("launcher.title.main")) + override val titleProperty = SimpleStringProperty(this, "title", i18n("launcher.title.main")) @FXML lateinit var btnLaunch: JFXButton @FXML lateinit var btnRefresh: JFXButton @@ -59,15 +59,20 @@ class MainPage : StackPane(), DecoratorPage { btnLaunch.limitWidth(40.0) btnLaunch.limitHeight(40.0) - EVENT_BUS.channel() += this::loadVersions + EVENT_BUS.channel() += { -> loadVersions() } EVENT_BUS.channel() += this::onProfilesLoading EVENT_BUS.channel() += this::onProfileChanged - Settings.onProfileLoading() - btnAdd.setOnMouseClicked { Controllers.decorator.startWizard(DownloadWizardProvider(), "Install New Game") } btnRefresh.setOnMouseClicked { Settings.selectedProfile.repository.refreshVersions() } - btnLaunch.setOnMouseClicked { LauncherHelper.launch() } + btnLaunch.setOnMouseClicked { + if (Settings.selectedAccount == null) { + Controllers.dialog(i18n("login.no_Player007")) + } else if (Settings.selectedProfile.selectedVersion == null) { + Controllers.dialog(i18n("minecraft.no_selected_version")) + } else + LauncherHelper.launch() + } } private fun buildNode(i: Int, profile: Profile, version: String, game: String, group: ToggleGroup): Node { @@ -78,7 +83,7 @@ class MainPage : StackPane(), DecoratorPage { lblVersionName.text = version btnDelete.setOnMouseClicked { profile.repository.removeVersionFromDisk(version) - Platform.runLater(this@MainPage::loadVersions) + Platform.runLater { loadVersions() } } btnSettings.setOnMouseClicked { Controllers.decorator.showPage(Controllers.versionPane) @@ -94,15 +99,15 @@ class MainPage : StackPane(), DecoratorPage { // TODO: Profiles } - fun onProfileChanged(event: ProfileChangedEvent) { + fun onProfileChanged(event: ProfileChangedEvent) = runOnUiThread { val profile = event.value - profile.selectedVersionProperty.setChangedListener { + profile.selectedVersionProperty.setChangedListener { t -> versionChanged(profile.selectedVersion) } + loadVersions(profile) } - private fun loadVersions() { - val profile = Settings.selectedProfile + private fun loadVersions(profile: Profile = Settings.selectedProfile) { val group = ToggleGroup() val children = mutableListOf() var i = 0 @@ -117,7 +122,7 @@ class MainPage : StackPane(), DecoratorPage { } @Suppress("UNCHECKED_CAST") - fun versionChanged(selectedVersion: String) { + fun versionChanged(selectedVersion: String?) { masonryPane.children .filter { it is RipplerContainer && it.properties["version"] is Pair<*, *> } .forEach { (it as RipplerContainer).selected = (it.properties["version"] as Pair).first == selectedVersion } diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ProfilePage.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ProfilePage.kt new file mode 100644 index 000000000..80dbbf959 --- /dev/null +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/ProfilePage.kt @@ -0,0 +1,84 @@ +/* + * 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.JFXTextField +import javafx.beans.property.SimpleStringProperty +import javafx.fxml.FXML +import javafx.scene.layout.StackPane +import org.jackhuang.hmcl.i18n +import org.jackhuang.hmcl.setting.Profile +import org.jackhuang.hmcl.setting.Settings +import org.jackhuang.hmcl.ui.construct.FileItem +import org.jackhuang.hmcl.ui.wizard.DecoratorPage +import org.jackhuang.hmcl.util.onChangeAndOperate +import java.io.File + +/** + * @param profile null if creating a new profile. + */ +class ProfilePage(private val profile: Profile?): StackPane(), DecoratorPage { + override val titleProperty = SimpleStringProperty(this, "title", + if (profile == null) i18n("ui.newProfileWindow.title") else i18n("ui.label.profile") + " - " + profile.name) + private val locationProperty = SimpleStringProperty(this, "location", + profile?.gameDir?.absolutePath ?: "") + @FXML lateinit var txtProfileName: JFXTextField + @FXML lateinit var gameDir: FileItem + @FXML lateinit var btnSave: JFXButton + @FXML lateinit var btnDelete: JFXButton + + init { + loadFXML("/assets/fxml/profile.fxml") + + txtProfileName.text = profile?.name ?: "" + txtProfileName.textProperty().onChangeAndOperate { + btnSave.isDisable = !txtProfileName.validate() || locationProperty.get().isNullOrBlank() + } + gameDir.setProperty(locationProperty) + locationProperty.onChangeAndOperate { + btnSave.isDisable = !txtProfileName.validate() || locationProperty.get().isNullOrBlank() + } + + if (profile == null) + btnDelete.isVisible = false + } + + fun onDelete() { + if (profile != null) { + Settings.deleteProfile(profile) + Controllers.navigate(null) + } + } + + fun onSave() { + if (profile != null) { // editing a profile + profile.name = txtProfileName.text + if (locationProperty.get() != null) + profile.gameDir = File(locationProperty.get()) + } else { + if (locationProperty.get().isNullOrBlank()) { + gameDir.onExplore() + } + Settings.putProfile(Profile(name = txtProfileName.text, initialGameDir = File(locationProperty.get()))) + } + + Settings.onProfileLoading() + Controllers.navigate(null) + } +} \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/VersionItem.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/VersionItem.kt index bf01c6e0b..beaeaa805 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/VersionItem.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/VersionItem.kt @@ -61,7 +61,7 @@ class VersionItem(i: Int, group: ToggleGroup) : StackPane() { header.style = "-fx-background-radius: 2 2 0 0; -fx-background-color: " + headerColor // create image view - icon.translateYProperty().bind(Bindings.createDoubleBinding(Callable { header.boundsInParent.height - icon.height }, header.boundsInParentProperty(), icon.heightProperty())) + icon.translateYProperty().bind(Bindings.createDoubleBinding(Callable { header.boundsInParent.height - icon.height / 2 - 32.0 }, header.boundsInParentProperty(), icon.heightProperty())) iconView.limitSize(32.0, 32.0) } diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/VersionListItem.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/VersionListItem.kt index f59f48138..280316c28 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/VersionListItem.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/VersionListItem.kt @@ -23,7 +23,7 @@ import javafx.scene.control.Label import javafx.scene.image.ImageView import javafx.scene.layout.StackPane -class VersionListItem(versionName: String, gameVersion: String) : StackPane() { +class VersionListItem(versionName: String, gameVersion: String = "") : StackPane() { @FXML lateinit var lblVersionName: Label @FXML lateinit var lblGameVersion: Label 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 682af6baa..d154b78b8 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 @@ -52,14 +52,7 @@ class FileItem : BorderPane() { right = JFXButton().apply { graphic = SVG.pencil("black", 15.0, 15.0) styleClass += "toggle-icon4" - setOnMouseClicked { - val chooser = DirectoryChooser() - chooser.titleProperty().bind(titleProperty) - val selectedDir = chooser.showDialog(Controllers.stage) - if (selectedDir != null) - property.value = selectedDir.absolutePath - chooser.titleProperty().unbind() - } + setOnMouseClicked { onExplore() } } Tooltip.install(this, Tooltip().apply { @@ -67,6 +60,15 @@ class FileItem : BorderPane() { }) } + fun onExplore() { + val chooser = DirectoryChooser() + chooser.titleProperty().bind(titleProperty) + val selectedDir = chooser.showDialog(Controllers.stage) + if (selectedDir != null) + property.value = selectedDir.absolutePath + chooser.titleProperty().unbind() + } + fun setProperty(property: Property) { this.property = property x.textProperty().bind(property) diff --git a/HMCL/src/main/resources/assets/fxml/decorator.fxml b/HMCL/src/main/resources/assets/fxml/decorator.fxml index f3cec60b5..a179df0cb 100644 --- a/HMCL/src/main/resources/assets/fxml/decorator.fxml +++ b/HMCL/src/main/resources/assets/fxml/decorator.fxml @@ -27,6 +27,17 @@
+ + + + + + + + + + + diff --git a/HMCL/src/main/resources/assets/fxml/profile.fxml b/HMCL/src/main/resources/assets/fxml/profile.fxml new file mode 100644 index 000000000..a65b02a83 --- /dev/null +++ b/HMCL/src/main/resources/assets/fxml/profile.fxml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 6a0098478..8d442570c 100644 --- a/HMCL/src/main/resources/assets/fxml/version-list-item.fxml +++ b/HMCL/src/main/resources/assets/fxml/version-list-item.fxml @@ -16,7 +16,7 @@ - + diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/DefaultGameRepository.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/DefaultGameRepository.kt index cb8e3fa1e..dc793ebf2 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/DefaultGameRepository.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/DefaultGameRepository.kt @@ -36,6 +36,8 @@ import java.util.logging.Level */ open class DefaultGameRepository(var baseDirectory: File): GameRepository { protected val versions: MutableMap = TreeMap() + var isLoaded: Boolean = false + protected set override fun hasVersion(id: String) = versions.containsKey(id) override fun getVersion(id: String): Version { @@ -135,7 +137,7 @@ open class DefaultGameRepository(var baseDirectory: File): GameRepository { versions[id] = version EVENT_BUS.fireEvent(LoadedOneVersionEvent(this, id)) } - + isLoaded = true } @Synchronized diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ImmediateProperties.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ImmediateProperties.kt index 7d126a681..ebd32c914 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ImmediateProperties.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ImmediateProperties.kt @@ -21,6 +21,9 @@ import javafx.beans.property.* import javafx.beans.value.ChangeListener import javafx.beans.value.ObservableValue +/** + * Any operation of properties should run on JavaFX thread. + */ open class ImmediateStringProperty(bean: Any, name: String, initialValue: String): SimpleStringProperty(bean, name, initialValue) { override fun set(newValue: String) {