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 javafx.beans.InvalidationListener
import javafx.beans.property.*
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider
import org.jackhuang.hmcl.download.DefaultDependencyManager
import org.jackhuang.hmcl.download.DependencyManager
import org.jackhuang.hmcl.game.HMCLGameRepository
import org.jackhuang.hmcl.mod.ModManager
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.lang.reflect.Type
class Profile(var name: String = "Default", gameDir: File = File(".minecraft")) {
val globalProperty = SimpleObjectProperty<VersionSetting>(this, "global", VersionSetting())
class Profile(var name: String = "Default", initialGameDir: File = File(".minecraft"), initialSelectedVersion: String = "") {
val globalProperty = ImmediateObjectProperty<VersionSetting>(this, "global", VersionSetting())
var global: VersionSetting by globalProperty
val selectedVersionProperty = SimpleStringProperty(this, "selectedVersion", "")
val selectedVersionProperty = ImmediateStringProperty(this, "selectedVersion", initialSelectedVersion)
var selectedVersion: String by selectedVersionProperty
val gameDirProperty = SimpleObjectProperty<File>(this, "gameDir", gameDir)
val gameDirProperty = ImmediateObjectProperty<File>(this, "gameDir", initialGameDir)
var gameDir: File by gameDirProperty
val noCommonProperty = SimpleBooleanProperty(this, "noCommon", false)
val noCommonProperty = ImmediateBooleanProperty(this, "noCommon", false)
var noCommon: Boolean by noCommonProperty
var repository = HMCLGameRepository(gameDir)
var repository = HMCLGameRepository(initialGameDir)
var dependency = DefaultDependencyManager(repository, BMCLAPIDownloadProvider)
var modManager = ModManager(repository)
init {
gameDirProperty.addListener { _, _, newValue ->
repository.baseDirectory = newValue
gameDirProperty.addListener { _ ->
repository.baseDirectory = gameDir
repository.refreshVersions()
}
selectedVersionProperty.addListener { _, _, newValue ->
if (newValue.isNotBlank() && !repository.hasVersion(newValue)) {
selectedVersionProperty.addListener { _ ->
if (selectedVersion.isNotBlank() && !repository.hasVersion(selectedVersion)) {
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 ?: ""
}
}
@@ -110,9 +113,8 @@ class Profile(var name: String = "Default", gameDir: File = File(".minecraft"))
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext): Profile? {
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)
selectedVersion = json["selectedVersion"]?.asString ?: ""
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.DownloadProvider
import org.jackhuang.hmcl.download.MojangDownloadProvider
import org.jackhuang.hmcl.util.GSON
import org.jackhuang.hmcl.util.LOG
import java.io.File
import java.util.logging.Level
import org.jackhuang.hmcl.ProfileLoadingEvent
import org.jackhuang.hmcl.ProfileChangedEvent
import org.jackhuang.hmcl.auth.Account
import org.jackhuang.hmcl.auth.Accounts
import org.jackhuang.hmcl.util.*
import org.jackhuang.hmcl.auth.OfflineAccount
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
import org.jackhuang.hmcl.event.EVENT_BUS
import org.jackhuang.hmcl.util.FileTypeAdapter
import org.jackhuang.hmcl.util.ignoreException
import org.jackhuang.hmcl.util.property.ImmediateObjectProperty
import java.net.Proxy
import java.util.*
object Settings {
val GSON = GsonBuilder()
.registerTypeAdapter(VersionSetting::class.java, VersionSetting)
@@ -53,7 +50,7 @@ object Settings {
val SETTINGS_FILE = File("hmcl.json").absoluteFile
val SETTINGS: Config
private val SETTINGS: Config
private val ACCOUNTS = mutableMapOf<String, Account>()
@@ -82,10 +79,10 @@ object Settings {
save()
if (!getProfiles().containsKey(DEFAULT_PROFILE))
getProfiles().put(DEFAULT_PROFILE, Profile());
if (!getProfileMap().containsKey(DEFAULT_PROFILE))
getProfileMap().put(DEFAULT_PROFILE, Profile());
for ((name, profile) in getProfiles().entries) {
for ((name, profile) in getProfileMap().entries) {
profile.name = name
profile.addPropertyChangedListener(InvalidationListener { save() })
}
@@ -151,23 +148,35 @@ object Settings {
return getProfile(SETTINGS.selectedProfile)
}
val selectedAccount: Account?
get() {
val a = getAccount(SETTINGS.selectedAccount)
if (a == null && ACCOUNTS.isNotEmpty()) {
val (key, acc) = ACCOUNTS.entries.first()
SETTINGS.selectedAccount = key
val selectedAccountProperty = object : ImmediateObjectProperty<Account?>(this, "selectedAccount", getAccount(SETTINGS.selectedAccount)) {
override fun get(): Account? {
val a = super.get()
if (a == null || !ACCOUNTS.containsKey(a.username)) {
val acc = if (ACCOUNTS.isEmpty()) null else ACCOUNTS.values.first()
set(acc)
return acc
}
return a
} else return a
}
fun setSelectedAccount(name: String) {
if (ACCOUNTS.containsKey(name))
SETTINGS.selectedAccount = name
override fun set(newValue: Account?) {
if (newValue == null || ACCOUNTS.containsKey(newValue.username)) {
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_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) {
ACCOUNTS[account.username] = account
@@ -183,36 +192,38 @@ object Settings {
fun deleteAccount(name: String) {
ACCOUNTS.remove(name)
selectedAccountProperty.get()
}
fun getProfile(name: String?): Profile {
var p: Profile? = getProfiles()[name ?: DEFAULT_PROFILE]
var p: Profile? = getProfileMap()[name ?: DEFAULT_PROFILE]
if (p == null)
if (getProfiles().containsKey(DEFAULT_PROFILE))
p = getProfiles()[DEFAULT_PROFILE]!!
if (getProfileMap().containsKey(DEFAULT_PROFILE))
p = getProfileMap()[DEFAULT_PROFILE]!!
else {
p = Profile()
getProfiles().put(DEFAULT_PROFILE, p)
getProfileMap().put(DEFAULT_PROFILE, p)
}
return p
}
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
}
fun getProfilesFiltered(): Collection<Profile> {
return getProfiles().values.filter { t -> t.name.isNotBlank() }
fun getProfiles(): Collection<Profile> {
return getProfileMap().values.filter { t -> t.name.isNotBlank() }
}
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
getProfiles().put(ver.name, ver)
getProfileMap().put(ver.name, ver)
return true
}
@@ -227,15 +238,15 @@ object Settings {
var notify = false
if (selectedProfile.name == ver)
notify = true
val flag = getProfiles().remove(ver) != null
val flag = getProfileMap().remove(ver) != null
if (notify && flag)
onProfileChanged()
return flag
}
internal fun onProfileChanged() {
EVENT_BUS.fireEvent(ProfileChangedEvent(SETTINGS, selectedProfile))
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 javafx.beans.InvalidationListener
import javafx.beans.property.*
import org.jackhuang.hmcl.MainApplication
import org.jackhuang.hmcl.game.LaunchOptions
import org.jackhuang.hmcl.util.*
import org.jackhuang.hmcl.util.property.*
import java.io.File
import java.io.IOException
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.
*/
val usesGlobalProperty = SimpleBooleanProperty(this, "usesGlobal", false)
val usesGlobalProperty = ImmediateBooleanProperty(this, "usesGlobal", false)
var usesGlobal: Boolean by usesGlobalProperty
// java
@@ -47,39 +47,39 @@ class VersionSetting() {
/**
* 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
/**
* 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
/**
* The command to launch java, i.e. optirun.
*/
val wrapperProperty = SimpleStringProperty(this, "wrapper", "")
val wrapperProperty = ImmediateStringProperty(this, "wrapper", "")
var wrapper: String by wrapperProperty
/**
* The permanent generation size of JVM garbage collection.
*/
val permSizeProperty = SimpleStringProperty(this, "permSize", "")
val permSizeProperty = ImmediateStringProperty(this, "permSize", "")
var permSize: String by permSizeProperty
/**
* The maximum memory that JVM can allocate.
* 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
/**
* The command that will be executed before launching the Minecraft.
* Operating system relevant.
*/
val precalledCommandProperty = SimpleStringProperty(this, "precalledCommand", "")
val precalledCommandProperty = ImmediateStringProperty(this, "precalledCommand", "")
var precalledCommand: String by precalledCommandProperty
// options
@@ -87,25 +87,25 @@ class VersionSetting() {
/**
* The user customized arguments passed to JVM.
*/
val javaArgsProperty = SimpleStringProperty(this, "javaArgs", "")
val javaArgsProperty = ImmediateStringProperty(this, "javaArgs", "")
var javaArgs: String by javaArgsProperty
/**
* The user customized arguments passed to Minecraft.
*/
val minecraftArgsProperty = SimpleStringProperty(this, "minecraftArgs", "")
val minecraftArgsProperty = ImmediateStringProperty(this, "minecraftArgs", "")
var minecraftArgs: String by minecraftArgsProperty
/**
* 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
/**
* 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
// Minecraft settings.
@@ -115,13 +115,13 @@ class VersionSetting() {
*
* Format: ip:port or without port.
*/
val serverIpProperty = SimpleStringProperty(this, "serverIp", "")
val serverIpProperty = ImmediateStringProperty(this, "serverIp", "")
var serverIp: String by serverIpProperty
/**
* True if Minecraft started in fullscreen mode.
*/
val fullscreenProperty = SimpleBooleanProperty(this, "fullscreen", false)
val fullscreenProperty = ImmediateBooleanProperty(this, "fullscreen", false)
var fullscreen: Boolean by fullscreenProperty
/**
@@ -131,7 +131,7 @@ class VersionSetting() {
* String type prevents unexpected value from causing JsonSyntaxException.
* We can only reset this field instead of recreating the whole setting file.
*/
val widthProperty = SimpleIntegerProperty(this, "width", 854)
val widthProperty = ImmediateIntegerProperty(this, "width", 854)
var width: Int by widthProperty
@@ -142,7 +142,7 @@ class VersionSetting() {
* String type prevents unexpected value from causing JsonSyntaxException.
* We can only reset this field instead of recreating the whole setting file.
*/
val heightProperty = SimpleIntegerProperty(this, "height", 480)
val heightProperty = ImmediateIntegerProperty(this, "height", 480)
var height: Int by heightProperty
@@ -150,7 +150,7 @@ class VersionSetting() {
* 0 - .minecraft<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
// launcher settings
@@ -160,7 +160,7 @@ class VersionSetting() {
* 1 - Hide the launcher when the game starts.<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
fun addPropertyChangedListener(listener: InvalidationListener) {
@@ -200,10 +200,10 @@ class VersionSetting() {
fullscreen = fullscreen,
serverIp = serverIp,
wrapper = wrapper,
proxyHost = Settings.SETTINGS.proxyHost,
proxyPort = Settings.SETTINGS.proxyPort,
proxyUser = Settings.SETTINGS.proxyUserName,
proxyPass = Settings.SETTINGS.proxyPassword,
proxyHost = Settings.PROXY_HOST,
proxyPort = Settings.PROXY_PORT,
proxyUser = Settings.PROXY_USER,
proxyPass = Settings.PROXY_PASS,
precalledCommand = precalledCommand,
noGeneratedJVMArgs = noJVMArgs
)
@@ -217,7 +217,7 @@ class VersionSetting() {
addProperty("usesGlobal", src.usesGlobal)
addProperty("javaArgs", src.javaArgs)
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("width", src.width)
addProperty("height", src.height)
@@ -239,11 +239,14 @@ class VersionSetting() {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): VersionSetting? {
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 {
usesGlobal = json["usesGlobal"]?.asBoolean ?: false
javaArgs = json["javaArgs"]?.asString ?: ""
minecraftArgs = json["minecraftArgs"]?.asString ?: ""
maxMemory = parseJsonPrimitive(json["maxMemory"]?.asJsonPrimitive)
maxMemory = maxMemoryN
permSize = json["permSize"]?.asString ?: ""
width = parseJsonPrimitive(json["width"]?.asJsonPrimitive)
height = parseJsonPrimitive(json["height"]?.asJsonPrimitive)

View File

@@ -18,38 +18,36 @@
package org.jackhuang.hmcl.ui
import com.jfoenix.controls.JFXButton
import com.jfoenix.controls.JFXCheckBox
import com.jfoenix.controls.JFXRadioButton
import com.jfoenix.effects.JFXDepthManager
import javafx.beans.binding.Bindings
import javafx.fxml.FXML
import javafx.scene.control.Label
import javafx.scene.control.ToggleGroup
import javafx.scene.layout.Pane
import javafx.scene.layout.StackPane
import javafx.scene.layout.VBox
import java.util.concurrent.Callable
class AccountItem(i: Int, width: Double, height: Double) : StackPane() {
class AccountItem(i: Int, width: Double, height: Double, group: ToggleGroup) : StackPane() {
@FXML lateinit var icon: Pane
@FXML lateinit var content: VBox
@FXML lateinit var header: StackPane
@FXML lateinit var body: StackPane
@FXML lateinit var btnDelete: JFXButton
@FXML lateinit var btnEdit: JFXButton
@FXML lateinit var lblUser: Label
@FXML lateinit var chkSelected: JFXRadioButton
@FXML lateinit var lblType: Label
init {
loadFXML("/assets/fxml/account-item.fxml")
minWidth = width
maxWidth = width
prefWidth = width
minHeight = height
maxHeight = height
prefHeight = height
JFXDepthManager.setDepth(this, 1)
chkSelected.toggleGroup = group
btnDelete.graphic = SVG.delete("white", 15.0, 15.0)
// create content
val headerColor = getDefaultColor(i % 12)
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.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty
import javafx.beans.value.ChangeListener
import javafx.scene.control.Label
import javafx.scene.control.ToggleGroup
import org.jackhuang.hmcl.auth.Account
import org.jackhuang.hmcl.auth.OfflineAccount
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.with
import org.jackhuang.hmcl.task.Task
import org.jackhuang.hmcl.ui.wizard.HasTitle
import org.jackhuang.hmcl.ui.wizard.DecoratorPage
import java.util.concurrent.Callable
class AccountsPage : StackPane(), HasTitle {
class AccountsPage() : StackPane(), DecoratorPage {
override val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Accounts")
@FXML lateinit var scrollPane: ScrollPane
@@ -48,12 +49,30 @@ class AccountsPage : StackPane(), HasTitle {
@FXML lateinit var lblCreationWarning: Label
@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 {
loadFXML("/assets/fxml/account.fxml")
children.remove(dialog)
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 ->
val visible = newValue != 0
@@ -62,27 +81,42 @@ class AccountsPage : StackPane(), HasTitle {
}
cboType.selectionModel.select(0)
Settings.selectedAccountProperty.addListener(listener)
loadAccounts()
if (Settings.getAccounts().isEmpty())
addNewAccount()
}
override fun onClose() {
Settings.selectedAccountProperty.removeListener(listener)
}
fun loadAccounts() {
val children = mutableListOf<Node>()
var i = 0
val group = ToggleGroup()
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)
Platform.runLater { scrollPane.requestLayout() }
Platform.runLater {
masonryPane.requestLayout()
scrollPane.requestLayout()
}
}
private fun buildNode(i: Int, account: Account): Node {
return AccountItem(i, Math.random() * 100 + 100, Math.random() * 100 + 100).apply {
private fun buildNode(i: Int, account: Account, group: ToggleGroup): Node {
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
lblType.text = when(account) {
is OfflineAccount -> "Offline Account"
is YggdrasilAccount -> "Yggdrasil Account"
else -> throw Error("Unsupported account: $account")
}
lblType.text = accountType(account)
btnDelete.setOnMouseClicked {
Settings.deleteAccount(account.username)
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() {
dialog.show()
}
@@ -149,4 +161,11 @@ class AccountsPage : StackPane(), HasTitle {
fun onCreationCancel() {
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) {
this.stage = stage
decorator = Decorator(stage, max = false)
decorator.mainPage = mainPane
decorator = Decorator(stage, mainPane, max = false)
decorator.showPage(null)
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
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.layout.*
import javafx.scene.paint.Color
import javafx.scene.shape.Rectangle
import javafx.stage.Screen
import javafx.stage.Stage
import javafx.stage.StageStyle
import javafx.scene.layout.BorderStrokeStyle
import javafx.scene.layout.BorderStroke
import org.jackhuang.hmcl.MainApplication
import org.jackhuang.hmcl.ui.animation.AnimationProducer
import org.jackhuang.hmcl.ui.animation.ContainerAnimations
@@ -52,7 +49,7 @@ import org.jackhuang.hmcl.util.*
import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue
class Decorator @JvmOverloads constructor(private val primaryStage: Stage, private val max: Boolean = true, min: Boolean = true) : GridPane(), AbstractWizardDisplayer {
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)
private var xOffset: Double = 0.0
@@ -370,16 +367,28 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, priva
if (content is WizardPage)
titleLabel.text = prefix + content.title
if (content is HasTitle)
if (content is DecoratorPage)
titleLabel.textProperty().bind(content.titleProperty)
}
lateinit var mainPage: Node
var category: String? = null
var nowPage: Node? = null
fun showPage(content: Node?) {
val c = content ?: mainPage
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) {

View File

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

View File

@@ -18,11 +18,14 @@
package org.jackhuang.hmcl.ui
import com.jfoenix.controls.JFXComboBox
import javafx.beans.property.StringProperty
import javafx.beans.value.ChangeListener
import javafx.scene.Node
import javafx.scene.layout.*
import javafx.scene.paint.Paint
import org.jackhuang.hmcl.ProfileChangedEvent
import org.jackhuang.hmcl.ProfileLoadingEvent
import org.jackhuang.hmcl.auth.Account
import org.jackhuang.hmcl.event.EVENT_BUS
import org.jackhuang.hmcl.event.RefreshedVersionsEvent
import org.jackhuang.hmcl.game.LauncherHelper
@@ -62,6 +65,21 @@ class LeftPaneController(val leftPane: VBox) {
Settings.selectedProfile.repository.refreshVersions()
}
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) {
@@ -81,13 +99,10 @@ class LeftPaneController(val leftPane: VBox) {
fun onProfileChanged(event: ProfileChangedEvent) {
val profile = event.value
profile.selectedVersionProperty.addListener { _, _, newValue ->
versionChanged(newValue)
profile.selectedVersionProperty.addListener { observable ->
versionChanged(profile.selectedVersion)
}
}
private fun loadAccounts() {
profile.selectedVersionProperty.fireValueChangedEvent()
}
private fun loadVersions() {
@@ -98,7 +113,7 @@ class LeftPaneController(val leftPane: VBox) {
val ripplerContainer = RipplerContainer(item)
item.onSettingsButtonClicked {
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.setOnMouseClicked {

View File

@@ -18,34 +18,16 @@
package org.jackhuang.hmcl.ui
import com.jfoenix.controls.JFXButton
import com.jfoenix.controls.JFXComboBox
import com.jfoenix.controls.JFXListCell
import com.jfoenix.controls.JFXListView
import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty
import javafx.collections.FXCollections
import javafx.fxml.FXML
import javafx.scene.Node
import javafx.scene.layout.BorderPane
import javafx.scene.layout.Pane
import javafx.scene.layout.StackPane
import org.jackhuang.hmcl.ProfileChangedEvent
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
import org.jackhuang.hmcl.ui.wizard.DecoratorPage
/**
* @see /assets/fxml/main.fxml
*/
class MainPage : BorderPane(), HasTitle {
class MainPage : StackPane(), DecoratorPage {
override val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Main Page")
@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 {
styleClass += "rippler-container"
this.container = container
/*
armedProperty().addListener { o, oldVal, newVal ->
if (newVal!!.booleanValue()) {
this.releaseManualRippler = this.buttonRippler.createManualRipple()
if (this.clickedAnimation != null) {
this.clickedAnimation!!.rate = 1.0
this.clickedAnimation!!.play()
}
} else {
if (this.releaseManualRippler != null) {
this.releaseManualRippler!!.run()
}
if (this.clickedAnimation != null) {
this.clickedAnimation!!.rate = -1.0
this.clickedAnimation!!.play()
}
}
}*/
this.buttonContainer.children.add(this.buttonRippler)
setOnMousePressed { e ->
setOnMousePressed {
if (this.clickedAnimation != null) {
this.clickedAnimation!!.rate = 1.0
this.clickedAnimation!!.play()
}
}
setOnMouseReleased { e ->
setOnMouseReleased {
if (this.clickedAnimation != null) {
this.clickedAnimation!!.rate = -1.0
this.clickedAnimation!!.play()
}
}
focusedProperty().addListener { o, oldVal, newVal ->
focusedProperty().addListener { _, _, newVal ->
if (newVal) {
if (!isPressed) {
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
this.buttonContainer.isPickOnBounds = false
this.buttonContainer.shapeProperty().bind(shapeProperty())
@@ -157,8 +137,8 @@ open class RipplerContainer(@NamedArg("container") container: Node): StackPane()
this.updateChildren()
containerProperty.addListener { _ -> updateChildren() }
selectedProperty.addListener { _, _, newValue ->
if (newValue) background = Background(BackgroundFill(ripplerFill, defaultRadii, null))
selectedProperty.addListener { _ ->
if (selected) background = Background(BackgroundFill(ripplerFill, defaultRadii, null))
else background = Background(BackgroundFill(Color.TRANSPARENT, defaultRadii, null))
}

View File

@@ -17,107 +17,38 @@
*/
package org.jackhuang.hmcl.ui
import com.jfoenix.controls.JFXScrollPane
import com.jfoenix.controls.JFXTextField
import com.jfoenix.controls.*
import javafx.beans.InvalidationListener
import javafx.beans.property.Property
import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty
import javafx.beans.value.ChangeListener
import javafx.fxml.FXML
import javafx.scene.control.Label
import javafx.scene.control.ScrollPane
import javafx.scene.layout.*
import javafx.stage.DirectoryChooser
import org.jackhuang.hmcl.setting.Profile
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)
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 versionSettingsController: VersionSettingsController
@FXML lateinit var modController: ModController
init {
loadFXML("/assets/fxml/version.fxml")
}
fun initialize() {
JFXScrollPane.smoothScrolling(scroll)
fun validation(field: JFXTextField) = InvalidationListener { field.validate() }
fun validator(nullable: Boolean = false) = NumberValidator(nullable).apply { message = "Must be a number." }
txtWidth.setValidators(validator())
txtWidth.textProperty().addListener(validation(txtWidth))
txtHeight.setValidators(validator())
txtHeight.textProperty().addListener(validation(txtHeight))
txtMaxMemory.setValidators(validator())
txtMaxMemory.textProperty().addListener(validation(txtMaxMemory))
txtMetaspace.setValidators(validator(true))
txtMetaspace.textProperty().addListener(validation(txtMetaspace))
}
fun loadVersionSetting(id: String, version: VersionSetting) {
fun load(id: String, profile: Profile) {
titleProperty.set("Version settings - " + id)
rootPane.children -= advancedSettingsPane
lastVersionSetting?.apply {
widthProperty.unbind()
heightProperty.unbind()
maxMemoryProperty.unbind()
javaArgsProperty.unbind()
minecraftArgsProperty.unbind()
permSizeProperty.unbind()
wrapperProperty.unbind()
precalledCommandProperty.unbind()
serverIpProperty.unbind()
}
bindInt(txtWidth, version.widthProperty)
bindInt(txtHeight, version.heightProperty)
bindInt(txtMaxMemory, version.maxMemoryProperty)
bindString(txtJVMArgs, version.javaArgsProperty)
bindString(txtGameArgs, version.minecraftArgsProperty)
bindString(txtMetaspace, version.permSizeProperty)
bindString(txtWrapper, version.wrapperProperty)
bindString(txtPrecallingCommand, version.precalledCommandProperty)
bindString(txtServerIP, version.serverIpProperty)
lastVersionSetting = version
}
private fun bindInt(textField: JFXTextField, property: Property<*>) {
textField.textProperty().unbind()
textField.textProperty().bindBidirectional(property as Property<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
versionSettingsController.loadVersionSetting(id, profile.getVersionSetting(id))
modController.modManager = profile.modManager
modController.versionId = id
modController.loadMods()
}
}

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
interface HasTitle {
interface DecoratorPage {
val titleProperty: StringProperty
fun onClose() {}
}

View File

@@ -3,11 +3,11 @@
<?import javafx.scene.layout.*?>
<?import com.jfoenix.controls.JFXButton?>
<?import javafx.scene.shape.SVGPath?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.image.Image?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import com.jfoenix.controls.JFXRadioButton?>
<fx:root xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
type="StackPane">
@@ -27,9 +27,11 @@
</center>
</BorderPane>
</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>
<StackPane fx:id="icon" StackPane.alignment="TOP_RIGHT">
<StackPane fx:id="icon" StackPane.alignment="TOP_RIGHT" pickOnBounds="false">
<ImageView StackPane.alignment="CENTER_RIGHT">
<StackPane.margin>
<Insets right="12" />

View File

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

View File

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

View File

@@ -4,13 +4,12 @@
<?import com.jfoenix.controls.*?>
<fx:root
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">
<BorderPane>
<center>
<StackPane fx:id="page" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
</center>
<left>
</left>
<bottom>
<BorderPane prefHeight="50.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<right>
@@ -22,4 +21,5 @@
</right>
</BorderPane>
</bottom>
</BorderPane>
</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"?>
<?import com.jfoenix.controls.*?>
<?import javafx.collections.FXCollections?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<fx:root xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
type="StackPane">
<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" 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>
<JFXTabPane>
<Tab text="Settings">
<fx:include source="version-settings.fxml" fx:id="versionSettings" />
</Tab>
<Tab text="Mods">
<fx:include source="mod.fxml" fx:id="mod" />
</Tab>
</JFXTabPane>
</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
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 {
if (username.isBlank() || uuid.isBlank())
throw AuthenticationException("Username cannot be empty")

View File

@@ -38,19 +38,19 @@ class DefaultDependencyManager(override val repository: DefaultGameRepository, o
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")
return ForgeInstallTask(this, version, libraryVersion) then { task ->
return ForgeInstallTask(this, gameVersion, version, libraryVersion) then { task ->
val newVersion = task.result!!
VersionJSONSaveTask(this@DefaultDependencyManager, newVersion)
}
else if (libraryId == "liteloader")
return LiteLoaderInstallTask(this, version, libraryVersion) then { task ->
return LiteLoaderInstallTask(this, gameVersion, version, libraryVersion) then { task ->
val newVersion = task.result!!
VersionJSONSaveTask(this@DefaultDependencyManager, newVersion)
}
else if (libraryId == "optifine")
return OptiFineInstallTask(this, version, libraryVersion) then { task ->
return OptiFineInstallTask(this, gameVersion, version, libraryVersion) then { task ->
val newVersion = task.result!!
VersionJSONSaveTask(this@DefaultDependencyManager, newVersion)
}

View File

@@ -27,9 +27,10 @@ class DefaultGameBuilder(val dependencyManager: DefaultDependencyManager): GameB
val downloadProvider = dependencyManager.downloadProvider
override fun buildAsync(): Task {
val gameVersion = gameVersion
return VersionJSONDownloadTask(gameVersion = gameVersion) then a@{ task ->
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(
GameAssetDownloadTask(dependencyManager, version),
GameLoggingDownloadTask(dependencyManager, version),
@@ -38,21 +39,21 @@ class DefaultGameBuilder(val dependencyManager: DefaultDependencyManager): GameB
) then VersionJSONSaveTask(dependencyManager, version)
if (toolVersions.containsKey("forge"))
result = result then libraryTaskHelper(version, "forge")
result = result then libraryTaskHelper(gameVersion, version, "forge")
if (toolVersions.containsKey("liteloader"))
result = result then libraryTaskHelper(version, "liteloader")
result = result then libraryTaskHelper(gameVersion, version, "liteloader")
if (toolVersions.containsKey("optifine"))
result = result then libraryTaskHelper(version, "optifine")
result = result then libraryTaskHelper(gameVersion, version, "optifine")
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
if (prev is TaskResult<*> && prev.result is 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() {

View File

@@ -36,7 +36,7 @@ abstract class DependencyManager(open val repository: GameRepository) {
*/
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.

View File

@@ -30,8 +30,9 @@ import java.io.IOException
import java.util.zip.ZipFile
class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager,
private val version: Version,
private val remoteVersion: String) : TaskResult<Version>() {
private val gameVersion: String,
private val version: Version,
private val remoteVersion: String) : TaskResult<Version>() {
private val forgeVersionList = dependencyManager.getVersionList("forge")
private val installer: File = File("forge-installer.jar").absoluteFile
lateinit var remote: RemoteVersion<*>
@@ -39,15 +40,13 @@ class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager,
override val dependencies: MutableCollection<Task> = mutableListOf()
init {
if (version.jar == null)
throw IllegalArgumentException()
if (!forgeVersionList.loaded)
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)
}
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)
}
}

View File

@@ -30,23 +30,22 @@ import org.jackhuang.hmcl.util.merge
* LiteLoader must be installed after Forge.
*/
class LiteLoaderInstallTask(private val dependencyManager: DefaultDependencyManager,
private val version: Version,
private val remoteVersion: String): TaskResult<Version>() {
private val gameVersion: String,
private val version: Version,
private val remoteVersion: String): TaskResult<Version>() {
private val liteLoaderVersionList = dependencyManager.getVersionList("liteloader") as LiteLoaderVersionList
lateinit var remote: RemoteVersion<LiteLoaderRemoteVersionTag>
override val dependents: MutableCollection<Task> = mutableListOf()
override val dependencies: MutableCollection<Task> = mutableListOf()
init {
if (version.jar == null)
throw IllegalArgumentException()
if (!liteLoaderVersionList.loaded)
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
}
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
class OptiFineInstallTask(private val dependencyManager: DefaultDependencyManager,
private val version: Version,
private val remoteVersion: String): TaskResult<Version>() {
private val gameVersion: String,
private val version: Version,
private val remoteVersion: String): TaskResult<Version>() {
private val optiFineVersionList = dependencyManager.getVersionList("optifine")
lateinit var remote: RemoteVersion<*>
override val dependents: MutableCollection<Task> = mutableListOf()
override val dependencies: MutableCollection<Task> = mutableListOf()
init {
if (version.jar == null)
throw IllegalArgumentException()
if (!optiFineVersionList.loaded)
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
}
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() {

View File

@@ -41,7 +41,7 @@ open class DefaultGameRepository(var baseDirectory: File): GameRepository {
override fun getRunDirectory(id: String) = baseDirectory
override fun getVersionJar(version: Version): File {
val v = version.resolve(this)
val id = v.id
val id = v.jar ?: v.id
return getVersionRoot(id).resolve("$id.jar")
}
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())
}
if (options.serverIp != null) {
if (options.serverIp != null && options.serverIp.isNotBlank()) {
val args = options.serverIp.split(":")
res.add("--server")
res.add(args[0])

View File

@@ -18,19 +18,34 @@
package org.jackhuang.hmcl.mod
import com.google.gson.JsonParseException
import org.jackhuang.hmcl.util.property.ImmediateBooleanProperty
import org.jackhuang.hmcl.util.*
import java.io.File
class ModInfo (
val file: File,
var file: File,
val name: String,
val description: String = "",
val authors: String = "",
val version: String = "",
val mcversion: String = "",
val authors: String = "unknown",
val version: String = "unknown",
val mcversion: String = "unknown",
val url: String = ""
): Comparable<ModInfo> {
val isActive: Boolean
get() = file.extension != DISABLED_EXTENSION
val activeProperty = object : ImmediateBooleanProperty(this, "active", 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(".")
@@ -53,22 +68,23 @@ class ModInfo (
val file = if (modFile.extension == DISABLED_EXTENSION)
modFile.absoluteFile.parentFile.resolve(modFile.nameWithoutExtension)
else modFile
var description = "Unrecognized mod file"
if (file.extension == "zip" || file.extension == "jar")
try {
return ForgeModMetadata.fromFile(modFile)
} catch (e: JsonParseException) {
throw e
} catch (ignore: Exception) {}
} catch (ignore: Exception) {
description = "May be Forge mod"
}
else if (file.extension == "litemod")
try {
return LiteModMetadata.fromFile(modFile)
} catch (e: JsonParseException) {
throw e
} catch (ignore: Exception) {}
} catch (ignore: Exception) {
description = "May be LiteLoader 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 {
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 memory = TOTAL_MEMORY / 1024 / 1024 / 4
val memory = TOTAL_MEMORY / 4
(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.launch.DefaultLauncher
import org.jackhuang.hmcl.game.LaunchOptions
import org.jackhuang.hmcl.game.minecraftVersion
import org.jackhuang.hmcl.launch.ProcessListener
import org.jackhuang.hmcl.util.makeCommand
import org.jackhuang.hmcl.task.Task
@@ -115,10 +116,11 @@ class Test {
fun installForge() {
val thread = Thread.currentThread()
val version = repository.getVersion("test").resolve(repository)
val minecraftVersion = minecraftVersion(repository.getVersionJar(version)) ?: ""
// optifine HD_U_C4
// forge 14.21.1.2426
// 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)
}.start()
try {