diff --git a/.gitignore b/.gitignore index ed6fc4071..529f413ad 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ hs_err_pid* .gradle .idea +*.lck +*.1 +*.2 *.log .mine* NVIDIA diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/FXUtils.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/FXUtils.kt index acd841fc3..cfc8df77b 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/FXUtils.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/FXUtils.kt @@ -18,11 +18,9 @@ package org.jackhuang.hmcl.ui import com.jfoenix.concurrency.JFXUtilities -import com.jfoenix.controls.JFXCheckBox -import com.jfoenix.controls.JFXComboBox -import com.jfoenix.controls.JFXScrollPane -import com.jfoenix.controls.JFXTextField +import com.jfoenix.controls.* import javafx.animation.Animation +import javafx.animation.Interpolator import javafx.animation.KeyFrame import javafx.animation.Timeline import javafx.beans.property.Property @@ -144,6 +142,11 @@ fun bindString(textField: JFXTextField, property: Property) { textField.textProperty().bindBidirectional(property) } +fun bindBoolean(toggleButton: JFXToggleButton, property: Property) { + toggleButton.selectedProperty().unbind() + toggleButton.selectedProperty().bindBidirectional(property) +} + fun bindBoolean(checkBox: JFXCheckBox, property: Property) { checkBox.selectedProperty().unbind() checkBox.selectedProperty().bindBidirectional(property) @@ -163,4 +166,20 @@ fun unbindEnum(comboBox: JFXComboBox<*>) { @Suppress("UNCHECKED_CAST") val listener = comboBox.properties["listener"] as? ChangeListener ?: return comboBox.selectionModel.selectedIndexProperty().removeListener(listener) +} + + +/** + * Built-in interpolator that provides discrete time interpolation. The + * return value of `interpolate()` is `endValue` only when the + * input `fraction` is 1.0, and `startValue` otherwise. + */ +val SINE: Interpolator = object : Interpolator() { + override fun curve(t: Double): Double { + return Math.sin(t * Math.PI / 2) + } + + override fun toString(): String { + return "Interpolator.DISCRETE" + } } \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MainPage.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MainPage.kt index ddfc2c807..c5fb7f47c 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MainPage.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/MainPage.kt @@ -54,6 +54,10 @@ class MainPage : StackPane(), DecoratorPage { init { loadFXML("/assets/fxml/main.fxml") + btnLaunch.graphic = SVG.launch("white", 15.0, 15.0) + btnLaunch.limitWidth(40.0) + btnLaunch.limitHeight(40.0) + EVENT_BUS.channel() += this::loadVersions EVENT_BUS.channel() += this::onProfilesLoading EVENT_BUS.channel() += this::onProfileChanged diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SVG.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SVG.kt index ae22d4c69..00ff04a2e 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SVG.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SVG.kt @@ -56,4 +56,12 @@ object SVG { 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) + fun expand(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z", fill, width, height) + fun collapse(fill: String = "white", width: Double = 20.0, height: Double = 20.0) = createSVGPath("M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z", 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.143" + + " 11.429 0 20.571 6.286z", fill, width, height) } \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SettingsPage.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SettingsPage.kt index e80c1444d..643d51ba1 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SettingsPage.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/SettingsPage.kt @@ -47,6 +47,9 @@ class SettingsPage : StackPane(), DecoratorPage { init { loadFXML("/assets/fxml/setting.fxml") + cboLanguage.limitWidth(400.0) + cboDownloadSource.limitWidth(400.0) + txtProxyHost.text = Settings.PROXY_HOST txtProxyHost.textProperty().addListener { _, _, newValue -> Settings.PROXY_HOST = newValue diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/VersionSettingsController.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/VersionSettingsController.kt index 2d70cced0..ce01a6383 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/VersionSettingsController.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/VersionSettingsController.kt @@ -17,9 +17,7 @@ */ package org.jackhuang.hmcl.ui -import com.jfoenix.controls.JFXCheckBox -import com.jfoenix.controls.JFXComboBox -import com.jfoenix.controls.JFXTextField +import com.jfoenix.controls.* import javafx.beans.InvalidationListener import javafx.fxml.FXML import javafx.scene.control.Label @@ -29,6 +27,7 @@ import javafx.scene.layout.VBox import javafx.stage.DirectoryChooser import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.setting.VersionSetting +import org.jackhuang.hmcl.ui.construct.ComponentList import org.jackhuang.hmcl.util.OS class VersionSettingsController { @@ -46,20 +45,31 @@ class VersionSettingsController { @FXML lateinit var txtPrecallingCommand: JFXTextField @FXML lateinit var txtServerIP: JFXTextField @FXML lateinit var txtGameDir: JFXTextField - @FXML lateinit var advancedSettingsPane: VBox + @FXML lateinit var advancedSettingsPane: ComponentList @FXML lateinit var cboLauncherVisibility: JFXComboBox<*> @FXML lateinit var cboRunDirectory: JFXComboBox<*> @FXML lateinit var chkFullscreen: JFXCheckBox @FXML lateinit var lblPhysicalMemory: Label - @FXML lateinit var chkNoJVMArgs: JFXCheckBox - @FXML lateinit var chkNoCommon: JFXCheckBox - @FXML lateinit var chkNoGameCheck: JFXCheckBox + @FXML lateinit var chkNoJVMArgs: JFXToggleButton + @FXML lateinit var chkNoCommon: JFXToggleButton + @FXML lateinit var chkNoGameCheck: JFXToggleButton fun initialize() { lblPhysicalMemory.text = i18n("settings.physical_memory") + ": ${OS.TOTAL_MEMORY}MB" scroll.smoothScrolling() + val limit = 300.0 + txtGameDir.limitWidth(limit) + txtMaxMemory.limitWidth(limit) + cboLauncherVisibility.limitWidth(limit) + cboRunDirectory.limitWidth(limit) + + val limitHeight = 10.0 + chkNoJVMArgs.limitHeight(limitHeight) + chkNoCommon.limitHeight(limitHeight) + chkNoGameCheck.limitHeight(limitHeight) + fun validation(field: JFXTextField) = InvalidationListener { field.validate() } fun validator(nullable: Boolean = false) = NumberValidator(nullable).apply { message = "Must be a number." } diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/construct/ComponentList.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/construct/ComponentList.kt new file mode 100644 index 000000000..cadeabb64 --- /dev/null +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/construct/ComponentList.kt @@ -0,0 +1,72 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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.beans.DefaultProperty +import javafx.beans.property.SimpleIntegerProperty +import javafx.beans.property.SimpleObjectProperty +import javafx.collections.FXCollections +import javafx.collections.ListChangeListener +import javafx.collections.ObservableList +import javafx.scene.Node +import javafx.scene.layout.StackPane +import javafx.scene.layout.VBox +import org.jackhuang.hmcl.util.* + +@DefaultProperty("content") +class ComponentList: StackPane() { + + val vbox: VBox + + val content: ObservableList = FXCollections.observableArrayList().apply { + addListener { change: ListChangeListener.Change -> + while (change.next()) { + for (i in change.from until change.to) { + addChildren(change.list[i]) + } + } + } + } + + init { + vbox = VBox().apply { + } + children.setAll(vbox) + + styleClass += "options-list" + } + + fun addChildren(node: Node) { + if (node is ComponentList) + node.properties["title"] = node.title + vbox.children += StackPane().apply { + children += ComponentListCell(this@ComponentList, node) + if (vbox.children.isEmpty()) + styleClass += "options-list-item-ahead" + else { + styleClass += "options-list-item" + } + } + } + + val titleProperty = SimpleObjectProperty(this, "title", "Group") + var title: String by titleProperty + + val depthProperty = SimpleIntegerProperty(this, "depth", 0) + var depth: Int by depthProperty +} \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/construct/ComponentListCell.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/construct/ComponentListCell.kt new file mode 100644 index 000000000..571041dd8 --- /dev/null +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/construct/ComponentListCell.kt @@ -0,0 +1,143 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 com.jfoenix.controls.JFXButton +import javafx.animation.* +import javafx.beans.property.SimpleBooleanProperty +import javafx.geometry.Insets +import javafx.geometry.Pos +import javafx.scene.Node +import javafx.scene.control.Label +import javafx.scene.layout.HBox +import javafx.scene.layout.Priority +import javafx.scene.layout.StackPane +import javafx.scene.layout.VBox +import javafx.scene.shape.Rectangle +import javafx.util.Duration +import org.jackhuang.hmcl.ui.SINE +import org.jackhuang.hmcl.ui.SVG +import org.jackhuang.hmcl.ui.limitHeight +import org.jackhuang.hmcl.util.* + +class ComponentListCell(private val superList: ComponentList, private val content: Node) : StackPane() { + + var expandAnimation: Animation? = null + private var clipRect: Rectangle? = null + private var animatedHeight = 0.0 + + private val expandedProperty = SimpleBooleanProperty(this, "expanded", false) + var expanded: Boolean by expandedProperty + + init { + updateLayout() + } + + private fun updateClip(newHeight: Double) { + clipRect?.height = newHeight + } + + override fun layoutChildren() { + super.layoutChildren() + + if (clipRect == null) { + clipRect = Rectangle(0.0, 0.0, width, height) + //clip = clipRect + } else { + clipRect?.x = 0.0 + clipRect?.y = 0.0 + clipRect?.height = height + clipRect?.width = width + } + } + + private fun updateLayout() { + val isSubList = content is ComponentList + + if (isSubList) { + content.styleClass -= "options-list" + content.styleClass += "options-sublist" + + val groupNode = StackPane() + groupNode.styleClass += "options-list-item-header" + + val expandIcon = SVG.expand("black", 10.0, 10.0) + val expandButton = JFXButton() + expandButton.graphic = expandIcon + expandButton.styleClass += "options-list-item-expand-button" + StackPane.setAlignment(expandButton, Pos.CENTER_RIGHT) + + groupNode.children.setAll( + Label(content.properties["title"]?.toString() ?: "Group").apply { isMouseTransparent = true; StackPane.setAlignment(this, Pos.CENTER_LEFT) }, + expandButton) + + val container = VBox().apply { + limitHeight(0.0) + children.setAll(content) + } + + val holder = VBox() + holder.children.setAll(groupNode, container) + holder.styleClass += "options-list-item-container" + + expandButton.setOnMouseClicked { + if (expandAnimation != null && expandAnimation!!.status == Animation.Status.RUNNING) { + expandAnimation!!.stop() + } + + expanded = !expanded + + val newAnimatedHeight = content.prefHeight(-1.0) * (if (expanded) 1.0 else -1.0) + val newHeight = if (expanded) height + newAnimatedHeight else prefHeight(-1.0) + val contentHeight = if (expanded) newAnimatedHeight else 0.0 + + if (expanded) { + updateClip(newHeight) + } + + animatedHeight = newAnimatedHeight + + expandAnimation = Timeline(KeyFrame(Duration(320.0), + KeyValue(container.minHeightProperty(), contentHeight, SINE), + KeyValue(container.maxHeightProperty(), contentHeight, SINE) + )) + + if (!expanded) { + expandAnimation?.setOnFinished { + updateClip(newHeight) + animatedHeight = 0.0 + } + } + + expandAnimation?.play() + } + + expandedProperty.addListener { _, _, newValue -> + if (newValue) { + expandIcon.rotate = 180.0 + } else { + expandIcon.rotate = 0.0 + } + } + + children.setAll(holder) + } else { + children.setAll(content) + } + } +} \ No newline at end of file diff --git a/HMCL/src/main/resources/assets/css/jfoenix-main-demo.css b/HMCL/src/main/resources/assets/css/jfoenix-main-demo.css index efc8bef3c..427f4f213 100644 --- a/HMCL/src/main/resources/assets/css/jfoenix-main-demo.css +++ b/HMCL/src/main/resources/assets/css/jfoenix-main-demo.css @@ -567,6 +567,54 @@ -fx-border-width: 1 0 1 0; } +.options-list { + -fx-background-color: transparent; + -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2); +} + +.options-sublist { + -fx-background-color: white; +} + +.options-list-item { + -fx-background-color: white; + -fx-border-color: #e0e0e0; + -fx-border-width: 1 0 0 0; + -fx-padding: 10 16 10 16; + -fx-font-size: 12; +} + +.options-list-item-ahead { + -fx-background-radius: 2 2 0 0; + -fx-background-color: white; + -fx-padding: 10 16 10 16; + -fx-font-size: 12; +} + +.options-list-item-expand-button { + -fx-toggle-icon4-size: 15px; + -fx-pref-width: -fx-toggle-icon4-size; + -fx-max-width: -fx-toggle-icon4-size; + -fx-min-width: -fx-toggle-icon4-size; + -fx-pref-height: -fx-toggle-icon4-size; + -fx-max-height: -fx-toggle-icon4-size; + -fx-min-height: -fx-toggle-icon4-size; + -fx-background-radius: 50px; + -fx-background-color: transparent; + -jfx-toggle-color: rgba(128, 128, 255, 0.2); + -jfx-untoggle-color: transparent; +} + +.options-list-item-expand-button .icon { + -fx-fill: rgb(204.0, 204.0, 51.0); + -fx-padding: 10.0; +} + +.options-list-item-expand-button .jfx-rippler { + -jfx-rippler-fill: #0F9D58; + -jfx-mask-type: CIRCLE; +} + .custom-jfx-list-view .jfx-list-cell .sublist-header > .drop-icon { -fx-background-color: GRAY; } diff --git a/HMCL/src/main/resources/assets/fxml/setting.fxml b/HMCL/src/main/resources/assets/fxml/setting.fxml index ad6ac7ea2..a5b640c99 100644 --- a/HMCL/src/main/resources/assets/fxml/setting.fxml +++ b/HMCL/src/main/resources/assets/fxml/setting.fxml @@ -1,12 +1,12 @@ - + @@ -14,30 +14,19 @@ style="-fx-font-size: 14; -fx-pref-width: 100%; " fitToHeight="true" fitToWidth="true"> - - - - - - + + + diff --git a/HMCL/src/main/resources/assets/fxml/version-settings.fxml b/HMCL/src/main/resources/assets/fxml/version-settings.fxml index 96e044313..3ed03b92c 100644 --- a/HMCL/src/main/resources/assets/fxml/version-settings.fxml +++ b/HMCL/src/main/resources/assets/fxml/version-settings.fxml @@ -12,6 +12,9 @@ + + + @@ -19,90 +22,97 @@ style="-fx-font-size: 14; -fx-pref-width: 100%; " fitToHeight="true" fitToWidth="true"> - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - - - - - + + + + + + + + + diff --git a/HMCL/src/main/resources/assets/svg/paper-plane.fxml b/HMCL/src/main/resources/assets/svg/paper-plane.fxml new file mode 100644 index 000000000..ae3293a75 --- /dev/null +++ b/HMCL/src/main/resources/assets/svg/paper-plane.fxml @@ -0,0 +1,2 @@ + + diff --git a/lib/JFoenix.jar b/lib/JFoenix.jar index bb0b105a8..34464f5aa 100644 Binary files a/lib/JFoenix.jar and b/lib/JFoenix.jar differ