mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-04-06 18:20:26 +08:00
Scheduler JavaFX and Swing now have Future
This commit is contained in:
parent
736a8c1b30
commit
f1ffa5ca03
@ -34,12 +34,14 @@ object Controllers {
|
||||
lateinit var versionController: VersionController
|
||||
val versionPane: Pane = loadPane("version")
|
||||
|
||||
lateinit var decorator: Decorator
|
||||
|
||||
fun initialize(stage: Stage) {
|
||||
this.stage = stage
|
||||
|
||||
val decorator = Decorator(stage, mainPane, max = false)
|
||||
// Let root pane fix window size.
|
||||
(mainPane.parent as StackPane).run {
|
||||
with(mainPane.parent as StackPane) {
|
||||
mainPane.prefWidthProperty().bind(widthProperty())
|
||||
mainPane.prefHeightProperty().bind(heightProperty())
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui
|
||||
|
||||
import com.jfoenix.controls.JFXButton
|
||||
import com.jfoenix.effects.JFXDepthManager
|
||||
import com.jfoenix.svg.SVGGlyph
|
||||
import javafx.animation.*
|
||||
import javafx.application.Platform
|
||||
@ -31,6 +32,7 @@ import javafx.geometry.Bounds
|
||||
import javafx.geometry.Insets
|
||||
import javafx.scene.Cursor
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.control.Tooltip
|
||||
import javafx.scene.input.MouseEvent
|
||||
import javafx.scene.layout.*
|
||||
@ -39,8 +41,11 @@ import javafx.scene.shape.Rectangle
|
||||
import javafx.stage.Screen
|
||||
import javafx.stage.Stage
|
||||
import javafx.stage.StageStyle
|
||||
import javafx.scene.layout.BorderStrokeStyle
|
||||
import javafx.scene.layout.BorderStroke
|
||||
import org.jackhuang.hmcl.util.*
|
||||
|
||||
class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node: Node, private val max: Boolean = true, min: Boolean = true) : VBox() {
|
||||
class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node: Node, private val max: Boolean = true, min: Boolean = true) : GridPane() {
|
||||
private var xOffset: Double = 0.0
|
||||
private var yOffset: Double = 0.0
|
||||
private var newX: Double = 0.0
|
||||
@ -53,66 +58,62 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node:
|
||||
@FXML lateinit var contentPlaceHolder: StackPane
|
||||
@FXML lateinit var titleContainer: BorderPane
|
||||
@FXML lateinit var buttonsContainer: HBox
|
||||
private val onCloseButtonAction: ObjectProperty<Runnable>
|
||||
private val customMaximize: BooleanProperty
|
||||
@FXML lateinit var backNavButton: JFXButton
|
||||
@FXML lateinit var refreshNavButton: JFXButton
|
||||
@FXML lateinit var closeNavButton: JFXButton
|
||||
@FXML lateinit var refreshMenuButton: JFXButton
|
||||
@FXML lateinit var addMenuButton: JFXButton
|
||||
@FXML lateinit var titleLabel: Label
|
||||
@FXML lateinit var leftPane: VBox
|
||||
|
||||
private val onCloseButtonActionProperty: ObjectProperty<Runnable> = SimpleObjectProperty(Runnable { this.primaryStage.close() })
|
||||
@JvmName("onCloseButtonActionProperty") get
|
||||
var onCloseButtonAction: Runnable by onCloseButtonActionProperty
|
||||
|
||||
val customMaximizeProperty: BooleanProperty = SimpleBooleanProperty(false)
|
||||
@JvmName("customMaximizeProperty") get
|
||||
var isCustomMaximize: Boolean by customMaximizeProperty
|
||||
|
||||
private var maximized: Boolean = false
|
||||
private var originalBox: BoundingBox? = null
|
||||
private var maximizedBox: BoundingBox? = null
|
||||
@FXML lateinit var btnMin: JFXButton
|
||||
@FXML lateinit var btnMax: JFXButton
|
||||
@FXML lateinit var btnClose: JFXButton
|
||||
private val minus: SVGGlyph
|
||||
private val resizeMax: SVGGlyph
|
||||
private val resizeMin: SVGGlyph
|
||||
private val close: SVGGlyph
|
||||
private val minus = SVGGlyph(0, "MINUS", "M804.571 420.571v109.714q0 22.857-16 38.857t-38.857 16h-694.857q-22.857 0-38.857-16t-16-38.857v-109.714q0-22.857 16-38.857t38.857-16h694.857q22.857 0 38.857 16t16 38.857z", Color.WHITE)
|
||||
.apply { setSize(12.0, 2.0); translateY = 4.0 }
|
||||
private val resizeMax = SVGGlyph(0, "RESIZE_MAX", "M726 810v-596h-428v596h428zM726 44q34 0 59 25t25 59v768q0 34-25 60t-59 26h-428q-34 0-59-26t-25-60v-768q0-34 25-60t59-26z", Color.WHITE)
|
||||
.apply { setPrefSize(12.0, 12.0); setSize(12.0, 12.0) }
|
||||
private val resizeMin = SVGGlyph(0, "RESIZE_MIN", "M80.842 943.158v-377.264h565.894v377.264h-565.894zM0 404.21v619.79h727.578v-619.79h-727.578zM377.264 161.684h565.894v377.264h-134.736v80.842h215.578v-619.79h-727.578v323.37h80.842v-161.686z", Color.WHITE)
|
||||
.apply { setPrefSize(12.0, 12.0); setSize(12.0, 12.0) }
|
||||
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) }
|
||||
|
||||
init {
|
||||
loadFXML("/assets/fxml/decorator.fxml")
|
||||
|
||||
this.xOffset = 0.0
|
||||
this.yOffset = 0.0
|
||||
this.allowMove = false
|
||||
this.isDragging = false
|
||||
this.onCloseButtonAction = SimpleObjectProperty(Runnable { this.primaryStage.close() })
|
||||
this.customMaximize = SimpleBooleanProperty(false)
|
||||
this.maximized = false
|
||||
this.primaryStage.initStyle(StageStyle.UNDECORATED)
|
||||
minus = SVGGlyph(0, "MINUS", "M804.571 420.571v109.714q0 22.857-16 38.857t-38.857 16h-694.857q-22.857 0-38.857-16t-16-38.857v-109.714q0-22.857 16-38.857t38.857-16h694.857q22.857 0 38.857 16t16 38.857z", Color.WHITE)
|
||||
minus.setSize(12.0, 2.0)
|
||||
minus.translateY = 4.0
|
||||
resizeMax = SVGGlyph(0, "RESIZE_MAX", "M726 810v-596h-428v596h428zM726 44q34 0 59 25t25 59v768q0 34-25 60t-59 26h-428q-34 0-59-26t-25-60v-768q0-34 25-60t59-26z", Color.WHITE)
|
||||
resizeMax.setSize(12.0, 12.0)
|
||||
resizeMin = SVGGlyph(0, "RESIZE_MIN", "M80.842 943.158v-377.264h565.894v377.264h-565.894zM0 404.21v619.79h727.578v-619.79h-727.578zM377.264 161.684h565.894v377.264h-134.736v80.842h215.578v-619.79h-727.578v323.37h80.842v-161.686z", Color.WHITE)
|
||||
resizeMin.setSize(12.0, 12.0)
|
||||
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)
|
||||
close.setSize(12.0, 12.0)
|
||||
btnClose.graphic = close
|
||||
btnMin.graphic = minus
|
||||
this.btnMax.graphic = resizeMax
|
||||
btnMax.graphic = resizeMax
|
||||
|
||||
buttonsContainer.background = Background(*arrayOf(BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)))
|
||||
titleContainer.addEventHandler(MouseEvent.MOUSE_CLICKED) { mouseEvent ->
|
||||
if (mouseEvent.clickCount == 2) {
|
||||
this.btnMax.fire()
|
||||
btnMax.fire()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!min) buttonsContainer.children.remove(btnMin)
|
||||
|
||||
if (!max) buttonsContainer.children.remove(btnMax)
|
||||
|
||||
titleContainer.addEventHandler(MouseEvent.MOUSE_ENTERED) { enter -> this.allowMove = true }
|
||||
titleContainer.addEventHandler(MouseEvent.MOUSE_EXITED) { enter ->
|
||||
if (!this.isDragging) {
|
||||
this.allowMove = false
|
||||
}
|
||||
|
||||
}
|
||||
JFXDepthManager.setDepth(titleContainer, 1)
|
||||
titleContainer.addEventHandler(MouseEvent.MOUSE_ENTERED) { this.allowMove = true }
|
||||
titleContainer.addEventHandler(MouseEvent.MOUSE_EXITED) { if (!this.isDragging) this.allowMove = false }
|
||||
|
||||
this.contentPlaceHolder.children.add(node)
|
||||
(node as Region).setMinSize(0.0, 0.0)
|
||||
this.contentPlaceHolder.border = Border(BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderWidths(0.0, 4.0, 4.0, 4.0)))
|
||||
this.border = Border(BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderWidths(0.0, 4.0, 4.0, 4.0)))
|
||||
val clip = Rectangle()
|
||||
clip.widthProperty().bind(node.widthProperty())
|
||||
clip.heightProperty().bind(node.heightProperty())
|
||||
@ -127,7 +128,7 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node:
|
||||
val x = mouseEvent.x
|
||||
val y = mouseEvent.y
|
||||
val boundsInParent = this.boundsInParent
|
||||
if (this.contentPlaceHolder.border != null && this.contentPlaceHolder.border.strokes.size > 0) {
|
||||
if (this.border != null && this.border.strokes.size > 0) {
|
||||
val borderWidth = this.contentPlaceHolder.snappedLeftInset()
|
||||
if (this.isRightEdge(x, y, boundsInParent)) {
|
||||
if (y < borderWidth) {
|
||||
@ -274,7 +275,7 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node:
|
||||
}
|
||||
|
||||
fun onClose() {
|
||||
(this.onCloseButtonAction.get() as Runnable).run()
|
||||
this.onCloseButtonAction.run()
|
||||
}
|
||||
|
||||
private fun updateInitMouseValues(mouseEvent: MouseEvent) {
|
||||
@ -328,18 +329,6 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node:
|
||||
}
|
||||
}
|
||||
|
||||
fun setOnCloseButtonAction(onCloseButtonAction: Runnable) {
|
||||
this.onCloseButtonAction.set(onCloseButtonAction)
|
||||
}
|
||||
|
||||
fun customMaximizeProperty(): BooleanProperty {
|
||||
return this.customMaximize
|
||||
}
|
||||
|
||||
var isCustomMaximize: Boolean
|
||||
get() = this.customMaximizeProperty().get()
|
||||
set(customMaximize) = this.customMaximizeProperty().set(customMaximize)
|
||||
|
||||
fun setMaximized(maximized: Boolean) {
|
||||
if (this.maximized != maximized) {
|
||||
Platform.runLater { this.btnMax.fire() }
|
||||
|
@ -106,6 +106,8 @@ fun setOverflowHidden(node: Pane) {
|
||||
node.clip = rectangle
|
||||
}
|
||||
|
||||
val stylesheets = arrayOf(Controllers::class.java.getResource("/css/jfoenix-design.css").toExternalForm(),
|
||||
val stylesheets = arrayOf(
|
||||
Controllers::class.java.getResource("/css/jfoenix-fonts.css").toExternalForm(),
|
||||
Controllers::class.java.getResource("/css/jfoenix-design.css").toExternalForm(),
|
||||
Controllers::class.java.getResource("/assets/css/jfoenix-components.css").toExternalForm(),
|
||||
Controllers::class.java.getResource("/assets/css/jfoenix-main-demo.css").toExternalForm())
|
||||
Controllers::class.java.getResource("/assets/css/jfoenix-main-demo.css").toExternalForm())
|
@ -212,7 +212,7 @@
|
||||
|
||||
.jfx-tool-bar HBox {
|
||||
-fx-alignment: center;
|
||||
-fx-spacing: 25;
|
||||
/* -fx-spacing: 25;*/
|
||||
-fx-padding: 0 10;
|
||||
}
|
||||
|
||||
|
@ -199,8 +199,12 @@
|
||||
|
||||
.jfx-tool-bar HBox {
|
||||
-fx-alignment: center;
|
||||
-fx-spacing: 25.0;
|
||||
-fx-padding: 0.0 10.0;
|
||||
/* -fx-spacing: 25.0;*/
|
||||
-fx-padding: 0.0 5.0;
|
||||
}
|
||||
|
||||
.jfx-tool-bar .jfx-decorator-button {
|
||||
-fx-cursor: head;
|
||||
}
|
||||
|
||||
.jfx-tool-bar Label {
|
||||
@ -219,6 +223,26 @@
|
||||
-jfx-rippler-fill: WHITE;
|
||||
}
|
||||
|
||||
.jfx-decorator-button {
|
||||
-fx-max-width: 35px;
|
||||
-fx-background-radius: 40px;
|
||||
-fx-max-height: 35px;
|
||||
-fx-background-color: transparent;
|
||||
-jfx-toggle-color: rgba(128, 128, 255, 0.2);
|
||||
-jfx-untoggle-color: transparent;
|
||||
}
|
||||
|
||||
.jfx-decorator-button .icon {
|
||||
-fx-fill: rgb(204.0, 204.0, 51.0);
|
||||
-fx-padding: 0.0;
|
||||
}
|
||||
|
||||
.jfx-decorator-button .jfx-rippler {
|
||||
-jfx-rippler-fill: white;
|
||||
-jfx-mask-type: CIRCLE;
|
||||
-fx-padding: 0.0;
|
||||
}
|
||||
|
||||
.option-list-view {
|
||||
-fx-pref-width: 160.0px;
|
||||
-fx-background-color: WHITE;
|
||||
@ -617,7 +641,7 @@
|
||||
-jfx-mask-type: CIRCLE;
|
||||
}
|
||||
|
||||
.toggle-icon2, .toggle-icon3 {
|
||||
.toggle-icon2, .toggle-icon3, .jfx-decorator-button {
|
||||
-fx-pref-width: 50px;
|
||||
-fx-background-radius: 50px;
|
||||
-fx-pref-height: 50px;
|
||||
@ -805,16 +829,21 @@
|
||||
*******************************************************************************/
|
||||
|
||||
.jfx-decorator {
|
||||
-fx-decorator-color: derive(#5264AE, -20%);
|
||||
-fx-decorator-color: derive(#5264AE, 0);
|
||||
}
|
||||
|
||||
.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 4 4 4;
|
||||
.resize-border {
|
||||
-fx-border-color: #5264AE;
|
||||
-fx-border-width: 0 2 2 2;
|
||||
}
|
||||
|
||||
.debug-border {
|
||||
-fx-border-color: red;
|
||||
-fx-border-width: 1;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
|
@ -4,53 +4,117 @@
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<?import com.jfoenix.controls.JFXButton?>
|
||||
<?import javafx.scene.Cursor?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import java.lang.String?>
|
||||
<?import javafx.scene.shape.Rectangle?>
|
||||
<?import com.jfoenix.controls.JFXComboBox?>
|
||||
<?import com.jfoenix.controls.JFXListView?>
|
||||
<?import com.jfoenix.controls.JFXListCell?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
type="VBox"
|
||||
type="GridPane"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
styleClass="jfx-decorator"
|
||||
pickOnBounds="false"
|
||||
onMouseReleased="#onMouseReleased"
|
||||
onMouseDragged="#onMouseDragged"
|
||||
onMouseMoved="#onMouseMoved">
|
||||
|
||||
<BorderPane fx:id="titleContainer" styleClass="jfx-decorator-buttons-container" pickOnBounds="false">
|
||||
<left>
|
||||
<HBox fx:id="titleWrapper" alignment="CENTER_LEFT" style="-fx-padding: 15;">
|
||||
<Label text="Hello Minecraft! Launcher" mouseTransparent="false" style="-fx-background-color: transparent; -fx-text-fill: white; -fx-font-size: 15px;" />
|
||||
</HBox>
|
||||
</left>
|
||||
<right>
|
||||
<HBox fx:id="buttonsContainer" styleClass="jfx-decorator-buttons-container" alignment="CENTER_RIGHT" minWidth="180">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="4.0" />
|
||||
</padding>
|
||||
<JFXButton fx:id="btnMin" styleClass="jfx-decorator-button" ripplerFill="white" onAction="#onMin">
|
||||
<cursor>
|
||||
<Cursor fx:constant="HAND" />
|
||||
</cursor>
|
||||
</JFXButton>
|
||||
<JFXButton fx:id="btnMax" styleClass="jfx-decorator-button" ripplerFill="white" onAction="#onMax">
|
||||
<cursor>
|
||||
<Cursor fx:constant="HAND" />
|
||||
</cursor>
|
||||
</JFXButton>
|
||||
<JFXButton fx:id="btnClose" styleClass="jfx-decorator-button" ripplerFill="white" onAction="#onClose">
|
||||
<cursor>
|
||||
<Cursor fx:constant="HAND" />
|
||||
</cursor>
|
||||
</JFXButton>
|
||||
</HBox>
|
||||
</right>
|
||||
</BorderPane>
|
||||
|
||||
<StackPane fx:id="contentPlaceHolder" styleClass="jfx-decorator-content-container resize-border" minWidth="0" minHeight="0" VBox.vgrow="ALWAYS">
|
||||
onMouseReleased="#onMouseReleased"
|
||||
onMouseDragged="#onMouseDragged"
|
||||
onMouseMoved="#onMouseMoved">
|
||||
<styleClass>
|
||||
<String fx:value="jfx-decorator" />
|
||||
<String fx:value="resize-border" />
|
||||
</styleClass>
|
||||
<columnConstraints>
|
||||
<ColumnConstraints prefWidth="200" />
|
||||
<ColumnConstraints hgrow="ALWAYS" />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints />
|
||||
<RowConstraints vgrow="ALWAYS" />
|
||||
</rowConstraints>
|
||||
<StackPane GridPane.rowIndex="1" GridPane.columnIndex="0" VBox.vgrow="ALWAYS" styleClass="jfx-decorator-content-container">
|
||||
<BorderPane prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
|
||||
<center>
|
||||
<ScrollPane fitToHeight="true" fitToWidth="true">
|
||||
<VBox fx:id="leftPane">
|
||||
</VBox>
|
||||
</ScrollPane>
|
||||
</center>
|
||||
<bottom>
|
||||
<BorderPane fx:id="menuBottomBar">
|
||||
<left>
|
||||
<JFXButton fx:id="refreshMenuButton" styleClass="toggle-icon3">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/refresh-black.fxml"/>
|
||||
</graphic></JFXButton>
|
||||
</left>
|
||||
<right>
|
||||
<JFXButton fx:id="addMenuButton" styleClass="toggle-icon3">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/plus-black.fxml"/>
|
||||
</graphic></JFXButton>
|
||||
</right>
|
||||
</BorderPane>
|
||||
</bottom>
|
||||
</BorderPane>
|
||||
</StackPane>
|
||||
<StackPane GridPane.rowIndex="1" GridPane.columnIndex="1" fx:id="contentPlaceHolder" styleClass="jfx-decorator-content-container" minWidth="0" minHeight="0" VBox.vgrow="ALWAYS">
|
||||
<styleClass>
|
||||
<String fx:value="jfx-decorator-content-container" />
|
||||
<String fx:value="resize-border" />
|
||||
</styleClass>
|
||||
<!-- Node -->
|
||||
</StackPane>
|
||||
</fx:root>
|
||||
<BorderPane GridPane.rowIndex="0" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="titleContainer" styleClass="jfx-tool-bar" pickOnBounds="false">
|
||||
<left>
|
||||
<HBox minWidth="200" fx:id="titleWrapper" alignment="CENTER_LEFT">
|
||||
<Label text="Hello Minecraft! Launcher" mouseTransparent="false" style="-fx-background-color: transparent; -fx-text-fill: white; -fx-font-size: 15px;" />
|
||||
</HBox>
|
||||
</left>
|
||||
<center>
|
||||
<BorderPane fx:id="navBar">
|
||||
<left>
|
||||
<HBox fx:id="navLeft" alignment="CENTER_LEFT" style="-fx-padding: 0;">
|
||||
<Rectangle height="${navBar.height}" width="1" fill="gray" />
|
||||
<JFXButton fx:id="backNavButton" maxHeight="20" styleClass="toggle-icon3">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/arrow-left.fxml"/>
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
<Label fx:id="titleLabel" style="-fx-text-fill:WHITE; -fx-font-size: 15;"/>
|
||||
</HBox>
|
||||
</left>
|
||||
<right>
|
||||
<HBox fx:id="navRight" alignment="CENTER_LEFT">
|
||||
<JFXButton fx:id="refreshNavButton" maxHeight="20" styleClass="toggle-icon3" disable="true">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/refresh.fxml"/>
|
||||
</graphic>
|
||||
<StackPane.margin>
|
||||
<Insets left="20"/>
|
||||
</StackPane.margin>
|
||||
</JFXButton>
|
||||
<JFXButton fx:id="closeNavButton" maxHeight="20" styleClass="toggle-icon3">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/close.fxml"/>
|
||||
</graphic>
|
||||
<StackPane.margin>
|
||||
<Insets left="20"/>
|
||||
</StackPane.margin>
|
||||
</JFXButton>
|
||||
<Rectangle height="${navBar.height}" width="1" fill="gray" />
|
||||
</HBox>
|
||||
</right>
|
||||
</BorderPane>
|
||||
</center>
|
||||
<right>
|
||||
<HBox fx:id="buttonsContainer" style="-fx-background-color: transparent;" alignment="CENTER_RIGHT">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="4.0" />
|
||||
</padding>
|
||||
<JFXButton fx:id="btnMin" styleClass="jfx-decorator-button" ripplerFill="white" onAction="#onMin">
|
||||
</JFXButton>
|
||||
<JFXButton fx:id="btnMax" styleClass="jfx-decorator-button" ripplerFill="white" onAction="#onMax">
|
||||
</JFXButton>
|
||||
<JFXButton fx:id="btnClose" styleClass="jfx-decorator-button" ripplerFill="white" onAction="#onClose">
|
||||
</JFXButton>
|
||||
</HBox>
|
||||
</right>
|
||||
</BorderPane>
|
||||
</fx:root>
|
@ -1,9 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import com.jfoenix.controls.*?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<BorderPane
|
||||
maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
|
||||
fx:controller="org.jackhuang.hmcl.ui.MainController"
|
||||
@ -13,59 +11,7 @@
|
||||
<StackPane fx:id="page" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
|
||||
</center>
|
||||
<left>
|
||||
<BorderPane prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
|
||||
<top>
|
||||
<JFXComboBox fx:id="comboProfiles" prefWidth="150.0" BorderPane.alignment="CENTER" />
|
||||
</top>
|
||||
<center>
|
||||
<StackPane>
|
||||
<JFXListView fx:id="listVersions" styleClass="mylistview" style="-fx-background-color: #F1F1F1;" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
|
||||
<AnchorPane pickOnBounds="false">
|
||||
<JFXButton 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>
|
||||
</StackPane>
|
||||
</center>
|
||||
</BorderPane>
|
||||
</left>
|
||||
<top>
|
||||
<JFXToolbar fx:id="toolbar" styleClass="jfx-tool-bar">
|
||||
<leftItems>
|
||||
<JFXButton fx:id="closeButton" maxHeight="20" styleClass="toggle-icon3"
|
||||
StackPane.alignment="CENTER_RIGHT">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/close.fxml"/>
|
||||
</graphic>
|
||||
<StackPane.margin>
|
||||
<Insets left="20"/>
|
||||
</StackPane.margin>
|
||||
</JFXButton>
|
||||
<JFXButton fx:id="backButton" maxHeight="20" styleClass="toggle-icon3"
|
||||
StackPane.alignment="CENTER_LEFT">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/arrow-left.fxml"/>
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
<Label fx:id="titleLabel" style="-fx-text-fill:WHITE; -fx-font-size: 15;"
|
||||
StackPane.alignment="CENTER_LEFT"/>
|
||||
</leftItems>
|
||||
<rightItems>
|
||||
<JFXButton fx:id="refreshButton" maxHeight="20" styleClass="toggle-icon3" disable="true"
|
||||
StackPane.alignment="CENTER_RIGHT">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/refresh.fxml"/>
|
||||
</graphic>
|
||||
<StackPane.margin>
|
||||
<Insets left="20"/>
|
||||
</StackPane.margin>
|
||||
</JFXButton>
|
||||
</rightItems>
|
||||
</JFXToolbar>
|
||||
|
||||
</top>
|
||||
<bottom>
|
||||
<BorderPane prefHeight="50.0" prefWidth="200.0" BorderPane.alignment="CENTER">
|
||||
<right>
|
||||
|
2
HMCL/src/main/resources/assets/svg/plus-black.fxml
Normal file
2
HMCL/src/main/resources/assets/svg/plus-black.fxml
Normal file
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<javafx.scene.shape.SVGPath fill="black" content="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" />
|
2
HMCL/src/main/resources/assets/svg/refresh-black.fxml
Normal file
2
HMCL/src/main/resources/assets/svg/refresh-black.fxml
Normal file
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<javafx.scene.shape.SVGPath fill="black" content="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" />
|
@ -30,7 +30,7 @@ private class CoupleTask<P: Task>(private val pred: P, private val succ: Task.(P
|
||||
}
|
||||
}
|
||||
|
||||
infix fun <T: Task> T.then(b: Task): Task = CoupleTask(this, { b }, true)
|
||||
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.
|
||||
|
@ -33,7 +33,7 @@ import java.net.URL
|
||||
import java.math.BigInteger
|
||||
import java.util.logging.Level
|
||||
|
||||
class FileDownloadTask(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
|
||||
|
||||
var onFailed = EventManager<FailedEvent<URL>>()
|
||||
|
@ -25,7 +25,7 @@ import java.net.Proxy
|
||||
import java.net.URL
|
||||
import java.nio.charset.Charset
|
||||
|
||||
class GetTask(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 fun execute() {
|
||||
|
@ -18,38 +18,51 @@
|
||||
package org.jackhuang.hmcl.task
|
||||
|
||||
import javafx.application.Platform
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.*
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
interface Scheduler {
|
||||
fun schedule(block: Runnable): Future<*>?
|
||||
fun schedule(block: Callable<Unit>): Future<*>?
|
||||
|
||||
companion object Schedulers {
|
||||
val IMMEDIATE = object : Scheduler {
|
||||
override fun schedule(block: Runnable): Future<*>? {
|
||||
block.run()
|
||||
return null
|
||||
}
|
||||
}
|
||||
val JAVAFX: Scheduler = object : Scheduler {
|
||||
override fun schedule(block: Runnable): Future<*>? {
|
||||
Platform.runLater(block)
|
||||
return null
|
||||
}
|
||||
}
|
||||
val SWING: Scheduler = object : Scheduler {
|
||||
override fun schedule(block: Runnable): Future<*>? {
|
||||
SwingUtilities.invokeLater(block)
|
||||
return null
|
||||
val JAVAFX: Scheduler = SchedulerImpl(Platform::runLater)
|
||||
val SWING: Scheduler = SchedulerImpl(SwingUtilities::invokeLater)
|
||||
private class SchedulerImpl(val executor: (() -> Unit) -> Unit) : Scheduler {
|
||||
override fun schedule(block: Callable<Unit>): Future<*>? {
|
||||
val latch = CountDownLatch(1)
|
||||
val wrapper = AtomicReference<Exception>()
|
||||
executor {
|
||||
try {
|
||||
block.call()
|
||||
} catch (e: Exception) {
|
||||
wrapper.set(e)
|
||||
} finally {
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
return object : Future<Unit> {
|
||||
override fun get(timeout: Long, unit: TimeUnit) {
|
||||
latch.await(timeout, unit)
|
||||
val e = wrapper.get()
|
||||
if (e != null) throw ExecutionException(e)
|
||||
}
|
||||
override fun get() {
|
||||
latch.await()
|
||||
val e = wrapper.get()
|
||||
if (e != null) throw ExecutionException(e)
|
||||
}
|
||||
override fun isDone() = latch.count == 0L
|
||||
override fun isCancelled() = false
|
||||
override fun cancel(mayInterruptIfRunning: Boolean) = false
|
||||
}
|
||||
}
|
||||
}
|
||||
val NEW_THREAD: Scheduler = object : Scheduler {
|
||||
override fun schedule(block: Runnable) = CACHED_EXECUTOR.submit(block)
|
||||
override fun schedule(block: Callable<Unit>) = CACHED_EXECUTOR.submit(block)
|
||||
}
|
||||
val IO_THREAD: Scheduler = object : Scheduler {
|
||||
override fun schedule(block: Runnable) = IO_EXECUTOR.submit(block)
|
||||
override fun schedule(block: Callable<Unit>) = IO_EXECUTOR.submit(block)
|
||||
}
|
||||
val DEFAULT = NEW_THREAD
|
||||
private val CACHED_EXECUTOR: ExecutorService by lazy {
|
||||
@ -57,11 +70,11 @@ interface Scheduler {
|
||||
}
|
||||
|
||||
private val IO_EXECUTOR: ExecutorService by lazy {
|
||||
Executors.newFixedThreadPool(6, { r: Runnable ->
|
||||
Executors.newFixedThreadPool(6) { r: Runnable ->
|
||||
val thread: Thread = Executors.defaultThreadFactory().newThread(r)
|
||||
thread.isDaemon = true
|
||||
thread
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun shutdown() {
|
||||
|
@ -17,7 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.task
|
||||
|
||||
internal class SimpleTask(private val runnable: () -> Unit, override val scheduler: Scheduler = Scheduler.DEFAULT) : Task() {
|
||||
internal class SimpleTask @JvmOverloads constructor(private val runnable: () -> Unit, override val scheduler: Scheduler = Scheduler.DEFAULT) : Task() {
|
||||
override fun execute() {
|
||||
runnable()
|
||||
}
|
||||
|
@ -17,7 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.task
|
||||
|
||||
import javafx.beans.property.ReadOnlyDoubleProperty
|
||||
import javafx.beans.property.ReadOnlyDoubleWrapper
|
||||
import javafx.beans.property.ReadOnlyStringProperty
|
||||
import javafx.beans.property.ReadOnlyStringWrapper
|
||||
import org.jackhuang.hmcl.event.EventManager
|
||||
import org.jackhuang.hmcl.util.*
|
||||
@ -69,19 +71,23 @@ abstract class Task {
|
||||
protected open val progressInterval = 1000L
|
||||
private var lastTime = Long.MIN_VALUE
|
||||
private val progressUpdate = AtomicReference<Double>()
|
||||
val progressProperty = ReadOnlyDoubleWrapper(this, "progress", 0.0)
|
||||
private val progressPropertyImpl = ReadOnlyDoubleWrapper(this, "progress", 0.0)
|
||||
val progressProperty: ReadOnlyDoubleProperty = progressPropertyImpl.readOnlyProperty
|
||||
@JvmName("progressProperty") get
|
||||
protected fun updateProgress(progress: Int, total: Int) = updateProgress(1.0 * progress / total)
|
||||
protected fun updateProgress(progress: Double) {
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastTime >= progressInterval) {
|
||||
progressProperty.updateAsync(progress, progressUpdate)
|
||||
progressPropertyImpl.updateAsync(progress, progressUpdate)
|
||||
lastTime = now
|
||||
}
|
||||
}
|
||||
|
||||
private val messageUpdate = AtomicReference<String>()
|
||||
val messageProperty = ReadOnlyStringWrapper(this, "message", null)
|
||||
protected fun updateMessage(newMessage: String) = messageProperty.updateAsync(newMessage, messageUpdate)
|
||||
private val messagePropertyImpl = ReadOnlyStringWrapper(this, "message", null)
|
||||
val messageProperty: ReadOnlyStringProperty = messagePropertyImpl.readOnlyProperty
|
||||
@JvmName("messageProperty") get
|
||||
protected fun updateMessage(newMessage: String) = messagePropertyImpl.updateAsync(newMessage, messageUpdate)
|
||||
|
||||
val onDone = EventManager<TaskEvent>()
|
||||
|
||||
@ -97,11 +103,11 @@ abstract class Task {
|
||||
}
|
||||
|
||||
private val subTaskRunnable = { task: Task ->
|
||||
this.messageProperty.bind(task.messageProperty)
|
||||
this.progressProperty.bind(task.progressProperty)
|
||||
this.messagePropertyImpl.bind(task.messagePropertyImpl)
|
||||
this.progressPropertyImpl.bind(task.progressPropertyImpl)
|
||||
task.run()
|
||||
this.messageProperty.unbind()
|
||||
this.progressProperty.unbind()
|
||||
this.messagePropertyImpl.unbind()
|
||||
this.progressPropertyImpl.unbind()
|
||||
}
|
||||
|
||||
fun executor() = TaskExecutor().submit(this)
|
||||
|
@ -52,7 +52,7 @@ class TaskExecutor() {
|
||||
while (!taskQueue.isEmpty() && !canceled) {
|
||||
val task = taskQueue.poll()
|
||||
if (task != null) {
|
||||
val future = task.scheduler.schedule(Runnable { executeTask(task) })
|
||||
val future = task.scheduler.schedule(Callable { executeTask(task); Unit })
|
||||
try {
|
||||
future?.get()
|
||||
} catch (e: InterruptedException) {
|
||||
@ -142,8 +142,8 @@ class TaskExecutor() {
|
||||
return flag
|
||||
}
|
||||
|
||||
private inner class Invoker(val task: Task, val latch: CountDownLatch, val boolean: AtomicBoolean): Runnable {
|
||||
override fun run() {
|
||||
private inner class Invoker(val task: Task, val latch: CountDownLatch, val boolean: AtomicBoolean): Callable<Unit> {
|
||||
override fun call() {
|
||||
try {
|
||||
Thread.currentThread().name = task.title
|
||||
if (!executeTask(task))
|
||||
|
@ -15,6 +15,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
@file:JvmName("Constants")
|
||||
package org.jackhuang.hmcl.util
|
||||
|
||||
import javafx.application.Platform
|
||||
|
@ -15,6 +15,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
@file:JvmName("HMCLog")
|
||||
package org.jackhuang.hmcl.util
|
||||
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
@ -23,7 +23,6 @@ import java.lang.reflect.Method
|
||||
import java.security.PrivilegedExceptionAction
|
||||
import java.security.AccessController
|
||||
|
||||
|
||||
object ReflectionHelper {
|
||||
|
||||
private lateinit var unsafe: Unsafe
|
||||
|
@ -21,7 +21,7 @@ import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.util.logging.Level
|
||||
|
||||
internal class StreamPump(
|
||||
internal class StreamPump @JvmOverloads constructor(
|
||||
val inputStream: InputStream,
|
||||
val callback: (String) -> Unit = {}
|
||||
) : Runnable {
|
||||
|
@ -23,13 +23,6 @@ import java.io.IOException
|
||||
import java.util.zip.ZipOutputStream
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun zip(src: String, destZip: String) {
|
||||
zip(File(src), File(destZip), null)
|
||||
}
|
||||
|
||||
/**
|
||||
* 功能:把 src 目录下的所有文件进行 zip 格式的压缩,保存为指定 zip 文件
|
||||
|
||||
@ -43,8 +36,9 @@ fun zip(src: String, destZip: String) {
|
||||
* *
|
||||
* @throws java.io.IOException 压缩失败或无法读取
|
||||
*/
|
||||
@JvmOverloads
|
||||
@Throws(IOException::class)
|
||||
fun zip(src: File, destZip: File, pathNameCallback: ((String, Boolean) -> String?)?) {
|
||||
fun zip(src: File, destZip: File, pathNameCallback: ((String, Boolean) -> String?)? = null) {
|
||||
ZipOutputStream(destZip.outputStream()).use { zos ->
|
||||
val basePath: String
|
||||
if (src.isDirectory)
|
||||
@ -103,11 +97,6 @@ private fun zipFile(src: File,
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun unzip(zip: File, dest: File) {
|
||||
unzip(zip, dest, null, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将文件压缩成zip文件
|
||||
|
||||
@ -121,8 +110,9 @@ fun unzip(zip: File, dest: File) {
|
||||
* *
|
||||
* @throws java.io.IOException 解压失败或无法写入
|
||||
*/
|
||||
@JvmOverloads
|
||||
@Throws(IOException::class)
|
||||
fun unzip(zip: File, dest: File, callback: ((String) -> Boolean)?, ignoreExistsFile: Boolean) {
|
||||
fun unzip(zip: File, dest: File, callback: ((String) -> Boolean)? = null, ignoreExistsFile: Boolean = true) {
|
||||
val buf = ByteArray(1024)
|
||||
dest.mkdirs()
|
||||
ZipInputStream(zip.inputStream()).use { zipFile ->
|
||||
|
BIN
lib/JFoenix.jar
BIN
lib/JFoenix.jar
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user