Profiles editing and selecting

This commit is contained in:
huangyuhui 2017-08-25 20:18:27 +08:00
parent 3b4359f7eb
commit ff1a566bf4
17 changed files with 263 additions and 94 deletions

View File

@ -87,6 +87,14 @@ class HMCLGameRepository(val profile: Profile, baseDirectory: File)
} }
fun changeDirectory(newDir: File) {
baseDirectory = newDir
refreshVersions()
}
private fun checkModpack() {} private fun checkModpack() {}
private fun getVersionSettingFile(id: String) = getVersionRoot(id).resolve("hmclversion.cfg") private fun getVersionSettingFile(id: String) = getVersionRoot(id).resolve("hmclversion.cfg")

View File

@ -41,11 +41,12 @@ object LauncherHelper {
fun launch() { fun launch() {
val profile = Settings.selectedProfile val profile = Settings.selectedProfile
val selectedVersion = profile.selectedVersion ?: throw IllegalStateException("No version here")
val repository = profile.repository val repository = profile.repository
val dependency = profile.dependency val dependency = profile.dependency
val account = Settings.selectedAccount ?: throw IllegalStateException("No account here") val account = Settings.selectedAccount ?: throw IllegalStateException("No account here")
val version = repository.getVersion(profile.selectedVersion) val version = repository.getVersion(selectedVersion)
val setting = profile.getVersionSetting(profile.selectedVersion) val setting = profile.getVersionSetting(selectedVersion)
var finished = 0 var finished = 0
Controllers.dialog(launchingStepsPane) Controllers.dialog(launchingStepsPane)
@ -53,7 +54,7 @@ object LauncherHelper {
.then(dependency.checkGameCompletionAsync(version)) .then(dependency.checkGameCompletionAsync(version))
.then(task(Scheduler.JAVAFX) { emitStatus(LoadingState.MODS) }) .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(Scheduler.JAVAFX) { emitStatus(LoadingState.LOGIN) })
.then(task { .then(task {
@ -69,7 +70,7 @@ object LauncherHelper {
.then(task { .then(task {
it["launcher"] = HMCLGameLauncher( it["launcher"] = HMCLGameLauncher(
repository = repository, repository = repository,
versionId = profile.selectedVersion, versionId = selectedVersion,
options = setting.toLaunchOptions(profile.gameDir), options = setting.toLaunchOptions(profile.gameDir),
listener = HMCLProcessListener(it["account"], setting), listener = HMCLProcessListener(it["account"], setting),
account = it["account"] account = it["account"]

View File

@ -20,21 +20,24 @@ package org.jackhuang.hmcl.setting
import com.google.gson.* import com.google.gson.*
import javafx.beans.InvalidationListener import javafx.beans.InvalidationListener
import org.jackhuang.hmcl.download.DefaultDependencyManager 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.game.HMCLGameRepository
import org.jackhuang.hmcl.mod.ModManager import org.jackhuang.hmcl.mod.ModManager
import org.jackhuang.hmcl.util.ImmediateObjectProperty import org.jackhuang.hmcl.ui.runOnUiThread
import org.jackhuang.hmcl.util.ImmediateStringProperty import org.jackhuang.hmcl.util.*
import org.jackhuang.hmcl.util.getValue
import org.jackhuang.hmcl.util.setValue
import java.io.File import java.io.File
import java.lang.reflect.Type 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<VersionSetting>(this, "global", VersionSetting()) val globalProperty = ImmediateObjectProperty<VersionSetting>(this, "global", VersionSetting())
var global: VersionSetting by globalProperty var global: VersionSetting by globalProperty
val selectedVersionProperty = ImmediateStringProperty(this, "selectedVersion", initialSelectedVersion) val selectedVersionProperty = ImmediateObjectProperty<String?>(this, "selectedVersion", initialSelectedVersion)
var selectedVersion: String by selectedVersionProperty var selectedVersion: String? by selectedVersionProperty
val gameDirProperty = ImmediateObjectProperty<File>(this, "gameDir", initialGameDir) val gameDirProperty = ImmediateObjectProperty<File>(this, "gameDir", initialGameDir)
var gameDir: File by gameDirProperty var gameDir: File by gameDirProperty
@ -44,17 +47,21 @@ class Profile(var name: String = "Default", initialGameDir: File = File(".minecr
var modManager = ModManager(repository) var modManager = ModManager(repository)
init { init {
gameDirProperty.addListener { _ -> gameDirProperty.onChange { newGameDir ->
repository.baseDirectory = gameDir repository.baseDirectory = newGameDir!!
repository.refreshVersions() repository.refreshVersions()
} }
selectedVersionProperty.addListener { _ -> selectedVersionProperty.addListener { _ -> verifySelectedVersion() }
if (selectedVersion.isNotBlank() && !repository.hasVersion(selectedVersion)) { EVENT_BUS.channel<RefreshedVersionsEvent>() += { event -> if (event.source == repository) verifySelectedVersion() }
val newVersion = repository.getVersions().firstOrNull() }
// will cause anthor change event, we must ensure that there will not be dead recursion.
selectedVersion = newVersion?.id ?: "" 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 return vs
} }
fun getSelectedVersionSetting(): VersionSetting = fun getSelectedVersionSetting(): VersionSetting? = if (selectedVersion == null) null else getVersionSetting(selectedVersion!!)
getVersionSetting(selectedVersion)
fun addPropertyChangedListener(listener: InvalidationListener) { fun addPropertyChangedListener(listener: InvalidationListener) {
nameProperty.addListener(listener)
globalProperty.addListener(listener) globalProperty.addListener(listener)
selectedVersionProperty.addListener(listener) selectedVersionProperty.addListener(listener)
gameDirProperty.addListener(listener) gameDirProperty.addListener(listener)

View File

@ -28,6 +28,7 @@ import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider
import org.jackhuang.hmcl.download.DownloadProvider import org.jackhuang.hmcl.download.DownloadProvider
import org.jackhuang.hmcl.download.MojangDownloadProvider import org.jackhuang.hmcl.download.MojangDownloadProvider
import org.jackhuang.hmcl.event.EVENT_BUS import org.jackhuang.hmcl.event.EVENT_BUS
import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.util.* import org.jackhuang.hmcl.util.*
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@ -270,12 +271,20 @@ object Settings {
* PROFILES * * PROFILES *
****************************************/ ****************************************/
val selectedProfile: Profile var selectedProfile: Profile
get() { get() {
if (!hasProfile(SETTINGS.selectedProfile)) if (!hasProfile(SETTINGS.selectedProfile)) {
SETTINGS.selectedProfile = DEFAULT_PROFILE SETTINGS.selectedProfile = DEFAULT_PROFILE
Scheduler.COMPUTATION.schedule { onProfileChanged() }
}
return getProfile(SETTINGS.selectedProfile) 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 { fun getProfile(name: String?): Profile {
var p: Profile? = getProfileMap()[name ?: DEFAULT_PROFILE] var p: Profile? = getProfileMap()[name ?: DEFAULT_PROFILE]
@ -316,12 +325,10 @@ object Settings {
if (DEFAULT_PROFILE == ver) { if (DEFAULT_PROFILE == ver) {
return false return false
} }
var notify = false
if (selectedProfile.name == ver)
notify = true
val flag = getProfileMap().remove(ver) != null val flag = getProfileMap().remove(ver) != null
if (notify && flag) if (flag)
onProfileChanged() Scheduler.COMPUTATION.schedule { onProfileLoading() }
return flag return flag
} }

View File

@ -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 header.style = "-fx-background-radius: 2 2 0 0; -fx-background-color: " + headerColor
// create image view // 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.properties["account"] = account
chkSelected.isSelected = Settings.selectedAccount == account chkSelected.isSelected = Settings.selectedAccount == account

View File

@ -24,6 +24,7 @@ import javafx.scene.image.Image
import javafx.scene.layout.Region import javafx.scene.layout.Region
import javafx.stage.Stage import javafx.stage.Stage
import org.jackhuang.hmcl.Main import org.jackhuang.hmcl.Main
import org.jackhuang.hmcl.setting.Settings
object Controllers { object Controllers {
lateinit var scene: Scene private set lateinit var scene: Scene private set
@ -44,6 +45,8 @@ object Controllers {
decorator.showPage(null) decorator.showPage(null)
leftPaneController = LeftPaneController(decorator.leftPane) leftPaneController = LeftPaneController(decorator.leftPane)
Settings.onProfileLoading()
decorator.isCustomMaximize = false decorator.isCustomMaximize = false
scene = Scene(decorator, 800.0, 480.0) scene = Scene(decorator, 800.0, 480.0)

View File

@ -17,54 +17,51 @@
*/ */
package org.jackhuang.hmcl.ui package org.jackhuang.hmcl.ui
import com.jfoenix.controls.JFXComboBox
import javafx.scene.layout.VBox 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.auth.yggdrasil.YggdrasilAccount
import org.jackhuang.hmcl.event.EVENT_BUS
import org.jackhuang.hmcl.game.AccountHelper import org.jackhuang.hmcl.game.AccountHelper
import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.ui.construct.IconedItem import org.jackhuang.hmcl.ui.construct.IconedItem
import org.jackhuang.hmcl.ui.construct.RipplerContainer import org.jackhuang.hmcl.ui.construct.RipplerContainer
import org.jackhuang.hmcl.util.onChangeAndOperate import org.jackhuang.hmcl.util.onChangeAndOperate
import java.util.*
class LeftPaneController(private val leftPane: AdvancedListBox) { class LeftPaneController(private val leftPane: AdvancedListBox) {
val versionsPane = VBox() val profilePane = VBox()
val cboProfiles = JFXComboBox<String>().apply { items.add("Default"); prefWidthProperty().bind(leftPane.widthProperty()) }
val accountItem = VersionListItem("No Account", "unknown") val accountItem = VersionListItem("No Account", "unknown")
init { init {
leftPane leftPane
.startCategory("ACCOUNTS") .startCategory("ACCOUNTS")
.add(RipplerContainer(accountItem).apply { .add(RipplerContainer(accountItem).apply {
setOnMouseClicked {
Controllers.navigate(AccountsPage())
}
accountItem.onSettingsButtonClicked { accountItem.onSettingsButtonClicked {
Controllers.navigate(AccountsPage()) Controllers.navigate(AccountsPage())
} }
}) })
.startCategory(i18n("ui.label.profile"))
.add(cboProfiles)
.startCategory("LAUNCHER") .startCategory("LAUNCHER")
.add(IconedItem(SVG.gear("black"), "Settings").apply { .add(IconedItem(SVG.gear("black"), i18n("launcher.title.launcher")).apply {
prefWidthProperty().bind(leftPane.widthProperty()) prefWidthProperty().bind(leftPane.widthProperty())
setOnMouseClicked { setOnMouseClicked {
Controllers.navigate(Controllers.settingsPane) Controllers.navigate(Controllers.settingsPane)
} }
}) })
/* .startCategory(i18n("ui.label.version")) .startCategory(i18n("ui.label.profile"))
.add(versionsPane) .add(profilePane)
EVENT_BUS.channel<RefreshedVersionsEvent>() += this::loadVersions
EVENT_BUS.channel<ProfileLoadingEvent>() += this::onProfilesLoading EVENT_BUS.channel<ProfileLoadingEvent>() += this::onProfilesLoading
EVENT_BUS.channel<ProfileChangedEvent>() += this::onProfileChanged EVENT_BUS.channel<ProfileChangedEvent>() += this::onProfileChanged
Settings.onProfileLoading()
Controllers.decorator.addMenuButton.setOnMouseClicked { 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 { Settings.selectedAccountProperty.onChangeAndOperate {
if (it == null) { if (it == null) {
@ -86,46 +83,36 @@ class LeftPaneController(private val leftPane: AdvancedListBox) {
if (Settings.getAccounts().isEmpty()) if (Settings.getAccounts().isEmpty())
Controllers.navigate(AccountsPage()) Controllers.navigate(AccountsPage())
} }
/*
fun onProfilesLoading() {
// TODO: Profiles
}
fun onProfileChanged(event: ProfileChangedEvent) { fun onProfileChanged(event: ProfileChangedEvent) {
val profile = event.value val profile = event.value
profile.selectedVersionProperty.addListener { _ ->
versionChanged(profile.selectedVersion) profilePane.children
} .filter { it is RipplerContainer && it.properties["profile"] is Pair<*, *> }
profile.selectedVersionProperty.fireValueChangedEvent() .forEach { (it as RipplerContainer).selected = (it.properties["profile"] as Pair<String, VersionListItem>).first == profile.name }
} }
private fun loadVersions() { fun onProfilesLoading() {
val profile = Settings.selectedProfile val list = LinkedList<RipplerContainer>()
versionsPane.children.clear() Settings.getProfiles().forEach { profile ->
profile.repository.getVersions().forEach { version -> val item = VersionListItem(profile.name).apply {
val item = VersionListItem(version.id, minecraftVersion(profile.repository.getVersionJar(version.id)) ?: "Unknown") lblGameVersion.textProperty().bind(profile.selectedVersionProperty)
}
val ripplerContainer = RipplerContainer(item) val ripplerContainer = RipplerContainer(item)
item.onSettingsButtonClicked { item.onSettingsButtonClicked {
Controllers.decorator.showPage(Controllers.versionPane) Controllers.decorator.showPage(ProfilePage(profile))
Controllers.versionPane.load(item.versionName, profile)
} }
ripplerContainer.ripplerFill = Paint.valueOf("#89E1F9") ripplerContainer.ripplerFill = Paint.valueOf("#89E1F9")
ripplerContainer.setOnMouseClicked { ripplerContainer.setOnMouseClicked {
// clean selected property // 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 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()) 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<String, VersionListItem>).first == selectedVersion }
}*/
} }

View File

@ -45,7 +45,7 @@ import org.jackhuang.hmcl.util.onChange
* @see /assets/fxml/main.fxml * @see /assets/fxml/main.fxml
*/ */
class MainPage : StackPane(), DecoratorPage { 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 btnLaunch: JFXButton
@FXML lateinit var btnRefresh: JFXButton @FXML lateinit var btnRefresh: JFXButton
@ -59,15 +59,20 @@ class MainPage : StackPane(), DecoratorPage {
btnLaunch.limitWidth(40.0) btnLaunch.limitWidth(40.0)
btnLaunch.limitHeight(40.0) btnLaunch.limitHeight(40.0)
EVENT_BUS.channel<RefreshedVersionsEvent>() += this::loadVersions EVENT_BUS.channel<RefreshedVersionsEvent>() += { -> loadVersions() }
EVENT_BUS.channel<ProfileLoadingEvent>() += this::onProfilesLoading EVENT_BUS.channel<ProfileLoadingEvent>() += this::onProfilesLoading
EVENT_BUS.channel<ProfileChangedEvent>() += this::onProfileChanged EVENT_BUS.channel<ProfileChangedEvent>() += this::onProfileChanged
Settings.onProfileLoading()
btnAdd.setOnMouseClicked { Controllers.decorator.startWizard(DownloadWizardProvider(), "Install New Game") } btnAdd.setOnMouseClicked { Controllers.decorator.startWizard(DownloadWizardProvider(), "Install New Game") }
btnRefresh.setOnMouseClicked { Settings.selectedProfile.repository.refreshVersions() } 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 { 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 lblVersionName.text = version
btnDelete.setOnMouseClicked { btnDelete.setOnMouseClicked {
profile.repository.removeVersionFromDisk(version) profile.repository.removeVersionFromDisk(version)
Platform.runLater(this@MainPage::loadVersions) Platform.runLater { loadVersions() }
} }
btnSettings.setOnMouseClicked { btnSettings.setOnMouseClicked {
Controllers.decorator.showPage(Controllers.versionPane) Controllers.decorator.showPage(Controllers.versionPane)
@ -94,15 +99,15 @@ class MainPage : StackPane(), DecoratorPage {
// TODO: Profiles // TODO: Profiles
} }
fun onProfileChanged(event: ProfileChangedEvent) { fun onProfileChanged(event: ProfileChangedEvent) = runOnUiThread {
val profile = event.value val profile = event.value
profile.selectedVersionProperty.setChangedListener { profile.selectedVersionProperty.setChangedListener { t ->
versionChanged(profile.selectedVersion) versionChanged(profile.selectedVersion)
} }
loadVersions(profile)
} }
private fun loadVersions() { private fun loadVersions(profile: Profile = Settings.selectedProfile) {
val profile = Settings.selectedProfile
val group = ToggleGroup() val group = ToggleGroup()
val children = mutableListOf<Node>() val children = mutableListOf<Node>()
var i = 0 var i = 0
@ -117,7 +122,7 @@ class MainPage : StackPane(), DecoratorPage {
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun versionChanged(selectedVersion: String) { fun versionChanged(selectedVersion: String?) {
masonryPane.children masonryPane.children
.filter { it is RipplerContainer && it.properties["version"] is Pair<*, *> } .filter { it is RipplerContainer && it.properties["version"] is Pair<*, *> }
.forEach { (it as RipplerContainer).selected = (it.properties["version"] as Pair<String, VersionListItem>).first == selectedVersion } .forEach { (it as RipplerContainer).selected = (it.properties["version"] as Pair<String, VersionListItem>).first == selectedVersion }

View File

@ -0,0 +1,84 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui
import com.jfoenix.controls.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)
}
}

View File

@ -61,7 +61,7 @@ class VersionItem(i: Int, group: ToggleGroup) : StackPane() {
header.style = "-fx-background-radius: 2 2 0 0; -fx-background-color: " + headerColor header.style = "-fx-background-radius: 2 2 0 0; -fx-background-color: " + headerColor
// create image view // 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) iconView.limitSize(32.0, 32.0)
} }

View File

@ -23,7 +23,7 @@ import javafx.scene.control.Label
import javafx.scene.image.ImageView import javafx.scene.image.ImageView
import javafx.scene.layout.StackPane 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 lblVersionName: Label
@FXML lateinit var lblGameVersion: Label @FXML lateinit var lblGameVersion: Label

View File

@ -52,14 +52,7 @@ class FileItem : BorderPane() {
right = JFXButton().apply { right = JFXButton().apply {
graphic = SVG.pencil("black", 15.0, 15.0) graphic = SVG.pencil("black", 15.0, 15.0)
styleClass += "toggle-icon4" styleClass += "toggle-icon4"
setOnMouseClicked { setOnMouseClicked { onExplore() }
val chooser = DirectoryChooser()
chooser.titleProperty().bind(titleProperty)
val selectedDir = chooser.showDialog(Controllers.stage)
if (selectedDir != null)
property.value = selectedDir.absolutePath
chooser.titleProperty().unbind()
}
} }
Tooltip.install(this, Tooltip().apply { 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<String>) { fun setProperty(property: Property<String>) {
this.property = property this.property = property
x.textProperty().bind(property) x.textProperty().bind(property)

View File

@ -27,6 +27,17 @@
<center> <center>
<AdvancedListBox fx:id="leftPane"/> <AdvancedListBox fx:id="leftPane"/>
</center> </center>
<bottom>
<BorderPane fx:id="menuBottomBar">
<right>
<JFXButton fx:id="addMenuButton" styleClass="toggle-icon4">
<graphic>
<fx:include source="/assets/svg/plus-black.fxml"/>
</graphic>
</JFXButton>
</right>
</BorderPane>
</bottom>
</BorderPane> </BorderPane>
</center> </center>
<right> <right>

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.JFXButton?>
<?import com.jfoenix.controls.JFXTextField?>
<?import com.jfoenix.validation.RequiredFieldValidator?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.layout.*?>
<?import org.jackhuang.hmcl.ui.construct.ComponentList?>
<?import org.jackhuang.hmcl.ui.construct.FileItem?>
<fx:root xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
type="StackPane">
<ScrollPane fx:id="scroll" fitToHeight="true" fitToWidth="true">
<VBox fx:id="rootPane" style="-fx-padding: 20;">
<ComponentList depth="1">
<BorderPane> <!-- Name -->
<left>
<VBox>
<Label text="%ui.label.profile" BorderPane.alignment="CENTER_LEFT"/>
</VBox>
</left>
<right>
<JFXTextField fx:id="txtProfileName" BorderPane.alignment="CENTER_RIGHT">
<validators>
<RequiredFieldValidator/>
</validators>
</JFXTextField>
</right>
</BorderPane>
<FileItem fx:id="gameDir" name="%settings.game_directory" title="%settings.choose_gamedir"/>
</ComponentList>
</VBox>
</ScrollPane>
<BorderPane pickOnBounds="false" style="-fx-padding: 20;">
<left>
<JFXButton BorderPane.alignment="BOTTOM_LEFT" fx:id="btnDelete" onMouseClicked="#onDelete" prefWidth="100" prefHeight="40"
buttonType="RAISED" text="%ui.button.delete" styleClass="jfx-button-raised" />
</left>
<right>
<JFXButton BorderPane.alignment="BOTTOM_RIGHT" fx:id="btnSave" onMouseClicked="#onSave" prefWidth="100" prefHeight="40"
buttonType="RAISED" text="%ui.button.save" styleClass="jfx-button-raised"/>
</right>
</BorderPane>
</fx:root>

View File

@ -16,7 +16,7 @@
<HBox alignment="CENTER" mouseTransparent="true"> <HBox alignment="CENTER" mouseTransparent="true">
<StackPane fx:id="imageViewContainer"> <StackPane fx:id="imageViewContainer">
<ImageView preserveRatio="true" fx:id="imageView" smooth="false"> <ImageView preserveRatio="true" fx:id="imageView" smooth="false">
<Image url="/assets/img/icon.png" requestedWidth="25" requestedHeight="25" /> <Image url="/assets/img/icon.png" />
</ImageView> </ImageView>
</StackPane> </StackPane>
<BorderPane style="-fx-padding: 0 0 0 10;"> <BorderPane style="-fx-padding: 0 0 0 10;">

View File

@ -36,6 +36,8 @@ import java.util.logging.Level
*/ */
open class DefaultGameRepository(var baseDirectory: File): GameRepository { open class DefaultGameRepository(var baseDirectory: File): GameRepository {
protected val versions: MutableMap<String, Version> = TreeMap<String, Version>() protected val versions: MutableMap<String, Version> = TreeMap<String, Version>()
var isLoaded: Boolean = false
protected set
override fun hasVersion(id: String) = versions.containsKey(id) override fun hasVersion(id: String) = versions.containsKey(id)
override fun getVersion(id: String): Version { override fun getVersion(id: String): Version {
@ -135,7 +137,7 @@ open class DefaultGameRepository(var baseDirectory: File): GameRepository {
versions[id] = version versions[id] = version
EVENT_BUS.fireEvent(LoadedOneVersionEvent(this, id)) EVENT_BUS.fireEvent(LoadedOneVersionEvent(this, id))
} }
isLoaded = true
} }
@Synchronized @Synchronized

View File

@ -21,6 +21,9 @@ import javafx.beans.property.*
import javafx.beans.value.ChangeListener import javafx.beans.value.ChangeListener
import javafx.beans.value.ObservableValue 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) { open class ImmediateStringProperty(bean: Any, name: String, initialValue: String): SimpleStringProperty(bean, name, initialValue) {
override fun set(newValue: String) { override fun set(newValue: String) {