This commit is contained in:
huangyuhui
2017-08-10 18:44:21 +08:00
parent da66102bc0
commit 9bd7071297
38 changed files with 878 additions and 414 deletions

View File

@@ -19,41 +19,44 @@ package org.jackhuang.hmcl.setting
import com.google.gson.* import com.google.gson.*
import javafx.beans.InvalidationListener import javafx.beans.InvalidationListener
import javafx.beans.property.*
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider
import org.jackhuang.hmcl.download.DefaultDependencyManager import org.jackhuang.hmcl.download.DefaultDependencyManager
import org.jackhuang.hmcl.download.DependencyManager
import org.jackhuang.hmcl.game.HMCLGameRepository import org.jackhuang.hmcl.game.HMCLGameRepository
import org.jackhuang.hmcl.mod.ModManager
import org.jackhuang.hmcl.util.* import org.jackhuang.hmcl.util.*
import org.jackhuang.hmcl.util.property.ImmediateBooleanProperty
import org.jackhuang.hmcl.util.property.ImmediateObjectProperty
import org.jackhuang.hmcl.util.property.ImmediateStringProperty
import java.io.File import java.io.File
import java.lang.reflect.Type import java.lang.reflect.Type
class Profile(var name: String = "Default", gameDir: File = File(".minecraft")) { class Profile(var name: String = "Default", initialGameDir: File = File(".minecraft"), initialSelectedVersion: String = "") {
val globalProperty = SimpleObjectProperty<VersionSetting>(this, "global", VersionSetting()) val globalProperty = ImmediateObjectProperty<VersionSetting>(this, "global", VersionSetting())
var global: VersionSetting by globalProperty var global: VersionSetting by globalProperty
val selectedVersionProperty = SimpleStringProperty(this, "selectedVersion", "") val selectedVersionProperty = ImmediateStringProperty(this, "selectedVersion", initialSelectedVersion)
var selectedVersion: String by selectedVersionProperty var selectedVersion: String by selectedVersionProperty
val gameDirProperty = SimpleObjectProperty<File>(this, "gameDir", gameDir) val gameDirProperty = ImmediateObjectProperty<File>(this, "gameDir", initialGameDir)
var gameDir: File by gameDirProperty var gameDir: File by gameDirProperty
val noCommonProperty = SimpleBooleanProperty(this, "noCommon", false) val noCommonProperty = ImmediateBooleanProperty(this, "noCommon", false)
var noCommon: Boolean by noCommonProperty var noCommon: Boolean by noCommonProperty
var repository = HMCLGameRepository(gameDir) var repository = HMCLGameRepository(initialGameDir)
var dependency = DefaultDependencyManager(repository, BMCLAPIDownloadProvider) var dependency = DefaultDependencyManager(repository, BMCLAPIDownloadProvider)
var modManager = ModManager(repository)
init { init {
gameDirProperty.addListener { _, _, newValue -> gameDirProperty.addListener { _ ->
repository.baseDirectory = newValue repository.baseDirectory = gameDir
repository.refreshVersions() repository.refreshVersions()
} }
selectedVersionProperty.addListener { _, _, newValue -> selectedVersionProperty.addListener { _ ->
if (newValue.isNotBlank() && !repository.hasVersion(newValue)) { if (selectedVersion.isNotBlank() && !repository.hasVersion(selectedVersion)) {
val newVersion = repository.getVersions().firstOrNull() val newVersion = repository.getVersions().firstOrNull()
// will cause anthor change event, we must insure that there will not be dead recursion. // will cause anthor change event, we must ensure that there will not be dead recursion.
selectedVersion = newVersion?.id ?: "" selectedVersion = newVersion?.id ?: ""
} }
} }
@@ -110,9 +113,8 @@ class Profile(var name: String = "Default", gameDir: File = File(".minecraft"))
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext): Profile? { override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext): Profile? {
if (json == null || json == JsonNull.INSTANCE || json !is JsonObject) return null if (json == null || json == JsonNull.INSTANCE || json !is JsonObject) return null
return Profile(gameDir = File(json["gameDir"]?.asString ?: "")).apply { return Profile(initialGameDir = File(json["gameDir"]?.asString ?: ""), initialSelectedVersion = json["selectedVersion"]?.asString ?: "").apply {
global = context.deserialize(json["global"], VersionSetting::class.java) global = context.deserialize(json["global"], VersionSetting::class.java)
selectedVersion = json["selectedVersion"]?.asString ?: ""
noCommon = json["noCommon"]?.asBoolean ?: false noCommon = json["noCommon"]?.asBoolean ?: false
} }
} }

View File

@@ -24,23 +24,20 @@ import org.jackhuang.hmcl.MainApplication
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider 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.util.GSON
import org.jackhuang.hmcl.util.LOG import org.jackhuang.hmcl.util.LOG
import java.io.File import java.io.File
import java.util.logging.Level import java.util.logging.Level
import org.jackhuang.hmcl.ProfileLoadingEvent import org.jackhuang.hmcl.ProfileLoadingEvent
import org.jackhuang.hmcl.ProfileChangedEvent import org.jackhuang.hmcl.ProfileChangedEvent
import org.jackhuang.hmcl.auth.Account import org.jackhuang.hmcl.auth.Account
import org.jackhuang.hmcl.auth.Accounts import org.jackhuang.hmcl.util.*
import org.jackhuang.hmcl.auth.OfflineAccount import org.jackhuang.hmcl.auth.OfflineAccount
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.event.EVENT_BUS
import org.jackhuang.hmcl.util.FileTypeAdapter import org.jackhuang.hmcl.util.property.ImmediateObjectProperty
import org.jackhuang.hmcl.util.ignoreException
import java.net.Proxy import java.net.Proxy
import java.util.* import java.util.*
object Settings { object Settings {
val GSON = GsonBuilder() val GSON = GsonBuilder()
.registerTypeAdapter(VersionSetting::class.java, VersionSetting) .registerTypeAdapter(VersionSetting::class.java, VersionSetting)
@@ -53,7 +50,7 @@ object Settings {
val SETTINGS_FILE = File("hmcl.json").absoluteFile val SETTINGS_FILE = File("hmcl.json").absoluteFile
val SETTINGS: Config private val SETTINGS: Config
private val ACCOUNTS = mutableMapOf<String, Account>() private val ACCOUNTS = mutableMapOf<String, Account>()
@@ -82,10 +79,10 @@ object Settings {
save() save()
if (!getProfiles().containsKey(DEFAULT_PROFILE)) if (!getProfileMap().containsKey(DEFAULT_PROFILE))
getProfiles().put(DEFAULT_PROFILE, Profile()); getProfileMap().put(DEFAULT_PROFILE, Profile());
for ((name, profile) in getProfiles().entries) { for ((name, profile) in getProfileMap().entries) {
profile.name = name profile.name = name
profile.addPropertyChangedListener(InvalidationListener { save() }) profile.addPropertyChangedListener(InvalidationListener { save() })
} }
@@ -151,23 +148,35 @@ object Settings {
return getProfile(SETTINGS.selectedProfile) return getProfile(SETTINGS.selectedProfile)
} }
val selectedAccount: Account? val selectedAccountProperty = object : ImmediateObjectProperty<Account?>(this, "selectedAccount", getAccount(SETTINGS.selectedAccount)) {
get() { override fun get(): Account? {
val a = getAccount(SETTINGS.selectedAccount) val a = super.get()
if (a == null && ACCOUNTS.isNotEmpty()) { if (a == null || !ACCOUNTS.containsKey(a.username)) {
val (key, acc) = ACCOUNTS.entries.first() val acc = if (ACCOUNTS.isEmpty()) null else ACCOUNTS.values.first()
SETTINGS.selectedAccount = key set(acc)
return acc return acc
} } else return a
return a
} }
fun setSelectedAccount(name: String) { override fun set(newValue: Account?) {
if (ACCOUNTS.containsKey(name)) if (newValue == null || ACCOUNTS.containsKey(newValue.username)) {
SETTINGS.selectedAccount = name super.set(newValue)
}
}
override fun invalidated() {
super.invalidated()
SETTINGS.selectedAccount = value?.username ?: ""
}
} }
var selectedAccount: Account? by selectedAccountProperty
val PROXY: Proxy = Proxy.NO_PROXY val PROXY: Proxy = Proxy.NO_PROXY
val PROXY_HOST: String? get() = SETTINGS.proxyHost
val PROXY_PORT: String? get() = SETTINGS.proxyPort
val PROXY_USER: String? get() = SETTINGS.proxyUserName
val PROXY_PASS: String? get() = SETTINGS.proxyPassword
fun addAccount(account: Account) { fun addAccount(account: Account) {
ACCOUNTS[account.username] = account ACCOUNTS[account.username] = account
@@ -183,36 +192,38 @@ object Settings {
fun deleteAccount(name: String) { fun deleteAccount(name: String) {
ACCOUNTS.remove(name) ACCOUNTS.remove(name)
selectedAccountProperty.get()
} }
fun getProfile(name: String?): Profile { fun getProfile(name: String?): Profile {
var p: Profile? = getProfiles()[name ?: DEFAULT_PROFILE] var p: Profile? = getProfileMap()[name ?: DEFAULT_PROFILE]
if (p == null) if (p == null)
if (getProfiles().containsKey(DEFAULT_PROFILE)) if (getProfileMap().containsKey(DEFAULT_PROFILE))
p = getProfiles()[DEFAULT_PROFILE]!! p = getProfileMap()[DEFAULT_PROFILE]!!
else { else {
p = Profile() p = Profile()
getProfiles().put(DEFAULT_PROFILE, p) getProfileMap().put(DEFAULT_PROFILE, p)
} }
return p return p
} }
fun hasProfile(name: String?): Boolean { fun hasProfile(name: String?): Boolean {
return getProfiles().containsKey(name ?: DEFAULT_PROFILE) return getProfileMap().containsKey(name ?: DEFAULT_PROFILE)
} }
fun getProfiles(): MutableMap<String, Profile> { fun getProfileMap(): MutableMap<String, Profile> {
return SETTINGS.configurations return SETTINGS.configurations
} }
fun getProfilesFiltered(): Collection<Profile> { fun getProfiles(): Collection<Profile> {
return getProfiles().values.filter { t -> t.name.isNotBlank() } return getProfileMap().values.filter { t -> t.name.isNotBlank() }
} }
fun putProfile(ver: Profile?): Boolean { fun putProfile(ver: Profile?): Boolean {
if (ver == null || ver.name.isBlank() || getProfiles().containsKey(ver.name)) if (ver == null || ver.name.isBlank() || getProfileMap().containsKey(ver.name))
return false return false
getProfiles().put(ver.name, ver) getProfileMap().put(ver.name, ver)
return true return true
} }
@@ -227,15 +238,15 @@ object Settings {
var notify = false var notify = false
if (selectedProfile.name == ver) if (selectedProfile.name == ver)
notify = true notify = true
val flag = getProfiles().remove(ver) != null val flag = getProfileMap().remove(ver) != null
if (notify && flag) if (notify && flag)
onProfileChanged() onProfileChanged()
return flag return flag
} }
internal fun onProfileChanged() { internal fun onProfileChanged() {
EVENT_BUS.fireEvent(ProfileChangedEvent(SETTINGS, selectedProfile))
selectedProfile.repository.refreshVersions() selectedProfile.repository.refreshVersions()
EVENT_BUS.fireEvent(ProfileChangedEvent(SETTINGS, selectedProfile))
} }
/** /**

View File

@@ -19,10 +19,10 @@ package org.jackhuang.hmcl.setting
import com.google.gson.* import com.google.gson.*
import javafx.beans.InvalidationListener import javafx.beans.InvalidationListener
import javafx.beans.property.*
import org.jackhuang.hmcl.MainApplication import org.jackhuang.hmcl.MainApplication
import org.jackhuang.hmcl.game.LaunchOptions import org.jackhuang.hmcl.game.LaunchOptions
import org.jackhuang.hmcl.util.* import org.jackhuang.hmcl.util.*
import org.jackhuang.hmcl.util.property.*
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.lang.reflect.Type import java.lang.reflect.Type
@@ -39,7 +39,7 @@ class VersionSetting() {
* *
* Defaults false because if one version uses global first, custom version file will not be generated. * Defaults false because if one version uses global first, custom version file will not be generated.
*/ */
val usesGlobalProperty = SimpleBooleanProperty(this, "usesGlobal", false) val usesGlobalProperty = ImmediateBooleanProperty(this, "usesGlobal", false)
var usesGlobal: Boolean by usesGlobalProperty var usesGlobal: Boolean by usesGlobalProperty
// java // java
@@ -47,39 +47,39 @@ class VersionSetting() {
/** /**
* Java version or null if user customizes java directory. * Java version or null if user customizes java directory.
*/ */
val javaProperty = SimpleStringProperty(this, "java", null) val javaProperty = ImmediateNullableStringProperty(this, "java", null)
var java: String? by javaProperty var java: String? by javaProperty
/** /**
* User customized java directory or null if user uses system Java. * User customized java directory or null if user uses system Java.
*/ */
val javaDirProperty = SimpleStringProperty(this, "javaDir", "") val javaDirProperty = ImmediateStringProperty(this, "javaDir", "")
var javaDir: String by javaDirProperty var javaDir: String by javaDirProperty
/** /**
* The command to launch java, i.e. optirun. * The command to launch java, i.e. optirun.
*/ */
val wrapperProperty = SimpleStringProperty(this, "wrapper", "") val wrapperProperty = ImmediateStringProperty(this, "wrapper", "")
var wrapper: String by wrapperProperty var wrapper: String by wrapperProperty
/** /**
* The permanent generation size of JVM garbage collection. * The permanent generation size of JVM garbage collection.
*/ */
val permSizeProperty = SimpleStringProperty(this, "permSize", "") val permSizeProperty = ImmediateStringProperty(this, "permSize", "")
var permSize: String by permSizeProperty var permSize: String by permSizeProperty
/** /**
* The maximum memory that JVM can allocate. * The maximum memory that JVM can allocate.
* The size of JVM heap. * The size of JVM heap.
*/ */
val maxMemoryProperty = SimpleIntegerProperty(this, "maxMemory", OS.SUGGESTED_MEMORY) val maxMemoryProperty = ImmediateIntegerProperty(this, "maxMemory", OS.SUGGESTED_MEMORY)
var maxMemory: Int by maxMemoryProperty var maxMemory: Int by maxMemoryProperty
/** /**
* The command that will be executed before launching the Minecraft. * The command that will be executed before launching the Minecraft.
* Operating system relevant. * Operating system relevant.
*/ */
val precalledCommandProperty = SimpleStringProperty(this, "precalledCommand", "") val precalledCommandProperty = ImmediateStringProperty(this, "precalledCommand", "")
var precalledCommand: String by precalledCommandProperty var precalledCommand: String by precalledCommandProperty
// options // options
@@ -87,25 +87,25 @@ class VersionSetting() {
/** /**
* The user customized arguments passed to JVM. * The user customized arguments passed to JVM.
*/ */
val javaArgsProperty = SimpleStringProperty(this, "javaArgs", "") val javaArgsProperty = ImmediateStringProperty(this, "javaArgs", "")
var javaArgs: String by javaArgsProperty var javaArgs: String by javaArgsProperty
/** /**
* The user customized arguments passed to Minecraft. * The user customized arguments passed to Minecraft.
*/ */
val minecraftArgsProperty = SimpleStringProperty(this, "minecraftArgs", "") val minecraftArgsProperty = ImmediateStringProperty(this, "minecraftArgs", "")
var minecraftArgs: String by minecraftArgsProperty var minecraftArgs: String by minecraftArgsProperty
/** /**
* True if disallow HMCL use default JVM arguments. * True if disallow HMCL use default JVM arguments.
*/ */
val noJVMArgsProperty = SimpleBooleanProperty(this, "noJVMArgs", false) val noJVMArgsProperty = ImmediateBooleanProperty(this, "noJVMArgs", false)
var noJVMArgs: Boolean by noJVMArgsProperty var noJVMArgs: Boolean by noJVMArgsProperty
/** /**
* True if HMCL does not check game's completeness. * True if HMCL does not check game's completeness.
*/ */
val notCheckGameProperty = SimpleBooleanProperty(this, "notCheckGame", false) val notCheckGameProperty = ImmediateBooleanProperty(this, "notCheckGame", false)
var notCheckGame: Boolean by notCheckGameProperty var notCheckGame: Boolean by notCheckGameProperty
// Minecraft settings. // Minecraft settings.
@@ -115,13 +115,13 @@ class VersionSetting() {
* *
* Format: ip:port or without port. * Format: ip:port or without port.
*/ */
val serverIpProperty = SimpleStringProperty(this, "serverIp", "") val serverIpProperty = ImmediateStringProperty(this, "serverIp", "")
var serverIp: String by serverIpProperty var serverIp: String by serverIpProperty
/** /**
* True if Minecraft started in fullscreen mode. * True if Minecraft started in fullscreen mode.
*/ */
val fullscreenProperty = SimpleBooleanProperty(this, "fullscreen", false) val fullscreenProperty = ImmediateBooleanProperty(this, "fullscreen", false)
var fullscreen: Boolean by fullscreenProperty var fullscreen: Boolean by fullscreenProperty
/** /**
@@ -131,7 +131,7 @@ class VersionSetting() {
* String type prevents unexpected value from causing JsonSyntaxException. * String type prevents unexpected value from causing JsonSyntaxException.
* We can only reset this field instead of recreating the whole setting file. * We can only reset this field instead of recreating the whole setting file.
*/ */
val widthProperty = SimpleIntegerProperty(this, "width", 854) val widthProperty = ImmediateIntegerProperty(this, "width", 854)
var width: Int by widthProperty var width: Int by widthProperty
@@ -142,7 +142,7 @@ class VersionSetting() {
* String type prevents unexpected value from causing JsonSyntaxException. * String type prevents unexpected value from causing JsonSyntaxException.
* We can only reset this field instead of recreating the whole setting file. * We can only reset this field instead of recreating the whole setting file.
*/ */
val heightProperty = SimpleIntegerProperty(this, "height", 480) val heightProperty = ImmediateIntegerProperty(this, "height", 480)
var height: Int by heightProperty var height: Int by heightProperty
@@ -150,7 +150,7 @@ class VersionSetting() {
* 0 - .minecraft<br/> * 0 - .minecraft<br/>
* 1 - .minecraft/versions/&lt;version&gt;/<br/> * 1 - .minecraft/versions/&lt;version&gt;/<br/>
*/ */
val gameDirTypeProperty = SimpleObjectProperty<EnumGameDirectory>(this, "gameDirTypeProperty", EnumGameDirectory.ROOT_FOLDER) val gameDirTypeProperty = ImmediateObjectProperty<EnumGameDirectory>(this, "gameDirTypeProperty", EnumGameDirectory.ROOT_FOLDER)
var gameDirType: EnumGameDirectory by gameDirTypeProperty var gameDirType: EnumGameDirectory by gameDirTypeProperty
// launcher settings // launcher settings
@@ -160,7 +160,7 @@ class VersionSetting() {
* 1 - Hide the launcher when the game starts.<br/> * 1 - Hide the launcher when the game starts.<br/>
* 2 - Keep the launcher open.<br/> * 2 - Keep the launcher open.<br/>
*/ */
val launcherVisibilityProperty = SimpleObjectProperty<LauncherVisibility>(this, "launcherVisibility", LauncherVisibility.HIDE) val launcherVisibilityProperty = ImmediateObjectProperty<LauncherVisibility>(this, "launcherVisibility", LauncherVisibility.HIDE)
var launcherVisibility: LauncherVisibility by launcherVisibilityProperty var launcherVisibility: LauncherVisibility by launcherVisibilityProperty
fun addPropertyChangedListener(listener: InvalidationListener) { fun addPropertyChangedListener(listener: InvalidationListener) {
@@ -200,10 +200,10 @@ class VersionSetting() {
fullscreen = fullscreen, fullscreen = fullscreen,
serverIp = serverIp, serverIp = serverIp,
wrapper = wrapper, wrapper = wrapper,
proxyHost = Settings.SETTINGS.proxyHost, proxyHost = Settings.PROXY_HOST,
proxyPort = Settings.SETTINGS.proxyPort, proxyPort = Settings.PROXY_PORT,
proxyUser = Settings.SETTINGS.proxyUserName, proxyUser = Settings.PROXY_USER,
proxyPass = Settings.SETTINGS.proxyPassword, proxyPass = Settings.PROXY_PASS,
precalledCommand = precalledCommand, precalledCommand = precalledCommand,
noGeneratedJVMArgs = noJVMArgs noGeneratedJVMArgs = noJVMArgs
) )
@@ -217,7 +217,7 @@ class VersionSetting() {
addProperty("usesGlobal", src.usesGlobal) addProperty("usesGlobal", src.usesGlobal)
addProperty("javaArgs", src.javaArgs) addProperty("javaArgs", src.javaArgs)
addProperty("minecraftArgs", src.minecraftArgs) addProperty("minecraftArgs", src.minecraftArgs)
addProperty("maxMemory", src.maxMemory) addProperty("maxMemory", if (src.maxMemory <= 0) OS.SUGGESTED_MEMORY else src.maxMemory)
addProperty("permSize", src.permSize) addProperty("permSize", src.permSize)
addProperty("width", src.width) addProperty("width", src.width)
addProperty("height", src.height) addProperty("height", src.height)
@@ -239,11 +239,14 @@ class VersionSetting() {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): VersionSetting? { override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): VersionSetting? {
if (json == null || json == JsonNull.INSTANCE || json !is JsonObject) return null if (json == null || json == JsonNull.INSTANCE || json !is JsonObject) return null
var maxMemoryN = parseJsonPrimitive(json["maxMemory"]?.asJsonPrimitive, OS.SUGGESTED_MEMORY)
if (maxMemoryN <= 0) maxMemoryN = OS.SUGGESTED_MEMORY
return VersionSetting().apply { return VersionSetting().apply {
usesGlobal = json["usesGlobal"]?.asBoolean ?: false usesGlobal = json["usesGlobal"]?.asBoolean ?: false
javaArgs = json["javaArgs"]?.asString ?: "" javaArgs = json["javaArgs"]?.asString ?: ""
minecraftArgs = json["minecraftArgs"]?.asString ?: "" minecraftArgs = json["minecraftArgs"]?.asString ?: ""
maxMemory = parseJsonPrimitive(json["maxMemory"]?.asJsonPrimitive) maxMemory = maxMemoryN
permSize = json["permSize"]?.asString ?: "" permSize = json["permSize"]?.asString ?: ""
width = parseJsonPrimitive(json["width"]?.asJsonPrimitive) width = parseJsonPrimitive(json["width"]?.asJsonPrimitive)
height = parseJsonPrimitive(json["height"]?.asJsonPrimitive) height = parseJsonPrimitive(json["height"]?.asJsonPrimitive)

View File

@@ -18,38 +18,36 @@
package org.jackhuang.hmcl.ui package org.jackhuang.hmcl.ui
import com.jfoenix.controls.JFXButton import com.jfoenix.controls.JFXButton
import com.jfoenix.controls.JFXCheckBox
import com.jfoenix.controls.JFXRadioButton
import com.jfoenix.effects.JFXDepthManager import com.jfoenix.effects.JFXDepthManager
import javafx.beans.binding.Bindings import javafx.beans.binding.Bindings
import javafx.fxml.FXML import javafx.fxml.FXML
import javafx.scene.control.Label import javafx.scene.control.Label
import javafx.scene.control.ToggleGroup
import javafx.scene.layout.Pane import javafx.scene.layout.Pane
import javafx.scene.layout.StackPane import javafx.scene.layout.StackPane
import javafx.scene.layout.VBox import javafx.scene.layout.VBox
import java.util.concurrent.Callable import java.util.concurrent.Callable
class AccountItem(i: Int, width: Double, height: Double) : StackPane() { class AccountItem(i: Int, width: Double, height: Double, group: ToggleGroup) : StackPane() {
@FXML lateinit var icon: Pane @FXML lateinit var icon: Pane
@FXML lateinit var content: VBox @FXML lateinit var content: VBox
@FXML lateinit var header: StackPane @FXML lateinit var header: StackPane
@FXML lateinit var body: StackPane @FXML lateinit var body: StackPane
@FXML lateinit var btnDelete: JFXButton @FXML lateinit var btnDelete: JFXButton
@FXML lateinit var btnEdit: JFXButton
@FXML lateinit var lblUser: Label @FXML lateinit var lblUser: Label
@FXML lateinit var chkSelected: JFXRadioButton
@FXML lateinit var lblType: Label @FXML lateinit var lblType: Label
init { init {
loadFXML("/assets/fxml/account-item.fxml") loadFXML("/assets/fxml/account-item.fxml")
minWidth = width
maxWidth = width
prefWidth = width
minHeight = height
maxHeight = height
prefHeight = height
JFXDepthManager.setDepth(this, 1) JFXDepthManager.setDepth(this, 1)
chkSelected.toggleGroup = group
btnDelete.graphic = SVG.delete("white", 15.0, 15.0)
// create content // create content
val headerColor = getDefaultColor(i % 12) val headerColor = getDefaultColor(i % 12)
header.style = "-fx-background-radius: 5 5 0 0; -fx-background-color: " + headerColor header.style = "-fx-background-radius: 5 5 0 0; -fx-background-color: " + headerColor

View File

@@ -25,18 +25,19 @@ import javafx.scene.control.ScrollPane
import javafx.scene.layout.StackPane import javafx.scene.layout.StackPane
import javafx.beans.property.SimpleStringProperty import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty import javafx.beans.property.StringProperty
import javafx.beans.value.ChangeListener
import javafx.scene.control.Label import javafx.scene.control.Label
import javafx.scene.control.ToggleGroup
import org.jackhuang.hmcl.auth.Account import org.jackhuang.hmcl.auth.Account
import org.jackhuang.hmcl.auth.OfflineAccount import org.jackhuang.hmcl.auth.OfflineAccount
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.task.Scheduler import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.with
import org.jackhuang.hmcl.task.Task import org.jackhuang.hmcl.task.Task
import org.jackhuang.hmcl.ui.wizard.HasTitle import org.jackhuang.hmcl.ui.wizard.DecoratorPage
import java.util.concurrent.Callable import java.util.concurrent.Callable
class AccountsPage : StackPane(), HasTitle { class AccountsPage() : StackPane(), DecoratorPage {
override val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Accounts") override val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Accounts")
@FXML lateinit var scrollPane: ScrollPane @FXML lateinit var scrollPane: ScrollPane
@@ -48,12 +49,30 @@ class AccountsPage : StackPane(), HasTitle {
@FXML lateinit var lblCreationWarning: Label @FXML lateinit var lblCreationWarning: Label
@FXML lateinit var cboType: JFXComboBox<String> @FXML lateinit var cboType: JFXComboBox<String>
val listener = ChangeListener<Account?> { _, _, newValue ->
masonryPane.children.forEach {
if (it is AccountItem) {
it.chkSelected.isSelected = newValue?.username == it.lblUser.text
}
}
}
init { init {
loadFXML("/assets/fxml/account.fxml") loadFXML("/assets/fxml/account.fxml")
children.remove(dialog) children.remove(dialog)
dialog.dialogContainer = this dialog.dialogContainer = this
JFXScrollPane.smoothScrolling(scrollPane) scrollPane.smoothScrolling()
txtUsername.textProperty().addListener { _ ->
txtUsername.validate()
}
txtUsername.validate()
txtPassword.textProperty().addListener { _ ->
txtPassword.validate()
}
txtPassword.validate()
cboType.selectionModel.selectedIndexProperty().addListener { _, _, newValue -> cboType.selectionModel.selectedIndexProperty().addListener { _, _, newValue ->
val visible = newValue != 0 val visible = newValue != 0
@@ -62,27 +81,42 @@ class AccountsPage : StackPane(), HasTitle {
} }
cboType.selectionModel.select(0) cboType.selectionModel.select(0)
Settings.selectedAccountProperty.addListener(listener)
loadAccounts() loadAccounts()
if (Settings.getAccounts().isEmpty())
addNewAccount()
}
override fun onClose() {
Settings.selectedAccountProperty.removeListener(listener)
} }
fun loadAccounts() { fun loadAccounts() {
val children = mutableListOf<Node>() val children = mutableListOf<Node>()
var i = 0 var i = 0
val group = ToggleGroup()
for ((_, account) in Settings.getAccounts()) { for ((_, account) in Settings.getAccounts()) {
children += buildNode(++i, account) children += buildNode(++i, account, group)
}
group.selectedToggleProperty().addListener { _, _, newValue ->
if (newValue != null)
Settings.selectedAccount = newValue.properties["account"] as Account
} }
masonryPane.children.setAll(children) masonryPane.children.setAll(children)
Platform.runLater { scrollPane.requestLayout() } Platform.runLater {
masonryPane.requestLayout()
scrollPane.requestLayout()
}
} }
private fun buildNode(i: Int, account: Account): Node { private fun buildNode(i: Int, account: Account, group: ToggleGroup): Node {
return AccountItem(i, Math.random() * 100 + 100, Math.random() * 100 + 100).apply { return AccountItem(i, Math.random() * 100 + 100, Math.random() * 100 + 100, group).apply {
chkSelected.properties["account"] = account
chkSelected.isSelected = Settings.selectedAccount == account
lblUser.text = account.username lblUser.text = account.username
lblType.text = when(account) { lblType.text = accountType(account)
is OfflineAccount -> "Offline Account"
is YggdrasilAccount -> "Yggdrasil Account"
else -> throw Error("Unsupported account: $account")
}
btnDelete.setOnMouseClicked { btnDelete.setOnMouseClicked {
Settings.deleteAccount(account.username) Settings.deleteAccount(account.username)
Platform.runLater(this@AccountsPage::loadAccounts) Platform.runLater(this@AccountsPage::loadAccounts)
@@ -90,28 +124,6 @@ class AccountsPage : StackPane(), HasTitle {
} }
} }
private fun getDefaultColor(i: Int): String {
var color = "#FFFFFF"
when (i) {
0 -> color = "#8F3F7E"
1 -> color = "#B5305F"
2 -> color = "#CE584A"
3 -> color = "#DB8D5C"
4 -> color = "#DA854E"
5 -> color = "#E9AB44"
6 -> color = "#FEE435"
7 -> color = "#99C286"
8 -> color = "#01A05E"
9 -> color = "#4A8895"
10 -> color = "#16669B"
11 -> color = "#2F65A5"
12 -> color = "#4E6A9C"
else -> {
}
}
return color
}
fun addNewAccount() { fun addNewAccount() {
dialog.show() dialog.show()
} }
@@ -150,3 +162,10 @@ class AccountsPage : StackPane(), HasTitle {
dialog.close() dialog.close()
} }
} }
fun accountType(account: Account) =
when(account) {
is OfflineAccount -> "Offline Account"
is YggdrasilAccount -> "Yggdrasil Account"
else -> throw Error("Unsupported account: $account")
}

View File

@@ -38,16 +38,10 @@ object Controllers {
fun initialize(stage: Stage) { fun initialize(stage: Stage) {
this.stage = stage this.stage = stage
decorator = Decorator(stage, max = false) decorator = Decorator(stage, mainPane, max = false)
decorator.mainPage = mainPane
decorator.showPage(null) decorator.showPage(null)
leftPaneController = LeftPaneController(decorator.leftPane) leftPaneController = LeftPaneController(decorator.leftPane)
// Let root pane fix window size.
with(mainPane.parent as StackPane) {
mainPane.prefWidthProperty().bind(widthProperty())
mainPane.prefHeightProperty().bind(heightProperty())
}
decorator.isCustomMaximize = false decorator.isCustomMaximize = false
scene = Scene(decorator, 800.0, 480.0) scene = Scene(decorator, 800.0, 480.0)

View File

@@ -37,12 +37,9 @@ import javafx.scene.control.Tooltip
import javafx.scene.input.MouseEvent import javafx.scene.input.MouseEvent
import javafx.scene.layout.* import javafx.scene.layout.*
import javafx.scene.paint.Color import javafx.scene.paint.Color
import javafx.scene.shape.Rectangle
import javafx.stage.Screen import javafx.stage.Screen
import javafx.stage.Stage import javafx.stage.Stage
import javafx.stage.StageStyle import javafx.stage.StageStyle
import javafx.scene.layout.BorderStrokeStyle
import javafx.scene.layout.BorderStroke
import org.jackhuang.hmcl.MainApplication import org.jackhuang.hmcl.MainApplication
import org.jackhuang.hmcl.ui.animation.AnimationProducer import org.jackhuang.hmcl.ui.animation.AnimationProducer
import org.jackhuang.hmcl.ui.animation.ContainerAnimations import org.jackhuang.hmcl.ui.animation.ContainerAnimations
@@ -52,7 +49,7 @@ import org.jackhuang.hmcl.util.*
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
class Decorator @JvmOverloads constructor(private val primaryStage: Stage, private val max: Boolean = true, min: Boolean = true) : GridPane(), AbstractWizardDisplayer { class Decorator @JvmOverloads constructor(private val primaryStage: Stage, private val mainPage: Node, private val max: Boolean = true, min: Boolean = true) : GridPane(), AbstractWizardDisplayer {
override val wizardController: WizardController = WizardController(this) override val wizardController: WizardController = WizardController(this)
private var xOffset: Double = 0.0 private var xOffset: Double = 0.0
@@ -370,16 +367,28 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, priva
if (content is WizardPage) if (content is WizardPage)
titleLabel.text = prefix + content.title titleLabel.text = prefix + content.title
if (content is HasTitle) if (content is DecoratorPage)
titleLabel.textProperty().bind(content.titleProperty) titleLabel.textProperty().bind(content.titleProperty)
} }
lateinit var mainPage: Node
var category: String? = null var category: String? = null
var nowPage: Node? = null
fun showPage(content: Node?) { fun showPage(content: Node?) {
val c = content ?: mainPage
onEnd() onEnd()
setContent(content ?: mainPage, ContainerAnimations.FADE.animationProducer) val nowPage = nowPage
if (nowPage is DecoratorPage)
nowPage.onClose()
this.nowPage = content
setContent(c, ContainerAnimations.FADE.animationProducer)
if (c is Region)
// Let root pane fix window size.
with(c.parent as StackPane) {
c.prefWidthProperty().bind(widthProperty())
c.prefHeightProperty().bind(heightProperty())
}
} }
fun startWizard(wizardProvider: WizardProvider, category: String? = null) { fun startWizard(wizardProvider: WizardProvider, category: String? = null) {

View File

@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.ui package org.jackhuang.hmcl.ui
import com.jfoenix.concurrency.JFXUtilities import com.jfoenix.concurrency.JFXUtilities
import com.jfoenix.controls.JFXScrollPane
import javafx.animation.Animation import javafx.animation.Animation
import javafx.animation.KeyFrame import javafx.animation.KeyFrame
import javafx.animation.Timeline import javafx.animation.Timeline
@@ -29,10 +30,10 @@ import javafx.scene.Parent
import javafx.scene.Scene import javafx.scene.Scene
import javafx.scene.control.ListView import javafx.scene.control.ListView
import javafx.scene.control.ScrollBar import javafx.scene.control.ScrollBar
import javafx.scene.control.ScrollPane
import javafx.scene.image.WritableImage import javafx.scene.image.WritableImage
import javafx.scene.input.MouseEvent import javafx.scene.input.MouseEvent
import javafx.scene.input.ScrollEvent import javafx.scene.input.ScrollEvent
import javafx.scene.layout.Pane
import javafx.scene.layout.Region import javafx.scene.layout.Region
import javafx.scene.shape.Rectangle import javafx.scene.shape.Rectangle
import javafx.util.Duration import javafx.util.Duration
@@ -90,6 +91,8 @@ fun ListView<*>.smoothScrolling() {
} }
} }
fun ScrollPane.smoothScrolling() = JFXScrollPane.smoothScrolling(this)
fun runOnUiThread(runnable: () -> Unit) = { fun runOnUiThread(runnable: () -> Unit) = {
JFXUtilities.runInFX(runnable) JFXUtilities.runInFX(runnable)
} }
@@ -109,6 +112,5 @@ fun setOverflowHidden(node: Region) {
val stylesheets = arrayOf( val stylesheets = arrayOf(
Controllers::class.java.getResource("/css/jfoenix-fonts.css").toExternalForm(), Controllers::class.java.getResource("/css/jfoenix-fonts.css").toExternalForm(),
Controllers::class.java.getResource("/css/jfoenix-design.css").toExternalForm(), Controllers::class.java.getResource("/css/jfoenix-design.css").toExternalForm(),
//Controllers::class.java.getResource("/assets/css/jfoenix-components.css").toExternalForm(),
Controllers::class.java.getResource("/assets/css/jfoenix-main-demo.css").toExternalForm()) Controllers::class.java.getResource("/assets/css/jfoenix-main-demo.css").toExternalForm())

View File

@@ -18,11 +18,14 @@
package org.jackhuang.hmcl.ui package org.jackhuang.hmcl.ui
import com.jfoenix.controls.JFXComboBox import com.jfoenix.controls.JFXComboBox
import javafx.beans.property.StringProperty
import javafx.beans.value.ChangeListener
import javafx.scene.Node import javafx.scene.Node
import javafx.scene.layout.* import javafx.scene.layout.*
import javafx.scene.paint.Paint import javafx.scene.paint.Paint
import org.jackhuang.hmcl.ProfileChangedEvent import org.jackhuang.hmcl.ProfileChangedEvent
import org.jackhuang.hmcl.ProfileLoadingEvent import org.jackhuang.hmcl.ProfileLoadingEvent
import org.jackhuang.hmcl.auth.Account
import org.jackhuang.hmcl.event.EVENT_BUS import org.jackhuang.hmcl.event.EVENT_BUS
import org.jackhuang.hmcl.event.RefreshedVersionsEvent import org.jackhuang.hmcl.event.RefreshedVersionsEvent
import org.jackhuang.hmcl.game.LauncherHelper import org.jackhuang.hmcl.game.LauncherHelper
@@ -62,6 +65,21 @@ class LeftPaneController(val leftPane: VBox) {
Settings.selectedProfile.repository.refreshVersions() Settings.selectedProfile.repository.refreshVersions()
} }
Controllers.mainPane.buttonLaunch.setOnMouseClicked { LauncherHelper.launch() } Controllers.mainPane.buttonLaunch.setOnMouseClicked { LauncherHelper.launch() }
val listener = ChangeListener<Account?> { _, _, newValue ->
if (newValue == null) {
accountItem.lblVersionName.text = "mojang@mojang.com"
accountItem.lblGameVersion.text = "Yggdrasil"
} else {
accountItem.lblVersionName.text = newValue.username
accountItem.lblGameVersion.text = accountType(newValue)
}
}
Settings.selectedAccountProperty.addListener(listener)
listener.changed(null, null, Settings.selectedAccount)
if (Settings.getAccounts().isEmpty())
Controllers.navigate(AccountsPage())
} }
private fun addChildren(content: Node) { private fun addChildren(content: Node) {
@@ -81,13 +99,10 @@ class LeftPaneController(val leftPane: VBox) {
fun onProfileChanged(event: ProfileChangedEvent) { fun onProfileChanged(event: ProfileChangedEvent) {
val profile = event.value val profile = event.value
profile.selectedVersionProperty.addListener { _, _, newValue -> profile.selectedVersionProperty.addListener { observable ->
versionChanged(newValue) versionChanged(profile.selectedVersion)
} }
} profile.selectedVersionProperty.fireValueChangedEvent()
private fun loadAccounts() {
} }
private fun loadVersions() { private fun loadVersions() {
@@ -98,7 +113,7 @@ class LeftPaneController(val leftPane: VBox) {
val ripplerContainer = RipplerContainer(item) val ripplerContainer = RipplerContainer(item)
item.onSettingsButtonClicked { item.onSettingsButtonClicked {
Controllers.decorator.showPage(Controllers.versionPane) Controllers.decorator.showPage(Controllers.versionPane)
Controllers.versionPane.loadVersionSetting(item.versionName, profile.getVersionSetting(item.versionName)) Controllers.versionPane.load(item.versionName, profile)
} }
ripplerContainer.ripplerFill = Paint.valueOf("#89E1F9") ripplerContainer.ripplerFill = Paint.valueOf("#89E1F9")
ripplerContainer.setOnMouseClicked { ripplerContainer.setOnMouseClicked {

View File

@@ -18,34 +18,16 @@
package org.jackhuang.hmcl.ui package org.jackhuang.hmcl.ui
import com.jfoenix.controls.JFXButton import com.jfoenix.controls.JFXButton
import com.jfoenix.controls.JFXComboBox
import com.jfoenix.controls.JFXListCell
import com.jfoenix.controls.JFXListView
import javafx.beans.property.SimpleStringProperty import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty import javafx.beans.property.StringProperty
import javafx.collections.FXCollections
import javafx.fxml.FXML import javafx.fxml.FXML
import javafx.scene.Node
import javafx.scene.layout.BorderPane
import javafx.scene.layout.Pane
import javafx.scene.layout.StackPane import javafx.scene.layout.StackPane
import org.jackhuang.hmcl.ProfileChangedEvent import org.jackhuang.hmcl.ui.wizard.DecoratorPage
import org.jackhuang.hmcl.ProfileLoadingEvent
import org.jackhuang.hmcl.event.EVENT_BUS
import org.jackhuang.hmcl.event.RefreshedVersionsEvent
import org.jackhuang.hmcl.game.minecraftVersion
import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.setting.VersionSetting
import org.jackhuang.hmcl.ui.animation.ContainerAnimations
import org.jackhuang.hmcl.ui.download.DownloadWizardProvider
import org.jackhuang.hmcl.ui.animation.TransitionHandler
import org.jackhuang.hmcl.ui.wizard.HasTitle
import org.jackhuang.hmcl.ui.wizard.Wizard
/** /**
* @see /assets/fxml/main.fxml * @see /assets/fxml/main.fxml
*/ */
class MainPage : BorderPane(), HasTitle { class MainPage : StackPane(), DecoratorPage {
override val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Main Page") override val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Main Page")
@FXML lateinit var buttonLaunch: JFXButton @FXML lateinit var buttonLaunch: JFXButton

View File

@@ -0,0 +1,51 @@
/*
* 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 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 java.util.concurrent.Callable
class ModController {
@FXML lateinit var scrollPane: ScrollPane
@FXML lateinit var rootPane: VBox
lateinit var modManager: ModManager
lateinit var versionId: String
fun initialize() {
scrollPane.smoothScrolling()
}
fun loadMods() {
Task.of(Callable {
modManager.refreshMods(versionId)
}).subscribe(Scheduler.JAVAFX) {
rootPane.children.clear()
for (modInfo in modManager.getMods(versionId)) {
rootPane.children += ModItem(modInfo) {
modManager.removeMods(versionId, modInfo)
}
}
}
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.JFXCheckBox
import javafx.fxml.FXML
import javafx.scene.control.Label
import javafx.scene.layout.BorderPane
import org.jackhuang.hmcl.mod.ModInfo
class ModItem(info: ModInfo, private val deleteCallback: () -> Unit) : BorderPane() {
@FXML lateinit var lblModFileName: Label
@FXML lateinit var lblModAuthor: Label
@FXML lateinit var chkEnabled: JFXCheckBox
init {
loadFXML("/assets/fxml/mod-item.fxml")
lblModFileName.text = info.fileName
lblModAuthor.text = "${info.name}, Version: ${info.version}, Game Version: ${info.mcversion}, Authors: ${info.authors}"
chkEnabled.isSelected = info.isActive
chkEnabled.selectedProperty().addListener { _, _, newValue ->
info.activeProperty.set(newValue)
}
}
fun onDelete() {
deleteCallback()
}
}

View File

@@ -81,42 +81,22 @@ open class RipplerContainer(@NamedArg("container") container: Node): StackPane()
init { init {
styleClass += "rippler-container" styleClass += "rippler-container"
this.container = container this.container = container
/*
armedProperty().addListener { o, oldVal, newVal ->
if (newVal!!.booleanValue()) {
this.releaseManualRippler = this.buttonRippler.createManualRipple()
if (this.clickedAnimation != null) {
this.clickedAnimation!!.rate = 1.0
this.clickedAnimation!!.play()
}
} else {
if (this.releaseManualRippler != null) {
this.releaseManualRippler!!.run()
}
if (this.clickedAnimation != null) {
this.clickedAnimation!!.rate = -1.0
this.clickedAnimation!!.play()
}
}
}*/
this.buttonContainer.children.add(this.buttonRippler) this.buttonContainer.children.add(this.buttonRippler)
setOnMousePressed { e -> setOnMousePressed {
if (this.clickedAnimation != null) { if (this.clickedAnimation != null) {
this.clickedAnimation!!.rate = 1.0 this.clickedAnimation!!.rate = 1.0
this.clickedAnimation!!.play() this.clickedAnimation!!.play()
} }
} }
setOnMouseReleased { e -> setOnMouseReleased {
if (this.clickedAnimation != null) { if (this.clickedAnimation != null) {
this.clickedAnimation!!.rate = -1.0 this.clickedAnimation!!.rate = -1.0
this.clickedAnimation!!.play() this.clickedAnimation!!.play()
} }
} }
focusedProperty().addListener { o, oldVal, newVal -> focusedProperty().addListener { _, _, newVal ->
if (newVal) { if (newVal) {
if (!isPressed) { if (!isPressed) {
this.buttonRippler.showOverlay() this.buttonRippler.showOverlay()
@@ -126,7 +106,7 @@ open class RipplerContainer(@NamedArg("container") container: Node): StackPane()
} }
} }
pressedProperty().addListener { _, _, _ -> this.buttonRippler.hideOverlay() } pressedProperty().addListener { _ -> this.buttonRippler.hideOverlay() }
isPickOnBounds = false isPickOnBounds = false
this.buttonContainer.isPickOnBounds = false this.buttonContainer.isPickOnBounds = false
this.buttonContainer.shapeProperty().bind(shapeProperty()) this.buttonContainer.shapeProperty().bind(shapeProperty())
@@ -157,8 +137,8 @@ open class RipplerContainer(@NamedArg("container") container: Node): StackPane()
this.updateChildren() this.updateChildren()
containerProperty.addListener { _ -> updateChildren() } containerProperty.addListener { _ -> updateChildren() }
selectedProperty.addListener { _, _, newValue -> selectedProperty.addListener { _ ->
if (newValue) background = Background(BackgroundFill(ripplerFill, defaultRadii, null)) if (selected) background = Background(BackgroundFill(ripplerFill, defaultRadii, null))
else background = Background(BackgroundFill(Color.TRANSPARENT, defaultRadii, null)) else background = Background(BackgroundFill(Color.TRANSPARENT, defaultRadii, null))
} }

View File

@@ -17,107 +17,38 @@
*/ */
package org.jackhuang.hmcl.ui package org.jackhuang.hmcl.ui
import com.jfoenix.controls.JFXScrollPane import com.jfoenix.controls.*
import com.jfoenix.controls.JFXTextField
import javafx.beans.InvalidationListener import javafx.beans.InvalidationListener
import javafx.beans.property.Property import javafx.beans.property.Property
import javafx.beans.property.SimpleStringProperty import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty import javafx.beans.property.StringProperty
import javafx.beans.value.ChangeListener
import javafx.fxml.FXML import javafx.fxml.FXML
import javafx.scene.control.Label
import javafx.scene.control.ScrollPane import javafx.scene.control.ScrollPane
import javafx.scene.layout.* import javafx.scene.layout.*
import javafx.stage.DirectoryChooser import javafx.stage.DirectoryChooser
import org.jackhuang.hmcl.setting.Profile
import org.jackhuang.hmcl.setting.VersionSetting import org.jackhuang.hmcl.setting.VersionSetting
import org.jackhuang.hmcl.ui.wizard.HasTitle import org.jackhuang.hmcl.ui.wizard.DecoratorPage
import org.jackhuang.hmcl.util.OS
class VersionPage : StackPane(), HasTitle { class VersionPage : StackPane(), DecoratorPage {
override val titleProperty: StringProperty = SimpleStringProperty(this, "title", null) override val titleProperty: StringProperty = SimpleStringProperty(this, "title", null)
var lastVersionSetting: VersionSetting? = null
@FXML lateinit var rootPane: VBox @FXML lateinit var versionSettingsController: VersionSettingsController
@FXML lateinit var scroll: ScrollPane @FXML lateinit var modController: ModController
@FXML lateinit var settingsPane: GridPane
@FXML lateinit var txtWidth: JFXTextField
@FXML lateinit var txtHeight: JFXTextField
@FXML lateinit var txtMaxMemory: JFXTextField
@FXML lateinit var txtJVMArgs: JFXTextField
@FXML lateinit var txtGameArgs: JFXTextField
@FXML lateinit var txtMetaspace: JFXTextField
@FXML lateinit var txtWrapper: JFXTextField
@FXML lateinit var txtPrecallingCommand: JFXTextField
@FXML lateinit var txtServerIP: JFXTextField
@FXML lateinit var txtGameDir: JFXTextField
@FXML lateinit var advancedSettingsPane: VBox
init { init {
loadFXML("/assets/fxml/version.fxml") loadFXML("/assets/fxml/version.fxml")
} }
fun initialize() { fun load(id: String, profile: Profile) {
JFXScrollPane.smoothScrolling(scroll)
fun validation(field: JFXTextField) = InvalidationListener { field.validate() }
fun validator(nullable: Boolean = false) = NumberValidator(nullable).apply { message = "Must be a number." }
txtWidth.setValidators(validator())
txtWidth.textProperty().addListener(validation(txtWidth))
txtHeight.setValidators(validator())
txtHeight.textProperty().addListener(validation(txtHeight))
txtMaxMemory.setValidators(validator())
txtMaxMemory.textProperty().addListener(validation(txtMaxMemory))
txtMetaspace.setValidators(validator(true))
txtMetaspace.textProperty().addListener(validation(txtMetaspace))
}
fun loadVersionSetting(id: String, version: VersionSetting) {
titleProperty.set("Version settings - " + id) titleProperty.set("Version settings - " + id)
rootPane.children -= advancedSettingsPane
lastVersionSetting?.apply { versionSettingsController.loadVersionSetting(id, profile.getVersionSetting(id))
widthProperty.unbind() modController.modManager = profile.modManager
heightProperty.unbind() modController.versionId = id
maxMemoryProperty.unbind() modController.loadMods()
javaArgsProperty.unbind()
minecraftArgsProperty.unbind()
permSizeProperty.unbind()
wrapperProperty.unbind()
precalledCommandProperty.unbind()
serverIpProperty.unbind()
}
bindInt(txtWidth, version.widthProperty)
bindInt(txtHeight, version.heightProperty)
bindInt(txtMaxMemory, version.maxMemoryProperty)
bindString(txtJVMArgs, version.javaArgsProperty)
bindString(txtGameArgs, version.minecraftArgsProperty)
bindString(txtMetaspace, version.permSizeProperty)
bindString(txtWrapper, version.wrapperProperty)
bindString(txtPrecallingCommand, version.precalledCommandProperty)
bindString(txtServerIP, version.serverIpProperty)
lastVersionSetting = version
}
private fun bindInt(textField: JFXTextField, property: Property<*>) {
textField.textProperty().unbind()
textField.textProperty().bindBidirectional(property as Property<Int>, SafeIntStringConverter())
}
private fun bindString(textField: JFXTextField, property: Property<String>) {
textField.textProperty().unbind()
textField.textProperty().bindBidirectional(property)
}
fun onShowAdvanced() {
if (!rootPane.children.contains(advancedSettingsPane))
rootPane.children += advancedSettingsPane
}
fun onExploreJavaDir() {
val chooser = DirectoryChooser()
chooser.title = "Selecting Java Directory"
val selectedDir = chooser.showDialog(Controllers.stage)
if (selectedDir != null)
txtGameDir.text = selectedDir.absolutePath
} }
} }

View File

@@ -0,0 +1,151 @@
/*
* 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.JFXCheckBox
import com.jfoenix.controls.JFXComboBox
import com.jfoenix.controls.JFXTextField
import javafx.beans.InvalidationListener
import javafx.beans.property.Property
import javafx.beans.value.ChangeListener
import javafx.fxml.FXML
import javafx.scene.control.Label
import javafx.scene.control.ScrollPane
import javafx.scene.layout.GridPane
import javafx.scene.layout.VBox
import javafx.stage.DirectoryChooser
import org.jackhuang.hmcl.setting.VersionSetting
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
@FXML lateinit var txtJVMArgs: JFXTextField
@FXML lateinit var txtGameArgs: JFXTextField
@FXML lateinit var txtMetaspace: JFXTextField
@FXML lateinit var txtWrapper: JFXTextField
@FXML lateinit var txtPrecallingCommand: JFXTextField
@FXML lateinit var txtServerIP: JFXTextField
@FXML lateinit var txtGameDir: JFXTextField
@FXML lateinit var advancedSettingsPane: VBox
@FXML lateinit var cboLauncherVisibility: JFXComboBox<*>
@FXML lateinit var cboRunDirectory: JFXComboBox<*>
@FXML lateinit var chkFullscreen: JFXCheckBox
@FXML lateinit var lblPhysicalMemory: Label
fun initialize() {
lblPhysicalMemory.text = "Physical Memory: ${OS.TOTAL_MEMORY}MB"
scroll.smoothScrolling()
fun validation(field: JFXTextField) = InvalidationListener { field.validate() }
fun validator(nullable: Boolean = false) = NumberValidator(nullable).apply { message = "Must be a number." }
txtWidth.setValidators(validator())
txtWidth.textProperty().addListener(validation(txtWidth))
txtHeight.setValidators(validator())
txtHeight.textProperty().addListener(validation(txtHeight))
txtMaxMemory.setValidators(validator())
txtMaxMemory.textProperty().addListener(validation(txtMaxMemory))
txtMetaspace.setValidators(validator(true))
txtMetaspace.textProperty().addListener(validation(txtMetaspace))
}
fun loadVersionSetting(id: String, version: VersionSetting) {
rootPane.children -= advancedSettingsPane
lastVersionSetting?.apply {
widthProperty.unbind()
heightProperty.unbind()
maxMemoryProperty.unbind()
javaArgsProperty.unbind()
minecraftArgsProperty.unbind()
permSizeProperty.unbind()
wrapperProperty.unbind()
precalledCommandProperty.unbind()
serverIpProperty.unbind()
fullscreenProperty.unbind()
unbindEnum(cboLauncherVisibility)
unbindEnum(cboRunDirectory)
}
bindInt(txtWidth, version.widthProperty)
bindInt(txtHeight, version.heightProperty)
bindInt(txtMaxMemory, version.maxMemoryProperty)
bindString(txtJVMArgs, version.javaArgsProperty)
bindString(txtGameArgs, version.minecraftArgsProperty)
bindString(txtMetaspace, version.permSizeProperty)
bindString(txtWrapper, version.wrapperProperty)
bindString(txtPrecallingCommand, version.precalledCommandProperty)
bindString(txtServerIP, version.serverIpProperty)
bindEnum(cboLauncherVisibility, version.launcherVisibilityProperty)
bindEnum(cboRunDirectory, version.gameDirTypeProperty)
chkFullscreen.selectedProperty().unbind()
chkFullscreen.selectedProperty().bindBidirectional(version.fullscreenProperty)
lastVersionSetting = version
}
private fun bindInt(textField: JFXTextField, property: Property<*>) {
textField.textProperty().unbind()
@Suppress("UNCHECKED_CAST")
textField.textProperty().bindBidirectional(property as Property<Int>, SafeIntStringConverter())
}
private fun bindString(textField: JFXTextField, property: Property<String>) {
textField.textProperty().unbind()
textField.textProperty().bindBidirectional(property)
}
private fun bindEnum(comboBox: JFXComboBox<*>, property: Property<out Enum<*>>) {
unbindEnum(comboBox)
val listener = ChangeListener<Number> { _, _, newValue ->
property.value = property.value.javaClass.enumConstants[newValue.toInt()]
}
comboBox.selectionModel.select(property.value.ordinal)
comboBox.properties["listener"] = listener
comboBox.selectionModel.selectedIndexProperty().addListener(listener)
}
private fun unbindEnum(comboBox: JFXComboBox<*>) {
@Suppress("UNCHECKED_CAST")
val listener = comboBox.properties["listener"] as? ChangeListener<Number> ?: return
comboBox.selectionModel.selectedIndexProperty().removeListener(listener)
}
fun onShowAdvanced() {
if (!rootPane.children.contains(advancedSettingsPane))
rootPane.children += advancedSettingsPane
else
rootPane.children.remove(advancedSettingsPane)
}
fun onExploreJavaDir() {
val chooser = DirectoryChooser()
chooser.title = "Selecting Java Directory"
val selectedDir = chooser.showDialog(Controllers.stage)
if (selectedDir != null)
txtGameDir.text = selectedDir.absolutePath
}
}

View File

@@ -19,6 +19,8 @@ package org.jackhuang.hmcl.ui.wizard
import javafx.beans.property.StringProperty import javafx.beans.property.StringProperty
interface HasTitle { interface DecoratorPage {
val titleProperty: StringProperty val titleProperty: StringProperty
fun onClose() {}
} }

View File

@@ -3,11 +3,11 @@
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<?import com.jfoenix.controls.JFXButton?> <?import com.jfoenix.controls.JFXButton?>
<?import javafx.scene.shape.SVGPath?>
<?import javafx.scene.image.ImageView?> <?import javafx.scene.image.ImageView?>
<?import javafx.scene.image.Image?> <?import javafx.scene.image.Image?>
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Label?>
<?import com.jfoenix.controls.JFXRadioButton?>
<fx:root xmlns="http://javafx.com/javafx" <fx:root xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
type="StackPane"> type="StackPane">
@@ -27,9 +27,11 @@
</center> </center>
</BorderPane> </BorderPane>
</StackPane> </StackPane>
<StackPane fx:id="body" style="-fx-background-radius: 0 0 5 5; -fx-background-color: rgb(255,255,255,0.87);" /> <StackPane fx:id="body" style="-fx-background-radius: 0 0 5 5; -fx-background-color: rgb(255,255,255,0.87); -fx-padding: 0 0 10 0;">
<JFXRadioButton fx:id="chkSelected" StackPane.alignment="BOTTOM_RIGHT" />
</StackPane>
</VBox> </VBox>
<StackPane fx:id="icon" StackPane.alignment="TOP_RIGHT"> <StackPane fx:id="icon" StackPane.alignment="TOP_RIGHT" pickOnBounds="false">
<ImageView StackPane.alignment="CENTER_RIGHT"> <ImageView StackPane.alignment="CENTER_RIGHT">
<StackPane.margin> <StackPane.margin>
<Insets right="12" /> <Insets right="12" />

View File

@@ -13,6 +13,7 @@
<?import javafx.collections.FXCollections?> <?import javafx.collections.FXCollections?>
<?import java.lang.String?> <?import java.lang.String?>
<?import com.jfoenix.validation.RequiredFieldValidator?> <?import com.jfoenix.validation.RequiredFieldValidator?>
<?import com.jfoenix.controls.JFXNodesList?>
<fx:root xmlns="http://javafx.com/javafx" <fx:root xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
type="StackPane"> type="StackPane">
@@ -34,10 +35,10 @@
<Label>Create a new account</Label> <Label>Create a new account</Label>
</heading> </heading>
<body> <body>
<GridPane vgap="30" hgap="30"> <GridPane vgap="15" hgap="15" style="-fx-padding: 15 0 0 0;">
<columnConstraints> <columnConstraints>
<ColumnConstraints maxWidth="100" />
<ColumnConstraints /> <ColumnConstraints />
<ColumnConstraints hgrow="ALWAYS" />
</columnConstraints> </columnConstraints>
<Label text="Type" GridPane.halignment="RIGHT" GridPane.columnIndex="0" GridPane.rowIndex="0" /> <Label text="Type" GridPane.halignment="RIGHT" GridPane.columnIndex="0" GridPane.rowIndex="0" />
<Label text="Username" GridPane.halignment="RIGHT" GridPane.columnIndex="0" GridPane.rowIndex="1" /> <Label text="Username" GridPane.halignment="RIGHT" GridPane.columnIndex="0" GridPane.rowIndex="1" />

View File

@@ -39,13 +39,13 @@
<bottom> <bottom>
<BorderPane fx:id="menuBottomBar"> <BorderPane fx:id="menuBottomBar">
<left> <left>
<JFXButton fx:id="refreshMenuButton" styleClass="toggle-icon3"> <JFXButton fx:id="refreshMenuButton" styleClass="toggle-icon4">
<graphic> <graphic>
<fx:include source="/assets/svg/refresh-black.fxml"/> <fx:include source="/assets/svg/refresh-black.fxml"/>
</graphic></JFXButton> </graphic></JFXButton>
</left> </left>
<right> <right>
<JFXButton fx:id="addMenuButton" styleClass="toggle-icon3"> <JFXButton fx:id="addMenuButton" styleClass="toggle-icon4">
<graphic> <graphic>
<fx:include source="/assets/svg/plus-black.fxml"/> <fx:include source="/assets/svg/plus-black.fxml"/>
</graphic></JFXButton> </graphic></JFXButton>

View File

@@ -4,13 +4,12 @@
<?import com.jfoenix.controls.*?> <?import com.jfoenix.controls.*?>
<fx:root <fx:root
maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
style="-fx-background-color: white;" type="BorderPane" style="-fx-background-color: white;" type="StackPane"
xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1"> xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1">
<BorderPane>
<center> <center>
<StackPane fx:id="page" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" /> <StackPane fx:id="page" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
</center> </center>
<left>
</left>
<bottom> <bottom>
<BorderPane prefHeight="50.0" prefWidth="200.0" BorderPane.alignment="CENTER"> <BorderPane prefHeight="50.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<right> <right>
@@ -22,4 +21,5 @@
</right> </right>
</BorderPane> </BorderPane>
</bottom> </bottom>
</BorderPane>
</fx:root> </fx:root>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import com.jfoenix.controls.JFXCheckBox?>
<?import com.jfoenix.controls.JFXButton?>
<fx:root xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
type="BorderPane">
<left>
<JFXCheckBox fx:id="chkEnabled" BorderPane.alignment="CENTER" />
</left>
<center>
<VBox BorderPane.alignment="CENTER">
<Label fx:id="lblModFileName" style="-fx-font-size: 15;" />
<Label fx:id="lblModAuthor" style="-fx-font-size: 10;" />
</VBox>
</center>
<right>
<JFXButton onMouseClicked="#onDelete" styleClass="toggle-icon4" BorderPane.alignment="CENTER">
<graphic>
<fx:include source="/assets/svg/delete-black.fxml"/>
</graphic>
</JFXButton>
</right>
</fx:root>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<StackPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.jackhuang.hmcl.ui.ModController">
<ScrollPane fx:id="scrollPane" fitToWidth="true" fitToHeight="true">
<VBox fx:id="rootPane" spacing="10" style="-fx-padding: 10 0 40 0;">
</VBox>
</ScrollPane>
</StackPane>

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import com.jfoenix.controls.JFXTextField?>
<?import com.jfoenix.controls.JFXButton?>
<?import com.jfoenix.controls.JFXCheckBox?>
<?import javafx.geometry.Insets?>
<?import com.jfoenix.controls.JFXComboBox?>
<?import javafx.collections.FXCollections?>
<StackPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.jackhuang.hmcl.ui.VersionSettingsController">
<ScrollPane fx:id="scroll"
style="-fx-font-size: 14; -fx-pref-width: 100%; "
fitToHeight="true" fitToWidth="true">
<VBox fx:id="rootPane" style="-fx-padding: 20;">
<GridPane fx:id="settingsPane" hgap="5" vgap="10">
<columnConstraints>
<ColumnConstraints />
<ColumnConstraints hgrow="ALWAYS" />
<ColumnConstraints />
</columnConstraints>
<Label GridPane.rowIndex="0" GridPane.columnIndex="0">Java Directory</Label>
<Label GridPane.rowIndex="1" GridPane.columnIndex="0">Max Memory</Label>
<Label GridPane.rowIndex="2" GridPane.columnIndex="0">Launcher Visibility</Label>
<Label GridPane.rowIndex="3" GridPane.columnIndex="0">Run Directory</Label>
<Label GridPane.rowIndex="4" GridPane.columnIndex="0">Dimension</Label>
<Label GridPane.rowIndex="5" GridPane.columnIndex="0"> </Label>
<JFXTextField styleClass="fit-width" fx:id="txtGameDir" GridPane.rowIndex="0" GridPane.columnIndex="1"
maxWidth="Infinity"/>
<BorderPane GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.columnSpan="2">
<left>
<JFXTextField fx:id="txtMaxMemory" prefWidth="Infinity" />
</left>
<right>
<Label fx:id="lblPhysicalMemory" />
</right>
</BorderPane>
<JFXComboBox fx:id="cboLauncherVisibility" GridPane.rowIndex="2" GridPane.columnIndex="1"
GridPane.columnSpan="2" prefWidth="Infinity">
<items>
<FXCollections fx:factory="observableArrayList">
<Label>Close</Label>
<Label>Hide</Label>
<Label>Keep</Label>
<Label>Hide and Reopen</Label>
</FXCollections>
</items>
</JFXComboBox>
<JFXComboBox fx:id="cboRunDirectory" GridPane.rowIndex="3" GridPane.columnIndex="1"
GridPane.columnSpan="2" maxWidth="Infinity">
<items>
<FXCollections fx:factory="observableArrayList">
<Label>Default(.minecraft/)</Label>
<Label>Divided(.minecraft/versions/&lt;versionName&gt;)</Label>
</FXCollections>
</items>
</JFXComboBox>
<BorderPane GridPane.rowIndex="4" GridPane.columnIndex="1" GridPane.columnSpan="2">
<left>
<HBox prefWidth="210">
<JFXTextField fx:id="txtWidth" promptText="800" prefWidth="100" />
<Label>x</Label>
<JFXTextField fx:id="txtHeight" promptText="480" prefWidth="100" />
</HBox>
</left>
<right>
<JFXCheckBox fx:id="chkFullscreen" text="Fullscreen" alignment="CENTER">
<BorderPane.margin>
<Insets right="7"/>
</BorderPane.margin>
</JFXCheckBox>
</right>
</BorderPane>
<JFXButton GridPane.rowIndex="0" GridPane.columnIndex="2" text="Explore" onMouseClicked="#onExploreJavaDir" />
</GridPane>
<HBox alignment="CENTER">
<JFXButton text="Show advanced settings" onMouseClicked="#onShowAdvanced" />
</HBox>
<VBox fx:id="advancedSettingsPane" spacing="30">
<JFXTextField labelFloat="true" promptText="JVM Args" styleClass="fit-width" fx:id="txtJVMArgs" prefWidth="${advancedSettingsPane.width}" />
<JFXTextField labelFloat="true" promptText="Game Args" styleClass="fit-width" fx:id="txtGameArgs" prefWidth="${advancedSettingsPane.width}" />
<JFXTextField labelFloat="true" promptText="Metaspace" styleClass="fit-width" fx:id="txtMetaspace" prefWidth="${advancedSettingsPane.width}" />
<JFXTextField labelFloat="true" promptText="Wrapper Launcher(like optirun)" styleClass="fit-width" fx:id="txtWrapper" prefWidth="${advancedSettingsPane.width}" />
<JFXTextField labelFloat="true" promptText="Pre-calling command" styleClass="fit-width" fx:id="txtPrecallingCommand" prefWidth="${advancedSettingsPane.width}" />
<JFXTextField labelFloat="true" promptText="Server IP" styleClass="fit-width" fx:id="txtServerIP" prefWidth="${advancedSettingsPane.width}" />
</VBox>
</VBox>
</ScrollPane>
</StackPane>

View File

@@ -1,94 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.*?> <?import com.jfoenix.controls.*?>
<?import javafx.collections.FXCollections?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?> <?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<fx:root xmlns="http://javafx.com/javafx" <fx:root xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
type="StackPane"> type="StackPane">
<ScrollPane fx:id="scroll" <JFXTabPane>
style="-fx-font-size: 14; -fx-pref-width: 100%; " <Tab text="Settings">
fitToHeight="true" fitToWidth="true"> <fx:include source="version-settings.fxml" fx:id="versionSettings" />
<VBox fx:id="rootPane" style="-fx-padding: 20;"> </Tab>
<GridPane fx:id="settingsPane" hgap="5" vgap="10"> <Tab text="Mods">
<columnConstraints> <fx:include source="mod.fxml" fx:id="mod" />
<ColumnConstraints /> </Tab>
<ColumnConstraints hgrow="ALWAYS" /> </JFXTabPane>
<ColumnConstraints />
</columnConstraints>
<Label GridPane.rowIndex="0" GridPane.columnIndex="0">Java Directory</Label>
<Label GridPane.rowIndex="1" GridPane.columnIndex="0">Max Memory</Label>
<Label GridPane.rowIndex="2" GridPane.columnIndex="0">Launcher Visibility</Label>
<Label GridPane.rowIndex="3" GridPane.columnIndex="0">Run Directory</Label>
<Label GridPane.rowIndex="4" GridPane.columnIndex="0">Dimension</Label>
<Label GridPane.rowIndex="5" GridPane.columnIndex="0"> </Label>
<JFXTextField styleClass="fit-width" fx:id="txtGameDir" GridPane.rowIndex="0" GridPane.columnIndex="1"
maxWidth="Infinity"/>
<BorderPane GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.columnSpan="2">
<left>
<JFXTextField fx:id="txtMaxMemory" maxWidth="Infinity"/>
</left>
<right>
<Label>Physical Memory: 16000MB
<BorderPane.margin>
<Insets right="7"/>
</BorderPane.margin>
</Label>
</right>
</BorderPane>
<JFXComboBox fx:id="cboLauncherVisibility" GridPane.rowIndex="2" GridPane.columnIndex="1"
GridPane.columnSpan="2" maxWidth="Infinity">
<items>
<FXCollections fx:factory="observableArrayList">
<Label>Close</Label>
<Label>Hide</Label>
<Label>Keep</Label>
<Label>Hide and Reopen</Label>
</FXCollections>
</items>
</JFXComboBox>
<JFXComboBox fx:id="cboRunDirectory" GridPane.rowIndex="3" GridPane.columnIndex="1"
GridPane.columnSpan="2" maxWidth="Infinity">
<items>
<FXCollections fx:factory="observableArrayList">
<Label>Default(.minecraft/)</Label>
<Label>Divided(.minecraft/versions/&lt;versionName&gt;)</Label>
</FXCollections>
</items>
</JFXComboBox>
<BorderPane GridPane.rowIndex="4" GridPane.columnIndex="1" GridPane.columnSpan="2">
<left>
<HBox prefWidth="210">
<JFXTextField fx:id="txtWidth" promptText="800" prefWidth="100" />
<Label>x</Label>
<JFXTextField fx:id="txtHeight" promptText="480" prefWidth="100" />
</HBox>
</left>
<right>
<JFXCheckBox fx:id="chkFullscreen" text="Fullscreen" alignment="CENTER">
<BorderPane.margin>
<Insets right="7"/>
</BorderPane.margin>
</JFXCheckBox>
</right>
</BorderPane>
<JFXButton GridPane.rowIndex="0" GridPane.columnIndex="2" text="Explore" onMouseClicked="#onExploreJavaDir" />
</GridPane>
<HBox alignment="CENTER">
<JFXButton text="Show advanced settings" onMouseClicked="#onShowAdvanced" />
</HBox>
<VBox fx:id="advancedSettingsPane" spacing="30">
<JFXTextField labelFloat="true" promptText="JVM Args" styleClass="fit-width" fx:id="txtJVMArgs" prefWidth="${advancedSettingsPane.width}" />
<JFXTextField labelFloat="true" promptText="Game Args" styleClass="fit-width" fx:id="txtGameArgs" prefWidth="${advancedSettingsPane.width}" />
<JFXTextField labelFloat="true" promptText="Metaspace" styleClass="fit-width" fx:id="txtMetaspace" prefWidth="${advancedSettingsPane.width}" />
<JFXTextField labelFloat="true" promptText="Wrapper Launcher(like optirun)" styleClass="fit-width" fx:id="txtWrapper" prefWidth="${advancedSettingsPane.width}" />
<JFXTextField labelFloat="true" promptText="Pre-calling command" styleClass="fit-width" fx:id="txtPrecallingCommand" prefWidth="${advancedSettingsPane.width}" />
<JFXTextField labelFloat="true" promptText="Server IP" styleClass="fit-width" fx:id="txtServerIP" prefWidth="${advancedSettingsPane.width}" />
</VBox>
</VBox>
</ScrollPane>
</fx:root> </fx:root>

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<javafx.scene.shape.SVGPath fill="black" content="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z" />

View File

@@ -21,6 +21,12 @@ import org.jackhuang.hmcl.util.DigestUtils
import java.net.Proxy import java.net.Proxy
class OfflineAccount private constructor(val uuid: String, override val username: String): Account() { class OfflineAccount private constructor(val uuid: String, override val username: String): Account() {
init {
if (username.isBlank())
throw IllegalArgumentException("Username cannot be blank")
}
override fun logIn(proxy: Proxy): AuthInfo { override fun logIn(proxy: Proxy): AuthInfo {
if (username.isBlank() || uuid.isBlank()) if (username.isBlank() || uuid.isBlank())
throw AuthenticationException("Username cannot be empty") throw AuthenticationException("Username cannot be empty")

View File

@@ -38,19 +38,19 @@ class DefaultDependencyManager(override val repository: DefaultGameRepository, o
return ParallelTask(*tasks) return ParallelTask(*tasks)
} }
override fun installLibraryAsync(version: Version, libraryId: String, libraryVersion: String): Task { override fun installLibraryAsync(gameVersion: String, version: Version, libraryId: String, libraryVersion: String): Task {
if (libraryId == "forge") if (libraryId == "forge")
return ForgeInstallTask(this, version, libraryVersion) then { task -> return ForgeInstallTask(this, gameVersion, version, libraryVersion) then { task ->
val newVersion = task.result!! val newVersion = task.result!!
VersionJSONSaveTask(this@DefaultDependencyManager, newVersion) VersionJSONSaveTask(this@DefaultDependencyManager, newVersion)
} }
else if (libraryId == "liteloader") else if (libraryId == "liteloader")
return LiteLoaderInstallTask(this, version, libraryVersion) then { task -> return LiteLoaderInstallTask(this, gameVersion, version, libraryVersion) then { task ->
val newVersion = task.result!! val newVersion = task.result!!
VersionJSONSaveTask(this@DefaultDependencyManager, newVersion) VersionJSONSaveTask(this@DefaultDependencyManager, newVersion)
} }
else if (libraryId == "optifine") else if (libraryId == "optifine")
return OptiFineInstallTask(this, version, libraryVersion) then { task -> return OptiFineInstallTask(this, gameVersion, version, libraryVersion) then { task ->
val newVersion = task.result!! val newVersion = task.result!!
VersionJSONSaveTask(this@DefaultDependencyManager, newVersion) VersionJSONSaveTask(this@DefaultDependencyManager, newVersion)
} }

View File

@@ -27,9 +27,10 @@ class DefaultGameBuilder(val dependencyManager: DefaultDependencyManager): GameB
val downloadProvider = dependencyManager.downloadProvider val downloadProvider = dependencyManager.downloadProvider
override fun buildAsync(): Task { override fun buildAsync(): Task {
val gameVersion = gameVersion
return VersionJSONDownloadTask(gameVersion = gameVersion) then a@{ task -> return VersionJSONDownloadTask(gameVersion = gameVersion) then a@{ task ->
var version = GSON.fromJson<Version>(task.result!!) ?: return@a null var version = GSON.fromJson<Version>(task.result!!) ?: return@a null
version = version.copy(jar = version.id, id = name) version = version.copy(id = name)
var result = ParallelTask( var result = ParallelTask(
GameAssetDownloadTask(dependencyManager, version), GameAssetDownloadTask(dependencyManager, version),
GameLoggingDownloadTask(dependencyManager, version), GameLoggingDownloadTask(dependencyManager, version),
@@ -38,21 +39,21 @@ class DefaultGameBuilder(val dependencyManager: DefaultDependencyManager): GameB
) then VersionJSONSaveTask(dependencyManager, version) ) then VersionJSONSaveTask(dependencyManager, version)
if (toolVersions.containsKey("forge")) if (toolVersions.containsKey("forge"))
result = result then libraryTaskHelper(version, "forge") result = result then libraryTaskHelper(gameVersion, version, "forge")
if (toolVersions.containsKey("liteloader")) if (toolVersions.containsKey("liteloader"))
result = result then libraryTaskHelper(version, "liteloader") result = result then libraryTaskHelper(gameVersion, version, "liteloader")
if (toolVersions.containsKey("optifine")) if (toolVersions.containsKey("optifine"))
result = result then libraryTaskHelper(version, "optifine") result = result then libraryTaskHelper(gameVersion, version, "optifine")
result result
} }
} }
private fun libraryTaskHelper(version: Version, libraryId: String): Task.(Task) -> Task = { prev -> private fun libraryTaskHelper(gameVersion: String, version: Version, libraryId: String): Task.(Task) -> Task = { prev ->
var thisVersion = version var thisVersion = version
if (prev is TaskResult<*> && prev.result is Version) { if (prev is TaskResult<*> && prev.result is Version) {
thisVersion = prev.result as Version thisVersion = prev.result as Version
} }
dependencyManager.installLibraryAsync(thisVersion, libraryId, toolVersions[libraryId]!!) dependencyManager.installLibraryAsync(gameVersion, thisVersion, libraryId, toolVersions[libraryId]!!)
} }
inner class VersionJSONDownloadTask(val gameVersion: String): Task() { inner class VersionJSONDownloadTask(val gameVersion: String): Task() {

View File

@@ -36,7 +36,7 @@ abstract class DependencyManager(open val repository: GameRepository) {
*/ */
abstract fun gameBuilder(): GameBuilder abstract fun gameBuilder(): GameBuilder
abstract fun installLibraryAsync(version: Version, libraryId: String, libraryVersion: String): Task abstract fun installLibraryAsync(gameVersion: String, version: Version, libraryId: String, libraryVersion: String): Task
/** /**
* Get registered version list. * Get registered version list.

View File

@@ -30,8 +30,9 @@ import java.io.IOException
import java.util.zip.ZipFile import java.util.zip.ZipFile
class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager, class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager,
private val version: Version, private val gameVersion: String,
private val remoteVersion: String) : TaskResult<Version>() { private val version: Version,
private val remoteVersion: String) : TaskResult<Version>() {
private val forgeVersionList = dependencyManager.getVersionList("forge") private val forgeVersionList = dependencyManager.getVersionList("forge")
private val installer: File = File("forge-installer.jar").absoluteFile private val installer: File = File("forge-installer.jar").absoluteFile
lateinit var remote: RemoteVersion<*> lateinit var remote: RemoteVersion<*>
@@ -39,15 +40,13 @@ class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager,
override val dependencies: MutableCollection<Task> = mutableListOf() override val dependencies: MutableCollection<Task> = mutableListOf()
init { init {
if (version.jar == null)
throw IllegalArgumentException()
if (!forgeVersionList.loaded) if (!forgeVersionList.loaded)
dependents += forgeVersionList.refreshAsync(dependencyManager.downloadProvider) then { dependents += forgeVersionList.refreshAsync(dependencyManager.downloadProvider) then {
remote = forgeVersionList.getVersion(version.jar, remoteVersion) ?: throw IllegalArgumentException("Remote forge version ${version.jar}, $remoteVersion not found") remote = forgeVersionList.getVersion(gameVersion, remoteVersion) ?: throw IllegalArgumentException("Remote forge version $gameVersion, $remoteVersion not found")
FileDownloadTask(remote.url.toURL(), installer) FileDownloadTask(remote.url.toURL(), installer)
} }
else { else {
remote = forgeVersionList.getVersion(version.jar, remoteVersion) ?: throw IllegalArgumentException("Remote forge version ${version.jar}, $remoteVersion not found") remote = forgeVersionList.getVersion(gameVersion, remoteVersion) ?: throw IllegalArgumentException("Remote forge version $gameVersion, $remoteVersion not found")
dependents += FileDownloadTask(remote.url.toURL(), installer) dependents += FileDownloadTask(remote.url.toURL(), installer)
} }
} }

View File

@@ -30,23 +30,22 @@ import org.jackhuang.hmcl.util.merge
* LiteLoader must be installed after Forge. * LiteLoader must be installed after Forge.
*/ */
class LiteLoaderInstallTask(private val dependencyManager: DefaultDependencyManager, class LiteLoaderInstallTask(private val dependencyManager: DefaultDependencyManager,
private val version: Version, private val gameVersion: String,
private val remoteVersion: String): TaskResult<Version>() { private val version: Version,
private val remoteVersion: String): TaskResult<Version>() {
private val liteLoaderVersionList = dependencyManager.getVersionList("liteloader") as LiteLoaderVersionList private val liteLoaderVersionList = dependencyManager.getVersionList("liteloader") as LiteLoaderVersionList
lateinit var remote: RemoteVersion<LiteLoaderRemoteVersionTag> lateinit var remote: RemoteVersion<LiteLoaderRemoteVersionTag>
override val dependents: MutableCollection<Task> = mutableListOf() override val dependents: MutableCollection<Task> = mutableListOf()
override val dependencies: MutableCollection<Task> = mutableListOf() override val dependencies: MutableCollection<Task> = mutableListOf()
init { init {
if (version.jar == null)
throw IllegalArgumentException()
if (!liteLoaderVersionList.loaded) if (!liteLoaderVersionList.loaded)
dependents += LiteLoaderVersionList.refreshAsync(dependencyManager.downloadProvider) then { dependents += LiteLoaderVersionList.refreshAsync(dependencyManager.downloadProvider) then {
remote = liteLoaderVersionList.getVersion(version.jar, remoteVersion) ?: throw IllegalArgumentException("Remote LiteLoader version ${version.jar}, $remoteVersion not found") remote = liteLoaderVersionList.getVersion(gameVersion, remoteVersion) ?: throw IllegalArgumentException("Remote LiteLoader version $gameVersion, $remoteVersion not found")
null null
} }
else { else {
remote = liteLoaderVersionList.getVersion(version.jar, remoteVersion) ?: throw IllegalArgumentException("Remote LiteLoader version ${version.jar}, $remoteVersion not found") remote = liteLoaderVersionList.getVersion(gameVersion, remoteVersion) ?: throw IllegalArgumentException("Remote LiteLoader version $gameVersion, $remoteVersion not found")
} }
} }

View File

@@ -27,23 +27,22 @@ import org.jackhuang.hmcl.task.then
import org.jackhuang.hmcl.util.merge import org.jackhuang.hmcl.util.merge
class OptiFineInstallTask(private val dependencyManager: DefaultDependencyManager, class OptiFineInstallTask(private val dependencyManager: DefaultDependencyManager,
private val version: Version, private val gameVersion: String,
private val remoteVersion: String): TaskResult<Version>() { private val version: Version,
private val remoteVersion: String): TaskResult<Version>() {
private val optiFineVersionList = dependencyManager.getVersionList("optifine") private val optiFineVersionList = dependencyManager.getVersionList("optifine")
lateinit var remote: RemoteVersion<*> lateinit var remote: RemoteVersion<*>
override val dependents: MutableCollection<Task> = mutableListOf() override val dependents: MutableCollection<Task> = mutableListOf()
override val dependencies: MutableCollection<Task> = mutableListOf() override val dependencies: MutableCollection<Task> = mutableListOf()
init { init {
if (version.jar == null)
throw IllegalArgumentException()
if (!optiFineVersionList.loaded) if (!optiFineVersionList.loaded)
dependents += optiFineVersionList.refreshAsync(dependencyManager.downloadProvider) then { dependents += optiFineVersionList.refreshAsync(dependencyManager.downloadProvider) then {
remote = optiFineVersionList.getVersion(version.jar, remoteVersion) ?: throw IllegalArgumentException("Remote LiteLoader version $remoteVersion not found") remote = optiFineVersionList.getVersion(gameVersion, remoteVersion) ?: throw IllegalArgumentException("Remote OptiFine version $gameVersion-$remoteVersion not found")
null null
} }
else { else {
remote = optiFineVersionList.getVersion(version.jar, remoteVersion) ?: throw IllegalArgumentException("Remote LiteLoader version $remoteVersion not found") remote = optiFineVersionList.getVersion(gameVersion, remoteVersion) ?: throw IllegalArgumentException("Remote OptiFine version $gameVersion-$remoteVersion not found")
} }
} }
override fun execute() { override fun execute() {

View File

@@ -41,7 +41,7 @@ open class DefaultGameRepository(var baseDirectory: File): GameRepository {
override fun getRunDirectory(id: String) = baseDirectory override fun getRunDirectory(id: String) = baseDirectory
override fun getVersionJar(version: Version): File { override fun getVersionJar(version: Version): File {
val v = version.resolve(this) val v = version.resolve(this)
val id = v.id val id = v.jar ?: v.id
return getVersionRoot(id).resolve("$id.jar") return getVersionRoot(id).resolve("$id.jar")
} }
override fun getNativeDirectory(id: String) = File(getVersionRoot(id), "$id-natives") override fun getNativeDirectory(id: String) = File(getVersionRoot(id), "$id-natives")

View File

@@ -167,7 +167,7 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
res.add(options.width.toString()) res.add(options.width.toString())
} }
if (options.serverIp != null) { if (options.serverIp != null && options.serverIp.isNotBlank()) {
val args = options.serverIp.split(":") val args = options.serverIp.split(":")
res.add("--server") res.add("--server")
res.add(args[0]) res.add(args[0])

View File

@@ -18,19 +18,34 @@
package org.jackhuang.hmcl.mod package org.jackhuang.hmcl.mod
import com.google.gson.JsonParseException import com.google.gson.JsonParseException
import org.jackhuang.hmcl.util.property.ImmediateBooleanProperty
import org.jackhuang.hmcl.util.*
import java.io.File import java.io.File
class ModInfo ( class ModInfo (
val file: File, var file: File,
val name: String, val name: String,
val description: String = "", val description: String = "",
val authors: String = "", val authors: String = "unknown",
val version: String = "", val version: String = "unknown",
val mcversion: String = "", val mcversion: String = "unknown",
val url: String = "" val url: String = ""
): Comparable<ModInfo> { ): Comparable<ModInfo> {
val isActive: Boolean val activeProperty = object : ImmediateBooleanProperty(this, "active", file.extension != DISABLED_EXTENSION) {
get() = file.extension != DISABLED_EXTENSION override fun invalidated() {
val f = file.absoluteFile
val newf: File
if (f.extension == DISABLED_EXTENSION)
newf = File(f.parentFile, f.nameWithoutExtension)
else
newf = File(f.parentFile, f.name + ".disabled")
if (f.renameTo(newf))
file = newf
}
}
@JvmName("activeProperty") get
var isActive: Boolean by activeProperty
val fileName: String = (if (isActive) file.name else file.nameWithoutExtension).substringBeforeLast(".") val fileName: String = (if (isActive) file.name else file.nameWithoutExtension).substringBeforeLast(".")
@@ -53,22 +68,23 @@ class ModInfo (
val file = if (modFile.extension == DISABLED_EXTENSION) val file = if (modFile.extension == DISABLED_EXTENSION)
modFile.absoluteFile.parentFile.resolve(modFile.nameWithoutExtension) modFile.absoluteFile.parentFile.resolve(modFile.nameWithoutExtension)
else modFile else modFile
var description = "Unrecognized mod file"
if (file.extension == "zip" || file.extension == "jar") if (file.extension == "zip" || file.extension == "jar")
try { try {
return ForgeModMetadata.fromFile(modFile) return ForgeModMetadata.fromFile(modFile)
} catch (e: JsonParseException) { } catch (ignore: Exception) {
throw e description = "May be Forge mod"
} catch (ignore: Exception) {} }
else if (file.extension == "litemod") else if (file.extension == "litemod")
try { try {
return LiteModMetadata.fromFile(modFile) return LiteModMetadata.fromFile(modFile)
} catch (e: JsonParseException) { } catch (ignore: Exception) {
throw e description = "May be LiteLoader mod"
} catch (ignore: Exception) {} }
else throw IllegalArgumentException("File $modFile is not mod") else throw IllegalArgumentException("File $modFile is not mod")
return ModInfo(file = modFile, name = modFile.nameWithoutExtension, description = "Unrecognized mod file") return ModInfo(file = modFile, name = modFile.nameWithoutExtension, description = description)
} }
} }
} }

View File

@@ -46,11 +46,13 @@ enum class OS {
} }
val TOTAL_MEMORY: Long by lazy { val TOTAL_MEMORY: Long by lazy {
ReflectionHelper.get<Long>(ManagementFactory.getOperatingSystemMXBean(), "getTotalPhysicalMemorySize") ?: 1024L val bytes = ReflectionHelper.invoke<Long>(ManagementFactory.getOperatingSystemMXBean(), "getTotalPhysicalMemorySize")
if (bytes == null) 1024
else bytes / 1024 / 1024
} }
val SUGGESTED_MEMORY: Int by lazy { val SUGGESTED_MEMORY: Int by lazy {
val memory = TOTAL_MEMORY / 1024 / 1024 / 4 val memory = TOTAL_MEMORY / 4
(Math.round(1.0 * memory / 128.0) * 128).toInt() (Math.round(1.0 * memory / 128.0) * 128).toInt()
} }

View File

@@ -0,0 +1,173 @@
/*
* 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.util.property
import javafx.beans.property.*
import javafx.beans.value.ObservableValue
open class ImmediateStringProperty(bean: Any, name: String, initialValue: String): SimpleStringProperty(bean, name, initialValue) {
override fun set(newValue: String) {
super.get()
super.set(newValue)
}
protected fun superSet(newValue: String) {
super.set(newValue)
}
override fun bind(newObservable: ObservableValue<out String>) {
super.get()
super.bind(newObservable)
}
override fun unbind() {
super.get()
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)
}
protected fun superSet(newValue: String?) {
super.set(newValue)
}
override fun bind(newObservable: ObservableValue<out String?>) {
super.get()
super.bind(newObservable)
}
override fun unbind() {
super.get()
super.unbind()
}
public override fun fireValueChangedEvent() {
super.fireValueChangedEvent()
}
}
open class ImmediateBooleanProperty(bean: Any, name: String, initialValue: Boolean): SimpleBooleanProperty(bean, name, initialValue) {
override fun set(newValue: Boolean) {
super.get()
super.set(newValue)
}
protected fun superSet(newValue: Boolean) {
super.set(newValue)
}
override fun bind(rawObservable: ObservableValue<out Boolean>?) {
super.get()
super.bind(rawObservable)
}
override fun unbind() {
super.get()
super.unbind()
}
public override fun fireValueChangedEvent() {
super.fireValueChangedEvent()
}
}
open class ImmediateIntegerProperty(bean: Any, name: String, initialValue: Int): SimpleIntegerProperty(bean, name, initialValue) {
override fun set(newValue: Int) {
super.get()
super.set(newValue)
}
protected fun superSet(newValue: Int) {
super.set(newValue)
}
override fun bind(rawObservable: ObservableValue<out Number>) {
super.get()
super.bind(rawObservable)
}
override fun unbind() {
super.get()
super.unbind()
}
public override fun fireValueChangedEvent() {
super.fireValueChangedEvent()
}
}
open class ImmediateDoubleProperty(bean: Any, name: String, initialValue: Double): SimpleDoubleProperty(bean, name, initialValue) {
override fun set(newValue: Double) {
super.get()
super.set(newValue)
}
protected fun superSet(newValue: Double) {
super.set(newValue)
}
override fun bind(rawObservable: ObservableValue<out Number>) {
super.get()
super.bind(rawObservable)
}
override fun unbind() {
super.get()
super.unbind()
}
public override fun fireValueChangedEvent() {
super.fireValueChangedEvent()
}
}
open class ImmediateObjectProperty<T>(bean: Any, name: String, initialValue: T): SimpleObjectProperty<T>(bean, name, initialValue) {
override fun set(newValue: T) {
super.get()
super.set(newValue)
}
override fun bind(rawObservable: ObservableValue<out T>) {
super.get()
super.bind(rawObservable)
}
override fun unbind() {
super.get()
super.unbind()
}
public override fun fireValueChangedEvent() {
super.fireValueChangedEvent()
}
}

View File

@@ -25,6 +25,7 @@ import org.jackhuang.hmcl.download.MojangDownloadProvider
import org.jackhuang.hmcl.game.DefaultGameRepository import org.jackhuang.hmcl.game.DefaultGameRepository
import org.jackhuang.hmcl.launch.DefaultLauncher import org.jackhuang.hmcl.launch.DefaultLauncher
import org.jackhuang.hmcl.game.LaunchOptions import org.jackhuang.hmcl.game.LaunchOptions
import org.jackhuang.hmcl.game.minecraftVersion
import org.jackhuang.hmcl.launch.ProcessListener import org.jackhuang.hmcl.launch.ProcessListener
import org.jackhuang.hmcl.util.makeCommand import org.jackhuang.hmcl.util.makeCommand
import org.jackhuang.hmcl.task.Task import org.jackhuang.hmcl.task.Task
@@ -115,10 +116,11 @@ class Test {
fun installForge() { fun installForge() {
val thread = Thread.currentThread() val thread = Thread.currentThread()
val version = repository.getVersion("test").resolve(repository) val version = repository.getVersion("test").resolve(repository)
val minecraftVersion = minecraftVersion(repository.getVersionJar(version)) ?: ""
// optifine HD_U_C4 // optifine HD_U_C4
// forge 14.21.1.2426 // forge 14.21.1.2426
// liteloader 1.12-SNAPSHOT-4 // liteloader 1.12-SNAPSHOT-4
dependency.installLibraryAsync(version, "liteloader", "1.12-SNAPSHOT-4").executor().apply { dependency.installLibraryAsync(minecraftVersion, version, "liteloader", "1.12-SNAPSHOT-4").executor().apply {
taskListener = taskListener(thread) taskListener = taskListener(thread)
}.start() }.start()
try { try {