Skin portrait

This commit is contained in:
huangyuhui
2017-08-18 22:42:11 +08:00
parent dc468f1a76
commit 27939c6b61
24 changed files with 213 additions and 142 deletions

View File

@@ -54,6 +54,7 @@ class Main : Application() {
val VERSION = "@HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@" val VERSION = "@HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@"
val NAME = "HMCL" val NAME = "HMCL"
val TITLE = "$NAME $VERSION" val TITLE = "$NAME $VERSION"
val APPDATA = getWorkingDirectory("hmcl")
lateinit var PRIMARY_STAGE: Stage lateinit var PRIMARY_STAGE: Stage
@JvmStatic @JvmStatic

View File

@@ -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
}
}

View File

@@ -19,48 +19,81 @@ package org.jackhuang.hmcl.ui
import com.jfoenix.controls.JFXButton import com.jfoenix.controls.JFXButton
import com.jfoenix.controls.JFXCheckBox import com.jfoenix.controls.JFXCheckBox
import com.jfoenix.controls.JFXProgressBar
import com.jfoenix.controls.JFXRadioButton import com.jfoenix.controls.JFXRadioButton
import com.jfoenix.effects.JFXDepthManager import com.jfoenix.effects.JFXDepthManager
import javafx.beans.binding.Bindings import javafx.beans.binding.Bindings
import javafx.fxml.FXML import javafx.fxml.FXML
import javafx.geometry.Rectangle2D
import javafx.scene.control.Label import javafx.scene.control.Label
import javafx.scene.control.ToggleGroup import javafx.scene.control.ToggleGroup
import javafx.scene.effect.BlurType import javafx.scene.effect.BlurType
import javafx.scene.effect.DropShadow import javafx.scene.effect.DropShadow
import javafx.scene.image.ImageView
import javafx.scene.layout.Pane import javafx.scene.layout.Pane
import javafx.scene.layout.StackPane import javafx.scene.layout.StackPane
import javafx.scene.layout.VBox import javafx.scene.layout.VBox
import javafx.scene.paint.Color 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 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 icon: Pane
@FXML lateinit var content: VBox @FXML lateinit var content: VBox
@FXML lateinit var header: StackPane @FXML lateinit var header: StackPane
@FXML lateinit var body: StackPane @FXML lateinit var body: StackPane
@FXML lateinit var btnDelete: JFXButton @FXML lateinit var btnDelete: JFXButton
@FXML lateinit var btnRefresh: JFXButton
@FXML lateinit var lblUser: Label @FXML lateinit var lblUser: Label
@FXML lateinit var chkSelected: JFXRadioButton @FXML lateinit var chkSelected: JFXRadioButton
@FXML lateinit var lblType: Label @FXML lateinit var lblType: Label
@FXML lateinit var pgsSkin: JFXProgressBar
@FXML lateinit var portraitView: ImageView
init { init {
loadFXML("/assets/fxml/account-item.fxml") loadFXML("/assets/fxml/account-item.fxml")
limitWidth(150.0) limitWidth(160.0)
limitHeight(140.0) limitHeight(156.0)
effect = DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -0.5, 1.0) effect = DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -0.5, 1.0)
chkSelected.toggleGroup = group 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 // create content
val headerColor = getDefaultColor(i % 12) val headerColor = getDefaultColor(i % 12)
header.style = "-fx-background-radius: 2 2 0 0; -fx-background-color: " + headerColor header.style = "-fx-background-radius: 2 2 0 0; -fx-background-color: " + headerColor
body.minHeight = Math.random() * 20 + 50
// create image view // create image view
icon.translateYProperty().bind(Bindings.createDoubleBinding(Callable { header.boundsInParent.height - icon.height / 2 }, header.boundsInParentProperty(), icon.heightProperty())) 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 { private fun getDefaultColor(i: Int): String {

View File

@@ -32,6 +32,7 @@ import org.jackhuang.hmcl.auth.Account
import org.jackhuang.hmcl.auth.OfflineAccount import org.jackhuang.hmcl.auth.OfflineAccount
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.setting.AccountSkin
import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.task.Scheduler import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.taskResult import org.jackhuang.hmcl.task.taskResult
@@ -111,15 +112,16 @@ class AccountsPage() : StackPane(), DecoratorPage {
} }
private fun buildNode(i: Int, account: Account, group: ToggleGroup): Node { private fun buildNode(i: Int, account: Account, group: ToggleGroup): Node {
return AccountItem(i, group).apply { return AccountItem(i, account, group).apply {
chkSelected.properties["account"] = account
chkSelected.isSelected = Settings.selectedAccount == account
lblUser.text = account.username
lblType.text = accountType(account)
btnDelete.setOnMouseClicked { btnDelete.setOnMouseClicked {
Settings.deleteAccount(account.username) Settings.deleteAccount(account.username)
Platform.runLater(this@AccountsPage::loadAccounts) Platform.runLater(this@AccountsPage::loadAccounts)
} }
if (account is YggdrasilAccount)
AccountSkin.loadSkinAsync(account).subscribe(Scheduler.JAVAFX) {
loadSkin()
}
} }
} }

View File

@@ -19,10 +19,12 @@ package org.jackhuang.hmcl.ui
import com.jfoenix.controls.JFXComboBox import com.jfoenix.controls.JFXComboBox
import javafx.scene.layout.* import javafx.scene.layout.*
import org.jackhuang.hmcl.auth.Account
import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.ui.construct.IconedItem import org.jackhuang.hmcl.ui.construct.IconedItem
import org.jackhuang.hmcl.ui.construct.RipplerContainer import org.jackhuang.hmcl.ui.construct.RipplerContainer
import javax.swing.event.ChangeListener
class LeftPaneController(private val leftPane: AdvancedListBox) { class LeftPaneController(private val leftPane: AdvancedListBox) {
val versionsPane = VBox() val versionsPane = VBox()
@@ -63,16 +65,15 @@ class LeftPaneController(private val leftPane: AdvancedListBox) {
} }
Controllers.mainPane.buttonLaunch.setOnMouseClicked { LauncherHelper.launch() }*/ Controllers.mainPane.buttonLaunch.setOnMouseClicked { LauncherHelper.launch() }*/
Settings.selectedAccountProperty.addListener { _, _, newValue -> Settings.selectedAccountProperty.setChangedListener {
if (newValue == null) { if (it == null) {
accountItem.lblVersionName.text = "mojang@mojang.com" accountItem.lblVersionName.text = "mojang@mojang.com"
accountItem.lblGameVersion.text = "Yggdrasil" accountItem.lblGameVersion.text = "Yggdrasil"
} else { } else {
accountItem.lblVersionName.text = newValue.username accountItem.lblVersionName.text = it.username
accountItem.lblGameVersion.text = accountType(newValue) accountItem.lblGameVersion.text = accountType(it)
} }
} }
Settings.selectedAccountProperty.fireValueChangedEvent()
if (Settings.getAccounts().isEmpty()) if (Settings.getAccounts().isEmpty())
Controllers.navigate(AccountsPage()) Controllers.navigate(AccountsPage())

View File

@@ -91,10 +91,9 @@ class MainPage : StackPane(), DecoratorPage {
fun onProfileChanged(event: ProfileChangedEvent) { fun onProfileChanged(event: ProfileChangedEvent) {
val profile = event.value val profile = event.value
profile.selectedVersionProperty.addListener { _ -> profile.selectedVersionProperty.setChangedListener {
versionChanged(profile.selectedVersion) versionChanged(profile.selectedVersion)
} }
profile.selectedVersionProperty.fireValueChangedEvent()
} }
private fun loadVersions() { private fun loadVersions() {

View File

@@ -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 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 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 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)
} }

View File

@@ -47,7 +47,7 @@ class VersionItem(i: Int, group: ToggleGroup) : StackPane() {
init { init {
loadFXML("/assets/fxml/version-item.fxml") loadFXML("/assets/fxml/version-item.fxml")
limitWidth(190.0) limitWidth(160.0)
limitHeight(156.0) limitHeight(156.0)
effect = DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -1.0, 1.0) effect = DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -1.0, 1.0)

View File

@@ -60,7 +60,7 @@ class VersionSettingsController {
@FXML lateinit var chkNoGameCheck: JFXToggleButton @FXML lateinit var chkNoGameCheck: JFXToggleButton
@FXML lateinit var componentJava: ComponentList @FXML lateinit var componentJava: ComponentList
@FXML lateinit var javaPane: VBox @FXML lateinit var javaPane: VBox
@FXML lateinit var javaPaneCustom: HBox @FXML lateinit var javaPaneCustom: BorderPane
@FXML lateinit var radioCustom: JFXRadioButton @FXML lateinit var radioCustom: JFXRadioButton
@FXML lateinit var btnJavaSelect: JFXButton @FXML lateinit var btnJavaSelect: JFXButton
@@ -100,21 +100,21 @@ class VersionSettingsController {
javaPane.children += createJavaPane(javaVersion, javaGroup) javaPane.children += createJavaPane(javaVersion, javaGroup)
} }
javaPane.children += javaPaneCustom javaPane.children += javaPaneCustom
javaPaneCustom.limitHeight(20.0)
radioCustom.toggleGroup = javaGroup radioCustom.toggleGroup = javaGroup
txtJavaDir.disableProperty().bind(radioCustom.selectedProperty().not()) txtJavaDir.disableProperty().bind(radioCustom.selectedProperty().not())
btnJavaSelect.disableProperty().bind(radioCustom.selectedProperty().not()) btnJavaSelect.disableProperty().bind(radioCustom.selectedProperty().not())
} }
private fun createJavaPane(java: JavaVersion, group: ToggleGroup): Pane { private fun createJavaPane(java: JavaVersion, group: ToggleGroup): Pane {
return HBox().apply { return BorderPane().apply {
style = "-fx-padding: 3;" style = "-fx-padding: 3;"
spacing = 8.0 limitHeight(20.0)
alignment = Pos.CENTER_LEFT left = JFXRadioButton(java.longVersion).apply {
children += JFXRadioButton(java.longVersion).apply {
toggleGroup = group toggleGroup = group
userData = java userData = java
} }
children += Label(java.binary.absolutePath).apply { right = Label(java.binary.absolutePath).apply {
styleClass += "subtitle-label" styleClass += "subtitle-label"
style += "-fx-font-size: 10;" style += "-fx-font-size: 10;"
} }

View File

@@ -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
}

View File

@@ -8,6 +8,7 @@
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Label?>
<?import com.jfoenix.controls.JFXRadioButton?> <?import com.jfoenix.controls.JFXRadioButton?>
<?import com.jfoenix.controls.JFXProgressBar?>
<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">
@@ -16,23 +17,34 @@
<BorderPane> <BorderPane>
<top> <top>
<HBox alignment="CENTER_RIGHT"> <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> </HBox>
</top> </top>
<center> <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="lblUser" style="-fx-font-size: 20;" wrapText="true" textAlignment="JUSTIFY" />
<Label fx:id="lblType" style="-fx-font-size: 10;" /> <Label fx:id="lblType" style="-fx-font-size: 10;" />
</VBox> </VBox>
</center> </center>
</BorderPane> </BorderPane>
</StackPane> </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;"> <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">
<JFXRadioButton fx:id="chkSelected" StackPane.alignment="BOTTOM_RIGHT" /> <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> </StackPane>
</VBox> </VBox>
<StackPane fx:id="icon" StackPane.alignment="TOP_RIGHT" pickOnBounds="false"> <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> <StackPane.margin>
<Insets right="12" /> <Insets right="12" />
</StackPane.margin> </StackPane.margin>

View File

@@ -18,7 +18,7 @@
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
type="StackPane"> type="StackPane">
<ScrollPane fitToHeight="true" fitToWidth="true" fx:id="scrollPane" hbarPolicy="NEVER"> <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> </JFXMasonryPane>
</ScrollPane> </ScrollPane>
<AnchorPane pickOnBounds="false"> <AnchorPane pickOnBounds="false">

View File

@@ -8,7 +8,7 @@
styleClass="transparent" type="StackPane" styleClass="transparent" type="StackPane"
xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1"> xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1">
<ScrollPane fitToHeight="true" fitToWidth="true" fx:id="scrollPane" hbarPolicy="NEVER"> <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> </JFXMasonryPane>
</ScrollPane> </ScrollPane>
<VBox style="-fx-padding: 15;" spacing="15" pickOnBounds="false" alignment="BOTTOM_RIGHT"> <VBox style="-fx-padding: 15;" spacing="15" pickOnBounds="false" alignment="BOTTOM_RIGHT">

View File

@@ -22,16 +22,22 @@
<ComponentList depth="1"> <ComponentList depth="1">
<ComponentList fx:id="componentJava" title="%settings.java_dir" hasSubtitle="true"> <!-- Java Directory --> <ComponentList fx:id="componentJava" title="%settings.java_dir" hasSubtitle="true"> <!-- Java Directory -->
<VBox fx:id="javaPane" spacing="8"> <VBox fx:id="javaPane" spacing="8" style="-fx-padding: 0 0 10 0;">
<HBox fx:id="javaPaneCustom" style="-fx-padding: 3;" spacing="3" alignment="CENTER_LEFT"> <BorderPane fx:id="javaPaneCustom" style="-fx-padding: 3;">
<JFXRadioButton fx:id="radioCustom" text="%settings.custom" /> <left>
<JFXTextField fx:id="txtJavaDir" BorderPane.alignment="CENTER_RIGHT" /> <JFXRadioButton fx:id="radioCustom" text="%settings.custom" />
<JFXButton fx:id="btnJavaSelect" onMouseClicked="#onExploreJavaDir"> </left>
<graphic> <right>
<fx:include source="/assets/svg/folder-open.fxml" /> <HBox spacing="3">
</graphic> <JFXTextField fx:id="txtJavaDir" BorderPane.alignment="CENTER_RIGHT" />
</JFXButton> <JFXButton fx:id="btnJavaSelect" onMouseClicked="#onExploreJavaDir">
</HBox> <graphic>
<fx:include source="/assets/svg/folder-open.fxml" />
</graphic>
</JFXButton>
</HBox>
</right>
</BorderPane>
</VBox> </VBox>
</ComponentList> </ComponentList>

View File

@@ -17,7 +17,7 @@
*/ */
package org.jackhuang.hmcl.auth package org.jackhuang.hmcl.auth
class AuthenticationException : Exception { open class AuthenticationException : Exception {
constructor() : super() {} constructor() : super() {}
constructor(message: String) : super(message) {} constructor(message: String) : super(message) {}
constructor(message: String, cause: Throwable) : super(message, cause) {} constructor(message: String, cause: Throwable) : super(message, cause) {}

View 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.auth.yggdrasil
import org.jackhuang.hmcl.auth.AuthenticationException
class InvalidTokenException(val account: YggdrasilAccount) : AuthenticationException()
class InvalidCredentialsException(val account: YggdrasilAccount) : AuthenticationException()

View File

@@ -36,7 +36,8 @@ class YggdrasilAccount private constructor(override val username: String): Accou
private var clientToken: String = UUID.randomUUID().toString() private var clientToken: String = UUID.randomUUID().toString()
private var isOnline: Boolean = false private var isOnline: Boolean = false
private var userProperties = PropertyMap() private var userProperties = PropertyMap()
private var selectedProfile: GameProfile? = null var selectedProfile: GameProfile? = null
private set
private var profiles: Array<GameProfile>? = null private var profiles: Array<GameProfile>? = null
private var userType: UserType = UserType.LEGACY private var userType: UserType = UserType.LEGACY
@@ -166,7 +167,11 @@ class YggdrasilAccount private constructor(override val username: String): Accou
if (response.error?.isNotBlank() ?: false) { 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) 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 return response

View File

@@ -34,7 +34,7 @@ import java.math.BigInteger
import java.util.logging.Level 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() { 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>>() var onFailed = EventManager<FailedEvent<URL>>()
private var rFile: RandomAccessFile? = null private var rFile: RandomAccessFile? = null

View File

@@ -26,7 +26,7 @@ import java.net.URL
import java.nio.charset.Charset 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>() { 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 val id = ID
override fun execute() { override fun execute() {

View File

@@ -62,7 +62,7 @@ interface Scheduler {
val NEW_THREAD: Scheduler = object : Scheduler { val NEW_THREAD: Scheduler = object : Scheduler {
override fun schedule(block: Callable<Unit>) = CACHED_EXECUTOR.submit(block) 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) override fun schedule(block: Callable<Unit>) = IO_EXECUTOR.submit(block)
} }
val DEFAULT = NEW_THREAD val DEFAULT = NEW_THREAD

View File

@@ -58,7 +58,6 @@ abstract class Task {
@Throws(Exception::class) @Throws(Exception::class)
abstract fun execute() abstract fun execute()
infix fun parallel(couple: Task): Task = ParallelTask(this, couple)
infix fun then(b: Task): Task = CoupleTask(this, { b }, true) infix fun then(b: Task): Task = CoupleTask(this, { b }, true)
infix fun with(b: Task): Task = CoupleTask(this, { b }, false) infix fun with(b: Task): Task = CoupleTask(this, { b }, false)
@@ -116,7 +115,7 @@ abstract class Task {
fun executor() = TaskExecutor().submit(this) fun executor() = TaskExecutor().submit(this)
fun executor(taskListener: TaskListener) = TaskExecutor().submit(this).apply { this.taskListener = taskListener } fun executor(taskListener: TaskListener) = TaskExecutor().submit(this).apply { this.taskListener = taskListener }
fun start() = executor().start()
fun subscribe(subscriber: Task) = executor().apply { fun subscribe(subscriber: Task) = executor().apply {
submit(subscriber).start() submit(subscriber).start()
} }

View File

@@ -82,7 +82,7 @@ data class JavaVersion internal constructor(
val parsedVersion = parseVersion(thisVersion) val parsedVersion = parseVersion(thisVersion)
if (parsedVersion == UNKNOWN) if (parsedVersion == UNKNOWN)
throw IOException("Java version '$thisVersion' can not be recognized") 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) = private fun fromExecutable(file: File, version: String) =

View File

@@ -26,14 +26,14 @@ import java.util.logging.*
import java.util.logging.Formatter import java.util.logging.Formatter
val LOG = Logger.getLogger("HMCL").apply { val LOG = Logger.getLogger("HMCL").apply {
level = Level.FINER level = Level.FINEST
useParentHandlers = false useParentHandlers = false
addHandler(FileHandler("hmcl.log").apply { addHandler(FileHandler("hmcl.log").apply {
level = Level.FINER level = Level.FINEST
formatter = DefaultFormatter formatter = DefaultFormatter
}) })
addHandler(ConsoleHandler().apply { addHandler(ConsoleHandler().apply {
level = Level.FINER level = Level.FINEST
formatter = DefaultFormatter formatter = DefaultFormatter
}) })
} }

View File

@@ -50,10 +50,6 @@ open class ImmediateStringProperty(bean: Any, name: String, initialValue: String
init { init {
addListener(changeListener) addListener(changeListener)
} }
public override fun fireValueChangedEvent() {
super.fireValueChangedEvent()
}
} }
open class ImmediateBooleanProperty(bean: Any, name: String, initialValue: Boolean): SimpleBooleanProperty(bean, name, initialValue) { 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 { init {
addListener(changeListener) addListener(changeListener)
} }
public override fun fireValueChangedEvent() {
super.fireValueChangedEvent()
}
} }
open class ImmediateIntegerProperty(bean: Any, name: String, initialValue: Int): SimpleIntegerProperty(bean, name, initialValue) { 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 { init {
addListener(changeListener) addListener(changeListener)
} }
public override fun fireValueChangedEvent() {
super.fireValueChangedEvent()
}
} }
open class ImmediateDoubleProperty(bean: Any, name: String, initialValue: Double): SimpleDoubleProperty(bean, name, initialValue) { 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 { init {
addListener(changeListener) addListener(changeListener)
} }
public override fun fireValueChangedEvent() {
super.fireValueChangedEvent()
}
} }
open class ImmediateObjectProperty<T>(bean: Any, name: String, initialValue: T): SimpleObjectProperty<T>(bean, name, initialValue) { 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) { fun setChangedListener(listener: (T) -> Unit) {
myListener = listener myListener = listener
listener(get())
} }
init { init {
addListener(changeListener) addListener(changeListener)
} }
public override fun fireValueChangedEvent() {
super.fireValueChangedEvent()
}
} }