Skin portrait
This commit is contained in:
@@ -54,6 +54,7 @@ class Main : Application() {
|
||||
val VERSION = "@HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@"
|
||||
val NAME = "HMCL"
|
||||
val TITLE = "$NAME $VERSION"
|
||||
val APPDATA = getWorkingDirectory("hmcl")
|
||||
lateinit var PRIMARY_STAGE: Stage
|
||||
|
||||
@JvmStatic
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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.setting
|
||||
|
||||
import javafx.scene.image.Image
|
||||
import org.jackhuang.hmcl.Main
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask
|
||||
import org.jackhuang.hmcl.task.Scheduler
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.util.toURL
|
||||
import java.net.Proxy
|
||||
|
||||
object AccountSkin {
|
||||
val SKIN_DIR = Main.APPDATA.resolve("skins")
|
||||
|
||||
fun loadSkins(proxy: Proxy = Settings.proxy) {
|
||||
for (account in Settings.getAccounts().values) {
|
||||
if (account is YggdrasilAccount) {
|
||||
SkinLoadTask(account, proxy, false).start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loadSkinAsync(account: YggdrasilAccount, proxy: Proxy = Settings.proxy): Task =
|
||||
SkinLoadTask(account, proxy, false)
|
||||
|
||||
fun refreshSkinAsync(account: YggdrasilAccount, proxy: Proxy = Settings.proxy): Task =
|
||||
SkinLoadTask(account, proxy, true)
|
||||
|
||||
private class SkinLoadTask(val account: YggdrasilAccount, val proxy: Proxy, val refresh: Boolean = false): Task() {
|
||||
override val scheduler = Scheduler.IO
|
||||
override val dependencies = mutableListOf<Task>()
|
||||
|
||||
override fun execute() {
|
||||
if (account.canLogIn && (account.selectedProfile == null || refresh))
|
||||
account.logIn(proxy)
|
||||
val profile = account.selectedProfile ?: return
|
||||
val name = profile.name ?: return
|
||||
val url = "http://skins.minecraft.net/MinecraftSkins/$name.png"
|
||||
val file = getSkinFile(name)
|
||||
if (!refresh && file.exists())
|
||||
return
|
||||
dependencies += FileDownloadTask(url.toURL(), file, proxy = proxy)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSkinFile(name: String) = SKIN_DIR.resolve("$name.png")
|
||||
private val DEFAULT_IMAGE = Image("/assets/img/icon.png")
|
||||
|
||||
fun getSkin(account: YggdrasilAccount, scaleRatio: Double = 1.0): Image {
|
||||
if (account.selectedProfile == null) return DEFAULT_IMAGE
|
||||
val name = account.selectedProfile?.name ?: return DEFAULT_IMAGE
|
||||
val file = getSkinFile(name)
|
||||
if (file.exists()) {
|
||||
val original = Image("file:" + file.absolutePath)
|
||||
return Image("file:" + file.absolutePath, original.width * scaleRatio, original.height * scaleRatio, false, false)
|
||||
}
|
||||
else return DEFAULT_IMAGE
|
||||
}
|
||||
}
|
||||
@@ -19,48 +19,81 @@ package org.jackhuang.hmcl.ui
|
||||
|
||||
import com.jfoenix.controls.JFXButton
|
||||
import com.jfoenix.controls.JFXCheckBox
|
||||
import com.jfoenix.controls.JFXProgressBar
|
||||
import com.jfoenix.controls.JFXRadioButton
|
||||
import com.jfoenix.effects.JFXDepthManager
|
||||
import javafx.beans.binding.Bindings
|
||||
import javafx.fxml.FXML
|
||||
import javafx.geometry.Rectangle2D
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.control.ToggleGroup
|
||||
import javafx.scene.effect.BlurType
|
||||
import javafx.scene.effect.DropShadow
|
||||
import javafx.scene.image.ImageView
|
||||
import javafx.scene.layout.Pane
|
||||
import javafx.scene.layout.StackPane
|
||||
import javafx.scene.layout.VBox
|
||||
import javafx.scene.paint.Color
|
||||
import org.jackhuang.hmcl.auth.Account
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
|
||||
import org.jackhuang.hmcl.setting.AccountSkin
|
||||
import org.jackhuang.hmcl.setting.Settings
|
||||
import org.jackhuang.hmcl.task.Scheduler
|
||||
import java.util.concurrent.Callable
|
||||
|
||||
class AccountItem(i: Int, group: ToggleGroup) : StackPane() {
|
||||
class AccountItem(i: Int, val account: Account, group: ToggleGroup) : StackPane() {
|
||||
@FXML lateinit var icon: Pane
|
||||
@FXML lateinit var content: VBox
|
||||
@FXML lateinit var header: StackPane
|
||||
@FXML lateinit var body: StackPane
|
||||
@FXML lateinit var btnDelete: JFXButton
|
||||
@FXML lateinit var btnRefresh: JFXButton
|
||||
@FXML lateinit var lblUser: Label
|
||||
@FXML lateinit var chkSelected: JFXRadioButton
|
||||
@FXML lateinit var lblType: Label
|
||||
@FXML lateinit var pgsSkin: JFXProgressBar
|
||||
@FXML lateinit var portraitView: ImageView
|
||||
|
||||
init {
|
||||
loadFXML("/assets/fxml/account-item.fxml")
|
||||
|
||||
limitWidth(150.0)
|
||||
limitHeight(140.0)
|
||||
limitWidth(160.0)
|
||||
limitHeight(156.0)
|
||||
|
||||
effect = DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -0.5, 1.0)
|
||||
|
||||
chkSelected.toggleGroup = group
|
||||
btnDelete.graphic = SVG.delete("white", 15.0, 15.0)
|
||||
btnDelete.graphic = SVG.delete("black", 15.0, 15.0)
|
||||
btnRefresh.graphic = SVG.refresh("black", 15.0, 15.0)
|
||||
|
||||
// create content
|
||||
val headerColor = getDefaultColor(i % 12)
|
||||
header.style = "-fx-background-radius: 2 2 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()))
|
||||
|
||||
chkSelected.properties["account"] = account
|
||||
chkSelected.isSelected = Settings.selectedAccount == account
|
||||
lblUser.text = account.username
|
||||
lblType.text = accountType(account)
|
||||
|
||||
if (account is YggdrasilAccount)
|
||||
btnRefresh.setOnMouseClicked {
|
||||
pgsSkin.isVisible = true
|
||||
AccountSkin.refreshSkinAsync(account).subscribe(Scheduler.JAVAFX) { loadSkin() }
|
||||
}
|
||||
}
|
||||
|
||||
fun loadSkin() {
|
||||
if (account !is YggdrasilAccount)
|
||||
return
|
||||
pgsSkin.isVisible = false
|
||||
val size = 8.0 * 4
|
||||
portraitView.viewport = Rectangle2D(size, size, size, size)
|
||||
portraitView.image = AccountSkin.getSkin(account, 4.0)
|
||||
portraitView.fitHeight = 32.0
|
||||
portraitView.fitWidth = 32.0
|
||||
}
|
||||
|
||||
private fun getDefaultColor(i: Int): String {
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.jackhuang.hmcl.auth.Account
|
||||
import org.jackhuang.hmcl.auth.OfflineAccount
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
|
||||
import org.jackhuang.hmcl.i18n
|
||||
import org.jackhuang.hmcl.setting.AccountSkin
|
||||
import org.jackhuang.hmcl.setting.Settings
|
||||
import org.jackhuang.hmcl.task.Scheduler
|
||||
import org.jackhuang.hmcl.task.taskResult
|
||||
@@ -111,15 +112,16 @@ class AccountsPage() : StackPane(), DecoratorPage {
|
||||
}
|
||||
|
||||
private fun buildNode(i: Int, account: Account, group: ToggleGroup): Node {
|
||||
return AccountItem(i, group).apply {
|
||||
chkSelected.properties["account"] = account
|
||||
chkSelected.isSelected = Settings.selectedAccount == account
|
||||
lblUser.text = account.username
|
||||
lblType.text = accountType(account)
|
||||
return AccountItem(i, account, group).apply {
|
||||
btnDelete.setOnMouseClicked {
|
||||
Settings.deleteAccount(account.username)
|
||||
Platform.runLater(this@AccountsPage::loadAccounts)
|
||||
}
|
||||
|
||||
if (account is YggdrasilAccount)
|
||||
AccountSkin.loadSkinAsync(account).subscribe(Scheduler.JAVAFX) {
|
||||
loadSkin()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,12 @@ package org.jackhuang.hmcl.ui
|
||||
|
||||
import com.jfoenix.controls.JFXComboBox
|
||||
import javafx.scene.layout.*
|
||||
import org.jackhuang.hmcl.auth.Account
|
||||
import org.jackhuang.hmcl.i18n
|
||||
import org.jackhuang.hmcl.setting.Settings
|
||||
import org.jackhuang.hmcl.ui.construct.IconedItem
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer
|
||||
import javax.swing.event.ChangeListener
|
||||
|
||||
class LeftPaneController(private val leftPane: AdvancedListBox) {
|
||||
val versionsPane = VBox()
|
||||
@@ -63,16 +65,15 @@ class LeftPaneController(private val leftPane: AdvancedListBox) {
|
||||
}
|
||||
Controllers.mainPane.buttonLaunch.setOnMouseClicked { LauncherHelper.launch() }*/
|
||||
|
||||
Settings.selectedAccountProperty.addListener { _, _, newValue ->
|
||||
if (newValue == null) {
|
||||
Settings.selectedAccountProperty.setChangedListener {
|
||||
if (it == null) {
|
||||
accountItem.lblVersionName.text = "mojang@mojang.com"
|
||||
accountItem.lblGameVersion.text = "Yggdrasil"
|
||||
} else {
|
||||
accountItem.lblVersionName.text = newValue.username
|
||||
accountItem.lblGameVersion.text = accountType(newValue)
|
||||
accountItem.lblVersionName.text = it.username
|
||||
accountItem.lblGameVersion.text = accountType(it)
|
||||
}
|
||||
}
|
||||
Settings.selectedAccountProperty.fireValueChangedEvent()
|
||||
|
||||
if (Settings.getAccounts().isEmpty())
|
||||
Controllers.navigate(AccountsPage())
|
||||
|
||||
@@ -91,10 +91,9 @@ class MainPage : StackPane(), DecoratorPage {
|
||||
|
||||
fun onProfileChanged(event: ProfileChangedEvent) {
|
||||
val profile = event.value
|
||||
profile.selectedVersionProperty.addListener { _ ->
|
||||
profile.selectedVersionProperty.setChangedListener {
|
||||
versionChanged(profile.selectedVersion)
|
||||
}
|
||||
profile.selectedVersionProperty.fireValueChangedEvent()
|
||||
}
|
||||
|
||||
private fun loadVersions() {
|
||||
|
||||
@@ -61,5 +61,5 @@ object SVG {
|
||||
fun navigate(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z", fill, width, height)
|
||||
fun launch(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M1008 6.286q18.857 13.714 15.429 36.571l-146.286 877.714q-2.857 16.571-18.286 25.714-8 4.571-17.714 4.571-6.286 0-13.714-2.857l-258.857-105.714-138.286 168.571q-10.286 13.143-28 13.143-7.429 0-12.571-2.286-10.857-4-17.429-13.429t-6.571-20.857v-199.429l493.714-605.143-610.857 528.571-225.714-92.571q-21.143-8-22.857-31.429-1.143-22.857 18.286-33.714l950.857-548.571q8.571-5.143 18.286-5.14311.429 0 20.571 6.286z", fill, width, height)
|
||||
fun pencil(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M20.71,4.04C21.1,3.65 21.1,3 20.71,2.63L18.37,0.29C18,-0.1 17.35,-0.1 16.96,0.29L15,2.25L18.75,6M17.75,7L14,3.25L4,13.25V17H7.75L17.75,7Z", fill, width, height)
|
||||
|
||||
fun refresh(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z", fill, width, height)
|
||||
}
|
||||
@@ -47,7 +47,7 @@ class VersionItem(i: Int, group: ToggleGroup) : StackPane() {
|
||||
init {
|
||||
loadFXML("/assets/fxml/version-item.fxml")
|
||||
|
||||
limitWidth(190.0)
|
||||
limitWidth(160.0)
|
||||
limitHeight(156.0)
|
||||
|
||||
effect = DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -1.0, 1.0)
|
||||
|
||||
@@ -60,7 +60,7 @@ class VersionSettingsController {
|
||||
@FXML lateinit var chkNoGameCheck: JFXToggleButton
|
||||
@FXML lateinit var componentJava: ComponentList
|
||||
@FXML lateinit var javaPane: VBox
|
||||
@FXML lateinit var javaPaneCustom: HBox
|
||||
@FXML lateinit var javaPaneCustom: BorderPane
|
||||
@FXML lateinit var radioCustom: JFXRadioButton
|
||||
@FXML lateinit var btnJavaSelect: JFXButton
|
||||
|
||||
@@ -100,21 +100,21 @@ class VersionSettingsController {
|
||||
javaPane.children += createJavaPane(javaVersion, javaGroup)
|
||||
}
|
||||
javaPane.children += javaPaneCustom
|
||||
javaPaneCustom.limitHeight(20.0)
|
||||
radioCustom.toggleGroup = javaGroup
|
||||
txtJavaDir.disableProperty().bind(radioCustom.selectedProperty().not())
|
||||
btnJavaSelect.disableProperty().bind(radioCustom.selectedProperty().not())
|
||||
}
|
||||
|
||||
private fun createJavaPane(java: JavaVersion, group: ToggleGroup): Pane {
|
||||
return HBox().apply {
|
||||
return BorderPane().apply {
|
||||
style = "-fx-padding: 3;"
|
||||
spacing = 8.0
|
||||
alignment = Pos.CENTER_LEFT
|
||||
children += JFXRadioButton(java.longVersion).apply {
|
||||
limitHeight(20.0)
|
||||
left = JFXRadioButton(java.longVersion).apply {
|
||||
toggleGroup = group
|
||||
userData = java
|
||||
}
|
||||
children += Label(java.binary.absolutePath).apply {
|
||||
right = Label(java.binary.absolutePath).apply {
|
||||
styleClass += "subtitle-label"
|
||||
style += "-fx-font-size: 10;"
|
||||
}
|
||||
|
||||
@@ -1,68 +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.construct
|
||||
|
||||
import javafx.scene.canvas.GraphicsContext
|
||||
import javafx.scene.paint.Color
|
||||
|
||||
object RenderedPartlyTexture {
|
||||
|
||||
fun renderAvatar(ctx: GraphicsContext, skinScale: Int, skinImg: Array<Array<Color>>, width: Double) {
|
||||
val helmBs = width / (1.0 * skinScale)
|
||||
val headBs = (width - 1.0 * helmBs) / (1.0 * skinScale)
|
||||
val headOffset = helmBs / 2.0
|
||||
|
||||
// render head.front
|
||||
renderRange(ctx, skinImg, head_front_x * skinScale, head_front_y * skinScale, head_front_w * skinScale, head_front_h * skinScale, headOffset, headOffset, headBs)
|
||||
|
||||
// render helm.front
|
||||
renderRange(ctx, skinImg, helm_front_x * skinScale, helm_front_y * skinScale, helm_front_w * skinScale, helm_front_h * skinScale, 0.0, 0.0, helmBs)
|
||||
}
|
||||
|
||||
private fun renderRange(ctx: GraphicsContext, img: Array<Array<Color>>, x0: Int, y0: Int, w: Int, h: Int, tx0: Double, ty0: Double, bs: Double) {
|
||||
var x: Int
|
||||
var y: Int
|
||||
var tx: Double
|
||||
var ty: Double
|
||||
for (dx in 0..w - 1) {
|
||||
for (dy in 0..h - 1) {
|
||||
x = x0 + dx
|
||||
y = y0 + dy
|
||||
tx = tx0 + bs * dx
|
||||
ty = ty0 + bs * dy
|
||||
ctx.fill = img[x][y]
|
||||
ctx.fillRect(tx, ty, bs, bs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The specification of skin can be found at https://github.com/minotar/skin-spec
|
||||
*/
|
||||
|
||||
internal val head_front_x = 1
|
||||
internal val head_front_y = 1
|
||||
internal val head_front_w = 1
|
||||
internal val head_front_h = 1
|
||||
|
||||
internal val helm_front_x = 5
|
||||
internal val helm_front_y = 1
|
||||
internal val helm_front_w = 1
|
||||
internal val helm_front_h = 1
|
||||
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import com.jfoenix.controls.JFXRadioButton?>
|
||||
<?import com.jfoenix.controls.JFXProgressBar?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
@@ -16,23 +17,34 @@
|
||||
<BorderPane>
|
||||
<top>
|
||||
<HBox alignment="CENTER_RIGHT">
|
||||
<JFXButton fx:id="btnDelete" styleClass="toggle-icon3" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30" />
|
||||
<JFXProgressBar fx:id="pgsSkin" visible="false" />
|
||||
</HBox>
|
||||
</top>
|
||||
<center>
|
||||
<VBox style="-fx-padding: 0 0 5 20;">
|
||||
<VBox style="-fx-padding: 20 20 0 20;">
|
||||
<Label fx:id="lblUser" style="-fx-font-size: 20;" wrapText="true" textAlignment="JUSTIFY" />
|
||||
<Label fx:id="lblType" style="-fx-font-size: 10;" />
|
||||
</VBox>
|
||||
</center>
|
||||
</BorderPane>
|
||||
</StackPane>
|
||||
<StackPane fx:id="body" style="-fx-background-radius: 0 0 2 2; -fx-background-color: rgb(255,255,255,0.87); -fx-padding: 0 0 10 0;">
|
||||
<JFXRadioButton fx:id="chkSelected" StackPane.alignment="BOTTOM_RIGHT" />
|
||||
<StackPane fx:id="body" style="-fx-background-radius: 0 0 2 2; -fx-background-color: rgb(255,255,255,0.87); -fx-padding: 8;" minHeight="40">
|
||||
<BorderPane>
|
||||
<left>
|
||||
<HBox spacing="8">
|
||||
<JFXButton fx:id="btnRefresh" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30" />
|
||||
<JFXButton fx:id="btnDelete" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30" />
|
||||
</HBox>
|
||||
</left>
|
||||
<right>
|
||||
<JFXRadioButton fx:id="chkSelected" BorderPane.alignment="CENTER_RIGHT" />
|
||||
</right>
|
||||
</BorderPane>
|
||||
|
||||
</StackPane>
|
||||
</VBox>
|
||||
<StackPane fx:id="icon" StackPane.alignment="TOP_RIGHT" pickOnBounds="false">
|
||||
<ImageView StackPane.alignment="CENTER_RIGHT">
|
||||
<ImageView fx:id="portraitView" StackPane.alignment="CENTER_RIGHT" smooth="false">
|
||||
<StackPane.margin>
|
||||
<Insets right="12" />
|
||||
</StackPane.margin>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
<ScrollPane fitToHeight="true" fitToWidth="true" fx:id="scrollPane" hbarPolicy="NEVER">
|
||||
<JFXMasonryPane fx:id="masonryPane">
|
||||
<JFXMasonryPane fx:id="masonryPane" HSpacing="3" VSpacing="3" cellWidth="182" cellHeight="160">
|
||||
</JFXMasonryPane>
|
||||
</ScrollPane>
|
||||
<AnchorPane pickOnBounds="false">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
styleClass="transparent" type="StackPane"
|
||||
xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<ScrollPane fitToHeight="true" fitToWidth="true" fx:id="scrollPane" hbarPolicy="NEVER">
|
||||
<JFXMasonryPane fx:id="masonryPane" HSpacing="3" VSpacing="3" cellWidth="192" cellHeight="160">
|
||||
<JFXMasonryPane fx:id="masonryPane" HSpacing="3" VSpacing="3" cellWidth="182" cellHeight="160">
|
||||
</JFXMasonryPane>
|
||||
</ScrollPane>
|
||||
<VBox style="-fx-padding: 15;" spacing="15" pickOnBounds="false" alignment="BOTTOM_RIGHT">
|
||||
|
||||
@@ -22,9 +22,13 @@
|
||||
<ComponentList depth="1">
|
||||
|
||||
<ComponentList fx:id="componentJava" title="%settings.java_dir" hasSubtitle="true"> <!-- Java Directory -->
|
||||
<VBox fx:id="javaPane" spacing="8">
|
||||
<HBox fx:id="javaPaneCustom" style="-fx-padding: 3;" spacing="3" alignment="CENTER_LEFT">
|
||||
<VBox fx:id="javaPane" spacing="8" style="-fx-padding: 0 0 10 0;">
|
||||
<BorderPane fx:id="javaPaneCustom" style="-fx-padding: 3;">
|
||||
<left>
|
||||
<JFXRadioButton fx:id="radioCustom" text="%settings.custom" />
|
||||
</left>
|
||||
<right>
|
||||
<HBox spacing="3">
|
||||
<JFXTextField fx:id="txtJavaDir" BorderPane.alignment="CENTER_RIGHT" />
|
||||
<JFXButton fx:id="btnJavaSelect" onMouseClicked="#onExploreJavaDir">
|
||||
<graphic>
|
||||
@@ -32,6 +36,8 @@
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
</HBox>
|
||||
</right>
|
||||
</BorderPane>
|
||||
</VBox>
|
||||
</ComponentList>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth
|
||||
|
||||
class AuthenticationException : Exception {
|
||||
open class AuthenticationException : Exception {
|
||||
constructor() : super() {}
|
||||
constructor(message: String) : super(message) {}
|
||||
constructor(message: String, cause: Throwable) : super(message, cause) {}
|
||||
|
||||
@@ -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.auth.yggdrasil
|
||||
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException
|
||||
|
||||
class InvalidTokenException(val account: YggdrasilAccount) : AuthenticationException()
|
||||
|
||||
class InvalidCredentialsException(val account: YggdrasilAccount) : AuthenticationException()
|
||||
@@ -36,7 +36,8 @@ class YggdrasilAccount private constructor(override val username: String): Accou
|
||||
private var clientToken: String = UUID.randomUUID().toString()
|
||||
private var isOnline: Boolean = false
|
||||
private var userProperties = PropertyMap()
|
||||
private var selectedProfile: GameProfile? = null
|
||||
var selectedProfile: GameProfile? = null
|
||||
private set
|
||||
private var profiles: Array<GameProfile>? = null
|
||||
private var userType: UserType = UserType.LEGACY
|
||||
|
||||
@@ -166,7 +167,11 @@ class YggdrasilAccount private constructor(override val username: String): Accou
|
||||
|
||||
if (response.error?.isNotBlank() ?: false) {
|
||||
LOG.severe("Failed to log in, the auth server returned an error: " + response.error + ", message: " + response.errorMessage + ", cause: " + response.cause)
|
||||
throw AuthenticationException("Request error: ${response.errorMessage}")
|
||||
throw when (response.errorMessage) {
|
||||
"Invalid token." -> InvalidTokenException(this)
|
||||
"Invalid credentials. Invalid username or password." -> InvalidCredentialsException(this)
|
||||
else -> AuthenticationException("Request error: ${response.errorMessage}")
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
||||
|
||||
@@ -34,7 +34,7 @@ import java.math.BigInteger
|
||||
import java.util.logging.Level
|
||||
|
||||
class FileDownloadTask @JvmOverloads constructor(val url: URL, val file: File, val hash: String? = null, val retry: Int = 5, val proxy: Proxy = Proxy.NO_PROXY): Task() {
|
||||
override val scheduler: Scheduler = Scheduler.IO_THREAD
|
||||
override val scheduler: Scheduler = Scheduler.IO
|
||||
|
||||
var onFailed = EventManager<FailedEvent<URL>>()
|
||||
private var rFile: RandomAccessFile? = null
|
||||
|
||||
@@ -26,7 +26,7 @@ import java.net.URL
|
||||
import java.nio.charset.Charset
|
||||
|
||||
class GetTask @JvmOverloads constructor(val url: URL, val encoding: Charset = Charsets.UTF_8, private val retry: Int = 5, private val proxy: Proxy = Proxy.NO_PROXY): TaskResult<String>() {
|
||||
override val scheduler: Scheduler = Scheduler.IO_THREAD
|
||||
override val scheduler: Scheduler = Scheduler.IO
|
||||
override val id = ID
|
||||
|
||||
override fun execute() {
|
||||
|
||||
@@ -62,7 +62,7 @@ interface Scheduler {
|
||||
val NEW_THREAD: Scheduler = object : Scheduler {
|
||||
override fun schedule(block: Callable<Unit>) = CACHED_EXECUTOR.submit(block)
|
||||
}
|
||||
val IO_THREAD: Scheduler = object : Scheduler {
|
||||
val IO: Scheduler = object : Scheduler {
|
||||
override fun schedule(block: Callable<Unit>) = IO_EXECUTOR.submit(block)
|
||||
}
|
||||
val DEFAULT = NEW_THREAD
|
||||
|
||||
@@ -58,7 +58,6 @@ abstract class Task {
|
||||
@Throws(Exception::class)
|
||||
abstract fun execute()
|
||||
|
||||
infix fun parallel(couple: Task): Task = ParallelTask(this, couple)
|
||||
infix fun then(b: Task): Task = CoupleTask(this, { b }, true)
|
||||
infix fun with(b: Task): Task = CoupleTask(this, { b }, false)
|
||||
|
||||
@@ -116,7 +115,7 @@ abstract class Task {
|
||||
|
||||
fun executor() = TaskExecutor().submit(this)
|
||||
fun executor(taskListener: TaskListener) = TaskExecutor().submit(this).apply { this.taskListener = taskListener }
|
||||
|
||||
fun start() = executor().start()
|
||||
fun subscribe(subscriber: Task) = executor().apply {
|
||||
submit(subscriber).start()
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ data class JavaVersion internal constructor(
|
||||
val parsedVersion = parseVersion(thisVersion)
|
||||
if (parsedVersion == UNKNOWN)
|
||||
throw IOException("Java version '$thisVersion' can not be recognized")
|
||||
return JavaVersion(file.parentFile, thisVersion, platform)
|
||||
return JavaVersion(file, thisVersion, platform)
|
||||
}
|
||||
|
||||
private fun fromExecutable(file: File, version: String) =
|
||||
|
||||
@@ -26,14 +26,14 @@ import java.util.logging.*
|
||||
import java.util.logging.Formatter
|
||||
|
||||
val LOG = Logger.getLogger("HMCL").apply {
|
||||
level = Level.FINER
|
||||
level = Level.FINEST
|
||||
useParentHandlers = false
|
||||
addHandler(FileHandler("hmcl.log").apply {
|
||||
level = Level.FINER
|
||||
level = Level.FINEST
|
||||
formatter = DefaultFormatter
|
||||
})
|
||||
addHandler(ConsoleHandler().apply {
|
||||
level = Level.FINER
|
||||
level = Level.FINEST
|
||||
formatter = DefaultFormatter
|
||||
})
|
||||
}
|
||||
|
||||
@@ -50,10 +50,6 @@ open class ImmediateStringProperty(bean: Any, name: String, initialValue: String
|
||||
init {
|
||||
addListener(changeListener)
|
||||
}
|
||||
|
||||
public override fun fireValueChangedEvent() {
|
||||
super.fireValueChangedEvent()
|
||||
}
|
||||
}
|
||||
|
||||
open class ImmediateBooleanProperty(bean: Any, name: String, initialValue: Boolean): SimpleBooleanProperty(bean, name, initialValue) {
|
||||
@@ -85,10 +81,6 @@ open class ImmediateBooleanProperty(bean: Any, name: String, initialValue: Boole
|
||||
init {
|
||||
addListener(changeListener)
|
||||
}
|
||||
|
||||
public override fun fireValueChangedEvent() {
|
||||
super.fireValueChangedEvent()
|
||||
}
|
||||
}
|
||||
|
||||
open class ImmediateIntegerProperty(bean: Any, name: String, initialValue: Int): SimpleIntegerProperty(bean, name, initialValue) {
|
||||
@@ -120,10 +112,6 @@ open class ImmediateIntegerProperty(bean: Any, name: String, initialValue: Int):
|
||||
init {
|
||||
addListener(changeListener)
|
||||
}
|
||||
|
||||
public override fun fireValueChangedEvent() {
|
||||
super.fireValueChangedEvent()
|
||||
}
|
||||
}
|
||||
|
||||
open class ImmediateDoubleProperty(bean: Any, name: String, initialValue: Double): SimpleDoubleProperty(bean, name, initialValue) {
|
||||
@@ -155,10 +143,6 @@ open class ImmediateDoubleProperty(bean: Any, name: String, initialValue: Double
|
||||
init {
|
||||
addListener(changeListener)
|
||||
}
|
||||
|
||||
public override fun fireValueChangedEvent() {
|
||||
super.fireValueChangedEvent()
|
||||
}
|
||||
}
|
||||
|
||||
open class ImmediateObjectProperty<T>(bean: Any, name: String, initialValue: T): SimpleObjectProperty<T>(bean, name, initialValue) {
|
||||
@@ -185,13 +169,10 @@ open class ImmediateObjectProperty<T>(bean: Any, name: String, initialValue: T):
|
||||
|
||||
fun setChangedListener(listener: (T) -> Unit) {
|
||||
myListener = listener
|
||||
listener(get())
|
||||
}
|
||||
|
||||
init {
|
||||
addListener(changeListener)
|
||||
}
|
||||
|
||||
public override fun fireValueChangedEvent() {
|
||||
super.fireValueChangedEvent()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user