Scheduler JavaFX and Swing now have Future

This commit is contained in:
huangyuhui 2017-08-06 11:45:12 +08:00
parent 736a8c1b30
commit f1ffa5ca03
22 changed files with 255 additions and 209 deletions

View File

@ -34,12 +34,14 @@ object Controllers {
lateinit var versionController: VersionController lateinit var versionController: VersionController
val versionPane: Pane = loadPane("version") val versionPane: Pane = loadPane("version")
lateinit var decorator: Decorator
fun initialize(stage: Stage) { fun initialize(stage: Stage) {
this.stage = stage this.stage = stage
val decorator = Decorator(stage, mainPane, max = false) val decorator = Decorator(stage, mainPane, max = false)
// Let root pane fix window size. // Let root pane fix window size.
(mainPane.parent as StackPane).run { with(mainPane.parent as StackPane) {
mainPane.prefWidthProperty().bind(widthProperty()) mainPane.prefWidthProperty().bind(widthProperty())
mainPane.prefHeightProperty().bind(heightProperty()) mainPane.prefHeightProperty().bind(heightProperty())
} }

View File

@ -18,6 +18,7 @@
package org.jackhuang.hmcl.ui package org.jackhuang.hmcl.ui
import com.jfoenix.controls.JFXButton import com.jfoenix.controls.JFXButton
import com.jfoenix.effects.JFXDepthManager
import com.jfoenix.svg.SVGGlyph import com.jfoenix.svg.SVGGlyph
import javafx.animation.* import javafx.animation.*
import javafx.application.Platform import javafx.application.Platform
@ -31,6 +32,7 @@ import javafx.geometry.Bounds
import javafx.geometry.Insets import javafx.geometry.Insets
import javafx.scene.Cursor import javafx.scene.Cursor
import javafx.scene.Node import javafx.scene.Node
import javafx.scene.control.Label
import javafx.scene.control.Tooltip import javafx.scene.control.Tooltip
import javafx.scene.input.MouseEvent import javafx.scene.input.MouseEvent
import javafx.scene.layout.* import javafx.scene.layout.*
@ -39,8 +41,11 @@ import javafx.scene.shape.Rectangle
import javafx.stage.Screen import javafx.stage.Screen
import javafx.stage.Stage import javafx.stage.Stage
import javafx.stage.StageStyle 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 xOffset: Double = 0.0
private var yOffset: Double = 0.0 private var yOffset: Double = 0.0
private var newX: Double = 0.0 private var newX: Double = 0.0
@ -53,66 +58,62 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node:
@FXML lateinit var contentPlaceHolder: StackPane @FXML lateinit var contentPlaceHolder: StackPane
@FXML lateinit var titleContainer: BorderPane @FXML lateinit var titleContainer: BorderPane
@FXML lateinit var buttonsContainer: HBox @FXML lateinit var buttonsContainer: HBox
private val onCloseButtonAction: ObjectProperty<Runnable> @FXML lateinit var backNavButton: JFXButton
private val customMaximize: BooleanProperty @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 maximized: Boolean = false
private var originalBox: BoundingBox? = null private var originalBox: BoundingBox? = null
private var maximizedBox: BoundingBox? = null private var maximizedBox: BoundingBox? = null
@FXML lateinit var btnMin: JFXButton @FXML lateinit var btnMin: JFXButton
@FXML lateinit var btnMax: JFXButton @FXML lateinit var btnMax: JFXButton
@FXML lateinit var btnClose: JFXButton @FXML lateinit var btnClose: JFXButton
private val minus: 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)
private val resizeMax: SVGGlyph .apply { setSize(12.0, 2.0); translateY = 4.0 }
private val resizeMin: SVGGlyph 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)
private val close: SVGGlyph .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 { init {
loadFXML("/assets/fxml/decorator.fxml") 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) 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 btnClose.graphic = close
btnMin.graphic = minus btnMin.graphic = minus
this.btnMax.graphic = resizeMax btnMax.graphic = resizeMax
buttonsContainer.background = Background(*arrayOf(BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY))) buttonsContainer.background = Background(*arrayOf(BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)))
titleContainer.addEventHandler(MouseEvent.MOUSE_CLICKED) { mouseEvent -> titleContainer.addEventHandler(MouseEvent.MOUSE_CLICKED) { mouseEvent ->
if (mouseEvent.clickCount == 2) { if (mouseEvent.clickCount == 2) {
this.btnMax.fire() btnMax.fire()
} }
} }
if (!min) buttonsContainer.children.remove(btnMin) if (!min) buttonsContainer.children.remove(btnMin)
if (!max) buttonsContainer.children.remove(btnMax) if (!max) buttonsContainer.children.remove(btnMax)
titleContainer.addEventHandler(MouseEvent.MOUSE_ENTERED) { enter -> this.allowMove = true } JFXDepthManager.setDepth(titleContainer, 1)
titleContainer.addEventHandler(MouseEvent.MOUSE_EXITED) { enter -> titleContainer.addEventHandler(MouseEvent.MOUSE_ENTERED) { this.allowMove = true }
if (!this.isDragging) { titleContainer.addEventHandler(MouseEvent.MOUSE_EXITED) { if (!this.isDragging) this.allowMove = false }
this.allowMove = false
}
}
this.contentPlaceHolder.children.add(node) this.contentPlaceHolder.children.add(node)
(node as Region).setMinSize(0.0, 0.0) (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() val clip = Rectangle()
clip.widthProperty().bind(node.widthProperty()) clip.widthProperty().bind(node.widthProperty())
clip.heightProperty().bind(node.heightProperty()) clip.heightProperty().bind(node.heightProperty())
@ -127,7 +128,7 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node:
val x = mouseEvent.x val x = mouseEvent.x
val y = mouseEvent.y val y = mouseEvent.y
val boundsInParent = this.boundsInParent 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() val borderWidth = this.contentPlaceHolder.snappedLeftInset()
if (this.isRightEdge(x, y, boundsInParent)) { if (this.isRightEdge(x, y, boundsInParent)) {
if (y < borderWidth) { if (y < borderWidth) {
@ -274,7 +275,7 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node:
} }
fun onClose() { fun onClose() {
(this.onCloseButtonAction.get() as Runnable).run() this.onCloseButtonAction.run()
} }
private fun updateInitMouseValues(mouseEvent: MouseEvent) { 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) { fun setMaximized(maximized: Boolean) {
if (this.maximized != maximized) { if (this.maximized != maximized) {
Platform.runLater { this.btnMax.fire() } Platform.runLater { this.btnMax.fire() }

View File

@ -106,6 +106,8 @@ fun setOverflowHidden(node: Pane) {
node.clip = rectangle 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-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())

View File

@ -212,7 +212,7 @@
.jfx-tool-bar HBox { .jfx-tool-bar HBox {
-fx-alignment: center; -fx-alignment: center;
-fx-spacing: 25; /* -fx-spacing: 25;*/
-fx-padding: 0 10; -fx-padding: 0 10;
} }

View File

@ -199,8 +199,12 @@
.jfx-tool-bar HBox { .jfx-tool-bar HBox {
-fx-alignment: center; -fx-alignment: center;
-fx-spacing: 25.0; /* -fx-spacing: 25.0;*/
-fx-padding: 0.0 10.0; -fx-padding: 0.0 5.0;
}
.jfx-tool-bar .jfx-decorator-button {
-fx-cursor: head;
} }
.jfx-tool-bar Label { .jfx-tool-bar Label {
@ -219,6 +223,26 @@
-jfx-rippler-fill: WHITE; -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 { .option-list-view {
-fx-pref-width: 160.0px; -fx-pref-width: 160.0px;
-fx-background-color: WHITE; -fx-background-color: WHITE;
@ -617,7 +641,7 @@
-jfx-mask-type: CIRCLE; -jfx-mask-type: CIRCLE;
} }
.toggle-icon2, .toggle-icon3 { .toggle-icon2, .toggle-icon3, .jfx-decorator-button {
-fx-pref-width: 50px; -fx-pref-width: 50px;
-fx-background-radius: 50px; -fx-background-radius: 50px;
-fx-pref-height: 50px; -fx-pref-height: 50px;
@ -805,16 +829,21 @@
*******************************************************************************/ *******************************************************************************/
.jfx-decorator { .jfx-decorator {
-fx-decorator-color: derive(#5264AE, -20%); -fx-decorator-color: derive(#5264AE, 0);
} }
.jfx-decorator .jfx-decorator-buttons-container { .jfx-decorator .jfx-decorator-buttons-container {
-fx-background-color: -fx-decorator-color; -fx-background-color: -fx-decorator-color;
} }
.jfx-decorator .resize-border { .resize-border {
-fx-border-color: -fx-decorator-color; -fx-border-color: #5264AE;
-fx-border-width: 0 4 4 4; -fx-border-width: 0 2 2 2;
}
.debug-border {
-fx-border-color: red;
-fx-border-width: 1;
} }
/******************************************************************************* /*******************************************************************************

View File

@ -4,53 +4,117 @@
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<?import com.jfoenix.controls.JFXButton?> <?import com.jfoenix.controls.JFXButton?>
<?import javafx.scene.Cursor?>
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import java.lang.String?> <?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" <fx:root xmlns="http://javafx.com/javafx"
type="VBox" type="GridPane"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
styleClass="jfx-decorator"
pickOnBounds="false" pickOnBounds="false"
onMouseReleased="#onMouseReleased" onMouseReleased="#onMouseReleased"
onMouseDragged="#onMouseDragged" onMouseDragged="#onMouseDragged"
onMouseMoved="#onMouseMoved"> onMouseMoved="#onMouseMoved">
<styleClass>
<BorderPane fx:id="titleContainer" styleClass="jfx-decorator-buttons-container" pickOnBounds="false"> <String fx:value="jfx-decorator" />
<left> <String fx:value="resize-border" />
<HBox fx:id="titleWrapper" alignment="CENTER_LEFT" style="-fx-padding: 15;"> </styleClass>
<Label text="Hello Minecraft! Launcher" mouseTransparent="false" style="-fx-background-color: transparent; -fx-text-fill: white; -fx-font-size: 15px;" /> <columnConstraints>
</HBox> <ColumnConstraints prefWidth="200" />
</left> <ColumnConstraints hgrow="ALWAYS" />
<right> </columnConstraints>
<HBox fx:id="buttonsContainer" styleClass="jfx-decorator-buttons-container" alignment="CENTER_RIGHT" minWidth="180"> <rowConstraints>
<padding> <RowConstraints />
<Insets topRightBottomLeft="4.0" /> <RowConstraints vgrow="ALWAYS" />
</padding> </rowConstraints>
<JFXButton fx:id="btnMin" styleClass="jfx-decorator-button" ripplerFill="white" onAction="#onMin"> <StackPane GridPane.rowIndex="1" GridPane.columnIndex="0" VBox.vgrow="ALWAYS" styleClass="jfx-decorator-content-container">
<cursor> <BorderPane prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<Cursor fx:constant="HAND" /> <center>
</cursor> <ScrollPane fitToHeight="true" fitToWidth="true">
</JFXButton> <VBox fx:id="leftPane">
<JFXButton fx:id="btnMax" styleClass="jfx-decorator-button" ripplerFill="white" onAction="#onMax"> </VBox>
<cursor> </ScrollPane>
<Cursor fx:constant="HAND" /> </center>
</cursor> <bottom>
</JFXButton> <BorderPane fx:id="menuBottomBar">
<JFXButton fx:id="btnClose" styleClass="jfx-decorator-button" ripplerFill="white" onAction="#onClose"> <left>
<cursor> <JFXButton fx:id="refreshMenuButton" styleClass="toggle-icon3">
<Cursor fx:constant="HAND" /> <graphic>
</cursor> <fx:include source="/assets/svg/refresh-black.fxml"/>
</JFXButton> </graphic></JFXButton>
</HBox> </left>
</right> <right>
</BorderPane> <JFXButton fx:id="addMenuButton" styleClass="toggle-icon3">
<graphic>
<StackPane fx:id="contentPlaceHolder" styleClass="jfx-decorator-content-container resize-border" minWidth="0" minHeight="0" VBox.vgrow="ALWAYS"> <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> <styleClass>
<String fx:value="jfx-decorator-content-container" /> <String fx:value="jfx-decorator-content-container" />
<String fx:value="resize-border" />
</styleClass> </styleClass>
<!-- Node --> <!-- Node -->
</StackPane> </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>

View File

@ -1,9 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<?import com.jfoenix.controls.*?> <?import com.jfoenix.controls.*?>
<?import javafx.geometry.Insets?>
<BorderPane <BorderPane
maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
fx:controller="org.jackhuang.hmcl.ui.MainController" fx:controller="org.jackhuang.hmcl.ui.MainController"
@ -13,59 +11,7 @@
<StackPane fx:id="page" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" /> <StackPane fx:id="page" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
</center> </center>
<left> <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> </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> <bottom>
<BorderPane prefHeight="50.0" prefWidth="200.0" BorderPane.alignment="CENTER"> <BorderPane prefHeight="50.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<right> <right>

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<javafx.scene.shape.SVGPath fill="black" content="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" />

View 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" />

View File

@ -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. * @param b A runnable that decides what to do next, You can also do something here.

View File

@ -33,7 +33,7 @@ import java.net.URL
import java.math.BigInteger import java.math.BigInteger
import java.util.logging.Level 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 override val scheduler: Scheduler = Scheduler.IO_THREAD
var onFailed = EventManager<FailedEvent<URL>>() var onFailed = EventManager<FailedEvent<URL>>()

View File

@ -25,7 +25,7 @@ import java.net.Proxy
import java.net.URL import java.net.URL
import java.nio.charset.Charset 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 val scheduler: Scheduler = Scheduler.IO_THREAD
override fun execute() { override fun execute() {

View File

@ -18,38 +18,51 @@
package org.jackhuang.hmcl.task package org.jackhuang.hmcl.task
import javafx.application.Platform import javafx.application.Platform
import java.util.concurrent.ExecutorService import java.util.concurrent.*
import java.util.concurrent.Executors import java.util.concurrent.atomic.AtomicReference
import java.util.concurrent.Future
import javax.swing.SwingUtilities import javax.swing.SwingUtilities
interface Scheduler { interface Scheduler {
fun schedule(block: Runnable): Future<*>? fun schedule(block: Callable<Unit>): Future<*>?
companion object Schedulers { companion object Schedulers {
val IMMEDIATE = object : Scheduler { val JAVAFX: Scheduler = SchedulerImpl(Platform::runLater)
override fun schedule(block: Runnable): Future<*>? { val SWING: Scheduler = SchedulerImpl(SwingUtilities::invokeLater)
block.run() private class SchedulerImpl(val executor: (() -> Unit) -> Unit) : Scheduler {
return null override fun schedule(block: Callable<Unit>): Future<*>? {
} val latch = CountDownLatch(1)
} val wrapper = AtomicReference<Exception>()
val JAVAFX: Scheduler = object : Scheduler { executor {
override fun schedule(block: Runnable): Future<*>? { try {
Platform.runLater(block) block.call()
return null } catch (e: Exception) {
} wrapper.set(e)
} } finally {
val SWING: Scheduler = object : Scheduler { latch.countDown()
override fun schedule(block: Runnable): Future<*>? { }
SwingUtilities.invokeLater(block) }
return null 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 { 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 { 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 val DEFAULT = NEW_THREAD
private val CACHED_EXECUTOR: ExecutorService by lazy { private val CACHED_EXECUTOR: ExecutorService by lazy {
@ -57,11 +70,11 @@ interface Scheduler {
} }
private val IO_EXECUTOR: ExecutorService by lazy { private val IO_EXECUTOR: ExecutorService by lazy {
Executors.newFixedThreadPool(6, { r: Runnable -> Executors.newFixedThreadPool(6) { r: Runnable ->
val thread: Thread = Executors.defaultThreadFactory().newThread(r) val thread: Thread = Executors.defaultThreadFactory().newThread(r)
thread.isDaemon = true thread.isDaemon = true
thread thread
}) }
} }
fun shutdown() { fun shutdown() {

View File

@ -17,7 +17,7 @@
*/ */
package org.jackhuang.hmcl.task 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() { override fun execute() {
runnable() runnable()
} }

View File

@ -17,7 +17,9 @@
*/ */
package org.jackhuang.hmcl.task package org.jackhuang.hmcl.task
import javafx.beans.property.ReadOnlyDoubleProperty
import javafx.beans.property.ReadOnlyDoubleWrapper import javafx.beans.property.ReadOnlyDoubleWrapper
import javafx.beans.property.ReadOnlyStringProperty
import javafx.beans.property.ReadOnlyStringWrapper import javafx.beans.property.ReadOnlyStringWrapper
import org.jackhuang.hmcl.event.EventManager import org.jackhuang.hmcl.event.EventManager
import org.jackhuang.hmcl.util.* import org.jackhuang.hmcl.util.*
@ -69,19 +71,23 @@ abstract class Task {
protected open val progressInterval = 1000L protected open val progressInterval = 1000L
private var lastTime = Long.MIN_VALUE private var lastTime = Long.MIN_VALUE
private val progressUpdate = AtomicReference<Double>() 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: Int, total: Int) = updateProgress(1.0 * progress / total)
protected fun updateProgress(progress: Double) { protected fun updateProgress(progress: Double) {
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
if (now - lastTime >= progressInterval) { if (now - lastTime >= progressInterval) {
progressProperty.updateAsync(progress, progressUpdate) progressPropertyImpl.updateAsync(progress, progressUpdate)
lastTime = now lastTime = now
} }
} }
private val messageUpdate = AtomicReference<String>() private val messageUpdate = AtomicReference<String>()
val messageProperty = ReadOnlyStringWrapper(this, "message", null) private val messagePropertyImpl = ReadOnlyStringWrapper(this, "message", null)
protected fun updateMessage(newMessage: String) = messageProperty.updateAsync(newMessage, messageUpdate) val messageProperty: ReadOnlyStringProperty = messagePropertyImpl.readOnlyProperty
@JvmName("messageProperty") get
protected fun updateMessage(newMessage: String) = messagePropertyImpl.updateAsync(newMessage, messageUpdate)
val onDone = EventManager<TaskEvent>() val onDone = EventManager<TaskEvent>()
@ -97,11 +103,11 @@ abstract class Task {
} }
private val subTaskRunnable = { task: Task -> private val subTaskRunnable = { task: Task ->
this.messageProperty.bind(task.messageProperty) this.messagePropertyImpl.bind(task.messagePropertyImpl)
this.progressProperty.bind(task.progressProperty) this.progressPropertyImpl.bind(task.progressPropertyImpl)
task.run() task.run()
this.messageProperty.unbind() this.messagePropertyImpl.unbind()
this.progressProperty.unbind() this.progressPropertyImpl.unbind()
} }
fun executor() = TaskExecutor().submit(this) fun executor() = TaskExecutor().submit(this)

View File

@ -52,7 +52,7 @@ class TaskExecutor() {
while (!taskQueue.isEmpty() && !canceled) { while (!taskQueue.isEmpty() && !canceled) {
val task = taskQueue.poll() val task = taskQueue.poll()
if (task != null) { if (task != null) {
val future = task.scheduler.schedule(Runnable { executeTask(task) }) val future = task.scheduler.schedule(Callable { executeTask(task); Unit })
try { try {
future?.get() future?.get()
} catch (e: InterruptedException) { } catch (e: InterruptedException) {
@ -142,8 +142,8 @@ class TaskExecutor() {
return flag return flag
} }
private inner class Invoker(val task: Task, val latch: CountDownLatch, val boolean: AtomicBoolean): Runnable { private inner class Invoker(val task: Task, val latch: CountDownLatch, val boolean: AtomicBoolean): Callable<Unit> {
override fun run() { override fun call() {
try { try {
Thread.currentThread().name = task.title Thread.currentThread().name = task.title
if (!executeTask(task)) if (!executeTask(task))

View File

@ -15,6 +15,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}. * along with this program. If not, see {http://www.gnu.org/licenses/}.
*/ */
@file:JvmName("Constants")
package org.jackhuang.hmcl.util package org.jackhuang.hmcl.util
import javafx.application.Platform import javafx.application.Platform

View File

@ -15,6 +15,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}. * along with this program. If not, see {http://www.gnu.org/licenses/}.
*/ */
@file:JvmName("HMCLog")
package org.jackhuang.hmcl.util package org.jackhuang.hmcl.util
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream

View File

@ -23,7 +23,6 @@ import java.lang.reflect.Method
import java.security.PrivilegedExceptionAction import java.security.PrivilegedExceptionAction
import java.security.AccessController import java.security.AccessController
object ReflectionHelper { object ReflectionHelper {
private lateinit var unsafe: Unsafe private lateinit var unsafe: Unsafe

View File

@ -21,7 +21,7 @@ import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.util.logging.Level import java.util.logging.Level
internal class StreamPump( internal class StreamPump @JvmOverloads constructor(
val inputStream: InputStream, val inputStream: InputStream,
val callback: (String) -> Unit = {} val callback: (String) -> Unit = {}
) : Runnable { ) : Runnable {

View File

@ -23,13 +23,6 @@ import java.io.IOException
import java.util.zip.ZipOutputStream import java.util.zip.ZipOutputStream
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
@Throws(IOException::class)
fun zip(src: String, destZip: String) {
zip(File(src), File(destZip), null)
}
/** /**
* 功能 src 目录下的所有文件进行 zip 格式的压缩保存为指定 zip 文件 * 功能 src 目录下的所有文件进行 zip 格式的压缩保存为指定 zip 文件
@ -43,8 +36,9 @@ fun zip(src: String, destZip: String) {
* * * *
* @throws java.io.IOException 压缩失败或无法读取 * @throws java.io.IOException 压缩失败或无法读取
*/ */
@JvmOverloads
@Throws(IOException::class) @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 -> ZipOutputStream(destZip.outputStream()).use { zos ->
val basePath: String val basePath: String
if (src.isDirectory) 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文件 * 将文件压缩成zip文件
@ -121,8 +110,9 @@ fun unzip(zip: File, dest: File) {
* * * *
* @throws java.io.IOException 解压失败或无法写入 * @throws java.io.IOException 解压失败或无法写入
*/ */
@JvmOverloads
@Throws(IOException::class) @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) val buf = ByteArray(1024)
dest.mkdirs() dest.mkdirs()
ZipInputStream(zip.inputStream()).use { zipFile -> ZipInputStream(zip.inputStream()).use { zipFile ->

Binary file not shown.