Able to launch game now
This commit is contained in:
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl
|
|||||||
|
|
||||||
import javafx.application.Application
|
import javafx.application.Application
|
||||||
import javafx.stage.Stage
|
import javafx.stage.Stage
|
||||||
|
import org.jackhuang.hmcl.task.Scheduler
|
||||||
import org.jackhuang.hmcl.ui.Controllers
|
import org.jackhuang.hmcl.ui.Controllers
|
||||||
import org.jackhuang.hmcl.util.DEFAULT_USER_AGENT
|
import org.jackhuang.hmcl.util.DEFAULT_USER_AGENT
|
||||||
import org.jackhuang.hmcl.util.OS
|
import org.jackhuang.hmcl.util.OS
|
||||||
@@ -27,6 +28,7 @@ import java.io.File
|
|||||||
class MainApplication : Application() {
|
class MainApplication : Application() {
|
||||||
|
|
||||||
override fun start(stage: Stage) {
|
override fun start(stage: Stage) {
|
||||||
|
PRIMARY_STAGE = stage
|
||||||
Controllers.initialize(stage)
|
Controllers.initialize(stage)
|
||||||
|
|
||||||
stage.isResizable = false
|
stage.isResizable = false
|
||||||
@@ -36,6 +38,10 @@ class MainApplication : Application() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
val VERSION = "@HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@"
|
||||||
|
val TITLE = "HMCL $VERSION"
|
||||||
|
lateinit var PRIMARY_STAGE: Stage
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
DEFAULT_USER_AGENT = "Hello Minecraft! Launcher"
|
DEFAULT_USER_AGENT = "Hello Minecraft! Launcher"
|
||||||
@@ -57,5 +63,10 @@ class MainApplication : Application() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getMinecraftDirectory(): File = getWorkingDirectory("minecraft")
|
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 {
|
class Config {
|
||||||
@SerializedName("last")
|
@SerializedName("last")
|
||||||
var last: String = ""
|
var selectedProfile: String = ""
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
Settings.save()
|
Settings.save()
|
||||||
@@ -97,7 +97,13 @@ class Config {
|
|||||||
Settings.save()
|
Settings.save()
|
||||||
}
|
}
|
||||||
@SerializedName("accounts")
|
@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) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
Settings.save()
|
Settings.save()
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ package org.jackhuang.hmcl.setting
|
|||||||
import com.google.gson.*
|
import com.google.gson.*
|
||||||
import javafx.beans.InvalidationListener
|
import javafx.beans.InvalidationListener
|
||||||
import javafx.beans.property.*
|
import 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.game.HMCLGameRepository
|
||||||
import org.jackhuang.hmcl.util.*
|
import org.jackhuang.hmcl.util.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -39,6 +42,7 @@ class Profile(var name: String = "Default", gameDir: File = File(".minecraft"))
|
|||||||
var noCommon: Boolean by noCommonProperty
|
var noCommon: Boolean by noCommonProperty
|
||||||
|
|
||||||
var repository = HMCLGameRepository(gameDir)
|
var repository = HMCLGameRepository(gameDir)
|
||||||
|
var dependency = DefaultDependencyManager(repository, BMCLAPIDownloadProvider)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
gameDirProperty.addListener { _, _, newValue ->
|
gameDirProperty.addListener { _, _, newValue ->
|
||||||
|
|||||||
@@ -30,8 +30,15 @@ import java.io.File
|
|||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
import org.jackhuang.hmcl.ProfileLoadingEvent
|
import org.jackhuang.hmcl.ProfileLoadingEvent
|
||||||
import org.jackhuang.hmcl.ProfileChangedEvent
|
import org.jackhuang.hmcl.ProfileChangedEvent
|
||||||
|
import org.jackhuang.hmcl.auth.Account
|
||||||
|
import org.jackhuang.hmcl.auth.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.event.EVENT_BUS
|
||||||
import org.jackhuang.hmcl.util.FileTypeAdapter
|
import org.jackhuang.hmcl.util.FileTypeAdapter
|
||||||
|
import org.jackhuang.hmcl.util.ignoreException
|
||||||
|
import java.net.Proxy
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
object Settings {
|
object Settings {
|
||||||
@@ -48,8 +55,31 @@ object Settings {
|
|||||||
|
|
||||||
val SETTINGS: Config
|
val SETTINGS: Config
|
||||||
|
|
||||||
|
private val ACCOUNTS = mutableMapOf<String, Account>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
SETTINGS = initSettings();
|
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()
|
save()
|
||||||
|
|
||||||
if (!getProfiles().containsKey(DEFAULT_PROFILE))
|
if (!getProfiles().containsKey(DEFAULT_PROFILE))
|
||||||
@@ -59,6 +89,10 @@ object Settings {
|
|||||||
profile.name = name
|
profile.name = name
|
||||||
profile.addPropertyChangedListener(InvalidationListener { save() })
|
profile.addPropertyChangedListener(InvalidationListener { save() })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ignoreException {
|
||||||
|
Runtime.getRuntime().addShutdownHook(Thread(this::save))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDownloadProvider(): DownloadProvider = when (SETTINGS.downloadtype) {
|
fun getDownloadProvider(): DownloadProvider = when (SETTINGS.downloadtype) {
|
||||||
@@ -93,16 +127,62 @@ object Settings {
|
|||||||
|
|
||||||
fun save() {
|
fun save() {
|
||||||
try {
|
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))
|
SETTINGS_FILE.writeText(GSON.toJson(SETTINGS))
|
||||||
} catch (ex: IOException) {
|
} catch (ex: IOException) {
|
||||||
LOG.log(Level.SEVERE, "Failed to save config", ex)
|
LOG.log(Level.SEVERE, "Failed to save config", ex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLastProfile(): Profile {
|
val selectedProfile: Profile
|
||||||
if (!hasProfile(SETTINGS.last))
|
get() {
|
||||||
SETTINGS.last = DEFAULT_PROFILE
|
if (!hasProfile(SETTINGS.selectedProfile))
|
||||||
return getProfile(SETTINGS.last)
|
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 {
|
fun getProfile(name: String?): Profile {
|
||||||
@@ -136,16 +216,16 @@ object Settings {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun delProfile(ver: Profile): Boolean {
|
fun deleteProfile(ver: Profile): Boolean {
|
||||||
return delProfile(ver.name)
|
return deleteProfile(ver.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun delProfile(ver: String): Boolean {
|
fun deleteProfile(ver: String): Boolean {
|
||||||
if (DEFAULT_PROFILE == ver) {
|
if (DEFAULT_PROFILE == ver) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
var notify = false
|
var notify = false
|
||||||
if (getLastProfile().name == ver)
|
if (selectedProfile.name == ver)
|
||||||
notify = true
|
notify = true
|
||||||
val flag = getProfiles().remove(ver) != null
|
val flag = getProfiles().remove(ver) != null
|
||||||
if (notify && flag)
|
if (notify && flag)
|
||||||
@@ -154,9 +234,8 @@ object Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun onProfileChanged() {
|
internal fun onProfileChanged() {
|
||||||
val p = getLastProfile()
|
EVENT_BUS.fireEvent(ProfileChangedEvent(SETTINGS, selectedProfile))
|
||||||
EVENT_BUS.fireEvent(ProfileChangedEvent(SETTINGS, p))
|
selectedProfile.repository.refreshVersions()
|
||||||
p.repository.refreshVersions()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ package org.jackhuang.hmcl.setting
|
|||||||
import com.google.gson.*
|
import com.google.gson.*
|
||||||
import javafx.beans.InvalidationListener
|
import javafx.beans.InvalidationListener
|
||||||
import javafx.beans.property.*
|
import javafx.beans.property.*
|
||||||
|
import org.jackhuang.hmcl.MainApplication
|
||||||
|
import org.jackhuang.hmcl.game.LaunchOptions
|
||||||
import org.jackhuang.hmcl.util.*
|
import org.jackhuang.hmcl.util.*
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
|
|
||||||
class VersionSetting() {
|
class VersionSetting() {
|
||||||
@@ -61,14 +65,14 @@ class VersionSetting() {
|
|||||||
/**
|
/**
|
||||||
* The permanent generation size of JVM garbage collection.
|
* The permanent generation size of JVM garbage collection.
|
||||||
*/
|
*/
|
||||||
val permSizeProperty = SimpleIntegerProperty(this, "permSize", 0)
|
val permSizeProperty = SimpleStringProperty(this, "permSize", "")
|
||||||
var permSize: Int by permSizeProperty
|
var permSize: String by permSizeProperty
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum memory that JVM can allocate.
|
* The maximum memory that JVM can allocate.
|
||||||
* The size of JVM heap.
|
* The size of JVM heap.
|
||||||
*/
|
*/
|
||||||
val maxMemoryProperty = SimpleIntegerProperty(this, "maxMemory", 0)
|
val maxMemoryProperty = SimpleIntegerProperty(this, "maxMemory", OS.SUGGESTED_MEMORY)
|
||||||
var maxMemory: Int by maxMemoryProperty
|
var maxMemory: Int by maxMemoryProperty
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,7 +131,7 @@ class VersionSetting() {
|
|||||||
* String type prevents unexpected value from causing JsonSyntaxException.
|
* String type prevents unexpected value from causing JsonSyntaxException.
|
||||||
* We can only reset this field instead of recreating the whole setting file.
|
* We can only reset this field instead of recreating the whole setting file.
|
||||||
*/
|
*/
|
||||||
val widthProperty = SimpleIntegerProperty(this, "width", 0)
|
val widthProperty = SimpleIntegerProperty(this, "width", 854)
|
||||||
var width: Int by widthProperty
|
var width: Int by widthProperty
|
||||||
|
|
||||||
|
|
||||||
@@ -138,7 +142,7 @@ class VersionSetting() {
|
|||||||
* String type prevents unexpected value from causing JsonSyntaxException.
|
* String type prevents unexpected value from causing JsonSyntaxException.
|
||||||
* We can only reset this field instead of recreating the whole setting file.
|
* We can only reset this field instead of recreating the whole setting file.
|
||||||
*/
|
*/
|
||||||
val heightProperty = SimpleIntegerProperty(this, "height", 0)
|
val heightProperty = SimpleIntegerProperty(this, "height", 480)
|
||||||
var height: Int by heightProperty
|
var height: Int by heightProperty
|
||||||
|
|
||||||
|
|
||||||
@@ -179,6 +183,32 @@ class VersionSetting() {
|
|||||||
launcherVisibilityProperty.addListener(listener)
|
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> {
|
companion object Serializer: JsonSerializer<VersionSetting>, JsonDeserializer<VersionSetting> {
|
||||||
override fun serialize(src: VersionSetting?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
|
override fun serialize(src: VersionSetting?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
|
||||||
if (src == null) return JsonNull.INSTANCE
|
if (src == null) return JsonNull.INSTANCE
|
||||||
@@ -214,7 +244,7 @@ class VersionSetting() {
|
|||||||
javaArgs = json["javaArgs"]?.asString ?: ""
|
javaArgs = json["javaArgs"]?.asString ?: ""
|
||||||
minecraftArgs = json["minecraftArgs"]?.asString ?: ""
|
minecraftArgs = json["minecraftArgs"]?.asString ?: ""
|
||||||
maxMemory = parseJsonPrimitive(json["maxMemory"]?.asJsonPrimitive)
|
maxMemory = parseJsonPrimitive(json["maxMemory"]?.asJsonPrimitive)
|
||||||
permSize = parseJsonPrimitive(json["permSize"]?.asJsonPrimitive)
|
permSize = json["permSize"]?.asString ?: ""
|
||||||
width = parseJsonPrimitive(json["width"]?.asJsonPrimitive)
|
width = parseJsonPrimitive(json["width"]?.asJsonPrimitive)
|
||||||
height = parseJsonPrimitive(json["height"]?.asJsonPrimitive)
|
height = parseJsonPrimitive(json["height"]?.asJsonPrimitive)
|
||||||
javaDir = json["javaDir"]?.asString ?: ""
|
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.fxml.FXMLLoader
|
||||||
import javafx.scene.Node
|
import javafx.scene.Node
|
||||||
import javafx.scene.Scene
|
import javafx.scene.Scene
|
||||||
import javafx.scene.layout.Pane
|
|
||||||
import javafx.scene.layout.StackPane
|
import javafx.scene.layout.StackPane
|
||||||
import javafx.stage.Stage
|
import javafx.stage.Stage
|
||||||
|
|
||||||
@@ -28,18 +27,22 @@ object Controllers {
|
|||||||
lateinit var scene: Scene private set
|
lateinit var scene: Scene private set
|
||||||
lateinit var stage: Stage private set
|
lateinit var stage: Stage private set
|
||||||
|
|
||||||
lateinit var mainController: MainController
|
val mainPane = MainPage()
|
||||||
private val mainPane: Pane = loadPane("main")
|
|
||||||
|
|
||||||
lateinit var versionController: VersionController
|
val versionPane = VersionPage()
|
||||||
val versionPane: Pane = loadPane("version")
|
|
||||||
|
lateinit var leftPaneController: LeftPaneController
|
||||||
|
|
||||||
lateinit var decorator: Decorator
|
lateinit var decorator: Decorator
|
||||||
|
|
||||||
fun initialize(stage: Stage) {
|
fun initialize(stage: Stage) {
|
||||||
this.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.
|
// Let root pane fix window size.
|
||||||
with(mainPane.parent as StackPane) {
|
with(mainPane.parent as StackPane) {
|
||||||
mainPane.prefWidthProperty().bind(widthProperty())
|
mainPane.prefWidthProperty().bind(widthProperty())
|
||||||
@@ -56,7 +59,7 @@ object Controllers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun navigate(node: Node?) {
|
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()
|
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.stage.StageStyle
|
||||||
import javafx.scene.layout.BorderStrokeStyle
|
import javafx.scene.layout.BorderStrokeStyle
|
||||||
import javafx.scene.layout.BorderStroke
|
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 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 xOffset: Double = 0.0
|
||||||
private var yOffset: Double = 0.0
|
private var yOffset: Double = 0.0
|
||||||
private var newX: 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 titleLabel: Label
|
||||||
@FXML lateinit var leftPane: VBox
|
@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
|
@JvmName("onCloseButtonActionProperty") get
|
||||||
var onCloseButtonAction: Runnable by onCloseButtonActionProperty
|
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)
|
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) }
|
.apply { setPrefSize(12.0, 12.0); setSize(12.0, 12.0) }
|
||||||
|
|
||||||
|
val animationHandler: TransitionHandler
|
||||||
|
override val cancelQueue: Queue<Any> = ConcurrentLinkedQueue<Any>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
loadFXML("/assets/fxml/decorator.fxml")
|
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_ENTERED) { this.allowMove = true }
|
||||||
titleContainer.addEventHandler(MouseEvent.MOUSE_EXITED) { if (!this.isDragging) this.allowMove = false }
|
titleContainer.addEventHandler(MouseEvent.MOUSE_EXITED) { if (!this.isDragging) this.allowMove = false }
|
||||||
|
|
||||||
this.contentPlaceHolder.children.add(node)
|
animationHandler = TransitionHandler(contentPlaceHolder)
|
||||||
(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)))
|
setOverflowHidden(lookup("#contentPlaceHolderRoot") as Pane)
|
||||||
val clip = Rectangle()
|
|
||||||
clip.widthProperty().bind(node.widthProperty())
|
|
||||||
clip.heightProperty().bind(node.heightProperty())
|
|
||||||
node.setClip(clip)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onMouseMoved(mouseEvent: MouseEvent) {
|
fun onMouseMoved(mouseEvent: MouseEvent) {
|
||||||
@@ -285,18 +293,22 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node:
|
|||||||
this.yOffset = mouseEvent.sceneY
|
this.yOffset = mouseEvent.sceneY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
private fun isRightEdge(x: Double, y: Double, boundsInParent: Bounds): Boolean {
|
private fun isRightEdge(x: Double, y: Double, boundsInParent: Bounds): Boolean {
|
||||||
return x < this.width && x > this.width - this.contentPlaceHolder.snappedLeftInset()
|
return x < this.width && x > this.width - this.contentPlaceHolder.snappedLeftInset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
private fun isTopEdge(x: Double, y: Double, boundsInParent: Bounds): Boolean {
|
private fun isTopEdge(x: Double, y: Double, boundsInParent: Bounds): Boolean {
|
||||||
return y >= 0.0 && y < this.contentPlaceHolder.snappedLeftInset()
|
return y >= 0.0 && y < this.contentPlaceHolder.snappedLeftInset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
private fun isBottomEdge(x: Double, y: Double, boundsInParent: Bounds): Boolean {
|
private fun isBottomEdge(x: Double, y: Double, boundsInParent: Bounds): Boolean {
|
||||||
return y < this.height && y > this.height - this.contentPlaceHolder.snappedLeftInset()
|
return y < this.height && y > this.height - this.contentPlaceHolder.snappedLeftInset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
private fun isLeftEdge(x: Double, y: Double, boundsInParent: Bounds): Boolean {
|
private fun isLeftEdge(x: Double, y: Double, boundsInParent: Bounds): Boolean {
|
||||||
return x >= 0.0 && x < this.contentPlaceHolder.snappedLeftInset()
|
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) {
|
private fun setContent(content: Node, animation: AnimationProducer) {
|
||||||
this.contentPlaceHolder.children.setAll(content)
|
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.MouseEvent
|
||||||
import javafx.scene.input.ScrollEvent
|
import javafx.scene.input.ScrollEvent
|
||||||
import javafx.scene.layout.Pane
|
import javafx.scene.layout.Pane
|
||||||
|
import javafx.scene.layout.Region
|
||||||
import javafx.scene.shape.Rectangle
|
import javafx.scene.shape.Rectangle
|
||||||
import javafx.util.Duration
|
import javafx.util.Duration
|
||||||
|
|
||||||
@@ -99,7 +100,7 @@ fun takeSnapshot(node: Parent, width: Double, height: Double): WritableImage {
|
|||||||
return scene.snapshot(null)
|
return scene.snapshot(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setOverflowHidden(node: Pane) {
|
fun setOverflowHidden(node: Region) {
|
||||||
val rectangle = Rectangle()
|
val rectangle = Rectangle()
|
||||||
rectangle.widthProperty().bind(node.widthProperty())
|
rectangle.widthProperty().bind(node.widthProperty())
|
||||||
rectangle.heightProperty().bind(node.heightProperty())
|
rectangle.heightProperty().bind(node.heightProperty())
|
||||||
@@ -109,5 +110,5 @@ fun setOverflowHidden(node: Pane) {
|
|||||||
val stylesheets = arrayOf(
|
val stylesheets = arrayOf(
|
||||||
Controllers::class.java.getResource("/css/jfoenix-fonts.css").toExternalForm(),
|
Controllers::class.java.getResource("/css/jfoenix-fonts.css").toExternalForm(),
|
||||||
Controllers::class.java.getResource("/css/jfoenix-design.css").toExternalForm(),
|
Controllers::class.java.getResource("/css/jfoenix-design.css").toExternalForm(),
|
||||||
Controllers::class.java.getResource("/assets/css/jfoenix-components.css").toExternalForm(),
|
//Controllers::class.java.getResource("/assets/css/jfoenix-components.css").toExternalForm(),
|
||||||
Controllers::class.java.getResource("/assets/css/jfoenix-main-demo.css").toExternalForm())
|
Controllers::class.java.getResource("/assets/css/jfoenix-main-demo.css").toExternalForm())
|
||||||
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.JFXComboBox
|
||||||
import com.jfoenix.controls.JFXListCell
|
import com.jfoenix.controls.JFXListCell
|
||||||
import com.jfoenix.controls.JFXListView
|
import com.jfoenix.controls.JFXListView
|
||||||
|
import javafx.beans.property.SimpleStringProperty
|
||||||
|
import javafx.beans.property.StringProperty
|
||||||
import javafx.collections.FXCollections
|
import javafx.collections.FXCollections
|
||||||
import javafx.fxml.FXML
|
import javafx.fxml.FXML
|
||||||
import javafx.scene.Node
|
import javafx.scene.Node
|
||||||
|
import javafx.scene.layout.BorderPane
|
||||||
import javafx.scene.layout.Pane
|
import javafx.scene.layout.Pane
|
||||||
import javafx.scene.layout.StackPane
|
import javafx.scene.layout.StackPane
|
||||||
import org.jackhuang.hmcl.ProfileChangedEvent
|
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.animation.ContainerAnimations
|
||||||
import org.jackhuang.hmcl.ui.download.DownloadWizardProvider
|
import org.jackhuang.hmcl.ui.download.DownloadWizardProvider
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler
|
import org.jackhuang.hmcl.ui.animation.TransitionHandler
|
||||||
|
import org.jackhuang.hmcl.ui.wizard.HasTitle
|
||||||
import org.jackhuang.hmcl.ui.wizard.Wizard
|
import org.jackhuang.hmcl.ui.wizard.Wizard
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see /assets/fxml/main.fxml
|
* @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
|
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 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"): Node = createSVGPath("M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z", fill)
|
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"): 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 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"): 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 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()
|
handler()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onLaunch() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onSettingsButtonClicked(handler: () -> Unit) {
|
fun onSettingsButtonClicked(handler: () -> Unit) {
|
||||||
this.handler = handler
|
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.animation.KeyValue
|
||||||
import javafx.util.Duration
|
import javafx.util.Duration
|
||||||
|
|
||||||
enum class ContainerAnimations(val animationProducer: (AnimationHandler) -> List<KeyFrame>) {
|
typealias AnimationProducer = (AnimationHandler) -> List<KeyFrame>
|
||||||
|
|
||||||
|
enum class ContainerAnimations(val animationProducer: AnimationProducer) {
|
||||||
/**
|
/**
|
||||||
* None
|
* None
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ import javafx.scene.SnapshotParameters
|
|||||||
import javafx.scene.image.ImageView
|
import javafx.scene.image.ImageView
|
||||||
import javafx.scene.image.WritableImage
|
import javafx.scene.image.WritableImage
|
||||||
import javafx.scene.layout.StackPane
|
import javafx.scene.layout.StackPane
|
||||||
|
import javafx.scene.shape.Rectangle
|
||||||
import javafx.util.Duration
|
import javafx.util.Duration
|
||||||
|
import org.jackhuang.hmcl.ui.setOverflowHidden
|
||||||
import org.jackhuang.hmcl.ui.takeSnapshot
|
import org.jackhuang.hmcl.ui.takeSnapshot
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,7 +45,7 @@ class TransitionHandler(override val view: StackPane): AnimationHandler {
|
|||||||
override lateinit var duration: Duration
|
override lateinit var duration: Duration
|
||||||
private set
|
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
|
this.duration = duration
|
||||||
|
|
||||||
val prevAnimation = animation
|
val prevAnimation = animation
|
||||||
@@ -82,8 +84,7 @@ class TransitionHandler(override val view: StackPane): AnimationHandler {
|
|||||||
|
|
||||||
snapshot.isVisible = true
|
snapshot.isVisible = true
|
||||||
snapshot.opacity = 1.0
|
snapshot.opacity = 1.0
|
||||||
view.children.setAll(snapshot)
|
view.children.setAll(snapshot, newView)
|
||||||
view.children.add(newView)
|
|
||||||
snapshot.toFront()
|
snapshot.toFront()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,14 +20,28 @@ package org.jackhuang.hmcl.ui.download
|
|||||||
import javafx.scene.Node
|
import javafx.scene.Node
|
||||||
import javafx.scene.layout.Pane
|
import javafx.scene.layout.Pane
|
||||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider
|
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.WizardController
|
||||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider
|
import org.jackhuang.hmcl.ui.wizard.WizardProvider
|
||||||
|
|
||||||
class DownloadWizardProvider(): WizardProvider() {
|
class DownloadWizardProvider(): WizardProvider() {
|
||||||
|
|
||||||
override fun finish(settings: Map<String, Any>): Any? {
|
override fun finish(settings: Map<String, Any>): Any? {
|
||||||
println(settings)
|
val builder = Settings.selectedProfile.dependency.gameBuilder()
|
||||||
return null
|
|
||||||
|
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 {
|
override fun createPage(controller: WizardController, step: Int, settings: Map<String, Any>): Node {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
package org.jackhuang.hmcl.ui.download
|
package org.jackhuang.hmcl.ui.download
|
||||||
|
|
||||||
import com.jfoenix.controls.JFXListView
|
import com.jfoenix.controls.JFXListView
|
||||||
|
import com.jfoenix.controls.JFXTextField
|
||||||
import javafx.fxml.FXML
|
import javafx.fxml.FXML
|
||||||
import javafx.scene.control.Label
|
import javafx.scene.control.Label
|
||||||
import javafx.scene.layout.StackPane
|
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 lblForge: Label
|
||||||
@FXML lateinit var lblLiteLoader: Label
|
@FXML lateinit var lblLiteLoader: Label
|
||||||
@FXML lateinit var lblOptiFine: Label
|
@FXML lateinit var lblOptiFine: Label
|
||||||
|
@FXML lateinit var txtName: JFXTextField
|
||||||
|
|
||||||
init {
|
init {
|
||||||
loadFXML("/assets/fxml/download/installers.fxml")
|
loadFXML("/assets/fxml/download/installers.fxml")
|
||||||
|
|
||||||
|
val gameVersion = controller.settings["game"] as String
|
||||||
|
txtName.text = gameVersion
|
||||||
|
|
||||||
list.selectionModel.selectedIndexProperty().addListener { _, _, newValue ->
|
list.selectionModel.selectedIndexProperty().addListener { _, _, newValue ->
|
||||||
controller.settings[INSTALLER_TYPE] = newValue
|
controller.settings[INSTALLER_TYPE] = newValue
|
||||||
controller.onNext(when (newValue){
|
controller.onNext(when (newValue){
|
||||||
0 -> VersionsPage(controller, controller.settings["game"] as String, downloadProvider, "forge") { controller.onPrev(false) }
|
0 -> VersionsPage(controller, gameVersion, downloadProvider, "forge") { controller.onPrev(false) }
|
||||||
1 -> VersionsPage(controller, controller.settings["game"] as String, downloadProvider, "liteloader") { controller.onPrev(false) }
|
1 -> VersionsPage(controller, gameVersion, downloadProvider, "liteloader") { controller.onPrev(false) }
|
||||||
2 -> VersionsPage(controller, controller.settings["game"] as String, downloadProvider, "optifine") { controller.onPrev(false) }
|
2 -> VersionsPage(controller, gameVersion, downloadProvider, "optifine") { controller.onPrev(false) }
|
||||||
else -> throw IllegalStateException()
|
else -> throw IllegalStateException()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -72,6 +78,11 @@ class InstallersPage(private val controller: WizardController, private val downl
|
|||||||
settings.remove(INSTALLER_TYPE)
|
settings.remove(INSTALLER_TYPE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onInstall() {
|
||||||
|
controller.settings["name"] = txtName.text
|
||||||
|
controller.onFinish()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val INSTALLER_TYPE = "INSTALLER_TYPE"
|
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.RemoteVersion
|
||||||
import org.jackhuang.hmcl.download.DownloadProvider
|
import org.jackhuang.hmcl.download.DownloadProvider
|
||||||
import org.jackhuang.hmcl.task.Scheduler
|
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.ContainerAnimations
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler
|
import org.jackhuang.hmcl.ui.animation.TransitionHandler
|
||||||
import org.jackhuang.hmcl.ui.loadFXML
|
import org.jackhuang.hmcl.ui.loadFXML
|
||||||
@@ -37,6 +38,7 @@ class VersionsPage(private val controller: WizardController, private val gameVer
|
|||||||
|
|
||||||
val transitionHandler = TransitionHandler(this)
|
val transitionHandler = TransitionHandler(this)
|
||||||
private val versionList = downloadProvider.getVersionListById(libraryId)
|
private val versionList = downloadProvider.getVersionListById(libraryId)
|
||||||
|
private var executor: TaskExecutor? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
loadFXML("/assets/fxml/download/versions.fxml")
|
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
|
controller.settings[libraryId] = newValue.remoteVersion.selfVersion
|
||||||
callback()
|
callback()
|
||||||
}
|
}
|
||||||
versionList.refreshAsync(downloadProvider).subscribe(Scheduler.JAVAFX) {
|
executor = versionList.refreshAsync(downloadProvider).subscribe(Scheduler.JAVAFX) {
|
||||||
val versions = ArrayList(versionList.getVersions(gameVersion))
|
val versions = ArrayList(versionList.getVersions(gameVersion))
|
||||||
versions.sortWith(RemoteVersion)
|
versions.sortWith(RemoteVersion)
|
||||||
for (version in versions) {
|
for (version in versions) {
|
||||||
@@ -61,5 +63,6 @@ class VersionsPage(private val controller: WizardController, private val gameVer
|
|||||||
|
|
||||||
override fun cleanup(settings: MutableMap<String, Any>) {
|
override fun cleanup(settings: MutableMap<String, Any>) {
|
||||||
settings.remove(libraryId)
|
settings.remove(libraryId)
|
||||||
|
executor?.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,10 +20,11 @@ package org.jackhuang.hmcl.ui.download
|
|||||||
import javafx.fxml.FXML
|
import javafx.fxml.FXML
|
||||||
import javafx.scene.control.Label
|
import javafx.scene.control.Label
|
||||||
import javafx.scene.layout.BorderPane
|
import javafx.scene.layout.BorderPane
|
||||||
|
import javafx.scene.layout.StackPane
|
||||||
import org.jackhuang.hmcl.download.RemoteVersion
|
import org.jackhuang.hmcl.download.RemoteVersion
|
||||||
import org.jackhuang.hmcl.ui.loadFXML
|
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 lblSelfVersion: Label
|
||||||
@FXML lateinit var lblGameVersion: 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
|
package org.jackhuang.hmcl.ui.wizard
|
||||||
|
|
||||||
import com.jfoenix.concurrency.JFXUtilities
|
|
||||||
import com.jfoenix.controls.JFXButton
|
import com.jfoenix.controls.JFXButton
|
||||||
import com.jfoenix.controls.JFXProgressBar
|
|
||||||
import com.jfoenix.controls.JFXToolbar
|
import com.jfoenix.controls.JFXToolbar
|
||||||
import com.jfoenix.effects.JFXDepthManager
|
|
||||||
import javafx.fxml.FXML
|
import javafx.fxml.FXML
|
||||||
import javafx.scene.Node
|
import javafx.scene.Node
|
||||||
import javafx.scene.control.Label
|
import javafx.scene.control.Label
|
||||||
import javafx.scene.layout.StackPane
|
import javafx.scene.layout.StackPane
|
||||||
import javafx.scene.layout.VBox
|
|
||||||
import org.jackhuang.hmcl.ui.Controllers
|
import org.jackhuang.hmcl.ui.Controllers
|
||||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler
|
import org.jackhuang.hmcl.ui.animation.TransitionHandler
|
||||||
import org.jackhuang.hmcl.ui.loadFXML
|
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
|
lateinit var transitionHandler: TransitionHandler
|
||||||
|
|
||||||
@FXML lateinit var root: StackPane
|
@FXML lateinit var root: StackPane
|
||||||
@FXML lateinit var closeButton: JFXButton
|
|
||||||
@FXML lateinit var backButton: JFXButton
|
@FXML lateinit var backButton: JFXButton
|
||||||
@FXML lateinit var toolbar: JFXToolbar
|
@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 refreshButton: JFXButton
|
||||||
@FXML lateinit var titleLabel: Label
|
@FXML lateinit var titleLabel: Label
|
||||||
|
|
||||||
|
lateinit var nowPage: Node
|
||||||
|
|
||||||
init {
|
init {
|
||||||
loadFXML("/assets/fxml/wizard.fxml")
|
loadFXML("/assets/fxml/wizard.fxml")
|
||||||
toolbar.effect = null
|
toolbar.effect = null
|
||||||
@@ -59,6 +58,16 @@ internal class DefaultWizardDisplayer(private val prefix: String, wizardProvider
|
|||||||
wizardController.onStart()
|
wizardController.onStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEnd() {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
fun back() {
|
fun back() {
|
||||||
wizardController.onPrev(true)
|
wizardController.onPrev(true)
|
||||||
}
|
}
|
||||||
@@ -68,58 +77,17 @@ internal class DefaultWizardDisplayer(private val prefix: String, wizardProvider
|
|||||||
Controllers.navigate(null)
|
Controllers.navigate(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun refresh() {
|
||||||
|
(nowPage as Refreshable).refresh()
|
||||||
|
}
|
||||||
|
|
||||||
override fun navigateTo(page: Node, nav: Navigation.NavigationDirection) {
|
override fun navigateTo(page: Node, nav: Navigation.NavigationDirection) {
|
||||||
backButton.isDisable = !wizardController.canPrev()
|
backButton.isDisable = !wizardController.canPrev()
|
||||||
transitionHandler.setContent(page, nav.animation.animationProducer)
|
transitionHandler.setContent(page, nav.animation.animationProducer)
|
||||||
val title = if (prefix.isEmpty()) "" else "$prefix - "
|
val title = if (prefix.isEmpty()) "" else "$prefix - "
|
||||||
if (page is WizardPage)
|
if (page is WizardPage)
|
||||||
titleLabel.text = title + page.title
|
titleLabel.text = title + page.title
|
||||||
}
|
refreshButton.isVisible = page is Refreshable
|
||||||
|
nowPage = page
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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 onPrev(cleanUp: Boolean)
|
||||||
fun canPrev(): Boolean
|
fun canPrev(): Boolean
|
||||||
fun onFinish()
|
fun onFinish()
|
||||||
|
fun onEnd()
|
||||||
fun onCancel()
|
fun onCancel()
|
||||||
|
|
||||||
enum class NavigationDirection(val animation: ContainerAnimations) {
|
enum class NavigationDirection(val animation: ContainerAnimations) {
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.ui.wizard
|
package org.jackhuang.hmcl.ui.wizard
|
||||||
|
|
||||||
interface WizardObserver {
|
interface Refreshable {
|
||||||
|
fun refresh()
|
||||||
fun stepsChanged(wizard: Wizard)
|
|
||||||
}
|
}
|
||||||
@@ -18,15 +18,20 @@
|
|||||||
package org.jackhuang.hmcl.ui.wizard
|
package org.jackhuang.hmcl.ui.wizard
|
||||||
|
|
||||||
import javafx.scene.Node
|
import javafx.scene.Node
|
||||||
|
import org.jackhuang.hmcl.task.Task
|
||||||
import java.util.*
|
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 settings = mutableMapOf<String, Any>()
|
||||||
val pages = Stack<Node>()
|
val pages = Stack<Node>()
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
|
settings.clear()
|
||||||
|
pages.clear()
|
||||||
val page = navigatingTo(0)
|
val page = navigatingTo(0)
|
||||||
pages.push(page)
|
pages.push(page)
|
||||||
|
displayer.onStart()
|
||||||
displayer.navigateTo(page, Navigation.NavigationDirection.START)
|
displayer.navigateTo(page, Navigation.NavigationDirection.START)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,11 +67,19 @@ class WizardController(protected val displayer: WizardDisplayer, protected val p
|
|||||||
when (result) {
|
when (result) {
|
||||||
is DeferredWizardResult -> displayer.handleDeferredWizardResult(settings, result)
|
is DeferredWizardResult -> displayer.handleDeferredWizardResult(settings, result)
|
||||||
is Summary -> displayer.navigateTo(result.component, Navigation.NavigationDirection.NEXT)
|
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 {
|
fun navigatingTo(step: Int): Node {
|
||||||
|
|||||||
@@ -18,9 +18,16 @@
|
|||||||
package org.jackhuang.hmcl.ui.wizard
|
package org.jackhuang.hmcl.ui.wizard
|
||||||
|
|
||||||
import javafx.scene.Node
|
import javafx.scene.Node
|
||||||
|
import org.jackhuang.hmcl.task.Task
|
||||||
|
|
||||||
interface WizardDisplayer {
|
interface WizardDisplayer {
|
||||||
|
fun onStart()
|
||||||
|
fun onEnd()
|
||||||
|
fun onCancel()
|
||||||
|
|
||||||
fun navigateTo(page: Node, nav: Navigation.NavigationDirection)
|
fun navigateTo(page: Node, nav: Navigation.NavigationDirection)
|
||||||
|
|
||||||
fun handleDeferredWizardResult(settings: Map<String, Any>, deferredResult: DeferredWizardResult)
|
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);
|
-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 *
|
* JFX Drawer *
|
||||||
@@ -84,6 +103,11 @@
|
|||||||
-fx-padding: 0.7em 0.8em;
|
-fx-padding: 0.7em 0.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog-cancel {
|
||||||
|
-fx-font-weight: BOLD;
|
||||||
|
-fx-padding: 0.7em 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* *
|
* *
|
||||||
* JFX Pop Up *
|
* JFX Pop Up *
|
||||||
@@ -453,7 +477,6 @@
|
|||||||
-fx-font-weight: BOLD;
|
-fx-font-weight: BOLD;
|
||||||
-fx-prompt-text-fill: #808080;
|
-fx-prompt-text-fill: #808080;
|
||||||
-fx-alignment: top-left;
|
-fx-alignment: top-left;
|
||||||
-fx-max-width: 300.0;
|
|
||||||
-fx-pref-width: 300.0;
|
-fx-pref-width: 300.0;
|
||||||
-jfx-focus-color: #4059A9;
|
-jfx-focus-color: #4059A9;
|
||||||
-jfx-unfocus-color: #4d4d4d;
|
-jfx-unfocus-color: #4d4d4d;
|
||||||
@@ -658,9 +681,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.toggle-icon3 {
|
.toggle-icon3 {
|
||||||
-fx-pref-width: 40px;
|
-fx-pref-width: 5px;
|
||||||
-fx-background-radius: 50px;
|
-fx-background-radius: 50px;
|
||||||
-fx-pref-height: 30px;
|
-fx-pref-height: 5px;
|
||||||
-fx-background-color: transparent;
|
-fx-background-color: transparent;
|
||||||
-jfx-toggle-color: rgba(128, 128, 255, 0.2);
|
-jfx-toggle-color: rgba(128, 128, 255, 0.2);
|
||||||
-jfx-untoggle-color: transparent;
|
-jfx-untoggle-color: transparent;
|
||||||
@@ -672,10 +695,33 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.toggle-icon3 .jfx-rippler {
|
.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-rippler-fill: #0F9D58;
|
||||||
-jfx-mask-type: CIRCLE;
|
-jfx-mask-type: CIRCLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.transparent {
|
||||||
|
-fx-background-color: null;
|
||||||
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* *
|
* *
|
||||||
* JFX Spinner *
|
* 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 javafx.geometry.Insets?>
|
||||||
<?import java.lang.String?>
|
<?import java.lang.String?>
|
||||||
<?import javafx.scene.shape.Rectangle?>
|
<?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"
|
<fx:root xmlns="http://javafx.com/javafx"
|
||||||
type="GridPane"
|
type="GridPane"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
@@ -22,7 +19,7 @@
|
|||||||
<String fx:value="resize-border" />
|
<String fx:value="resize-border" />
|
||||||
</styleClass>
|
</styleClass>
|
||||||
<columnConstraints>
|
<columnConstraints>
|
||||||
<ColumnConstraints prefWidth="200" />
|
<ColumnConstraints minWidth="200" maxWidth="200" />
|
||||||
<ColumnConstraints hgrow="ALWAYS" />
|
<ColumnConstraints hgrow="ALWAYS" />
|
||||||
</columnConstraints>
|
</columnConstraints>
|
||||||
<rowConstraints>
|
<rowConstraints>
|
||||||
@@ -30,49 +27,62 @@
|
|||||||
<RowConstraints vgrow="ALWAYS" />
|
<RowConstraints vgrow="ALWAYS" />
|
||||||
</rowConstraints>
|
</rowConstraints>
|
||||||
<StackPane GridPane.rowIndex="1" GridPane.columnIndex="0" VBox.vgrow="ALWAYS" styleClass="jfx-decorator-content-container">
|
<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>
|
<center>
|
||||||
<ScrollPane fitToHeight="true" fitToWidth="true">
|
<BorderPane>
|
||||||
<VBox fx:id="leftPane">
|
<center>
|
||||||
</VBox>
|
<ScrollPane fitToHeight="true" fitToWidth="true">
|
||||||
</ScrollPane>
|
<VBox fx:id="leftPane" styleClass="jfx-decorator-left-pane" spacing="5">
|
||||||
</center>
|
</VBox>
|
||||||
<bottom>
|
</ScrollPane>
|
||||||
<BorderPane fx:id="menuBottomBar">
|
</center>
|
||||||
<left>
|
<bottom>
|
||||||
<JFXButton fx:id="refreshMenuButton" styleClass="toggle-icon3">
|
<BorderPane fx:id="menuBottomBar">
|
||||||
<graphic>
|
<left>
|
||||||
<fx:include source="/assets/svg/refresh-black.fxml"/>
|
<JFXButton fx:id="refreshMenuButton" styleClass="toggle-icon3">
|
||||||
</graphic></JFXButton>
|
<graphic>
|
||||||
</left>
|
<fx:include source="/assets/svg/refresh-black.fxml"/>
|
||||||
<right>
|
</graphic></JFXButton>
|
||||||
<JFXButton fx:id="addMenuButton" styleClass="toggle-icon3">
|
</left>
|
||||||
<graphic>
|
<right>
|
||||||
<fx:include source="/assets/svg/plus-black.fxml"/>
|
<JFXButton fx:id="addMenuButton" styleClass="toggle-icon3">
|
||||||
</graphic></JFXButton>
|
<graphic>
|
||||||
</right>
|
<fx:include source="/assets/svg/plus-black.fxml"/>
|
||||||
|
</graphic></JFXButton>
|
||||||
|
</right>
|
||||||
|
</BorderPane>
|
||||||
|
</bottom>
|
||||||
</BorderPane>
|
</BorderPane>
|
||||||
</bottom>
|
</center>
|
||||||
|
<right>
|
||||||
|
<Rectangle height="${leftRootPane.height}" width="1" fill="gray" />
|
||||||
|
</right>
|
||||||
</BorderPane>
|
</BorderPane>
|
||||||
</StackPane>
|
</StackPane>
|
||||||
<StackPane GridPane.rowIndex="1" GridPane.columnIndex="1" fx:id="contentPlaceHolder" styleClass="jfx-decorator-content-container" minWidth="0" minHeight="0" VBox.vgrow="ALWAYS">
|
<StackPane fx:id="contentPlaceHolderRoot" GridPane.rowIndex="1" GridPane.columnIndex="1" styleClass="jfx-decorator-content-container" VBox.vgrow="ALWAYS">
|
||||||
<styleClass>
|
<StackPane fx:id="contentPlaceHolder" styleClass="jfx-decorator-content-container">
|
||||||
<String fx:value="jfx-decorator-content-container" />
|
<styleClass>
|
||||||
</styleClass>
|
<String fx:value="jfx-decorator-content-container" />
|
||||||
<!-- Node -->
|
</styleClass>
|
||||||
|
<!-- Node -->
|
||||||
|
</StackPane>
|
||||||
</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>
|
<left>
|
||||||
<HBox minWidth="200" fx:id="titleWrapper" alignment="CENTER_LEFT">
|
<BorderPane minWidth="200" maxWidth="200" fx:id="titleWrapper">
|
||||||
<Label text="Hello Minecraft! Launcher" mouseTransparent="false" style="-fx-background-color: transparent; -fx-text-fill: white; -fx-font-size: 15px;" />
|
<center>
|
||||||
</HBox>
|
<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>
|
</left>
|
||||||
<center>
|
<center>
|
||||||
<BorderPane fx:id="navBar">
|
<BorderPane fx:id="navBar">
|
||||||
<left>
|
<left>
|
||||||
<HBox fx:id="navLeft" alignment="CENTER_LEFT" style="-fx-padding: 0;">
|
<HBox fx:id="navLeft" alignment="CENTER_LEFT" style="-fx-padding: 0;">
|
||||||
<Rectangle height="${navBar.height}" width="1" fill="gray" />
|
<JFXButton fx:id="backNavButton" onMouseClicked="#onBack" maxHeight="20" styleClass="toggle-icon3">
|
||||||
<JFXButton fx:id="backNavButton" maxHeight="20" styleClass="toggle-icon3">
|
|
||||||
<graphic>
|
<graphic>
|
||||||
<fx:include source="/assets/svg/arrow-left.fxml"/>
|
<fx:include source="/assets/svg/arrow-left.fxml"/>
|
||||||
</graphic>
|
</graphic>
|
||||||
@@ -82,7 +92,7 @@
|
|||||||
</left>
|
</left>
|
||||||
<right>
|
<right>
|
||||||
<HBox fx:id="navRight" alignment="CENTER_LEFT">
|
<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>
|
<graphic>
|
||||||
<fx:include source="/assets/svg/refresh.fxml"/>
|
<fx:include source="/assets/svg/refresh.fxml"/>
|
||||||
</graphic>
|
</graphic>
|
||||||
@@ -90,7 +100,7 @@
|
|||||||
<Insets left="20"/>
|
<Insets left="20"/>
|
||||||
</StackPane.margin>
|
</StackPane.margin>
|
||||||
</JFXButton>
|
</JFXButton>
|
||||||
<JFXButton fx:id="closeNavButton" maxHeight="20" styleClass="toggle-icon3">
|
<JFXButton fx:id="closeNavButton" onMouseClicked="#onCloseNav" maxHeight="20" styleClass="toggle-icon3">
|
||||||
<graphic>
|
<graphic>
|
||||||
<fx:include source="/assets/svg/close.fxml"/>
|
<fx:include source="/assets/svg/close.fxml"/>
|
||||||
</graphic>
|
</graphic>
|
||||||
|
|||||||
@@ -7,14 +7,16 @@
|
|||||||
<?import javafx.scene.layout.BorderPane?>
|
<?import javafx.scene.layout.BorderPane?>
|
||||||
<?import com.jfoenix.controls.JFXButton?>
|
<?import com.jfoenix.controls.JFXButton?>
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import com.jfoenix.controls.JFXTextField?>
|
||||||
<fx:root xmlns="http://javafx.com/javafx"
|
<fx:root xmlns="http://javafx.com/javafx"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
type="StackPane">
|
type="StackPane">
|
||||||
<BorderPane>
|
<BorderPane>
|
||||||
<top>
|
<top>
|
||||||
<HBox alignment="CENTER" style="-fx-padding: 40px;">
|
<VBox alignment="CENTER" style="-fx-padding: 40px;" spacing="20">
|
||||||
<Label fx:id="lblGameVersion" alignment="CENTER" />
|
<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>
|
</top>
|
||||||
<center>
|
<center>
|
||||||
<JFXListView fx:id="list" styleClass="jfx-list-view" maxHeight="150" maxWidth="300">
|
<JFXListView fx:id="list" styleClass="jfx-list-view" maxHeight="150" maxWidth="300">
|
||||||
@@ -46,7 +48,7 @@
|
|||||||
</center>
|
</center>
|
||||||
<bottom>
|
<bottom>
|
||||||
<HBox alignment="CENTER">
|
<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;"/>
|
style="-fx-text-fill:WHITE;-fx-background-color:#5264AE;-fx-font-size:14px;"/>
|
||||||
</HBox>
|
</HBox>
|
||||||
</bottom>
|
</bottom>
|
||||||
|
|||||||
@@ -5,18 +5,18 @@
|
|||||||
|
|
||||||
<fx:root xmlns="http://javafx.com/javafx"
|
<fx:root xmlns="http://javafx.com/javafx"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
type="BorderPane" pickOnBounds="false">
|
type="StackPane" mouseTransparent="true">
|
||||||
<left>
|
<VBox alignment="CENTER">
|
||||||
<VBox alignment="CENTER_LEFT" mouseTransparent="true">
|
<Label alignment="CENTER" fx:id="lblGameVersion" style="-fx-color: gray;" />
|
||||||
<Label fx:id="lblSelfVersion" style="-fx-font-size: 15;" />
|
</VBox>
|
||||||
</VBox>
|
<BorderPane>
|
||||||
</left>
|
<left>
|
||||||
<center>
|
<VBox alignment="CENTER_LEFT">
|
||||||
<VBox alignment="CENTER" mouseTransparent="true">
|
<Label fx:id="lblSelfVersion" style="-fx-font-size: 15;" />
|
||||||
<Label alignment="CENTER" fx:id="lblGameVersion" style="-fx-color: gray;" />
|
</VBox>
|
||||||
</VBox>
|
</left>
|
||||||
</center>
|
<right>
|
||||||
<right>
|
<fx:include source="/assets/svg/arrow-right.fxml" />
|
||||||
<fx:include source="/assets/svg/arrow-right.fxml" />
|
</right>
|
||||||
</right>
|
</BorderPane>
|
||||||
</fx:root>
|
</fx:root>
|
||||||
|
|||||||
@@ -2,10 +2,9 @@
|
|||||||
|
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
<?import com.jfoenix.controls.*?>
|
<?import com.jfoenix.controls.*?>
|
||||||
<BorderPane
|
<fx:root
|
||||||
maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
|
maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
|
||||||
fx:controller="org.jackhuang.hmcl.ui.MainController"
|
style="-fx-background-color: white;" type="BorderPane"
|
||||||
style="-fx-background-color: white;"
|
|
||||||
xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1">
|
xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
<center>
|
<center>
|
||||||
<StackPane fx:id="page" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
|
<StackPane fx:id="page" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
|
||||||
@@ -23,4 +22,4 @@
|
|||||||
</right>
|
</right>
|
||||||
</BorderPane>
|
</BorderPane>
|
||||||
</bottom>
|
</bottom>
|
||||||
</BorderPane>
|
</fx:root>
|
||||||
|
|||||||
@@ -3,29 +3,31 @@
|
|||||||
<?import javafx.scene.control.*?>
|
<?import javafx.scene.control.*?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
|
|
||||||
|
<?import javafx.scene.image.ImageView?>
|
||||||
|
<?import javafx.scene.image.Image?>
|
||||||
<?import com.jfoenix.controls.JFXButton?>
|
<?import com.jfoenix.controls.JFXButton?>
|
||||||
<fx:root xmlns="http://javafx.com/javafx"
|
<fx:root xmlns="http://javafx.com/javafx"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
style="-fx-background-color: transparent; "
|
styleClass="transparent"
|
||||||
type="BorderPane" pickOnBounds="false">
|
type="BorderPane" pickOnBounds="false">
|
||||||
<left>
|
<left>
|
||||||
<VBox alignment="CENTER_LEFT" mouseTransparent="true">
|
<HBox alignment="CENTER" mouseTransparent="true">
|
||||||
<Label fx:id="lblVersionName" style="-fx-font-size: 15;" />
|
<ImageView>
|
||||||
<Label fx:id="lblGameVersion" />
|
<Image url="/assets/img/icon.png" requestedWidth="25" requestedHeight="25" />
|
||||||
</VBox>
|
</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>
|
</left>
|
||||||
<right>
|
<right>
|
||||||
<HBox>
|
<HBox alignment="CENTER" pickOnBounds="false">
|
||||||
<JFXButton onMouseClicked="#onSettings" styleClass="toggle-icon3">
|
<JFXButton styleClass="toggle-icon4" onMouseClicked="#onSettings">
|
||||||
<graphic>
|
<graphic>
|
||||||
<fx:include source="/assets/svg/gear.fxml"/>
|
<fx:include source="/assets/svg/gear.fxml"/>
|
||||||
</graphic>
|
</graphic>
|
||||||
</JFXButton>
|
</JFXButton>
|
||||||
<JFXButton onMouseClicked="#onLaunch" styleClass="toggle-icon3">
|
|
||||||
<graphic>
|
|
||||||
<fx:include source="/assets/svg/rocket.fxml"/>
|
|
||||||
</graphic>
|
|
||||||
</JFXButton>
|
|
||||||
</HBox>
|
</HBox>
|
||||||
</right>
|
</right>
|
||||||
</fx:root>
|
</fx:root>
|
||||||
|
|||||||
@@ -5,16 +5,19 @@
|
|||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.*?>
|
<?import javafx.scene.control.*?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
<StackPane xmlns="http://javafx.com/javafx"
|
<fx:root xmlns="http://javafx.com/javafx"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
fx:controller="org.jackhuang.hmcl.ui.VersionController">
|
type="StackPane">
|
||||||
<BorderPane>
|
<ScrollPane fx:id="scroll"
|
||||||
<center>
|
style="-fx-font-size: 14; -fx-pref-width: 100%; "
|
||||||
<ScrollPane fx:id="scroll"
|
|
||||||
style="-fx-background-color: white; -fx-font-size: 14; -fx-pref-width: 100%; "
|
|
||||||
fitToHeight="true" fitToWidth="true">
|
fitToHeight="true" fitToWidth="true">
|
||||||
<VBox>
|
<VBox fx:id="rootPane" style="-fx-padding: 20;">
|
||||||
<GridPane fx:id="settingsPane" style="-fx-margin-left: 10; -fx-background-color: white; " hgap="5" vgap="10">
|
<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="0" GridPane.columnIndex="0">Java Directory</Label>
|
||||||
<Label GridPane.rowIndex="1" GridPane.columnIndex="0">Max Memory</Label>
|
<Label GridPane.rowIndex="1" GridPane.columnIndex="0">Max Memory</Label>
|
||||||
<Label GridPane.rowIndex="2" GridPane.columnIndex="0">Launcher Visibility</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">
|
<BorderPane GridPane.rowIndex="4" GridPane.columnIndex="1" GridPane.columnSpan="2">
|
||||||
<left>
|
<left>
|
||||||
<HBox prefWidth="210">
|
<HBox prefWidth="210">
|
||||||
<JFXTextField fx:id="txtWidth" promptText="800" prefWidth="100"/>
|
<JFXTextField fx:id="txtWidth" promptText="800" prefWidth="100" />
|
||||||
<Label>x</Label>
|
<Label>x</Label>
|
||||||
<JFXTextField fx:id="txtHeight" promptText="480" prefWidth="100"/>
|
<JFXTextField fx:id="txtHeight" promptText="480" prefWidth="100" />
|
||||||
</HBox>
|
</HBox>
|
||||||
</left>
|
</left>
|
||||||
<right>
|
<right>
|
||||||
@@ -75,36 +78,17 @@
|
|||||||
|
|
||||||
<JFXButton GridPane.rowIndex="0" GridPane.columnIndex="2" text="Explore" onMouseClicked="#onExploreJavaDir" />
|
<JFXButton GridPane.rowIndex="0" GridPane.columnIndex="2" text="Explore" onMouseClicked="#onExploreJavaDir" />
|
||||||
</GridPane>
|
</GridPane>
|
||||||
<VBox fx:id="advancedSettingsPane" style="-fx-padding-bottom: 10; -fx-background-color: white; " spacing="30">
|
<HBox alignment="CENTER">
|
||||||
<JFXTextField labelFloat="true" promptText="JVM Args" styleClass="fit-width" fx:id="txtJVMArgs" maxWidth="Infinity"/>
|
<JFXButton text="Show advanced settings" onMouseClicked="#onShowAdvanced" />
|
||||||
<JFXTextField labelFloat="true" promptText="Game Args" styleClass="fit-width" fx:id="txtGameArgs" maxWidth="Infinity"/>
|
</HBox>
|
||||||
<JFXTextField labelFloat="true" promptText="Metaspace" styleClass="fit-width" fx:id="txtMetaspace" maxWidth="Infinity"/>
|
<VBox fx:id="advancedSettingsPane" spacing="30">
|
||||||
<JFXTextField labelFloat="true" promptText="Wrapper Launcher(like optirun)" styleClass="fit-width" fx:id="txtWrapper" maxWidth="Infinity"/>
|
<JFXTextField labelFloat="true" promptText="JVM Args" styleClass="fit-width" fx:id="txtJVMArgs" prefWidth="${advancedSettingsPane.width}" />
|
||||||
<JFXTextField labelFloat="true" promptText="Pre-calling command" styleClass="fit-width" fx:id="txtPrecallingCommand" maxWidth="Infinity"/>
|
<JFXTextField labelFloat="true" promptText="Game Args" styleClass="fit-width" fx:id="txtGameArgs" prefWidth="${advancedSettingsPane.width}" />
|
||||||
<JFXTextField labelFloat="true" promptText="Server IP" styleClass="fit-width" fx:id="txtServerIP" maxWidth="Infinity"/>
|
<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>
|
||||||
</VBox>
|
</VBox>
|
||||||
</ScrollPane>
|
</ScrollPane>
|
||||||
</center>
|
</fx:root>
|
||||||
<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>
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<VBox>
|
<VBox>
|
||||||
<JFXToolbar fx:id="toolbar" styleClass="jfx-tool-bar">
|
<JFXToolbar fx:id="toolbar" styleClass="jfx-tool-bar">
|
||||||
<leftItems>
|
<leftItems>
|
||||||
<JFXButton fx:id="closeButton" maxHeight="20" styleClass="toggle-icon3"
|
<JFXButton maxHeight="20" styleClass="toggle-icon3"
|
||||||
StackPane.alignment="CENTER_RIGHT" onMouseClicked="#close">
|
StackPane.alignment="CENTER_RIGHT" onMouseClicked="#close">
|
||||||
<graphic>
|
<graphic>
|
||||||
<fx:include source="/assets/svg/close.fxml"/>
|
<fx:include source="/assets/svg/close.fxml"/>
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
</leftItems>
|
</leftItems>
|
||||||
<rightItems>
|
<rightItems>
|
||||||
<JFXButton fx:id="refreshButton" maxHeight="20" styleClass="toggle-icon3" disable="true"
|
<JFXButton fx:id="refreshButton" maxHeight="20" styleClass="toggle-icon3" disable="true"
|
||||||
StackPane.alignment="CENTER_RIGHT">
|
StackPane.alignment="CENTER_RIGHT" onMouseClicked="#refrseh">
|
||||||
<graphic>
|
<graphic>
|
||||||
<fx:include source="/assets/svg/refresh.fxml"/>
|
<fx:include source="/assets/svg/refresh.fxml"/>
|
||||||
</graphic>
|
</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)
|
@Throws(AuthenticationException::class)
|
||||||
abstract fun logIn(proxy: Proxy = Proxy.NO_PROXY): AuthInfo
|
abstract fun logIn(proxy: Proxy = Proxy.NO_PROXY): AuthInfo
|
||||||
abstract fun logOut()
|
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.
|
// Offline account need not log out.
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toStorage(): Map<Any, Any> {
|
override fun toStorage(): MutableMap<Any, Any> {
|
||||||
return mapOf(
|
return mutableMapOf(
|
||||||
"uuid" to uuid,
|
"uuid" to uuid,
|
||||||
"username" to username
|
"username" to username
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString() = "OfflineAccount[username=$username,uuid=$uuid]"
|
||||||
|
|
||||||
companion object OfflineAccountFactory : AccountFactory<OfflineAccount> {
|
companion object OfflineAccountFactory : AccountFactory<OfflineAccount> {
|
||||||
|
|
||||||
override fun fromUsername(username: String, password: String): OfflineAccount {
|
override fun fromUsername(username: String, password: String): OfflineAccount {
|
||||||
|
|||||||
@@ -135,8 +135,8 @@ class YggdrasilAccount private constructor(override val username: String): Accou
|
|||||||
selectedProfile = null
|
selectedProfile = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toStorage(): Map<out Any, Any> {
|
override fun toStorage(): MutableMap<Any, Any> {
|
||||||
val result = HashMap<String, Any>()
|
val result = HashMap<Any, Any>()
|
||||||
|
|
||||||
result[STORAGE_KEY_USER_NAME] = username
|
result[STORAGE_KEY_USER_NAME] = username
|
||||||
if (userId != null)
|
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> {
|
companion object YggdrasilAccountFactory : AccountFactory<YggdrasilAccount> {
|
||||||
private val GSON = GsonBuilder()
|
private val GSON = GsonBuilder()
|
||||||
.registerTypeAdapter(GameProfile::class.java, GameProfile)
|
.registerTypeAdapter(GameProfile::class.java, GameProfile)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.launch
|
|||||||
|
|
||||||
import org.jackhuang.hmcl.auth.AuthInfo
|
import org.jackhuang.hmcl.auth.AuthInfo
|
||||||
import org.jackhuang.hmcl.game.*
|
import org.jackhuang.hmcl.game.*
|
||||||
|
import org.jackhuang.hmcl.task.TaskResult
|
||||||
import org.jackhuang.hmcl.util.*
|
import org.jackhuang.hmcl.util.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@@ -26,12 +27,12 @@ import java.util.*
|
|||||||
import kotlin.concurrent.thread
|
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 account The user account
|
||||||
* @param options The launching configuration
|
* @param options The launching configuration
|
||||||
*/
|
*/
|
||||||
open class DefaultLauncher(repository: GameRepository, version: Version, account: AuthInfo, options: LaunchOptions, listener: ProcessListener? = null, isDaemon: Boolean = true)
|
open class DefaultLauncher(repository: GameRepository, versionId: String, account: AuthInfo, options: LaunchOptions, listener: ProcessListener? = null, isDaemon: Boolean = true)
|
||||||
: Launcher(repository, version, account, options, listener, isDaemon) {
|
: Launcher(repository, versionId, account, options, listener, isDaemon) {
|
||||||
|
|
||||||
protected val native: File by lazy { repository.getNativeDirectory(version.id) }
|
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))
|
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)
|
val p = JavaProcess(builder.start(), rawCommandLine)
|
||||||
if (listener == null)
|
if (listener == null)
|
||||||
startMonitors(p)
|
startMonitors(p)
|
||||||
@@ -243,6 +244,14 @@ open class DefaultLauncher(repository: GameRepository, version: Version, account
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun launchAsync(): TaskResult<JavaProcess> {
|
||||||
|
return object : TaskResult<JavaProcess>() {
|
||||||
|
override fun execute() {
|
||||||
|
result = launch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun makeLaunchScript(file: String): File {
|
override fun makeLaunchScript(file: String): File {
|
||||||
val isWindows = OS.WINDOWS == OS.CURRENT_OS
|
val isWindows = OS.WINDOWS == OS.CURRENT_OS
|
||||||
val scriptFile = File(file + (if (isWindows) ".bat" else ".sh"))
|
val scriptFile = File(file + (if (isWindows) ".bat" else ".sh"))
|
||||||
|
|||||||
@@ -26,12 +26,13 @@ import java.io.File
|
|||||||
|
|
||||||
abstract class Launcher(
|
abstract class Launcher(
|
||||||
protected val repository: GameRepository,
|
protected val repository: GameRepository,
|
||||||
protected val version: Version,
|
protected val versionId: String,
|
||||||
protected val account: AuthInfo,
|
protected val account: AuthInfo,
|
||||||
protected val options: LaunchOptions,
|
protected val options: LaunchOptions,
|
||||||
protected val listener: ProcessListener? = null,
|
protected val listener: ProcessListener? = null,
|
||||||
protected val isDaemon: Boolean = true) {
|
protected val isDaemon: Boolean = true) {
|
||||||
|
|
||||||
|
val version: Version = repository.getVersion(versionId).resolve(repository)
|
||||||
abstract val rawCommandLine: List<String>
|
abstract val rawCommandLine: List<String>
|
||||||
abstract fun launch(): JavaProcess
|
abstract fun launch(): JavaProcess
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.task
|
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 hidden: Boolean = true
|
||||||
|
|
||||||
override val dependents: Collection<Task> = listOf(pred)
|
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.
|
* @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 <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
|
import javax.swing.SwingUtilities
|
||||||
|
|
||||||
interface Scheduler {
|
interface Scheduler {
|
||||||
|
fun schedule(block: () -> Unit): Future<*>? = schedule(Callable { block() })
|
||||||
fun schedule(block: Callable<Unit>): Future<*>?
|
fun schedule(block: Callable<Unit>): Future<*>?
|
||||||
|
|
||||||
companion object Schedulers {
|
companion object Schedulers {
|
||||||
val JAVAFX: Scheduler = SchedulerImpl(Platform::runLater)
|
val JAVAFX: Scheduler = SchedulerImpl(Platform::runLater)
|
||||||
val SWING: Scheduler = SchedulerImpl(SwingUtilities::invokeLater)
|
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<*>? {
|
override fun schedule(block: Callable<Unit>): Future<*>? {
|
||||||
val latch = CountDownLatch(1)
|
val latch = CountDownLatch(1)
|
||||||
val wrapper = AtomicReference<Exception>()
|
val wrapper = AtomicReference<Exception>()
|
||||||
executor {
|
executor.invoke(Runnable {
|
||||||
try {
|
try {
|
||||||
block.call()
|
block.call()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -40,7 +41,7 @@ interface Scheduler {
|
|||||||
} finally {
|
} finally {
|
||||||
latch.countDown()
|
latch.countDown()
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
return object : Future<Unit> {
|
return object : Future<Unit> {
|
||||||
override fun get(timeout: Long, unit: TimeUnit) {
|
override fun get(timeout: Long, unit: TimeUnit) {
|
||||||
latch.await(timeout, unit)
|
latch.await(timeout, unit)
|
||||||
@@ -66,7 +67,9 @@ interface Scheduler {
|
|||||||
}
|
}
|
||||||
val DEFAULT = NEW_THREAD
|
val DEFAULT = NEW_THREAD
|
||||||
private val CACHED_EXECUTOR: ExecutorService by lazy {
|
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 {
|
private val IO_EXECUTOR: ExecutorService by lazy {
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ abstract class Task {
|
|||||||
abstract fun execute()
|
abstract fun execute()
|
||||||
|
|
||||||
infix fun parallel(couple: Task): Task = ParallelTask(this, couple)
|
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.
|
* 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 executor() = TaskExecutor().submit(this)
|
||||||
|
|
||||||
fun subscribe(subscriber: Task) {
|
fun subscribe(subscriber: Task) = executor().apply {
|
||||||
executor().submit(subscriber).start()
|
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 {
|
override fun toString(): String {
|
||||||
return title
|
return title
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
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)
|
fun <V> of(callable: Callable<V>): TaskResult<V> = TaskCallable(callable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ class TaskExecutor() {
|
|||||||
|
|
||||||
var canceled = false
|
var canceled = false
|
||||||
private set
|
private set
|
||||||
private val totTask = AtomicInteger(0)
|
val totTask = AtomicInteger(0)
|
||||||
private val taskQueue = ConcurrentLinkedQueue<Task>()
|
private val taskQueue = ConcurrentLinkedQueue<Task>()
|
||||||
private val workerQueue = ConcurrentLinkedQueue<Future<*>>()
|
private val workerQueue = ConcurrentLinkedQueue<Future<*>>()
|
||||||
|
|
||||||
@@ -47,9 +47,10 @@ class TaskExecutor() {
|
|||||||
* Start the subscription and run all registered tasks asynchronously.
|
* Start the subscription and run all registered tasks asynchronously.
|
||||||
*/
|
*/
|
||||||
fun start() {
|
fun start() {
|
||||||
thread {
|
workerQueue.add(Scheduler.Schedulers.NEW_THREAD.schedule(Callable {
|
||||||
totTask.addAndGet(taskQueue.size)
|
totTask.addAndGet(taskQueue.size)
|
||||||
while (!taskQueue.isEmpty() && !canceled) {
|
while (!taskQueue.isEmpty()) {
|
||||||
|
if (canceled) break
|
||||||
val task = taskQueue.poll()
|
val task = taskQueue.poll()
|
||||||
if (task != null) {
|
if (task != null) {
|
||||||
val future = task.scheduler.schedule(Callable { executeTask(task); Unit })
|
val future = task.scheduler.schedule(Callable { executeTask(task); Unit })
|
||||||
@@ -61,9 +62,9 @@ class TaskExecutor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!canceled)
|
if (canceled || Thread.interrupted())
|
||||||
taskListener?.onTerminate()
|
taskListener?.onTerminate()
|
||||||
}
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -91,9 +92,11 @@ class TaskExecutor() {
|
|||||||
if (future != null)
|
if (future != null)
|
||||||
workerQueue.add(future)
|
workerQueue.add(future)
|
||||||
}
|
}
|
||||||
|
if (canceled)
|
||||||
|
return false
|
||||||
try {
|
try {
|
||||||
counter.await()
|
counter.await()
|
||||||
return success.get()
|
return success.get() && !canceled
|
||||||
} catch (e: InterruptedException) {
|
} catch (e: InterruptedException) {
|
||||||
Thread.currentThread().interrupt()
|
Thread.currentThread().interrupt()
|
||||||
// Once interrupted, we are aborting the subscription.
|
// Once interrupted, we are aborting the subscription.
|
||||||
@@ -110,9 +113,10 @@ class TaskExecutor() {
|
|||||||
LOG.fine("Executing task: ${t.title}")
|
LOG.fine("Executing task: ${t.title}")
|
||||||
taskListener?.onReady(t)
|
taskListener?.onReady(t)
|
||||||
val doDependentsSucceeded = executeTasks(t.dependents)
|
val doDependentsSucceeded = executeTasks(t.dependents)
|
||||||
|
|
||||||
var flag = false
|
var flag = false
|
||||||
try {
|
try {
|
||||||
if (!doDependentsSucceeded && t.reliant)
|
if (!doDependentsSucceeded && t.reliant || canceled)
|
||||||
throw SilentException()
|
throw SilentException()
|
||||||
|
|
||||||
t.execute()
|
t.execute()
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ data class JavaVersion internal constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
fun fromExecutable(file: File): JavaVersion {
|
fun fromExecutable(file: File): JavaVersion {
|
||||||
var platform = Platform.BIT_32
|
var platform = Platform.BIT_32
|
||||||
var version: String? = null
|
var version: String? = null
|
||||||
@@ -66,6 +67,7 @@ data class JavaVersion internal constructor(
|
|||||||
platform = Platform.BIT_64
|
platform = Platform.BIT_64
|
||||||
}
|
}
|
||||||
} catch (e: InterruptedException) {
|
} catch (e: InterruptedException) {
|
||||||
|
Thread.currentThread().interrupt()
|
||||||
throw IOException("Java process is interrupted", e)
|
throw IOException("Java process is interrupted", e)
|
||||||
}
|
}
|
||||||
val thisVersion = version ?: throw IOException("Java version not matched")
|
val thisVersion = version ?: throw IOException("Java version not matched")
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ enum class OS {
|
|||||||
ReflectionHelper.get<Long>(ManagementFactory.getOperatingSystemMXBean(), "getTotalPhysicalMemorySize") ?: 1024L
|
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
|
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
|
val PATH_SEPARATOR: String = File.pathSeparator
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ package org.jackhuang.hmcl
|
|||||||
|
|
||||||
import org.jackhuang.hmcl.auth.OfflineAccount
|
import org.jackhuang.hmcl.auth.OfflineAccount
|
||||||
import org.jackhuang.hmcl.download.DefaultDependencyManager
|
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.BMCLAPIDownloadProvider
|
||||||
import org.jackhuang.hmcl.download.MojangDownloadProvider
|
import org.jackhuang.hmcl.download.MojangDownloadProvider
|
||||||
import org.jackhuang.hmcl.game.DefaultGameRepository
|
import org.jackhuang.hmcl.game.DefaultGameRepository
|
||||||
@@ -50,7 +50,7 @@ class Test {
|
|||||||
fun launch() {
|
fun launch() {
|
||||||
val launcher = DefaultLauncher(
|
val launcher = DefaultLauncher(
|
||||||
repository = repository,
|
repository = repository,
|
||||||
version = repository.getVersion("test"),
|
versionId = "test",
|
||||||
account = OfflineAccount.fromUsername("player007").logIn(),
|
account = OfflineAccount.fromUsername("player007").logIn(),
|
||||||
options = LaunchOptions(gameDir = repository.baseDirectory),
|
options = LaunchOptions(gameDir = repository.baseDirectory),
|
||||||
listener = object : ProcessListener {
|
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
|
GNU General Public License, you may choose any version ever published
|
||||||
by the Free Software Foundation.
|
by the Free Software Foundation.
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
If the Program specifies that a PROXY can decide which future
|
||||||
versions of the GNU General Public License can be used, that proxy's
|
versions of the GNU General Public License can be used, that PROXY's
|
||||||
public statement of acceptance of a version permanently authorizes you
|
public statement of acceptance of a version permanently authorizes you
|
||||||
to choose that version for the Program.
|
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