Able to launch game now
This commit is contained in:
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl
|
||||
|
||||
import javafx.application.Application
|
||||
import javafx.stage.Stage
|
||||
import org.jackhuang.hmcl.task.Scheduler
|
||||
import org.jackhuang.hmcl.ui.Controllers
|
||||
import org.jackhuang.hmcl.util.DEFAULT_USER_AGENT
|
||||
import org.jackhuang.hmcl.util.OS
|
||||
@@ -27,6 +28,7 @@ import java.io.File
|
||||
class MainApplication : Application() {
|
||||
|
||||
override fun start(stage: Stage) {
|
||||
PRIMARY_STAGE = stage
|
||||
Controllers.initialize(stage)
|
||||
|
||||
stage.isResizable = false
|
||||
@@ -36,6 +38,10 @@ class MainApplication : Application() {
|
||||
|
||||
companion object {
|
||||
|
||||
val VERSION = "@HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@"
|
||||
val TITLE = "HMCL $VERSION"
|
||||
lateinit var PRIMARY_STAGE: Stage
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
DEFAULT_USER_AGENT = "Hello Minecraft! Launcher"
|
||||
@@ -57,5 +63,10 @@ class MainApplication : Application() {
|
||||
}
|
||||
|
||||
fun getMinecraftDirectory(): File = getWorkingDirectory("minecraft")
|
||||
|
||||
fun stop() {
|
||||
PRIMARY_STAGE.close()
|
||||
Scheduler.shutdown()
|
||||
}
|
||||
}
|
||||
}
|
||||
41
HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.kt
Normal file
41
HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.kt
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.game
|
||||
|
||||
import org.jackhuang.hmcl.launch.DefaultLauncher
|
||||
import org.jackhuang.hmcl.setting.Settings
|
||||
import org.jackhuang.hmcl.task.Scheduler
|
||||
|
||||
object LauncherHelper {
|
||||
fun launch() {
|
||||
val profile = Settings.selectedProfile
|
||||
val repository = profile.repository
|
||||
val account = Settings.selectedAccount ?: throw IllegalStateException("No account here")
|
||||
val version = repository.getVersion(profile.selectedVersion)
|
||||
val launcher = DefaultLauncher(
|
||||
repository = repository,
|
||||
versionId = profile.selectedVersion,
|
||||
options = profile.getVersionSetting(profile.selectedVersion).toLaunchOptions(profile.gameDir),
|
||||
account = account.logIn(Settings.PROXY)
|
||||
)
|
||||
|
||||
profile.dependency.checkGameCompletionAsync(version)
|
||||
.then(launcher.launchAsync())
|
||||
.subscribe(Scheduler.JAVAFX) { println("lalala") }
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ import java.util.TreeMap
|
||||
|
||||
class Config {
|
||||
@SerializedName("last")
|
||||
var last: String = ""
|
||||
var selectedProfile: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
Settings.save()
|
||||
@@ -97,7 +97,13 @@ class Config {
|
||||
Settings.save()
|
||||
}
|
||||
@SerializedName("accounts")
|
||||
var accounts: MutableMap<String, MutableMap<*, *>> = TreeMap()
|
||||
var accounts: MutableMap<String, MutableMap<Any, Any>> = TreeMap()
|
||||
set(value) {
|
||||
field = value
|
||||
Settings.save()
|
||||
}
|
||||
@SerializedName("selectedAccount")
|
||||
var selectedAccount: String = ""
|
||||
set(value) {
|
||||
field = value
|
||||
Settings.save()
|
||||
|
||||
@@ -20,6 +20,9 @@ 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.util.*
|
||||
import java.io.File
|
||||
@@ -39,6 +42,7 @@ class Profile(var name: String = "Default", gameDir: File = File(".minecraft"))
|
||||
var noCommon: Boolean by noCommonProperty
|
||||
|
||||
var repository = HMCLGameRepository(gameDir)
|
||||
var dependency = DefaultDependencyManager(repository, BMCLAPIDownloadProvider)
|
||||
|
||||
init {
|
||||
gameDirProperty.addListener { _, _, newValue ->
|
||||
|
||||
@@ -30,8 +30,15 @@ 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.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 java.net.Proxy
|
||||
import java.util.*
|
||||
|
||||
|
||||
object Settings {
|
||||
@@ -48,8 +55,31 @@ object Settings {
|
||||
|
||||
val SETTINGS: Config
|
||||
|
||||
private val ACCOUNTS = mutableMapOf<String, Account>()
|
||||
|
||||
init {
|
||||
SETTINGS = initSettings();
|
||||
|
||||
loop@for ((name, settings) in SETTINGS.accounts) {
|
||||
val factory = when(settings["type"]) {
|
||||
"yggdrasil" -> YggdrasilAccount
|
||||
"offline" -> OfflineAccount
|
||||
else -> {
|
||||
SETTINGS.accounts.remove(name)
|
||||
continue@loop
|
||||
}
|
||||
}
|
||||
|
||||
val account = factory.fromStorage(settings)
|
||||
|
||||
if (account.username != name) {
|
||||
SETTINGS.accounts.remove(name)
|
||||
continue
|
||||
}
|
||||
|
||||
ACCOUNTS[name] = account
|
||||
}
|
||||
|
||||
save()
|
||||
|
||||
if (!getProfiles().containsKey(DEFAULT_PROFILE))
|
||||
@@ -59,6 +89,10 @@ object Settings {
|
||||
profile.name = name
|
||||
profile.addPropertyChangedListener(InvalidationListener { save() })
|
||||
}
|
||||
|
||||
ignoreException {
|
||||
Runtime.getRuntime().addShutdownHook(Thread(this::save))
|
||||
}
|
||||
}
|
||||
|
||||
fun getDownloadProvider(): DownloadProvider = when (SETTINGS.downloadtype) {
|
||||
@@ -93,16 +127,62 @@ object Settings {
|
||||
|
||||
fun save() {
|
||||
try {
|
||||
SETTINGS.accounts.clear()
|
||||
for ((name, account) in ACCOUNTS) {
|
||||
val storage = account.toStorage()
|
||||
storage["type"] = when(account) {
|
||||
is OfflineAccount -> "offline"
|
||||
is YggdrasilAccount -> "yggdrasil"
|
||||
else -> ""
|
||||
}
|
||||
SETTINGS.accounts[name] = storage
|
||||
}
|
||||
|
||||
SETTINGS_FILE.writeText(GSON.toJson(SETTINGS))
|
||||
} catch (ex: IOException) {
|
||||
LOG.log(Level.SEVERE, "Failed to save config", ex)
|
||||
}
|
||||
}
|
||||
|
||||
fun getLastProfile(): Profile {
|
||||
if (!hasProfile(SETTINGS.last))
|
||||
SETTINGS.last = DEFAULT_PROFILE
|
||||
return getProfile(SETTINGS.last)
|
||||
val selectedProfile: Profile
|
||||
get() {
|
||||
if (!hasProfile(SETTINGS.selectedProfile))
|
||||
SETTINGS.selectedProfile = DEFAULT_PROFILE
|
||||
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
|
||||
return acc
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
fun setSelectedAccount(name: String) {
|
||||
if (ACCOUNTS.containsKey(name))
|
||||
SETTINGS.selectedAccount = name
|
||||
}
|
||||
|
||||
val PROXY: Proxy = Proxy.NO_PROXY
|
||||
|
||||
fun addAccount(account: Account) {
|
||||
ACCOUNTS[account.username] = account
|
||||
}
|
||||
|
||||
fun getAccount(name: String): Account? {
|
||||
return ACCOUNTS[name]
|
||||
}
|
||||
|
||||
fun getAccounts(): Map<String, Account> {
|
||||
return Collections.unmodifiableMap(ACCOUNTS)
|
||||
}
|
||||
|
||||
fun deleteAccount(name: String) {
|
||||
ACCOUNTS.remove(name)
|
||||
}
|
||||
|
||||
fun getProfile(name: String?): Profile {
|
||||
@@ -136,16 +216,16 @@ object Settings {
|
||||
return true
|
||||
}
|
||||
|
||||
fun delProfile(ver: Profile): Boolean {
|
||||
return delProfile(ver.name)
|
||||
fun deleteProfile(ver: Profile): Boolean {
|
||||
return deleteProfile(ver.name)
|
||||
}
|
||||
|
||||
fun delProfile(ver: String): Boolean {
|
||||
fun deleteProfile(ver: String): Boolean {
|
||||
if (DEFAULT_PROFILE == ver) {
|
||||
return false
|
||||
}
|
||||
var notify = false
|
||||
if (getLastProfile().name == ver)
|
||||
if (selectedProfile.name == ver)
|
||||
notify = true
|
||||
val flag = getProfiles().remove(ver) != null
|
||||
if (notify && flag)
|
||||
@@ -154,9 +234,8 @@ object Settings {
|
||||
}
|
||||
|
||||
internal fun onProfileChanged() {
|
||||
val p = getLastProfile()
|
||||
EVENT_BUS.fireEvent(ProfileChangedEvent(SETTINGS, p))
|
||||
p.repository.refreshVersions()
|
||||
EVENT_BUS.fireEvent(ProfileChangedEvent(SETTINGS, selectedProfile))
|
||||
selectedProfile.repository.refreshVersions()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,7 +20,11 @@ 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 java.io.File
|
||||
import java.io.IOException
|
||||
import java.lang.reflect.Type
|
||||
|
||||
class VersionSetting() {
|
||||
@@ -61,14 +65,14 @@ class VersionSetting() {
|
||||
/**
|
||||
* The permanent generation size of JVM garbage collection.
|
||||
*/
|
||||
val permSizeProperty = SimpleIntegerProperty(this, "permSize", 0)
|
||||
var permSize: Int by permSizeProperty
|
||||
val permSizeProperty = SimpleStringProperty(this, "permSize", "")
|
||||
var permSize: String by permSizeProperty
|
||||
|
||||
/**
|
||||
* The maximum memory that JVM can allocate.
|
||||
* The size of JVM heap.
|
||||
*/
|
||||
val maxMemoryProperty = SimpleIntegerProperty(this, "maxMemory", 0)
|
||||
val maxMemoryProperty = SimpleIntegerProperty(this, "maxMemory", OS.SUGGESTED_MEMORY)
|
||||
var maxMemory: Int by maxMemoryProperty
|
||||
|
||||
/**
|
||||
@@ -127,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", 0)
|
||||
val widthProperty = SimpleIntegerProperty(this, "width", 854)
|
||||
var width: Int by widthProperty
|
||||
|
||||
|
||||
@@ -138,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", 0)
|
||||
val heightProperty = SimpleIntegerProperty(this, "height", 480)
|
||||
var height: Int by heightProperty
|
||||
|
||||
|
||||
@@ -179,6 +183,32 @@ class VersionSetting() {
|
||||
launcherVisibilityProperty.addListener(listener)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun toLaunchOptions(gameDir: File): LaunchOptions {
|
||||
return LaunchOptions(
|
||||
gameDir = gameDir,
|
||||
java = if (java == null) JavaVersion.fromCurrentEnvironment()
|
||||
else JavaVersion.fromExecutable(File(java)),
|
||||
versionName = MainApplication.TITLE,
|
||||
profileName = MainApplication.TITLE,
|
||||
minecraftArgs = minecraftArgs,
|
||||
javaArgs = javaArgs,
|
||||
maxMemory = maxMemory,
|
||||
metaspace = permSize.toIntOrNull(),
|
||||
width = width,
|
||||
height = height,
|
||||
fullscreen = fullscreen,
|
||||
serverIp = serverIp,
|
||||
wrapper = wrapper,
|
||||
proxyHost = Settings.SETTINGS.proxyHost,
|
||||
proxyPort = Settings.SETTINGS.proxyPort,
|
||||
proxyUser = Settings.SETTINGS.proxyUserName,
|
||||
proxyPass = Settings.SETTINGS.proxyPassword,
|
||||
precalledCommand = precalledCommand,
|
||||
noGeneratedJVMArgs = noJVMArgs
|
||||
)
|
||||
}
|
||||
|
||||
companion object Serializer: JsonSerializer<VersionSetting>, JsonDeserializer<VersionSetting> {
|
||||
override fun serialize(src: VersionSetting?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
|
||||
if (src == null) return JsonNull.INSTANCE
|
||||
@@ -214,7 +244,7 @@ class VersionSetting() {
|
||||
javaArgs = json["javaArgs"]?.asString ?: ""
|
||||
minecraftArgs = json["minecraftArgs"]?.asString ?: ""
|
||||
maxMemory = parseJsonPrimitive(json["maxMemory"]?.asJsonPrimitive)
|
||||
permSize = parseJsonPrimitive(json["permSize"]?.asJsonPrimitive)
|
||||
permSize = json["permSize"]?.asString ?: ""
|
||||
width = parseJsonPrimitive(json["width"]?.asJsonPrimitive)
|
||||
height = parseJsonPrimitive(json["height"]?.asJsonPrimitive)
|
||||
javaDir = json["javaDir"]?.asString ?: ""
|
||||
|
||||
83
HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountItem.kt
Normal file
83
HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountItem.kt
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui
|
||||
|
||||
import com.jfoenix.controls.JFXButton
|
||||
import com.jfoenix.effects.JFXDepthManager
|
||||
import javafx.beans.binding.Bindings
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.Label
|
||||
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() {
|
||||
@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 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)
|
||||
|
||||
// create content
|
||||
val headerColor = getDefaultColor(i % 12)
|
||||
header.style = "-fx-background-radius: 5 5 0 0; -fx-background-color: " + headerColor
|
||||
body.minHeight = Math.random() * 20 + 50
|
||||
|
||||
// create image view
|
||||
icon.translateYProperty().bind(Bindings.createDoubleBinding(Callable { header.boundsInParent.height - icon.height / 2 }, header.boundsInParentProperty(), icon.heightProperty()))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
152
HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountsPage.kt
Normal file
152
HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountsPage.kt
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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.*
|
||||
import javafx.application.Platform
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.control.ScrollPane
|
||||
import javafx.scene.layout.StackPane
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import javafx.beans.property.StringProperty
|
||||
import javafx.scene.control.Label
|
||||
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 java.util.concurrent.Callable
|
||||
|
||||
class AccountsPage : StackPane(), HasTitle {
|
||||
override val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Accounts")
|
||||
|
||||
@FXML lateinit var scrollPane: ScrollPane
|
||||
@FXML lateinit var masonryPane: JFXMasonryPane
|
||||
@FXML lateinit var dialog: JFXDialog
|
||||
@FXML lateinit var txtUsername: JFXTextField
|
||||
@FXML lateinit var txtPassword: JFXPasswordField
|
||||
@FXML lateinit var lblPassword: Label
|
||||
@FXML lateinit var lblCreationWarning: Label
|
||||
@FXML lateinit var cboType: JFXComboBox<String>
|
||||
|
||||
init {
|
||||
loadFXML("/assets/fxml/account.fxml")
|
||||
children.remove(dialog)
|
||||
dialog.dialogContainer = this
|
||||
|
||||
JFXScrollPane.smoothScrolling(scrollPane)
|
||||
|
||||
cboType.selectionModel.selectedIndexProperty().addListener { _, _, newValue ->
|
||||
val visible = newValue != 0
|
||||
lblPassword.isVisible = visible
|
||||
txtPassword.isVisible = visible
|
||||
}
|
||||
cboType.selectionModel.select(0)
|
||||
|
||||
loadAccounts()
|
||||
}
|
||||
|
||||
fun loadAccounts() {
|
||||
val children = mutableListOf<Node>()
|
||||
var i = 0
|
||||
for ((_, account) in Settings.getAccounts()) {
|
||||
children += buildNode(++i, account)
|
||||
}
|
||||
masonryPane.children.setAll(children)
|
||||
Platform.runLater { scrollPane.requestLayout() }
|
||||
}
|
||||
|
||||
private fun buildNode(i: Int, account: Account): Node {
|
||||
return AccountItem(i, Math.random() * 100 + 100, Math.random() * 100 + 100).apply {
|
||||
lblUser.text = account.username
|
||||
lblType.text = when(account) {
|
||||
is OfflineAccount -> "Offline Account"
|
||||
is YggdrasilAccount -> "Yggdrasil Account"
|
||||
else -> throw Error("Unsupported account: $account")
|
||||
}
|
||||
btnDelete.setOnMouseClicked {
|
||||
Settings.deleteAccount(account.username)
|
||||
Platform.runLater(this@AccountsPage::loadAccounts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
fun onCreationAccept() {
|
||||
val type = cboType.selectionModel.selectedIndex
|
||||
val username = txtUsername.text
|
||||
val password = txtPassword.text
|
||||
val task = Task.of(Callable {
|
||||
try {
|
||||
val account = when (type) {
|
||||
0 -> OfflineAccount.fromUsername(username)
|
||||
1 -> YggdrasilAccount.fromUsername(username, password)
|
||||
else -> throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
account.logIn(Settings.PROXY)
|
||||
account
|
||||
} catch (e: Exception) {
|
||||
e
|
||||
}
|
||||
})
|
||||
task.subscribe(Scheduler.JAVAFX) {
|
||||
val account = task.result
|
||||
if (account is Account) {
|
||||
Settings.addAccount(account)
|
||||
dialog.close()
|
||||
loadAccounts()
|
||||
} else if (account is Exception) {
|
||||
lblCreationWarning.text = account.localizedMessage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onCreationCancel() {
|
||||
dialog.close()
|
||||
}
|
||||
}
|
||||
40
HMCL/src/main/java/org/jackhuang/hmcl/ui/ClassTitle.kt
Normal file
40
HMCL/src/main/java/org/jackhuang/hmcl/ui/ClassTitle.kt
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.scene.layout.StackPane
|
||||
import javafx.scene.layout.VBox
|
||||
import javafx.scene.paint.Color
|
||||
import javafx.scene.shape.Rectangle
|
||||
import javafx.scene.text.Text
|
||||
|
||||
class ClassTitle(val text: String) : StackPane() {
|
||||
|
||||
init {
|
||||
val vbox = VBox()
|
||||
vbox.children += Text(text).apply {
|
||||
}
|
||||
vbox.children += Rectangle().apply {
|
||||
widthProperty().bind(vbox.widthProperty())
|
||||
height = 1.0
|
||||
fill = Color.GRAY
|
||||
}
|
||||
children.setAll(vbox)
|
||||
styleClass += "class-title"
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ package org.jackhuang.hmcl.ui
|
||||
import javafx.fxml.FXMLLoader
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.Scene
|
||||
import javafx.scene.layout.Pane
|
||||
import javafx.scene.layout.StackPane
|
||||
import javafx.stage.Stage
|
||||
|
||||
@@ -28,18 +27,22 @@ object Controllers {
|
||||
lateinit var scene: Scene private set
|
||||
lateinit var stage: Stage private set
|
||||
|
||||
lateinit var mainController: MainController
|
||||
private val mainPane: Pane = loadPane("main")
|
||||
val mainPane = MainPage()
|
||||
|
||||
lateinit var versionController: VersionController
|
||||
val versionPane: Pane = loadPane("version")
|
||||
val versionPane = VersionPage()
|
||||
|
||||
lateinit var leftPaneController: LeftPaneController
|
||||
|
||||
lateinit var decorator: Decorator
|
||||
|
||||
fun initialize(stage: Stage) {
|
||||
this.stage = stage
|
||||
|
||||
val decorator = Decorator(stage, mainPane, max = false)
|
||||
decorator = Decorator(stage, max = false)
|
||||
decorator.mainPage = mainPane
|
||||
decorator.showPage(null)
|
||||
leftPaneController = LeftPaneController(decorator.leftPane)
|
||||
|
||||
// Let root pane fix window size.
|
||||
with(mainPane.parent as StackPane) {
|
||||
mainPane.prefWidthProperty().bind(widthProperty())
|
||||
@@ -56,7 +59,7 @@ object Controllers {
|
||||
}
|
||||
|
||||
fun navigate(node: Node?) {
|
||||
//mainController.setContentPage(node)
|
||||
decorator.showPage(node)
|
||||
}
|
||||
|
||||
private fun <T> loadPane(s: String): T = FXMLLoader(Controllers::class.java.getResource("/assets/fxml/$s.fxml")).load()
|
||||
|
||||
@@ -43,9 +43,18 @@ 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
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler
|
||||
import org.jackhuang.hmcl.ui.wizard.*
|
||||
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 {
|
||||
override val wizardController: WizardController = WizardController(this)
|
||||
|
||||
class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node: Node, private val max: Boolean = true, min: Boolean = true) : GridPane() {
|
||||
private var xOffset: Double = 0.0
|
||||
private var yOffset: Double = 0.0
|
||||
private var newX: Double = 0.0
|
||||
@@ -66,7 +75,7 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node:
|
||||
@FXML lateinit var titleLabel: Label
|
||||
@FXML lateinit var leftPane: VBox
|
||||
|
||||
private val onCloseButtonActionProperty: ObjectProperty<Runnable> = SimpleObjectProperty(Runnable { this.primaryStage.close() })
|
||||
private val onCloseButtonActionProperty: ObjectProperty<Runnable> = SimpleObjectProperty(Runnable { MainApplication.stop() })
|
||||
@JvmName("onCloseButtonActionProperty") get
|
||||
var onCloseButtonAction: Runnable by onCloseButtonActionProperty
|
||||
|
||||
@@ -89,6 +98,9 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node:
|
||||
private val close = SVGGlyph(0, "CLOSE", "M810 274l-238 238 238 238-60 60-238-238-238 238-60-60 238-238-238-238 60-60 238 238 238-238z", Color.WHITE)
|
||||
.apply { setPrefSize(12.0, 12.0); setSize(12.0, 12.0) }
|
||||
|
||||
val animationHandler: TransitionHandler
|
||||
override val cancelQueue: Queue<Any> = ConcurrentLinkedQueue<Any>()
|
||||
|
||||
init {
|
||||
loadFXML("/assets/fxml/decorator.fxml")
|
||||
|
||||
@@ -111,13 +123,9 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node:
|
||||
titleContainer.addEventHandler(MouseEvent.MOUSE_ENTERED) { this.allowMove = true }
|
||||
titleContainer.addEventHandler(MouseEvent.MOUSE_EXITED) { if (!this.isDragging) this.allowMove = false }
|
||||
|
||||
this.contentPlaceHolder.children.add(node)
|
||||
(node as Region).setMinSize(0.0, 0.0)
|
||||
this.border = Border(BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderWidths(0.0, 4.0, 4.0, 4.0)))
|
||||
val clip = Rectangle()
|
||||
clip.widthProperty().bind(node.widthProperty())
|
||||
clip.heightProperty().bind(node.heightProperty())
|
||||
node.setClip(clip)
|
||||
animationHandler = TransitionHandler(contentPlaceHolder)
|
||||
|
||||
setOverflowHidden(lookup("#contentPlaceHolderRoot") as Pane)
|
||||
}
|
||||
|
||||
fun onMouseMoved(mouseEvent: MouseEvent) {
|
||||
@@ -285,18 +293,22 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node:
|
||||
this.yOffset = mouseEvent.sceneY
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun isRightEdge(x: Double, y: Double, boundsInParent: Bounds): Boolean {
|
||||
return x < this.width && x > this.width - this.contentPlaceHolder.snappedLeftInset()
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun isTopEdge(x: Double, y: Double, boundsInParent: Bounds): Boolean {
|
||||
return y >= 0.0 && y < this.contentPlaceHolder.snappedLeftInset()
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun isBottomEdge(x: Double, y: Double, boundsInParent: Bounds): Boolean {
|
||||
return y < this.height && y > this.height - this.contentPlaceHolder.snappedLeftInset()
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun isLeftEdge(x: Double, y: Double, boundsInParent: Bounds): Boolean {
|
||||
return x >= 0.0 && x < this.contentPlaceHolder.snappedLeftInset()
|
||||
}
|
||||
@@ -335,7 +347,74 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node:
|
||||
}
|
||||
}
|
||||
|
||||
fun setContent(content: Node) {
|
||||
this.contentPlaceHolder.children.setAll(content)
|
||||
private fun setContent(content: Node, animation: AnimationProducer) {
|
||||
animationHandler.setContent(content, animation)
|
||||
|
||||
if (content is Region) {
|
||||
content.setMinSize(0.0, 0.0)
|
||||
setOverflowHidden(content)
|
||||
}
|
||||
|
||||
backNavButton.isDisable = !wizardController.canPrev()
|
||||
|
||||
if (content is Refreshable)
|
||||
refreshNavButton.isVisible = true
|
||||
|
||||
if (content != mainPage)
|
||||
closeNavButton.isVisible = true
|
||||
|
||||
val prefix = if (category == null) "" else category + " - "
|
||||
|
||||
titleLabel.textProperty().unbind()
|
||||
|
||||
if (content is WizardPage)
|
||||
titleLabel.text = prefix + content.title
|
||||
|
||||
if (content is HasTitle)
|
||||
titleLabel.textProperty().bind(content.titleProperty)
|
||||
}
|
||||
|
||||
lateinit var mainPage: Node
|
||||
var category: String? = null
|
||||
|
||||
fun showPage(content: Node?) {
|
||||
onEnd()
|
||||
setContent(content ?: mainPage, ContainerAnimations.FADE.animationProducer)
|
||||
}
|
||||
|
||||
fun startWizard(wizardProvider: WizardProvider, category: String? = null) {
|
||||
this.category = category
|
||||
wizardController.provider = wizardProvider
|
||||
wizardController.onStart()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
backNavButton.isVisible = true
|
||||
backNavButton.isDisable = false
|
||||
closeNavButton.isVisible = true
|
||||
refreshNavButton.isVisible = false
|
||||
}
|
||||
|
||||
override fun onEnd() {
|
||||
backNavButton.isVisible = false
|
||||
closeNavButton.isVisible = false
|
||||
refreshNavButton.isVisible = false
|
||||
}
|
||||
|
||||
override fun navigateTo(page: Node, nav: Navigation.NavigationDirection) {
|
||||
setContent(page, nav.animation.animationProducer)
|
||||
}
|
||||
|
||||
fun onRefresh() {
|
||||
(contentPlaceHolder.children.single() as Refreshable).refresh()
|
||||
}
|
||||
|
||||
fun onCloseNav() {
|
||||
wizardController.onCancel()
|
||||
showPage(null)
|
||||
}
|
||||
|
||||
fun onBack() {
|
||||
wizardController.onPrev(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ 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
|
||||
|
||||
@@ -99,7 +100,7 @@ fun takeSnapshot(node: Parent, width: Double, height: Double): WritableImage {
|
||||
return scene.snapshot(null)
|
||||
}
|
||||
|
||||
fun setOverflowHidden(node: Pane) {
|
||||
fun setOverflowHidden(node: Region) {
|
||||
val rectangle = Rectangle()
|
||||
rectangle.widthProperty().bind(node.widthProperty())
|
||||
rectangle.heightProperty().bind(node.heightProperty())
|
||||
@@ -109,5 +110,5 @@ fun setOverflowHidden(node: Pane) {
|
||||
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-components.css").toExternalForm(),
|
||||
Controllers::class.java.getResource("/assets/css/jfoenix-main-demo.css").toExternalForm())
|
||||
29
HMCL/src/main/java/org/jackhuang/hmcl/ui/IconedItem.kt
Normal file
29
HMCL/src/main/java/org/jackhuang/hmcl/ui/IconedItem.kt
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.geometry.Pos
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.layout.HBox
|
||||
|
||||
class IconedItem(val icon: Node, val text: String)
|
||||
: RipplerContainer(HBox().apply {
|
||||
children += icon.apply { isMouseTransparent = true }
|
||||
children += Label(text).apply { alignment = Pos.CENTER; isMouseTransparent = true }
|
||||
})
|
||||
120
HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.kt
Normal file
120
HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.kt
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.JFXComboBox
|
||||
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.event.EVENT_BUS
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent
|
||||
import org.jackhuang.hmcl.game.LauncherHelper
|
||||
import org.jackhuang.hmcl.game.minecraftVersion
|
||||
import org.jackhuang.hmcl.setting.Settings
|
||||
import org.jackhuang.hmcl.ui.download.DownloadWizardProvider
|
||||
|
||||
class LeftPaneController(val leftPane: VBox) {
|
||||
val versionsPane = VBox()
|
||||
val cboProfiles = JFXComboBox<String>().apply { items.add("Default"); prefWidthProperty().bind(leftPane.widthProperty()) }
|
||||
val accountItem = VersionListItem("mojang@mojang.com", "Yggdrasil")
|
||||
|
||||
init {
|
||||
addChildren(ClassTitle("ACCOUNTS"))
|
||||
addChildren(RipplerContainer(accountItem).apply {
|
||||
accountItem.onSettingsButtonClicked {
|
||||
Controllers.navigate(AccountsPage())
|
||||
}
|
||||
})
|
||||
addChildren(ClassTitle("LAUNCHER"))
|
||||
addChildren(IconedItem(SVG.gear("black"), "Settings").apply { prefWidthProperty().bind(leftPane.widthProperty()) })
|
||||
addChildren(ClassTitle("PROFILES"))
|
||||
addChildren(cboProfiles)
|
||||
addChildren(ClassTitle("VERSIONS"))
|
||||
addChildren(versionsPane)
|
||||
|
||||
EVENT_BUS.channel<RefreshedVersionsEvent>() += this::loadVersions
|
||||
EVENT_BUS.channel<ProfileLoadingEvent>() += this::onProfilesLoading
|
||||
EVENT_BUS.channel<ProfileChangedEvent>() += this::onProfileChanged
|
||||
|
||||
Settings.onProfileLoading()
|
||||
|
||||
Controllers.decorator.addMenuButton.setOnMouseClicked {
|
||||
Controllers.decorator.startWizard(DownloadWizardProvider(), "Install New Game")
|
||||
}
|
||||
Controllers.decorator.refreshMenuButton.setOnMouseClicked {
|
||||
Settings.selectedProfile.repository.refreshVersions()
|
||||
}
|
||||
Controllers.mainPane.buttonLaunch.setOnMouseClicked { LauncherHelper.launch() }
|
||||
}
|
||||
|
||||
private fun addChildren(content: Node) {
|
||||
if (content is Pane) {
|
||||
leftPane.children += content
|
||||
} else {
|
||||
val pane = StackPane()
|
||||
pane.styleClass += "left-pane-item"
|
||||
pane.children.setAll(content)
|
||||
leftPane.children += pane
|
||||
}
|
||||
}
|
||||
|
||||
fun onProfilesLoading() {
|
||||
// TODO: Profiles
|
||||
}
|
||||
|
||||
fun onProfileChanged(event: ProfileChangedEvent) {
|
||||
val profile = event.value
|
||||
profile.selectedVersionProperty.addListener { _, _, newValue ->
|
||||
versionChanged(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadAccounts() {
|
||||
|
||||
}
|
||||
|
||||
private fun loadVersions() {
|
||||
val profile = Settings.selectedProfile
|
||||
versionsPane.children.clear()
|
||||
profile.repository.getVersions().forEach { version ->
|
||||
val item = VersionListItem(version.id, minecraftVersion(profile.repository.getVersionJar(version.id)) ?: "Unknown")
|
||||
val ripplerContainer = RipplerContainer(item)
|
||||
item.onSettingsButtonClicked {
|
||||
Controllers.decorator.showPage(Controllers.versionPane)
|
||||
Controllers.versionPane.loadVersionSetting(item.versionName, profile.getVersionSetting(item.versionName))
|
||||
}
|
||||
ripplerContainer.ripplerFill = Paint.valueOf("#89E1F9")
|
||||
ripplerContainer.setOnMouseClicked {
|
||||
// clean selected property
|
||||
versionsPane.children.forEach { if (it is RipplerContainer) it.selected = false }
|
||||
ripplerContainer.selected = true
|
||||
profile.selectedVersion = version.id
|
||||
}
|
||||
ripplerContainer.userData = version.id to item
|
||||
versionsPane.children += ripplerContainer
|
||||
}
|
||||
}
|
||||
|
||||
fun versionChanged(selectedVersion: String) {
|
||||
versionsPane.children
|
||||
.filter { it is RipplerContainer && it.userData is Pair<*, *> }
|
||||
.forEach { (it as RipplerContainer).selected = (it.userData as Pair<String, VersionListItem>).first == selectedVersion }
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,12 @@ 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
|
||||
@@ -36,11 +39,19 @@ 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
|
||||
*/
|
||||
class MainController {
|
||||
class MainPage : BorderPane(), HasTitle {
|
||||
override val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Main Page")
|
||||
|
||||
@FXML lateinit var buttonLaunch: JFXButton
|
||||
|
||||
init {
|
||||
loadFXML("/assets/fxml/main.fxml")
|
||||
}
|
||||
|
||||
}
|
||||
46
HMCL/src/main/java/org/jackhuang/hmcl/ui/NumberValidator.kt
Normal file
46
HMCL/src/main/java/org/jackhuang/hmcl/ui/NumberValidator.kt
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.validation.base.ValidatorBase
|
||||
import javafx.scene.control.TextInputControl
|
||||
|
||||
class NumberValidator @JvmOverloads constructor(val nullable: Boolean = false) : ValidatorBase() {
|
||||
|
||||
override fun eval() {
|
||||
if (this.srcControl.get() is TextInputControl) {
|
||||
this.evalTextInputField()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun evalTextInputField() {
|
||||
val textField = this.srcControl.get() as TextInputControl
|
||||
|
||||
if (textField.text.isBlank())
|
||||
hasErrors.set(false)
|
||||
else
|
||||
try {
|
||||
Integer.parseInt(textField.text)
|
||||
this.hasErrors.set(false)
|
||||
} catch (var3: Exception) {
|
||||
this.hasErrors.set(true)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
230
HMCL/src/main/java/org/jackhuang/hmcl/ui/RipplerContainer.kt
Normal file
230
HMCL/src/main/java/org/jackhuang/hmcl/ui/RipplerContainer.kt
Normal file
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* 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.JFXRippler
|
||||
import javafx.animation.Transition
|
||||
import javafx.beans.DefaultProperty
|
||||
import javafx.beans.NamedArg
|
||||
import javafx.beans.Observable
|
||||
import javafx.beans.binding.Bindings
|
||||
import javafx.beans.property.SimpleBooleanProperty
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.geometry.Insets
|
||||
import javafx.geometry.Pos
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.layout.*
|
||||
import javafx.scene.paint.Color
|
||||
import javafx.scene.paint.Paint
|
||||
import javafx.scene.shape.Rectangle
|
||||
import javafx.scene.shape.Shape
|
||||
import org.jackhuang.hmcl.util.getValue
|
||||
import org.jackhuang.hmcl.util.setValue
|
||||
import java.util.concurrent.Callable
|
||||
|
||||
@DefaultProperty("container")
|
||||
open class RipplerContainer(@NamedArg("container") container: Node): StackPane() {
|
||||
val containerProperty = SimpleObjectProperty<Node>(this, "container", null)
|
||||
@JvmName("containerProperty") get
|
||||
var container: Node by containerProperty
|
||||
|
||||
val ripplerFillProperty = SimpleObjectProperty<Paint>(this, "ripplerFill", null)
|
||||
@JvmName("ripplerFillProperty") get
|
||||
var ripplerFill: Paint? by ripplerFillProperty
|
||||
|
||||
val selectedProperty = SimpleBooleanProperty(this, "selected", false)
|
||||
@JvmName("selectedProperty") get
|
||||
var selected: Boolean by selectedProperty
|
||||
|
||||
private val buttonContainer = StackPane()
|
||||
private val buttonRippler = object : JFXRippler(StackPane()) {
|
||||
override fun getMask(): Node {
|
||||
val mask = StackPane()
|
||||
mask.shapeProperty().bind(buttonContainer.shapeProperty())
|
||||
mask.backgroundProperty().bind(Bindings.createObjectBinding<Background>(Callable<Background> { Background(BackgroundFill(Color.WHITE, if (buttonContainer.backgroundProperty().get() != null && buttonContainer.getBackground().getFills().size > 0) buttonContainer.background.fills[0].radii else defaultRadii, if (buttonContainer.backgroundProperty().get() != null && buttonContainer.background.fills.size > 0) buttonContainer.background.fills[0].insets else Insets.EMPTY)) }, buttonContainer.backgroundProperty()))
|
||||
mask.resize(buttonContainer.width - buttonContainer.snappedRightInset() - buttonContainer.snappedLeftInset(), buttonContainer.height - buttonContainer.snappedBottomInset() - buttonContainer.snappedTopInset())
|
||||
return mask
|
||||
}
|
||||
|
||||
override fun initListeners() {
|
||||
this.ripplerPane.setOnMousePressed { event ->
|
||||
if (releaseManualRippler != null) {
|
||||
releaseManualRippler!!.run()
|
||||
}
|
||||
|
||||
releaseManualRippler = null
|
||||
this.createRipple(event.x, event.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
private var clickedAnimation: Transition? = null
|
||||
private val defaultRadii = CornerRadii(3.0)
|
||||
private var invalid = true
|
||||
private var releaseManualRippler: Runnable? = null
|
||||
|
||||
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 ->
|
||||
if (this.clickedAnimation != null) {
|
||||
this.clickedAnimation!!.rate = 1.0
|
||||
this.clickedAnimation!!.play()
|
||||
}
|
||||
|
||||
}
|
||||
setOnMouseReleased { e ->
|
||||
if (this.clickedAnimation != null) {
|
||||
this.clickedAnimation!!.rate = -1.0
|
||||
this.clickedAnimation!!.play()
|
||||
}
|
||||
|
||||
}
|
||||
focusedProperty().addListener { o, oldVal, newVal ->
|
||||
if (newVal) {
|
||||
if (!isPressed) {
|
||||
this.buttonRippler.showOverlay()
|
||||
}
|
||||
} else {
|
||||
this.buttonRippler.hideOverlay()
|
||||
}
|
||||
|
||||
}
|
||||
pressedProperty().addListener { _, _, _ -> this.buttonRippler.hideOverlay() }
|
||||
isPickOnBounds = false
|
||||
this.buttonContainer.isPickOnBounds = false
|
||||
this.buttonContainer.shapeProperty().bind(shapeProperty())
|
||||
this.buttonContainer.borderProperty().bind(borderProperty())
|
||||
this.buttonContainer.backgroundProperty().bind(Bindings.createObjectBinding<Background>(Callable<Background> {
|
||||
if (background == null || this.isJavaDefaultBackground(background) || this.isJavaDefaultClickedBackground(background)) {
|
||||
background = Background(BackgroundFill(Color.TRANSPARENT, defaultRadii, null))
|
||||
}
|
||||
|
||||
try {
|
||||
return@Callable(
|
||||
if (background != null && (background.fills[0] as BackgroundFill).insets == Insets(-0.2, -0.2, -0.2, -0.2))
|
||||
Background(BackgroundFill((if (background != null) (background.fills[0] as BackgroundFill).fill else Color.TRANSPARENT) as Paint,
|
||||
if (backgroundProperty().get() != null) background.fills[0].radii else defaultRadii, Insets.EMPTY))
|
||||
else
|
||||
Background(BackgroundFill((if (background != null) background.fills[0].fill else Color.TRANSPARENT) as Paint,
|
||||
if (background != null) background.fills[0].radii else defaultRadii, Insets.EMPTY))
|
||||
)
|
||||
} catch (var3: Exception) {
|
||||
return@Callable background
|
||||
}
|
||||
}, backgroundProperty()))
|
||||
ripplerFillProperty.addListener { o, oldVal, newVal -> this.buttonRippler.ripplerFill = newVal }
|
||||
if (background == null || this.isJavaDefaultBackground(background)) {
|
||||
background = Background(BackgroundFill(Color.TRANSPARENT, this.defaultRadii, null))
|
||||
}
|
||||
|
||||
this.updateChildren()
|
||||
|
||||
containerProperty.addListener { _ -> updateChildren() }
|
||||
selectedProperty.addListener { _, _, newValue ->
|
||||
if (newValue) background = Background(BackgroundFill(ripplerFill, defaultRadii, null))
|
||||
else background = Background(BackgroundFill(Color.TRANSPARENT, defaultRadii, null))
|
||||
}
|
||||
|
||||
shape = Rectangle().apply {
|
||||
widthProperty().bind(this@RipplerContainer.widthProperty())
|
||||
heightProperty().bind(this@RipplerContainer.heightProperty())
|
||||
}
|
||||
}
|
||||
|
||||
protected fun updateChildren() {
|
||||
children.add(container)
|
||||
|
||||
if (this.buttonContainer != null) {
|
||||
this.children.add(0, this.buttonContainer)
|
||||
}
|
||||
|
||||
for (i in 1..this.children.size - 1) {
|
||||
this.children[i].isPickOnBounds = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun layoutChildren(x: Double, y: Double, w: Double, h: Double) {
|
||||
if (this.invalid) {
|
||||
if (ripplerFill == null) {
|
||||
for (i in this.children.size - 1 downTo 1) {
|
||||
if (this.children[i] is Shape) {
|
||||
this.buttonRippler.ripplerFill = (this.children[i] as Shape).fill
|
||||
(this.children[i] as Shape).fillProperty().addListener { o, oldVal, newVal -> this.buttonRippler.ripplerFill = newVal }
|
||||
break
|
||||
}
|
||||
|
||||
if (this.children[i] is Label) {
|
||||
this.buttonRippler.ripplerFill = (this.children[i] as Label).textFill
|
||||
(this.children[i] as Label).textFillProperty().addListener { o, oldVal, newVal -> this.buttonRippler.ripplerFill = newVal }
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.buttonRippler.ripplerFill = ripplerFill
|
||||
}
|
||||
|
||||
this.invalid = false
|
||||
}
|
||||
|
||||
val shift = 1.0
|
||||
this.buttonContainer.resizeRelocate(layoutBounds.minX - shift, layoutBounds.minY - shift, width + 2.0 * shift, height + 2.0 * shift)
|
||||
//this.layoutLabelInArea(x, y, w, h)
|
||||
}
|
||||
|
||||
private fun isJavaDefaultBackground(background: Background): Boolean {
|
||||
try {
|
||||
val firstFill = (background.fills[0] as BackgroundFill).fill.toString()
|
||||
return "0xffffffba" == firstFill || "0xffffffbf" == firstFill || "0xffffffbd" == firstFill
|
||||
} catch (var3: Exception) {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun isJavaDefaultClickedBackground(background: Background): Boolean {
|
||||
try {
|
||||
return "0x039ed3ff" == (background.fills[0] as BackgroundFill).fill.toString()
|
||||
} catch (var3: Exception) {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -50,8 +50,10 @@ object SVG {
|
||||
return svg
|
||||
}
|
||||
|
||||
fun gear(): Node = createSVGPath("M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z")
|
||||
fun back(fill: String = "white"): Node = createSVGPath("M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z", fill)
|
||||
fun close(fill: String = "white"): Node = createSVGPath("M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z", fill)
|
||||
fun dotsVertical(fill: String = "white"): Node = createSVGPath("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z", fill)
|
||||
fun gear(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z", fill, width, height)
|
||||
fun back(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z", fill, width, height)
|
||||
fun close(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z", fill, width, height)
|
||||
fun dotsVertical(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z", fill, width, height)
|
||||
fun delete(fill: String = "white", width: Double = 20.0, height: Double = 20.0): Node = createSVGPath("M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z", fill, width, height)
|
||||
fun accountEdit(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M21.7,13.35L20.7,14.35L18.65,12.3L19.65,11.3C19.86,11.09 20.21,11.09 20.42,11.3L21.7,12.58C21.91,12.79 21.91,13.14 21.7,13.35M12,18.94L18.06,12.88L20.11,14.93L14.06,21H12V18.94M12,14C7.58,14 4,15.79 4,18V20H10V18.11L14,14.11C13.34,14.03 12.67,14 12,14M12,4A4,4 0 0,0 8,8A4,4 0 0,0 12,12A4,4 0 0,0 16,8A4,4 0 0,0 12,4Z", fill, width, height)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.util.StringConverter
|
||||
|
||||
class SafeIntStringConverter : StringConverter<Int>() {
|
||||
/** {@inheritDoc} */
|
||||
override fun fromString(value: String?) = value?.toIntOrNull()
|
||||
|
||||
/** {@inheritDoc} */
|
||||
override fun toString(value: Int?) = value?.toString() ?: ""
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui
|
||||
|
||||
import com.jfoenix.controls.JFXButton
|
||||
import com.jfoenix.controls.JFXScrollPane
|
||||
import com.jfoenix.controls.JFXTextField
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.control.ScrollPane
|
||||
import javafx.scene.layout.ColumnConstraints
|
||||
import javafx.scene.layout.GridPane
|
||||
import javafx.scene.layout.Priority
|
||||
import javafx.scene.paint.Color
|
||||
import javafx.stage.DirectoryChooser
|
||||
import org.jackhuang.hmcl.setting.VersionSetting
|
||||
|
||||
class VersionController {
|
||||
@FXML
|
||||
lateinit var titleLabel: Label
|
||||
@FXML
|
||||
lateinit var backButton: JFXButton
|
||||
@FXML
|
||||
lateinit var scroll: ScrollPane
|
||||
@FXML
|
||||
lateinit var settingsPane: GridPane
|
||||
@FXML
|
||||
lateinit var txtGameDir: JFXTextField
|
||||
|
||||
fun initialize() {
|
||||
Controllers.versionController = this
|
||||
|
||||
settingsPane.columnConstraints.addAll(
|
||||
ColumnConstraints(),
|
||||
ColumnConstraints().apply { hgrow = Priority.ALWAYS },
|
||||
ColumnConstraints()
|
||||
)
|
||||
|
||||
backButton.ripplerFill = Color.WHITE
|
||||
backButton.setOnMouseClicked {
|
||||
Controllers.navigate(null)
|
||||
}
|
||||
|
||||
JFXScrollPane.smoothScrolling(scroll)
|
||||
}
|
||||
|
||||
fun loadVersionSetting(id: String, version: VersionSetting) {
|
||||
titleLabel.text = id
|
||||
}
|
||||
|
||||
fun onExploreJavaDir() {
|
||||
val chooser = DirectoryChooser()
|
||||
chooser.title = "Selecting Java Directory"
|
||||
val selectedDir = chooser.showDialog(Controllers.stage)
|
||||
if (selectedDir != null)
|
||||
txtGameDir.text = selectedDir.absolutePath
|
||||
}
|
||||
}
|
||||
@@ -39,10 +39,6 @@ class VersionListItem(val versionName: String, val gameVersion: String) : Border
|
||||
handler()
|
||||
}
|
||||
|
||||
fun onLaunch() {
|
||||
|
||||
}
|
||||
|
||||
fun onSettingsButtonClicked(handler: () -> Unit) {
|
||||
this.handler = handler
|
||||
}
|
||||
|
||||
123
HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionPage.kt
Normal file
123
HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionPage.kt
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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.JFXScrollPane
|
||||
import com.jfoenix.controls.JFXTextField
|
||||
import javafx.beans.InvalidationListener
|
||||
import javafx.beans.property.Property
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import javafx.beans.property.StringProperty
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.ScrollPane
|
||||
import javafx.scene.layout.*
|
||||
import javafx.stage.DirectoryChooser
|
||||
import org.jackhuang.hmcl.setting.VersionSetting
|
||||
import org.jackhuang.hmcl.ui.wizard.HasTitle
|
||||
|
||||
class VersionPage : StackPane(), HasTitle {
|
||||
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
|
||||
|
||||
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) {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,9 @@ import javafx.animation.KeyFrame
|
||||
import javafx.animation.KeyValue
|
||||
import javafx.util.Duration
|
||||
|
||||
enum class ContainerAnimations(val animationProducer: (AnimationHandler) -> List<KeyFrame>) {
|
||||
typealias AnimationProducer = (AnimationHandler) -> List<KeyFrame>
|
||||
|
||||
enum class ContainerAnimations(val animationProducer: AnimationProducer) {
|
||||
/**
|
||||
* None
|
||||
*/
|
||||
|
||||
@@ -26,7 +26,9 @@ import javafx.scene.SnapshotParameters
|
||||
import javafx.scene.image.ImageView
|
||||
import javafx.scene.image.WritableImage
|
||||
import javafx.scene.layout.StackPane
|
||||
import javafx.scene.shape.Rectangle
|
||||
import javafx.util.Duration
|
||||
import org.jackhuang.hmcl.ui.setOverflowHidden
|
||||
import org.jackhuang.hmcl.ui.takeSnapshot
|
||||
|
||||
/**
|
||||
@@ -43,7 +45,7 @@ class TransitionHandler(override val view: StackPane): AnimationHandler {
|
||||
override lateinit var duration: Duration
|
||||
private set
|
||||
|
||||
fun setContent(newView: Node, transition: (TransitionHandler) -> List<KeyFrame>, duration: Duration = Duration.millis(320.0)) {
|
||||
fun setContent(newView: Node, transition: AnimationProducer, duration: Duration = Duration.millis(320.0)) {
|
||||
this.duration = duration
|
||||
|
||||
val prevAnimation = animation
|
||||
@@ -82,8 +84,7 @@ class TransitionHandler(override val view: StackPane): AnimationHandler {
|
||||
|
||||
snapshot.isVisible = true
|
||||
snapshot.opacity = 1.0
|
||||
view.children.setAll(snapshot)
|
||||
view.children.add(newView)
|
||||
view.children.setAll(snapshot, newView)
|
||||
snapshot.toFront()
|
||||
}
|
||||
}
|
||||
@@ -20,14 +20,28 @@ package org.jackhuang.hmcl.ui.download
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.layout.Pane
|
||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider
|
||||
import org.jackhuang.hmcl.setting.Settings
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider
|
||||
|
||||
class DownloadWizardProvider(): WizardProvider() {
|
||||
|
||||
override fun finish(settings: Map<String, Any>): Any? {
|
||||
println(settings)
|
||||
return null
|
||||
val builder = Settings.selectedProfile.dependency.gameBuilder()
|
||||
|
||||
builder.name(settings["name"] as String)
|
||||
builder.gameVersion(settings["game"] as String)
|
||||
|
||||
if (settings.containsKey("forge"))
|
||||
builder.version("forge", settings["forge"] as String)
|
||||
|
||||
if (settings.containsKey("liteloader"))
|
||||
builder.version("liteloader", settings["liteloader"] as String)
|
||||
|
||||
if (settings.containsKey("optifine"))
|
||||
builder.version("optifine", settings["optifine"] as String)
|
||||
|
||||
return builder.buildAsync()
|
||||
}
|
||||
|
||||
override fun createPage(controller: WizardController, step: Int, settings: Map<String, Any>): Node {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.download
|
||||
|
||||
import com.jfoenix.controls.JFXListView
|
||||
import com.jfoenix.controls.JFXTextField
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.layout.StackPane
|
||||
@@ -33,15 +34,20 @@ class InstallersPage(private val controller: WizardController, private val downl
|
||||
@FXML lateinit var lblForge: Label
|
||||
@FXML lateinit var lblLiteLoader: Label
|
||||
@FXML lateinit var lblOptiFine: Label
|
||||
@FXML lateinit var txtName: JFXTextField
|
||||
|
||||
init {
|
||||
loadFXML("/assets/fxml/download/installers.fxml")
|
||||
|
||||
val gameVersion = controller.settings["game"] as String
|
||||
txtName.text = gameVersion
|
||||
|
||||
list.selectionModel.selectedIndexProperty().addListener { _, _, newValue ->
|
||||
controller.settings[INSTALLER_TYPE] = newValue
|
||||
controller.onNext(when (newValue){
|
||||
0 -> VersionsPage(controller, controller.settings["game"] as String, downloadProvider, "forge") { controller.onPrev(false) }
|
||||
1 -> VersionsPage(controller, controller.settings["game"] as String, downloadProvider, "liteloader") { controller.onPrev(false) }
|
||||
2 -> VersionsPage(controller, controller.settings["game"] as String, downloadProvider, "optifine") { controller.onPrev(false) }
|
||||
0 -> VersionsPage(controller, gameVersion, downloadProvider, "forge") { controller.onPrev(false) }
|
||||
1 -> VersionsPage(controller, gameVersion, downloadProvider, "liteloader") { controller.onPrev(false) }
|
||||
2 -> VersionsPage(controller, gameVersion, downloadProvider, "optifine") { controller.onPrev(false) }
|
||||
else -> throw IllegalStateException()
|
||||
})
|
||||
}
|
||||
@@ -72,6 +78,11 @@ class InstallersPage(private val controller: WizardController, private val downl
|
||||
settings.remove(INSTALLER_TYPE)
|
||||
}
|
||||
|
||||
fun onInstall() {
|
||||
controller.settings["name"] = txtName.text
|
||||
controller.onFinish()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val INSTALLER_TYPE = "INSTALLER_TYPE"
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import javafx.scene.layout.StackPane
|
||||
import org.jackhuang.hmcl.download.RemoteVersion
|
||||
import org.jackhuang.hmcl.download.DownloadProvider
|
||||
import org.jackhuang.hmcl.task.Scheduler
|
||||
import org.jackhuang.hmcl.task.TaskExecutor
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler
|
||||
import org.jackhuang.hmcl.ui.loadFXML
|
||||
@@ -37,6 +38,7 @@ class VersionsPage(private val controller: WizardController, private val gameVer
|
||||
|
||||
val transitionHandler = TransitionHandler(this)
|
||||
private val versionList = downloadProvider.getVersionListById(libraryId)
|
||||
private var executor: TaskExecutor? = null
|
||||
|
||||
init {
|
||||
loadFXML("/assets/fxml/download/versions.fxml")
|
||||
@@ -45,7 +47,7 @@ class VersionsPage(private val controller: WizardController, private val gameVer
|
||||
controller.settings[libraryId] = newValue.remoteVersion.selfVersion
|
||||
callback()
|
||||
}
|
||||
versionList.refreshAsync(downloadProvider).subscribe(Scheduler.JAVAFX) {
|
||||
executor = versionList.refreshAsync(downloadProvider).subscribe(Scheduler.JAVAFX) {
|
||||
val versions = ArrayList(versionList.getVersions(gameVersion))
|
||||
versions.sortWith(RemoteVersion)
|
||||
for (version in versions) {
|
||||
@@ -61,5 +63,6 @@ class VersionsPage(private val controller: WizardController, private val gameVer
|
||||
|
||||
override fun cleanup(settings: MutableMap<String, Any>) {
|
||||
settings.remove(libraryId)
|
||||
executor?.cancel()
|
||||
}
|
||||
}
|
||||
@@ -20,10 +20,11 @@ package org.jackhuang.hmcl.ui.download
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.layout.BorderPane
|
||||
import javafx.scene.layout.StackPane
|
||||
import org.jackhuang.hmcl.download.RemoteVersion
|
||||
import org.jackhuang.hmcl.ui.loadFXML
|
||||
|
||||
class VersionsPageItem(val remoteVersion: RemoteVersion<*>) : BorderPane() {
|
||||
class VersionsPageItem(val remoteVersion: RemoteVersion<*>) : StackPane() {
|
||||
|
||||
@FXML lateinit var lblSelfVersion: Label
|
||||
@FXML lateinit var lblGameVersion: Label
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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.wizard
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities
|
||||
import com.jfoenix.controls.JFXProgressBar
|
||||
import javafx.application.Platform
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.layout.StackPane
|
||||
import javafx.scene.layout.VBox
|
||||
import org.jackhuang.hmcl.task.Scheduler
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.task.TaskExecutor
|
||||
import org.jackhuang.hmcl.task.TaskListener
|
||||
import java.util.*
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
interface AbstractWizardDisplayer : WizardDisplayer {
|
||||
val wizardController: WizardController
|
||||
val cancelQueue: Queue<Any>
|
||||
|
||||
override fun handleDeferredWizardResult(settings: Map<String, Any>, deferredResult: DeferredWizardResult) {
|
||||
val vbox = VBox()
|
||||
val progressBar = JFXProgressBar()
|
||||
val label = Label()
|
||||
progressBar.maxHeight = 10.0
|
||||
vbox.children += progressBar
|
||||
vbox.children += label
|
||||
|
||||
navigateTo(StackPane().apply { children += vbox }, Navigation.NavigationDirection.FINISH)
|
||||
|
||||
cancelQueue.add(thread {
|
||||
deferredResult.start(settings, object : ResultProgressHandle {
|
||||
private var running = true
|
||||
|
||||
override fun setProgress(currentStep: Int, totalSteps: Int) {
|
||||
progressBar.progress = 1.0 * currentStep / totalSteps
|
||||
}
|
||||
|
||||
override fun setProgress(description: String, currentStep: Int, totalSteps: Int) {
|
||||
label.text = description
|
||||
progressBar.progress = 1.0 * currentStep / totalSteps
|
||||
}
|
||||
|
||||
override fun setBusy(description: String) {
|
||||
progressBar.progress = JFXProgressBar.INDETERMINATE_PROGRESS
|
||||
}
|
||||
|
||||
override fun finished(result: Any) {
|
||||
running = false
|
||||
}
|
||||
|
||||
override fun failed(message: String, canNavigateBack: Boolean) {
|
||||
label.text = message
|
||||
running = false
|
||||
}
|
||||
|
||||
override val isRunning: Boolean
|
||||
get() = running
|
||||
|
||||
})
|
||||
|
||||
if (!Thread.currentThread().isInterrupted)
|
||||
JFXUtilities.runInFX {
|
||||
navigateTo(Label("Successful"), Navigation.NavigationDirection.FINISH)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun handleTask(settings: Map<String, Any>, task: Task) {
|
||||
val vbox = VBox()
|
||||
val tasksBar = JFXProgressBar()
|
||||
val label = Label()
|
||||
tasksBar.maxHeight = 10.0
|
||||
vbox.children += tasksBar
|
||||
vbox.children += label
|
||||
|
||||
var finishedTasks = 0
|
||||
|
||||
navigateTo(StackPane().apply { children += vbox }, Navigation.NavigationDirection.FINISH)
|
||||
|
||||
task.executor().let { executor ->
|
||||
executor.taskListener = object : TaskListener {
|
||||
override fun onReady(task: Task) {
|
||||
Platform.runLater { tasksBar.progressProperty().set(finishedTasks * 1.0 / executor.totTask.get()) }
|
||||
}
|
||||
|
||||
override fun onFinished(task: Task) {
|
||||
Platform.runLater {
|
||||
label.text = task.title
|
||||
++finishedTasks
|
||||
tasksBar.progressProperty().set(finishedTasks * 1.0 / executor.totTask.get())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailed(task: Task) {
|
||||
Platform.runLater {
|
||||
label.text = task.title
|
||||
++finishedTasks
|
||||
tasksBar.progressProperty().set(finishedTasks * 1.0 / executor.totTask.get())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
Platform.runLater { navigateTo(Label("Successful"), Navigation.NavigationDirection.FINISH) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cancelQueue.add(executor)
|
||||
|
||||
executor.submit(Task.of(Scheduler.JAVAFX) {
|
||||
navigateTo(Label("Successful"), Navigation.NavigationDirection.FINISH)
|
||||
})
|
||||
}.start()
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
while (cancelQueue.isNotEmpty()) {
|
||||
val x = cancelQueue.poll()
|
||||
when (x) {
|
||||
is TaskExecutor -> x.cancel()
|
||||
is Thread -> x.interrupt()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,29 +17,26 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.wizard
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities
|
||||
import com.jfoenix.controls.JFXButton
|
||||
import com.jfoenix.controls.JFXProgressBar
|
||||
import com.jfoenix.controls.JFXToolbar
|
||||
import com.jfoenix.effects.JFXDepthManager
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.layout.StackPane
|
||||
import javafx.scene.layout.VBox
|
||||
import org.jackhuang.hmcl.ui.Controllers
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler
|
||||
import org.jackhuang.hmcl.ui.loadFXML
|
||||
import kotlin.concurrent.thread
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
internal class DefaultWizardDisplayer(private val prefix: String, wizardProvider: WizardProvider) : StackPane(), WizardDisplayer {
|
||||
internal class DefaultWizardDisplayer(private val prefix: String, wizardProvider: WizardProvider) : StackPane(), AbstractWizardDisplayer {
|
||||
|
||||
val wizardController = WizardController(this, wizardProvider)
|
||||
override val wizardController = WizardController(this).apply { provider = wizardProvider }
|
||||
override val cancelQueue: Queue<Any> = ConcurrentLinkedQueue<Any>()
|
||||
|
||||
lateinit var transitionHandler: TransitionHandler
|
||||
|
||||
@FXML lateinit var root: StackPane
|
||||
@FXML lateinit var closeButton: JFXButton
|
||||
@FXML lateinit var backButton: JFXButton
|
||||
@FXML lateinit var toolbar: JFXToolbar
|
||||
/**
|
||||
@@ -48,6 +45,8 @@ internal class DefaultWizardDisplayer(private val prefix: String, wizardProvider
|
||||
@FXML lateinit var refreshButton: JFXButton
|
||||
@FXML lateinit var titleLabel: Label
|
||||
|
||||
lateinit var nowPage: Node
|
||||
|
||||
init {
|
||||
loadFXML("/assets/fxml/wizard.fxml")
|
||||
toolbar.effect = null
|
||||
@@ -59,6 +58,16 @@ internal class DefaultWizardDisplayer(private val prefix: String, wizardProvider
|
||||
wizardController.onStart()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
}
|
||||
|
||||
override fun onEnd() {
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
|
||||
}
|
||||
|
||||
fun back() {
|
||||
wizardController.onPrev(true)
|
||||
}
|
||||
@@ -68,58 +77,17 @@ internal class DefaultWizardDisplayer(private val prefix: String, wizardProvider
|
||||
Controllers.navigate(null)
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
(nowPage as Refreshable).refresh()
|
||||
}
|
||||
|
||||
override fun navigateTo(page: Node, nav: Navigation.NavigationDirection) {
|
||||
backButton.isDisable = !wizardController.canPrev()
|
||||
transitionHandler.setContent(page, nav.animation.animationProducer)
|
||||
val title = if (prefix.isEmpty()) "" else "$prefix - "
|
||||
if (page is WizardPage)
|
||||
titleLabel.text = title + page.title
|
||||
}
|
||||
|
||||
override fun handleDeferredWizardResult(settings: Map<String, Any>, deferredResult: DeferredWizardResult) {
|
||||
val vbox = VBox()
|
||||
val progressBar = JFXProgressBar()
|
||||
val label = Label()
|
||||
progressBar.maxHeight = 10.0
|
||||
vbox.children += progressBar
|
||||
vbox.children += label
|
||||
|
||||
root.children.setAll(progressBar)
|
||||
|
||||
thread {
|
||||
deferredResult.start(settings, object : ResultProgressHandle {
|
||||
private var running = true
|
||||
|
||||
override fun setProgress(currentStep: Int, totalSteps: Int) {
|
||||
progressBar.progress = 1.0 * currentStep / totalSteps
|
||||
}
|
||||
|
||||
override fun setProgress(description: String, currentStep: Int, totalSteps: Int) {
|
||||
label.text = description
|
||||
progressBar.progress = 1.0 * currentStep / totalSteps
|
||||
}
|
||||
|
||||
override fun setBusy(description: String) {
|
||||
progressBar.progress = JFXProgressBar.INDETERMINATE_PROGRESS
|
||||
}
|
||||
|
||||
override fun finished(result: Any) {
|
||||
running = false
|
||||
}
|
||||
|
||||
override fun failed(message: String, canNavigateBack: Boolean) {
|
||||
label.text = message
|
||||
running = false
|
||||
}
|
||||
|
||||
override val isRunning: Boolean
|
||||
get() = running
|
||||
|
||||
})
|
||||
|
||||
JFXUtilities.runInFX {
|
||||
navigateTo(Label("Successful"), Navigation.NavigationDirection.FINISH)
|
||||
}
|
||||
}
|
||||
refreshButton.isVisible = page is Refreshable
|
||||
nowPage = page
|
||||
}
|
||||
}
|
||||
24
HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/HasTitle.kt
Normal file
24
HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/HasTitle.kt
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.wizard
|
||||
|
||||
import javafx.beans.property.StringProperty
|
||||
|
||||
interface HasTitle {
|
||||
val titleProperty: StringProperty
|
||||
}
|
||||
@@ -25,6 +25,7 @@ interface Navigation {
|
||||
fun onPrev(cleanUp: Boolean)
|
||||
fun canPrev(): Boolean
|
||||
fun onFinish()
|
||||
fun onEnd()
|
||||
fun onCancel()
|
||||
|
||||
enum class NavigationDirection(val animation: ContainerAnimations) {
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.wizard
|
||||
|
||||
interface WizardObserver {
|
||||
|
||||
fun stepsChanged(wizard: Wizard)
|
||||
interface Refreshable {
|
||||
fun refresh()
|
||||
}
|
||||
@@ -18,15 +18,20 @@
|
||||
package org.jackhuang.hmcl.ui.wizard
|
||||
|
||||
import javafx.scene.Node
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import java.util.*
|
||||
|
||||
class WizardController(protected val displayer: WizardDisplayer, protected val provider: WizardProvider) : Navigation {
|
||||
class WizardController(protected val displayer: WizardDisplayer) : Navigation {
|
||||
lateinit var provider: WizardProvider
|
||||
val settings = mutableMapOf<String, Any>()
|
||||
val pages = Stack<Node>()
|
||||
|
||||
override fun onStart() {
|
||||
settings.clear()
|
||||
pages.clear()
|
||||
val page = navigatingTo(0)
|
||||
pages.push(page)
|
||||
displayer.onStart()
|
||||
displayer.navigateTo(page, Navigation.NavigationDirection.START)
|
||||
}
|
||||
|
||||
@@ -62,11 +67,19 @@ class WizardController(protected val displayer: WizardDisplayer, protected val p
|
||||
when (result) {
|
||||
is DeferredWizardResult -> displayer.handleDeferredWizardResult(settings, result)
|
||||
is Summary -> displayer.navigateTo(result.component, Navigation.NavigationDirection.NEXT)
|
||||
is Task -> displayer.handleTask(settings, result)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
override fun onEnd() {
|
||||
settings.clear()
|
||||
pages.clear()
|
||||
displayer.onEnd()
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
displayer.onCancel()
|
||||
onEnd()
|
||||
}
|
||||
|
||||
fun navigatingTo(step: Int): Node {
|
||||
|
||||
@@ -18,9 +18,16 @@
|
||||
package org.jackhuang.hmcl.ui.wizard
|
||||
|
||||
import javafx.scene.Node
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
|
||||
interface WizardDisplayer {
|
||||
fun onStart()
|
||||
fun onEnd()
|
||||
fun onCancel()
|
||||
|
||||
fun navigateTo(page: Node, nav: Navigation.NavigationDirection)
|
||||
|
||||
fun handleDeferredWizardResult(settings: Map<String, Any>, deferredResult: DeferredWizardResult)
|
||||
|
||||
fun handleTask(settings: Map<String, Any>, task: Task)
|
||||
}
|
||||
@@ -1,394 +0,0 @@
|
||||
.root {
|
||||
-fx-font-family: Roboto;
|
||||
src: "/resources/roboto/Roboto-Regular.ttf";
|
||||
}
|
||||
|
||||
/* Burgers Demo */
|
||||
|
||||
.jfx-hamburger {
|
||||
-fx-spacing: 5;
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
|
||||
.jfx-hamburger StackPane {
|
||||
-fx-pref-width: 40px;
|
||||
-fx-pref-height: 7px;
|
||||
-fx-background-color: #D63333;
|
||||
-fx-background-radius: 5px;
|
||||
}
|
||||
|
||||
/* Input Demo */
|
||||
|
||||
.text-field {
|
||||
-fx-max-width: 300;
|
||||
}
|
||||
|
||||
.jfx-text-field, .jfx-password-field {
|
||||
-fx-background-color: WHITE;
|
||||
-fx-font-weight: BOLD;
|
||||
-fx-prompt-text-fill: #808080;
|
||||
-fx-alignment: top-left;
|
||||
-jfx-focus-color: #4059A9;
|
||||
-jfx-unfocus-color: #4d4d4d;
|
||||
-fx-max-width: 300;
|
||||
}
|
||||
|
||||
.jfx-decorator {
|
||||
-fx-decorator-color: RED;
|
||||
}
|
||||
|
||||
.jfx-decorator .jfx-decorator-buttons-container {
|
||||
-fx-background-color: -fx-decorator-color;
|
||||
}
|
||||
|
||||
.jfx-decorator .resize-border {
|
||||
-fx-border-color: -fx-decorator-color;
|
||||
-fx-border-width: 0 1 1 1;
|
||||
}
|
||||
|
||||
.jfx-text-area, .text-area {
|
||||
-fx-font-weight: BOLD;
|
||||
}
|
||||
|
||||
.jfx-text-field:error, .jfx-password-field:error, .jfx-text-area:error {
|
||||
-jfx-focus-color: #D34336;
|
||||
-jfx-unfocus-color: #D34336;
|
||||
}
|
||||
|
||||
.jfx-text-field .error-label, .jfx-password-field .error-label, .jfx-text-area .error-label {
|
||||
-fx-text-fill: #D34336;
|
||||
-fx-font-size: 0.75em;
|
||||
}
|
||||
|
||||
.jfx-text-field .error-icon, .jfx-password-field .error-icon, .jfx-text-area .error-icon {
|
||||
-fx-text-fill: #D34336;
|
||||
-fx-font-size: 1em;
|
||||
}
|
||||
|
||||
/* Progress Bar Demo */
|
||||
|
||||
.progress-bar > .bar {
|
||||
-fx-min-width: 500;
|
||||
}
|
||||
|
||||
.jfx-progress-bar > .bar {
|
||||
-fx-min-width: 500;
|
||||
}
|
||||
|
||||
.jfx-progress-bar {
|
||||
-fx-progress-color: #0F9D58;
|
||||
-fx-stroke-width: 3;
|
||||
}
|
||||
|
||||
/* Icons Demo */
|
||||
.icon {
|
||||
-fx-text-fill: #FE774D;
|
||||
-fx-padding: 10;
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
|
||||
.icons-rippler {
|
||||
-jfx-rippler-fill: BLUE;
|
||||
-jfx-mask-type: CIRCLE;
|
||||
}
|
||||
|
||||
.icons-rippler:hover {
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
|
||||
.jfx-check-box {
|
||||
-fx-font-weight: BOLD;
|
||||
}
|
||||
|
||||
.custom-jfx-check-box {
|
||||
-jfx-checked-color: RED;
|
||||
-jfx-unchecked-color: BLACK;
|
||||
}
|
||||
|
||||
/* Button */
|
||||
.button {
|
||||
-fx-padding: 0.7em 0.57em;
|
||||
-fx-font-size: 14px;
|
||||
}
|
||||
|
||||
.button-raised {
|
||||
-fx-padding: 0.7em 0.57em;
|
||||
-fx-font-size: 14px;
|
||||
-jfx-button-type: RAISED;
|
||||
-fx-background-color: rgb(77, 102, 204);
|
||||
-fx-pref-width: 200;
|
||||
-fx-text-fill: WHITE;
|
||||
}
|
||||
|
||||
/* The main scrollbar **track** CSS class */
|
||||
.mylistview .scroll-bar:horizontal .track,
|
||||
.mylistview .scroll-bar:vertical .track {
|
||||
-fx-background-color: transparent;
|
||||
-fx-border-color: transparent;
|
||||
-fx-background-radius: 0em;
|
||||
-fx-border-radius: 2em;
|
||||
}
|
||||
|
||||
/* The increment and decrement button CSS class of scrollbar */
|
||||
.mylistview .scroll-bar:horizontal .increment-button,
|
||||
.mylistview .scroll-bar:horizontal .decrement-button {
|
||||
-fx-background-color: transparent;
|
||||
-fx-background-radius: 0em;
|
||||
-fx-padding: 0 0 10 0;
|
||||
}
|
||||
|
||||
/* The increment and decrement button CSS class of scrollbar */
|
||||
|
||||
.mylistview .scroll-bar:vertical .increment-button,
|
||||
.mylistview .scroll-bar:vertical .decrement-button {
|
||||
-fx-background-color: transparent;
|
||||
-fx-background-radius: 0em;
|
||||
-fx-padding: 0 10 0 0;
|
||||
|
||||
}
|
||||
|
||||
.mylistview .scroll-bar .increment-arrow,
|
||||
.mylistview .scroll-bar .decrement-arrow {
|
||||
-fx-shape: " ";
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
/* The main scrollbar **thumb** CSS class which we drag every time (movable) */
|
||||
.mylistview .scroll-bar:horizontal .thumb,
|
||||
.mylistview .scroll-bar:vertical .thumb {
|
||||
-fx-background-color: derive(black, 90%);
|
||||
-fx-background-insets: 2, 0, 0;
|
||||
-fx-background-radius: 2em;
|
||||
}
|
||||
|
||||
.jfx-list-cell-container {
|
||||
-fx-alignment: center-left;
|
||||
}
|
||||
|
||||
.jfx-list-cell-container > .label {
|
||||
-fx-text-fill: BLACK;
|
||||
}
|
||||
|
||||
.jfx-list-cell:odd:selected > .jfx-rippler > StackPane, .jfx-list-cell:even:selected > .jfx-rippler > StackPane {
|
||||
-fx-background-color: rgba(0, 0, 255, 0.2);
|
||||
}
|
||||
|
||||
.jfx-list-cell {
|
||||
-fx-background-insets: 0.0;
|
||||
-fx-text-fill: BLACK;
|
||||
}
|
||||
|
||||
.jfx-list-cell:odd, .jfx-list-cell:even {
|
||||
-fx-background-color: WHITE;
|
||||
}
|
||||
|
||||
.jfx-list-cell:filled:hover {
|
||||
-fx-text-fill: black;
|
||||
}
|
||||
|
||||
.jfx-list-cell .jfx-rippler {
|
||||
-jfx-rippler-fill: BLUE;
|
||||
}
|
||||
|
||||
.jfx-list-view {
|
||||
-fx-background-insets: 0;
|
||||
-jfx-cell-horizontal-margin: 0.0;
|
||||
-jfx-cell-vertical-margin: 5.0;
|
||||
-jfx-vertical-gap: 10;
|
||||
-jfx-expanded: false;
|
||||
-fx-pref-width: 200;
|
||||
}
|
||||
|
||||
.jfx-toggle-button {
|
||||
-jfx-toggle-color: RED;
|
||||
}
|
||||
|
||||
.jfx-tool-bar {
|
||||
-fx-font-size: 15;
|
||||
-fx-background-color: #5264AE;
|
||||
-fx-pref-width: 100%;
|
||||
-fx-pref-height: 64px;
|
||||
}
|
||||
|
||||
.jfx-tool-bar HBox {
|
||||
-fx-alignment: center;
|
||||
/* -fx-spacing: 25;*/
|
||||
-fx-padding: 0 10;
|
||||
}
|
||||
|
||||
.jfx-tool-bar Label {
|
||||
-fx-text-fill: WHITE;
|
||||
}
|
||||
|
||||
.jfx-popup-container {
|
||||
-fx-background-color: WHITE;
|
||||
}
|
||||
|
||||
.jfx-snackbar-content {
|
||||
-fx-background-color: #323232;
|
||||
-fx-padding: 5;
|
||||
-fx-spacing: 5;
|
||||
}
|
||||
|
||||
.jfx-snackbar-toast {
|
||||
-fx-text-fill: WHITE;
|
||||
}
|
||||
|
||||
.jfx-snackbar-action {
|
||||
-fx-text-fill: #ff4081;
|
||||
}
|
||||
|
||||
.jfx-list-cell-content-container {
|
||||
-fx-alignment: center-left;
|
||||
}
|
||||
|
||||
.jfx-list-cell-container .label {
|
||||
-fx-text-fill: BLACK;
|
||||
}
|
||||
|
||||
.combo-box-popup .list-view .jfx-list-cell:odd:selected .jfx-list-cell-container,
|
||||
.combo-box-popup .list-view .jfx-list-cell:even:selected .jfx-list-cell-container {
|
||||
-fx-background-color: rgba(0.0, 0.0, 255.0, 0.2);
|
||||
}
|
||||
|
||||
.combo-box-popup .list-view .jfx-list-cell {
|
||||
-fx-background-insets: 0.0;
|
||||
-fx-text-fill: BLACK;
|
||||
}
|
||||
|
||||
.combo-box-popup .list-view .jfx-list-cell:odd,
|
||||
.combo-box-popup .list-view .jfx-list-cell:even {
|
||||
-fx-background-color: WHITE;
|
||||
}
|
||||
|
||||
.combo-box-popup .list-view .jfx-list-cell:filled:hover {
|
||||
-fx-text-fill: black;
|
||||
}
|
||||
|
||||
.combo-box-popup .list-view .jfx-list-cell .jfx-rippler {
|
||||
-fx-rippler-fill: #5264AE;
|
||||
}
|
||||
|
||||
/*.combo-box .combo-box-button-container{
|
||||
-fx-border-color:BLACK;-fx-border-width: 0 0 1 0;
|
||||
}
|
||||
.combo-box .combo-box-selected-value-container{
|
||||
-fx-border-color:BLACK;
|
||||
} */
|
||||
|
||||
/*
|
||||
* TREE TABLE CSS
|
||||
*/
|
||||
|
||||
.tree-table-view {
|
||||
-fx-tree-table-color: rgba(255, 0, 0, 0.2);
|
||||
-fx-tree-table-rippler-color: rgba(255, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.tree-table-view:focused .tree-table-row-cell:selected {
|
||||
-fx-background-color: -fx-tree-table-color;
|
||||
-fx-table-cell-border-color: -fx-tree-table-color;
|
||||
-fx-text-fill: BLACK;
|
||||
}
|
||||
|
||||
.tree-table-view:focused .tree-table-row-cell:selected .tree-table-cell {
|
||||
-fx-text-fill: BLACK;
|
||||
}
|
||||
|
||||
.tree-table-view .jfx-rippler {
|
||||
-jfx-rippler-fill: -fx-tree-table-rippler-color;
|
||||
}
|
||||
|
||||
.tree-table-view .column-header,
|
||||
.tree-table-view .column-header-background,
|
||||
.tree-table-view .column-header-background .filler {
|
||||
-fx-background-color: TRANSPARENT;
|
||||
}
|
||||
|
||||
.tree-table-view .column-header {
|
||||
-fx-border-width: 0 1 0 1;
|
||||
-fx-border-color: #F3F3F3;
|
||||
}
|
||||
|
||||
.tree-table-view .column-header .label {
|
||||
-fx-text-fill: #949494;
|
||||
-fx-padding: 16 0 16 0;
|
||||
}
|
||||
|
||||
.tree-table-view .column-header .arrow, .tree-table-view .column-header .sort-order-dot {
|
||||
-fx-background-color: #949494;
|
||||
}
|
||||
|
||||
.tree-table-view .column-header:last-visible {
|
||||
-fx-border-width: 0 2 0 1;
|
||||
}
|
||||
|
||||
.tree-table-view .column-header-background {
|
||||
-fx-border-width: 0 0.0 1 0;
|
||||
-fx-border-color: #F3F3F3;
|
||||
}
|
||||
|
||||
.tree-table-view .tree-table-cell {
|
||||
-fx-border-width: 0 0 0 0;
|
||||
-fx-padding: 16 0 16 0;
|
||||
}
|
||||
|
||||
.tree-table-view .column-overlay {
|
||||
-fx-background-color: -fx-tree-table-color;
|
||||
}
|
||||
|
||||
.tree-table-view .column-resize-line, .tree-table-view .column-drag-header {
|
||||
-fx-background-color: -fx-tree-table-rippler-color;
|
||||
}
|
||||
|
||||
.tree-table-view:focused {
|
||||
-fx-background-color: -fx-tree-table-color, -fx-box-border, -fx-control-inner-background;
|
||||
-fx-background-insets: -1.4, 0, 1;
|
||||
-fx-background-radius: 1.4, 0, 0;
|
||||
/*....*/
|
||||
-fx-padding: 1; /* 0.083333em; */
|
||||
}
|
||||
|
||||
.tree-table-row-cell > .tree-disclosure-node > .arrow {
|
||||
-fx-background-color: -fx-text-fill;
|
||||
-fx-padding: 0.333333em 0.229em 0.333333em 0.229em; /* 4 */
|
||||
-fx-shape: "M 0 -3.5 L 4 0 L 0 3.5 z";
|
||||
}
|
||||
|
||||
.tree-table-row-cell .jfx-text-field {
|
||||
-fx-focus-color: rgba(240, 40, 40);
|
||||
}
|
||||
|
||||
.tree-table-row-group {
|
||||
-fx-background-color: rgba(230, 230, 230);
|
||||
}
|
||||
|
||||
.animated-option-button {
|
||||
-fx-pref-width: 50px;
|
||||
-fx-background-color: #44B449;
|
||||
-fx-background-radius: 50px;
|
||||
-fx-pref-height: 50px;
|
||||
-fx-text-fill: white;
|
||||
-fx-border-color: WHITE;
|
||||
-fx-border-radius: 50px;
|
||||
-fx-border-width: 4px;
|
||||
}
|
||||
|
||||
.animated-option-sub-button {
|
||||
-fx-background-color: #43609C;
|
||||
}
|
||||
|
||||
.animated-option-sub-button2 {
|
||||
-fx-background-color: rgb(203, 104, 96);
|
||||
}
|
||||
|
||||
.tree-table-view .menu-item:focused {
|
||||
-fx-background-color: -fx-tree-table-color;
|
||||
|
||||
}
|
||||
|
||||
.tree-table-view .menu-item .label {
|
||||
-fx-padding: 5 0 5 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,25 @@
|
||||
-fx-text-fill: rgba(0.0, 0.0, 0.0, 0.87);
|
||||
}
|
||||
|
||||
.class-title {
|
||||
-fx-font-size: 12px;
|
||||
-fx-padding: 0 16 0 16;
|
||||
}
|
||||
|
||||
.rippler-container HBox {
|
||||
-fx-font-size: 14px;
|
||||
-fx-padding: 10 16 10 16;
|
||||
-fx-spacing: 10;
|
||||
}
|
||||
|
||||
.left-pane-item {
|
||||
-fx-padding: 10 16 10 16;
|
||||
}
|
||||
|
||||
.jfx-decorator-left-pane {
|
||||
-fx-padding: 20 0 20 0;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* JFX Drawer *
|
||||
@@ -84,6 +103,11 @@
|
||||
-fx-padding: 0.7em 0.8em;
|
||||
}
|
||||
|
||||
.dialog-cancel {
|
||||
-fx-font-weight: BOLD;
|
||||
-fx-padding: 0.7em 0.8em;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* JFX Pop Up *
|
||||
@@ -453,7 +477,6 @@
|
||||
-fx-font-weight: BOLD;
|
||||
-fx-prompt-text-fill: #808080;
|
||||
-fx-alignment: top-left;
|
||||
-fx-max-width: 300.0;
|
||||
-fx-pref-width: 300.0;
|
||||
-jfx-focus-color: #4059A9;
|
||||
-jfx-unfocus-color: #4d4d4d;
|
||||
@@ -658,9 +681,9 @@
|
||||
}
|
||||
|
||||
.toggle-icon3 {
|
||||
-fx-pref-width: 40px;
|
||||
-fx-pref-width: 5px;
|
||||
-fx-background-radius: 50px;
|
||||
-fx-pref-height: 30px;
|
||||
-fx-pref-height: 5px;
|
||||
-fx-background-color: transparent;
|
||||
-jfx-toggle-color: rgba(128, 128, 255, 0.2);
|
||||
-jfx-untoggle-color: transparent;
|
||||
@@ -672,10 +695,33 @@
|
||||
}
|
||||
|
||||
.toggle-icon3 .jfx-rippler {
|
||||
-jfx-rippler-fill: white;
|
||||
-jfx-mask-type: CIRCLE;
|
||||
}
|
||||
|
||||
.toggle-icon4 {
|
||||
-fx-pref-width: 5px;
|
||||
-fx-background-radius: 50px;
|
||||
-fx-pref-height: 5px;
|
||||
-fx-background-color: transparent;
|
||||
-jfx-toggle-color: rgba(128, 128, 255, 0.2);
|
||||
-jfx-untoggle-color: transparent;
|
||||
}
|
||||
|
||||
.toggle-icon4 .icon {
|
||||
-fx-fill: rgb(204.0, 204.0, 51.0);
|
||||
-fx-padding: 10.0;
|
||||
}
|
||||
|
||||
.toggle-icon4 .jfx-rippler {
|
||||
-jfx-rippler-fill: #0F9D58;
|
||||
-jfx-mask-type: CIRCLE;
|
||||
}
|
||||
|
||||
.transparent {
|
||||
-fx-background-color: null;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* JFX Spinner *
|
||||
|
||||
40
HMCL/src/main/resources/assets/fxml/account-item.fxml
Normal file
40
HMCL/src/main/resources/assets/fxml/account-item.fxml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?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?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
<VBox fx:id="content">
|
||||
<StackPane fx:id="header" VBox.vgrow="ALWAYS">
|
||||
<BorderPane>
|
||||
<top>
|
||||
<HBox alignment="CENTER_RIGHT">
|
||||
<JFXButton fx:id="btnDelete" styleClass="toggle-icon3" />
|
||||
</HBox>
|
||||
</top>
|
||||
<center>
|
||||
<VBox style="-fx-padding: 0 0 0 20;">
|
||||
<Label fx:id="lblUser" style="-fx-font-size: 20;" />
|
||||
<Label fx:id="lblType" style="-fx-font-size: 10;" />
|
||||
</VBox>
|
||||
</center>
|
||||
</BorderPane>
|
||||
</StackPane>
|
||||
<StackPane fx:id="body" style="-fx-background-radius: 0 0 5 5; -fx-background-color: rgb(255,255,255,0.87);" />
|
||||
</VBox>
|
||||
<StackPane fx:id="icon" StackPane.alignment="TOP_RIGHT">
|
||||
<ImageView StackPane.alignment="CENTER_RIGHT">
|
||||
<StackPane.margin>
|
||||
<Insets right="12" />
|
||||
</StackPane.margin>
|
||||
<Image url="/assets/img/icon.png" />
|
||||
</ImageView>
|
||||
</StackPane>
|
||||
</fx:root>
|
||||
76
HMCL/src/main/resources/assets/fxml/account.fxml
Normal file
76
HMCL/src/main/resources/assets/fxml/account.fxml
Normal file
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<?import com.jfoenix.controls.JFXMasonryPane?>
|
||||
<?import com.jfoenix.controls.JFXButton?>
|
||||
<?import com.jfoenix.controls.JFXDialogLayout?>
|
||||
<?import com.jfoenix.controls.JFXDialog?>
|
||||
<?import com.jfoenix.controls.JFXPasswordField?>
|
||||
<?import com.jfoenix.controls.JFXTextField?>
|
||||
<?import com.jfoenix.controls.JFXComboBox?>
|
||||
<?import javafx.collections.FXCollections?>
|
||||
<?import java.lang.String?>
|
||||
<?import com.jfoenix.validation.RequiredFieldValidator?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
<ScrollPane fitToHeight="true" fitToWidth="true" fx:id="scrollPane" hbarPolicy="NEVER">
|
||||
<JFXMasonryPane fx:id="masonryPane">
|
||||
</JFXMasonryPane>
|
||||
</ScrollPane>
|
||||
<AnchorPane pickOnBounds="false">
|
||||
<JFXButton onMouseClicked="#addNewAccount" AnchorPane.bottomAnchor="16" AnchorPane.rightAnchor="16" buttonType="RAISED" prefWidth="40" prefHeight="40" style="-fx-text-fill:WHITE;-fx-background-color:#5264AE;-fx-font-size:14px;-fx-background-radius: 80px;">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/plus.fxml" />
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
</AnchorPane>
|
||||
|
||||
<JFXDialog fx:id="dialog" transitionType="CENTER">
|
||||
<JFXDialogLayout>
|
||||
<heading>
|
||||
<Label>Create a new account</Label>
|
||||
</heading>
|
||||
<body>
|
||||
<GridPane vgap="30" hgap="30">
|
||||
<columnConstraints>
|
||||
<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" />
|
||||
<Label fx:id="lblPassword" text="Password" GridPane.halignment="RIGHT" GridPane.columnIndex="0" GridPane.rowIndex="2" />
|
||||
|
||||
<JFXComboBox fx:id="cboType" GridPane.columnIndex="1" GridPane.rowIndex="0">
|
||||
<items>
|
||||
<FXCollections fx:factory="observableArrayList">
|
||||
<String fx:value="Offline Account"/>
|
||||
<String fx:value="Online Account" />
|
||||
</FXCollections>
|
||||
</items>
|
||||
</JFXComboBox>
|
||||
|
||||
<JFXTextField fx:id="txtUsername" promptText="Username" labelFloat="true" GridPane.columnIndex="1" GridPane.rowIndex="1">
|
||||
<validators>
|
||||
<RequiredFieldValidator message="Input Required!">
|
||||
</RequiredFieldValidator>
|
||||
</validators>
|
||||
</JFXTextField>
|
||||
<JFXPasswordField fx:id="txtPassword" promptText="Password" labelFloat="true" GridPane.columnIndex="1" GridPane.rowIndex="2">
|
||||
<validators>
|
||||
<RequiredFieldValidator message="Input Required!">
|
||||
</RequiredFieldValidator>
|
||||
</validators>
|
||||
</JFXPasswordField>
|
||||
</GridPane>
|
||||
</body>
|
||||
<actions>
|
||||
<Label fx:id="lblCreationWarning" />
|
||||
<JFXButton onMouseClicked="#onCreationAccept" text="Accept" styleClass="dialog-accept" />
|
||||
<JFXButton onMouseClicked="#onCreationCancel" text="Cancel" styleClass="dialog-cancel" />
|
||||
</actions>
|
||||
</JFXDialogLayout>
|
||||
</JFXDialog>
|
||||
</fx:root>
|
||||
@@ -7,9 +7,6 @@
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import java.lang.String?>
|
||||
<?import javafx.scene.shape.Rectangle?>
|
||||
<?import com.jfoenix.controls.JFXComboBox?>
|
||||
<?import com.jfoenix.controls.JFXListView?>
|
||||
<?import com.jfoenix.controls.JFXListCell?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
type="GridPane"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
@@ -22,7 +19,7 @@
|
||||
<String fx:value="resize-border" />
|
||||
</styleClass>
|
||||
<columnConstraints>
|
||||
<ColumnConstraints prefWidth="200" />
|
||||
<ColumnConstraints minWidth="200" maxWidth="200" />
|
||||
<ColumnConstraints hgrow="ALWAYS" />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
@@ -30,49 +27,62 @@
|
||||
<RowConstraints vgrow="ALWAYS" />
|
||||
</rowConstraints>
|
||||
<StackPane GridPane.rowIndex="1" GridPane.columnIndex="0" VBox.vgrow="ALWAYS" styleClass="jfx-decorator-content-container">
|
||||
<BorderPane prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
|
||||
<BorderPane fx:id="leftRootPane">
|
||||
<center>
|
||||
<ScrollPane fitToHeight="true" fitToWidth="true">
|
||||
<VBox fx:id="leftPane">
|
||||
</VBox>
|
||||
</ScrollPane>
|
||||
</center>
|
||||
<bottom>
|
||||
<BorderPane fx:id="menuBottomBar">
|
||||
<left>
|
||||
<JFXButton fx:id="refreshMenuButton" styleClass="toggle-icon3">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/refresh-black.fxml"/>
|
||||
</graphic></JFXButton>
|
||||
</left>
|
||||
<right>
|
||||
<JFXButton fx:id="addMenuButton" styleClass="toggle-icon3">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/plus-black.fxml"/>
|
||||
</graphic></JFXButton>
|
||||
</right>
|
||||
<BorderPane>
|
||||
<center>
|
||||
<ScrollPane fitToHeight="true" fitToWidth="true">
|
||||
<VBox fx:id="leftPane" styleClass="jfx-decorator-left-pane" spacing="5">
|
||||
</VBox>
|
||||
</ScrollPane>
|
||||
</center>
|
||||
<bottom>
|
||||
<BorderPane fx:id="menuBottomBar">
|
||||
<left>
|
||||
<JFXButton fx:id="refreshMenuButton" styleClass="toggle-icon3">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/refresh-black.fxml"/>
|
||||
</graphic></JFXButton>
|
||||
</left>
|
||||
<right>
|
||||
<JFXButton fx:id="addMenuButton" styleClass="toggle-icon3">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/plus-black.fxml"/>
|
||||
</graphic></JFXButton>
|
||||
</right>
|
||||
</BorderPane>
|
||||
</bottom>
|
||||
</BorderPane>
|
||||
</bottom>
|
||||
</center>
|
||||
<right>
|
||||
<Rectangle height="${leftRootPane.height}" width="1" fill="gray" />
|
||||
</right>
|
||||
</BorderPane>
|
||||
</StackPane>
|
||||
<StackPane GridPane.rowIndex="1" GridPane.columnIndex="1" fx:id="contentPlaceHolder" styleClass="jfx-decorator-content-container" minWidth="0" minHeight="0" VBox.vgrow="ALWAYS">
|
||||
<styleClass>
|
||||
<String fx:value="jfx-decorator-content-container" />
|
||||
</styleClass>
|
||||
<!-- Node -->
|
||||
<StackPane fx:id="contentPlaceHolderRoot" GridPane.rowIndex="1" GridPane.columnIndex="1" styleClass="jfx-decorator-content-container" VBox.vgrow="ALWAYS">
|
||||
<StackPane fx:id="contentPlaceHolder" styleClass="jfx-decorator-content-container">
|
||||
<styleClass>
|
||||
<String fx:value="jfx-decorator-content-container" />
|
||||
</styleClass>
|
||||
<!-- Node -->
|
||||
</StackPane>
|
||||
</StackPane>
|
||||
<BorderPane GridPane.rowIndex="0" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="titleContainer" styleClass="jfx-tool-bar" pickOnBounds="false">
|
||||
<BorderPane GridPane.rowIndex="0" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="titleContainer" minHeight="30" styleClass="jfx-tool-bar" pickOnBounds="false">
|
||||
<left>
|
||||
<HBox minWidth="200" fx:id="titleWrapper" alignment="CENTER_LEFT">
|
||||
<Label text="Hello Minecraft! Launcher" mouseTransparent="false" style="-fx-background-color: transparent; -fx-text-fill: white; -fx-font-size: 15px;" />
|
||||
</HBox>
|
||||
<BorderPane minWidth="200" maxWidth="200" fx:id="titleWrapper">
|
||||
<center>
|
||||
<Label text="Hello Minecraft! Launcher" BorderPane.alignment="CENTER" mouseTransparent="true" style="-fx-background-color: transparent; -fx-text-fill: white; -fx-font-size: 15px;" />
|
||||
</center>
|
||||
<right>
|
||||
<Rectangle height="${navBar.height}" width="1" fill="gray" />
|
||||
</right>
|
||||
</BorderPane>
|
||||
</left>
|
||||
<center>
|
||||
<BorderPane fx:id="navBar">
|
||||
<left>
|
||||
<HBox fx:id="navLeft" alignment="CENTER_LEFT" style="-fx-padding: 0;">
|
||||
<Rectangle height="${navBar.height}" width="1" fill="gray" />
|
||||
<JFXButton fx:id="backNavButton" maxHeight="20" styleClass="toggle-icon3">
|
||||
<JFXButton fx:id="backNavButton" onMouseClicked="#onBack" maxHeight="20" styleClass="toggle-icon3">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/arrow-left.fxml"/>
|
||||
</graphic>
|
||||
@@ -82,7 +92,7 @@
|
||||
</left>
|
||||
<right>
|
||||
<HBox fx:id="navRight" alignment="CENTER_LEFT">
|
||||
<JFXButton fx:id="refreshNavButton" maxHeight="20" styleClass="toggle-icon3" disable="true">
|
||||
<JFXButton fx:id="refreshNavButton" onMouseClicked="#onRefresh" maxHeight="20" styleClass="toggle-icon3" disable="true">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/refresh.fxml"/>
|
||||
</graphic>
|
||||
@@ -90,7 +100,7 @@
|
||||
<Insets left="20"/>
|
||||
</StackPane.margin>
|
||||
</JFXButton>
|
||||
<JFXButton fx:id="closeNavButton" maxHeight="20" styleClass="toggle-icon3">
|
||||
<JFXButton fx:id="closeNavButton" onMouseClicked="#onCloseNav" maxHeight="20" styleClass="toggle-icon3">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/close.fxml"/>
|
||||
</graphic>
|
||||
|
||||
@@ -7,14 +7,16 @@
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<?import com.jfoenix.controls.JFXButton?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import com.jfoenix.controls.JFXTextField?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
<BorderPane>
|
||||
<top>
|
||||
<HBox alignment="CENTER" style="-fx-padding: 40px;">
|
||||
<VBox alignment="CENTER" style="-fx-padding: 40px;" spacing="20">
|
||||
<Label fx:id="lblGameVersion" alignment="CENTER" />
|
||||
</HBox>
|
||||
<JFXTextField fx:id="txtName" labelFloat="true" promptText="Enter the name of this new version" maxWidth="300" />
|
||||
</VBox>
|
||||
</top>
|
||||
<center>
|
||||
<JFXListView fx:id="list" styleClass="jfx-list-view" maxHeight="150" maxWidth="300">
|
||||
@@ -46,7 +48,7 @@
|
||||
</center>
|
||||
<bottom>
|
||||
<HBox alignment="CENTER">
|
||||
<JFXButton fx:id="buttonLaunch" prefWidth="100" prefHeight="40" buttonType="RAISED" text="Install"
|
||||
<JFXButton onMouseClicked="#onInstall" prefWidth="100" prefHeight="40" buttonType="RAISED" text="Install"
|
||||
style="-fx-text-fill:WHITE;-fx-background-color:#5264AE;-fx-font-size:14px;"/>
|
||||
</HBox>
|
||||
</bottom>
|
||||
|
||||
@@ -5,18 +5,18 @@
|
||||
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="BorderPane" pickOnBounds="false">
|
||||
<left>
|
||||
<VBox alignment="CENTER_LEFT" mouseTransparent="true">
|
||||
<Label fx:id="lblSelfVersion" style="-fx-font-size: 15;" />
|
||||
</VBox>
|
||||
</left>
|
||||
<center>
|
||||
<VBox alignment="CENTER" mouseTransparent="true">
|
||||
<Label alignment="CENTER" fx:id="lblGameVersion" style="-fx-color: gray;" />
|
||||
</VBox>
|
||||
</center>
|
||||
<right>
|
||||
<fx:include source="/assets/svg/arrow-right.fxml" />
|
||||
</right>
|
||||
type="StackPane" mouseTransparent="true">
|
||||
<VBox alignment="CENTER">
|
||||
<Label alignment="CENTER" fx:id="lblGameVersion" style="-fx-color: gray;" />
|
||||
</VBox>
|
||||
<BorderPane>
|
||||
<left>
|
||||
<VBox alignment="CENTER_LEFT">
|
||||
<Label fx:id="lblSelfVersion" style="-fx-font-size: 15;" />
|
||||
</VBox>
|
||||
</left>
|
||||
<right>
|
||||
<fx:include source="/assets/svg/arrow-right.fxml" />
|
||||
</right>
|
||||
</BorderPane>
|
||||
</fx:root>
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import com.jfoenix.controls.*?>
|
||||
<BorderPane
|
||||
<fx:root
|
||||
maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
|
||||
fx:controller="org.jackhuang.hmcl.ui.MainController"
|
||||
style="-fx-background-color: white;"
|
||||
style="-fx-background-color: white;" type="BorderPane"
|
||||
xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<center>
|
||||
<StackPane fx:id="page" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
|
||||
@@ -23,4 +22,4 @@
|
||||
</right>
|
||||
</BorderPane>
|
||||
</bottom>
|
||||
</BorderPane>
|
||||
</fx:root>
|
||||
|
||||
@@ -3,29 +3,31 @@
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import com.jfoenix.controls.JFXButton?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
style="-fx-background-color: transparent; "
|
||||
styleClass="transparent"
|
||||
type="BorderPane" pickOnBounds="false">
|
||||
<left>
|
||||
<VBox alignment="CENTER_LEFT" mouseTransparent="true">
|
||||
<Label fx:id="lblVersionName" style="-fx-font-size: 15;" />
|
||||
<Label fx:id="lblGameVersion" />
|
||||
</VBox>
|
||||
<HBox alignment="CENTER" mouseTransparent="true">
|
||||
<ImageView>
|
||||
<Image url="/assets/img/icon.png" requestedWidth="25" requestedHeight="25" />
|
||||
</ImageView>
|
||||
<VBox alignment="CENTER_LEFT">
|
||||
<Label fx:id="lblVersionName" style="-fx-font-size: 15;" />
|
||||
<Label fx:id="lblGameVersion" style="-fx-font-size: 10;" />
|
||||
</VBox>
|
||||
</HBox>
|
||||
</left>
|
||||
<right>
|
||||
<HBox>
|
||||
<JFXButton onMouseClicked="#onSettings" styleClass="toggle-icon3">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/gear.fxml"/>
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
<JFXButton onMouseClicked="#onLaunch" styleClass="toggle-icon3">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/rocket.fxml"/>
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
<HBox alignment="CENTER" pickOnBounds="false">
|
||||
<JFXButton styleClass="toggle-icon4" onMouseClicked="#onSettings">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/gear.fxml"/>
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
</HBox>
|
||||
</right>
|
||||
</fx:root>
|
||||
|
||||
@@ -5,16 +5,19 @@
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?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.VersionController">
|
||||
<BorderPane>
|
||||
<center>
|
||||
<ScrollPane fx:id="scroll"
|
||||
style="-fx-background-color: white; -fx-font-size: 14; -fx-pref-width: 100%; "
|
||||
<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>
|
||||
<GridPane fx:id="settingsPane" style="-fx-margin-left: 10; -fx-background-color: white; " hgap="5" vgap="10">
|
||||
<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>
|
||||
@@ -59,9 +62,9 @@
|
||||
<BorderPane GridPane.rowIndex="4" GridPane.columnIndex="1" GridPane.columnSpan="2">
|
||||
<left>
|
||||
<HBox prefWidth="210">
|
||||
<JFXTextField fx:id="txtWidth" promptText="800" prefWidth="100"/>
|
||||
<JFXTextField fx:id="txtWidth" promptText="800" prefWidth="100" />
|
||||
<Label>x</Label>
|
||||
<JFXTextField fx:id="txtHeight" promptText="480" prefWidth="100"/>
|
||||
<JFXTextField fx:id="txtHeight" promptText="480" prefWidth="100" />
|
||||
</HBox>
|
||||
</left>
|
||||
<right>
|
||||
@@ -75,36 +78,17 @@
|
||||
|
||||
<JFXButton GridPane.rowIndex="0" GridPane.columnIndex="2" text="Explore" onMouseClicked="#onExploreJavaDir" />
|
||||
</GridPane>
|
||||
<VBox fx:id="advancedSettingsPane" style="-fx-padding-bottom: 10; -fx-background-color: white; " spacing="30">
|
||||
<JFXTextField labelFloat="true" promptText="JVM Args" styleClass="fit-width" fx:id="txtJVMArgs" maxWidth="Infinity"/>
|
||||
<JFXTextField labelFloat="true" promptText="Game Args" styleClass="fit-width" fx:id="txtGameArgs" maxWidth="Infinity"/>
|
||||
<JFXTextField labelFloat="true" promptText="Metaspace" styleClass="fit-width" fx:id="txtMetaspace" maxWidth="Infinity"/>
|
||||
<JFXTextField labelFloat="true" promptText="Wrapper Launcher(like optirun)" styleClass="fit-width" fx:id="txtWrapper" maxWidth="Infinity"/>
|
||||
<JFXTextField labelFloat="true" promptText="Pre-calling command" styleClass="fit-width" fx:id="txtPrecallingCommand" maxWidth="Infinity"/>
|
||||
<JFXTextField labelFloat="true" promptText="Server IP" styleClass="fit-width" fx:id="txtServerIP" maxWidth="Infinity"/>
|
||||
<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>
|
||||
</center>
|
||||
<top>
|
||||
<JFXToolbar maxHeight="20" styleClass="jfx-tool-bar">
|
||||
<leftItems>
|
||||
<JFXButton fx:id="backButton" maxHeight="20" styleClass="toggle-icon3"
|
||||
StackPane.alignment="CENTER_LEFT">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/arrow-left.fxml" />
|
||||
</graphic>
|
||||
<StackPane.margin>
|
||||
<Insets left="20" />
|
||||
</StackPane.margin>
|
||||
</JFXButton>
|
||||
<Label fx:id="titleLabel" style="-fx-text-fill:WHITE; -fx-font-size: 20;"
|
||||
StackPane.alignment="CENTER_LEFT"/>
|
||||
</leftItems>
|
||||
<rightItems>
|
||||
|
||||
</rightItems>
|
||||
</JFXToolbar>
|
||||
</top>
|
||||
</BorderPane>
|
||||
</StackPane>
|
||||
</VBox>
|
||||
</ScrollPane>
|
||||
</fx:root>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<VBox>
|
||||
<JFXToolbar fx:id="toolbar" styleClass="jfx-tool-bar">
|
||||
<leftItems>
|
||||
<JFXButton fx:id="closeButton" maxHeight="20" styleClass="toggle-icon3"
|
||||
<JFXButton maxHeight="20" styleClass="toggle-icon3"
|
||||
StackPane.alignment="CENTER_RIGHT" onMouseClicked="#close">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/close.fxml"/>
|
||||
@@ -31,7 +31,7 @@
|
||||
</leftItems>
|
||||
<rightItems>
|
||||
<JFXButton fx:id="refreshButton" maxHeight="20" styleClass="toggle-icon3" disable="true"
|
||||
StackPane.alignment="CENTER_RIGHT">
|
||||
StackPane.alignment="CENTER_RIGHT" onMouseClicked="#refrseh">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/refresh.fxml"/>
|
||||
</graphic>
|
||||
|
||||
BIN
HMCL/src/main/resources/assets/img/icon.png
Normal file
BIN
HMCL/src/main/resources/assets/img/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
@@ -24,5 +24,5 @@ abstract class Account() {
|
||||
@Throws(AuthenticationException::class)
|
||||
abstract fun logIn(proxy: Proxy = Proxy.NO_PROXY): AuthInfo
|
||||
abstract fun logOut()
|
||||
abstract fun toStorage(): Map<out Any, Any>
|
||||
abstract fun toStorage(): MutableMap<Any, Any>
|
||||
}
|
||||
27
HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Accounts.kt
Normal file
27
HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Accounts.kt
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.auth
|
||||
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
|
||||
|
||||
object Accounts {
|
||||
val ACCOUNTS = mapOf(
|
||||
"offline" to OfflineAccount,
|
||||
"yggdrasil" to YggdrasilAccount
|
||||
)
|
||||
}
|
||||
@@ -35,13 +35,15 @@ class OfflineAccount private constructor(val uuid: String, override val username
|
||||
// Offline account need not log out.
|
||||
}
|
||||
|
||||
override fun toStorage(): Map<Any, Any> {
|
||||
return mapOf(
|
||||
override fun toStorage(): MutableMap<Any, Any> {
|
||||
return mutableMapOf(
|
||||
"uuid" to uuid,
|
||||
"username" to username
|
||||
)
|
||||
}
|
||||
|
||||
override fun toString() = "OfflineAccount[username=$username,uuid=$uuid]"
|
||||
|
||||
companion object OfflineAccountFactory : AccountFactory<OfflineAccount> {
|
||||
|
||||
override fun fromUsername(username: String, password: String): OfflineAccount {
|
||||
|
||||
@@ -135,8 +135,8 @@ class YggdrasilAccount private constructor(override val username: String): Accou
|
||||
selectedProfile = null
|
||||
}
|
||||
|
||||
override fun toStorage(): Map<out Any, Any> {
|
||||
val result = HashMap<String, Any>()
|
||||
override fun toStorage(): MutableMap<Any, Any> {
|
||||
val result = HashMap<Any, Any>()
|
||||
|
||||
result[STORAGE_KEY_USER_NAME] = username
|
||||
if (userId != null)
|
||||
@@ -189,6 +189,8 @@ class YggdrasilAccount private constructor(override val username: String): Accou
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString() = "YggdrasilAccount[username=$username]"
|
||||
|
||||
companion object YggdrasilAccountFactory : AccountFactory<YggdrasilAccount> {
|
||||
private val GSON = GsonBuilder()
|
||||
.registerTypeAdapter(GameProfile::class.java, GameProfile)
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.launch
|
||||
|
||||
import org.jackhuang.hmcl.auth.AuthInfo
|
||||
import org.jackhuang.hmcl.game.*
|
||||
import org.jackhuang.hmcl.task.TaskResult
|
||||
import org.jackhuang.hmcl.util.*
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
@@ -26,12 +27,12 @@ import java.util.*
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
/**
|
||||
* @param version A resolved version(calling [Version.resolve])
|
||||
* @param versionId The version to be launched.
|
||||
* @param account The user account
|
||||
* @param options The launching configuration
|
||||
*/
|
||||
open class DefaultLauncher(repository: GameRepository, version: Version, account: AuthInfo, options: LaunchOptions, listener: ProcessListener? = null, isDaemon: Boolean = true)
|
||||
: Launcher(repository, version, account, options, listener, isDaemon) {
|
||||
open class DefaultLauncher(repository: GameRepository, versionId: String, account: AuthInfo, options: LaunchOptions, listener: ProcessListener? = null, isDaemon: Boolean = true)
|
||||
: Launcher(repository, versionId, account, options, listener, isDaemon) {
|
||||
|
||||
protected val native: File by lazy { repository.getNativeDirectory(version.id) }
|
||||
|
||||
@@ -234,7 +235,7 @@ open class DefaultLauncher(repository: GameRepository, version: Version, account
|
||||
}
|
||||
|
||||
builder.directory(repository.getRunDirectory(version.id))
|
||||
.environment().put("APPDATA", options.gameDir.parent)
|
||||
.environment().put("APPDATA", options.gameDir.absoluteFile.parent)
|
||||
val p = JavaProcess(builder.start(), rawCommandLine)
|
||||
if (listener == null)
|
||||
startMonitors(p)
|
||||
@@ -243,6 +244,14 @@ open class DefaultLauncher(repository: GameRepository, version: Version, account
|
||||
return p
|
||||
}
|
||||
|
||||
fun launchAsync(): TaskResult<JavaProcess> {
|
||||
return object : TaskResult<JavaProcess>() {
|
||||
override fun execute() {
|
||||
result = launch()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun makeLaunchScript(file: String): File {
|
||||
val isWindows = OS.WINDOWS == OS.CURRENT_OS
|
||||
val scriptFile = File(file + (if (isWindows) ".bat" else ".sh"))
|
||||
|
||||
@@ -26,12 +26,13 @@ import java.io.File
|
||||
|
||||
abstract class Launcher(
|
||||
protected val repository: GameRepository,
|
||||
protected val version: Version,
|
||||
protected val versionId: String,
|
||||
protected val account: AuthInfo,
|
||||
protected val options: LaunchOptions,
|
||||
protected val listener: ProcessListener? = null,
|
||||
protected val isDaemon: Boolean = true) {
|
||||
|
||||
val version: Version = repository.getVersion(versionId).resolve(repository)
|
||||
abstract val rawCommandLine: List<String>
|
||||
abstract fun launch(): JavaProcess
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.task
|
||||
|
||||
private class CoupleTask<P: Task>(private val pred: P, private val succ: Task.(P) -> Task?, override val reliant: Boolean) : Task() {
|
||||
internal class CoupleTask<P: Task>(private val pred: P, private val succ: Task.(P) -> Task?, override val reliant: Boolean) : Task() {
|
||||
override val hidden: Boolean = true
|
||||
|
||||
override val dependents: Collection<Task> = listOf(pred)
|
||||
@@ -30,11 +30,12 @@ private class CoupleTask<P: Task>(private val pred: P, private val succ: Task.(P
|
||||
}
|
||||
}
|
||||
|
||||
infix fun Task.then(b: Task): Task = CoupleTask(this, { b }, true)
|
||||
|
||||
/**
|
||||
* @param b A runnable that decides what to do next, You can also do something here.
|
||||
*/
|
||||
infix fun <T: Task> T.then(b: Task.(T) -> Task?): Task = CoupleTask(this, b, true)
|
||||
|
||||
infix fun Task.with(b: Task): Task = CoupleTask(this, { b }, false)
|
||||
/**
|
||||
* @param b A runnable that decides what to do next, You can also do something here.
|
||||
*/
|
||||
infix fun <T: Task> T.with(b: Task.(T) -> Task?): Task = CoupleTask(this, b, false)
|
||||
@@ -23,16 +23,17 @@ import java.util.concurrent.atomic.AtomicReference
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
interface Scheduler {
|
||||
fun schedule(block: () -> Unit): Future<*>? = schedule(Callable { block() })
|
||||
fun schedule(block: Callable<Unit>): Future<*>?
|
||||
|
||||
companion object Schedulers {
|
||||
val JAVAFX: Scheduler = SchedulerImpl(Platform::runLater)
|
||||
val SWING: Scheduler = SchedulerImpl(SwingUtilities::invokeLater)
|
||||
private class SchedulerImpl(val executor: (() -> Unit) -> Unit) : Scheduler {
|
||||
private class SchedulerImpl(val executor: (Runnable) -> Unit) : Scheduler {
|
||||
override fun schedule(block: Callable<Unit>): Future<*>? {
|
||||
val latch = CountDownLatch(1)
|
||||
val wrapper = AtomicReference<Exception>()
|
||||
executor {
|
||||
executor.invoke(Runnable {
|
||||
try {
|
||||
block.call()
|
||||
} catch (e: Exception) {
|
||||
@@ -40,7 +41,7 @@ interface Scheduler {
|
||||
} finally {
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
})
|
||||
return object : Future<Unit> {
|
||||
override fun get(timeout: Long, unit: TimeUnit) {
|
||||
latch.await(timeout, unit)
|
||||
@@ -66,7 +67,9 @@ interface Scheduler {
|
||||
}
|
||||
val DEFAULT = NEW_THREAD
|
||||
private val CACHED_EXECUTOR: ExecutorService by lazy {
|
||||
Executors.newCachedThreadPool()
|
||||
ThreadPoolExecutor(0, Integer.MAX_VALUE,
|
||||
60L, TimeUnit.SECONDS,
|
||||
SynchronousQueue<Runnable>());
|
||||
}
|
||||
|
||||
private val IO_EXECUTOR: ExecutorService by lazy {
|
||||
|
||||
@@ -57,6 +57,8 @@ abstract class Task {
|
||||
abstract fun execute()
|
||||
|
||||
infix fun parallel(couple: Task): Task = ParallelTask(this, couple)
|
||||
infix fun then(b: Task): Task = CoupleTask(this, { b }, true)
|
||||
infix fun with(b: Task): Task = CoupleTask(this, { b }, false)
|
||||
|
||||
/**
|
||||
* The collection of sub-tasks that should execute before this task running.
|
||||
@@ -112,18 +114,18 @@ abstract class Task {
|
||||
|
||||
fun executor() = TaskExecutor().submit(this)
|
||||
|
||||
fun subscribe(subscriber: Task) {
|
||||
executor().submit(subscriber).start()
|
||||
fun subscribe(subscriber: Task) = executor().apply {
|
||||
submit(subscriber).start()
|
||||
}
|
||||
|
||||
fun subscribe(scheduler: Scheduler = Scheduler.DEFAULT, closure: () -> Unit) = subscribe(Task.of(closure, scheduler))
|
||||
fun subscribe(scheduler: Scheduler = Scheduler.DEFAULT, closure: () -> Unit) = subscribe(Task.of(scheduler, closure))
|
||||
|
||||
override fun toString(): String {
|
||||
return title
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun of(closure: () -> Unit, scheduler: Scheduler = Scheduler.DEFAULT): Task = SimpleTask(closure, scheduler)
|
||||
fun of(scheduler: Scheduler = Scheduler.DEFAULT, closure: () -> Unit): Task = SimpleTask(closure, scheduler)
|
||||
fun <V> of(callable: Callable<V>): TaskResult<V> = TaskCallable(callable)
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ class TaskExecutor() {
|
||||
|
||||
var canceled = false
|
||||
private set
|
||||
private val totTask = AtomicInteger(0)
|
||||
val totTask = AtomicInteger(0)
|
||||
private val taskQueue = ConcurrentLinkedQueue<Task>()
|
||||
private val workerQueue = ConcurrentLinkedQueue<Future<*>>()
|
||||
|
||||
@@ -47,9 +47,10 @@ class TaskExecutor() {
|
||||
* Start the subscription and run all registered tasks asynchronously.
|
||||
*/
|
||||
fun start() {
|
||||
thread {
|
||||
workerQueue.add(Scheduler.Schedulers.NEW_THREAD.schedule(Callable {
|
||||
totTask.addAndGet(taskQueue.size)
|
||||
while (!taskQueue.isEmpty() && !canceled) {
|
||||
while (!taskQueue.isEmpty()) {
|
||||
if (canceled) break
|
||||
val task = taskQueue.poll()
|
||||
if (task != null) {
|
||||
val future = task.scheduler.schedule(Callable { executeTask(task); Unit })
|
||||
@@ -61,9 +62,9 @@ class TaskExecutor() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!canceled)
|
||||
if (canceled || Thread.interrupted())
|
||||
taskListener?.onTerminate()
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,9 +92,11 @@ class TaskExecutor() {
|
||||
if (future != null)
|
||||
workerQueue.add(future)
|
||||
}
|
||||
if (canceled)
|
||||
return false
|
||||
try {
|
||||
counter.await()
|
||||
return success.get()
|
||||
return success.get() && !canceled
|
||||
} catch (e: InterruptedException) {
|
||||
Thread.currentThread().interrupt()
|
||||
// Once interrupted, we are aborting the subscription.
|
||||
@@ -110,9 +113,10 @@ class TaskExecutor() {
|
||||
LOG.fine("Executing task: ${t.title}")
|
||||
taskListener?.onReady(t)
|
||||
val doDependentsSucceeded = executeTasks(t.dependents)
|
||||
|
||||
var flag = false
|
||||
try {
|
||||
if (!doDependentsSucceeded && t.reliant)
|
||||
if (!doDependentsSucceeded && t.reliant || canceled)
|
||||
throw SilentException()
|
||||
|
||||
t.execute()
|
||||
|
||||
@@ -52,6 +52,7 @@ data class JavaVersion internal constructor(
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun fromExecutable(file: File): JavaVersion {
|
||||
var platform = Platform.BIT_32
|
||||
var version: String? = null
|
||||
@@ -66,6 +67,7 @@ data class JavaVersion internal constructor(
|
||||
platform = Platform.BIT_64
|
||||
}
|
||||
} catch (e: InterruptedException) {
|
||||
Thread.currentThread().interrupt()
|
||||
throw IOException("Java process is interrupted", e)
|
||||
}
|
||||
val thisVersion = version ?: throw IOException("Java version not matched")
|
||||
|
||||
@@ -49,9 +49,9 @@ enum class OS {
|
||||
ReflectionHelper.get<Long>(ManagementFactory.getOperatingSystemMXBean(), "getTotalPhysicalMemorySize") ?: 1024L
|
||||
}
|
||||
|
||||
val SUGGESTED_MEMORY: Long by lazy {
|
||||
val SUGGESTED_MEMORY: Int by lazy {
|
||||
val memory = TOTAL_MEMORY / 1024 / 1024 / 4
|
||||
Math.round(1.0 * memory / 128.0) * 128
|
||||
(Math.round(1.0 * memory / 128.0) * 128).toInt()
|
||||
}
|
||||
|
||||
val PATH_SEPARATOR: String = File.pathSeparator
|
||||
|
||||
@@ -19,7 +19,7 @@ package org.jackhuang.hmcl
|
||||
|
||||
import org.jackhuang.hmcl.auth.OfflineAccount
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList
|
||||
import org.jackhuang.hmcl.download.LiteLoaderVersionList
|
||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider
|
||||
import org.jackhuang.hmcl.download.MojangDownloadProvider
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository
|
||||
@@ -50,7 +50,7 @@ class Test {
|
||||
fun launch() {
|
||||
val launcher = DefaultLauncher(
|
||||
repository = repository,
|
||||
version = repository.getVersion("test"),
|
||||
versionId = "test",
|
||||
account = OfflineAccount.fromUsername("player007").logIn(),
|
||||
options = LaunchOptions(gameDir = repository.baseDirectory),
|
||||
listener = object : ProcessListener {
|
||||
|
||||
4
LICENSE
4
LICENSE
@@ -576,8 +576,8 @@ Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
If the Program specifies that a PROXY can decide which future
|
||||
versions of the GNU General Public License can be used, that PROXY's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
|
||||
BIN
lib/JFoenix.jar
BIN
lib/JFoenix.jar
Binary file not shown.
Reference in New Issue
Block a user