FXML for decorator

This commit is contained in:
huangyuhui
2017-08-03 23:54:15 +08:00
parent 41498b5d93
commit 736a8c1b30
13 changed files with 336 additions and 322 deletions

View File

@@ -46,9 +46,7 @@ object Controllers {
decorator.isCustomMaximize = false decorator.isCustomMaximize = false
scene = Scene(decorator, 800.0, 480.0) scene = Scene(decorator, 800.0, 480.0)
scene.stylesheets.addAll(Controllers::class.java.getResource("/css/jfoenix-design.css").toExternalForm(), scene.stylesheets.addAll(*stylesheets)
Controllers::class.java.getResource("/assets/css/jfoenix-components.css").toExternalForm(),
Controllers::class.java.getResource("/assets/css/jfoenix-main-demo.css").toExternalForm())
stage.minWidth = 800.0 stage.minWidth = 800.0
stage.maxWidth = 800.0 stage.maxWidth = 800.0
stage.maxHeight = 480.0 stage.maxHeight = 480.0
@@ -56,7 +54,7 @@ object Controllers {
} }
fun navigate(node: Node?) { fun navigate(node: Node?) {
mainController.setContentPage(node) //mainController.setContentPage(node)
} }
private fun <T> loadPane(s: String): T = FXMLLoader(Controllers::class.java.getResource("/assets/fxml/$s.fxml")).load() private fun <T> loadPane(s: String): T = FXMLLoader(Controllers::class.java.getResource("/assets/fxml/$s.fxml")).load()

View File

@@ -25,14 +25,12 @@ import javafx.beans.property.BooleanProperty
import javafx.beans.property.ObjectProperty import javafx.beans.property.ObjectProperty
import javafx.beans.property.SimpleBooleanProperty import javafx.beans.property.SimpleBooleanProperty
import javafx.beans.property.SimpleObjectProperty import javafx.beans.property.SimpleObjectProperty
import javafx.fxml.FXML
import javafx.geometry.BoundingBox import javafx.geometry.BoundingBox
import javafx.geometry.Bounds import javafx.geometry.Bounds
import javafx.geometry.Insets import javafx.geometry.Insets
import javafx.geometry.Pos
import javafx.scene.Cursor import javafx.scene.Cursor
import javafx.scene.Node import javafx.scene.Node
import javafx.scene.control.Button
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.*
@@ -41,9 +39,8 @@ 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 java.util.ArrayList
class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node: Node, 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) : VBox() {
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,126 +50,58 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node:
private var allowMove: Boolean = false private var allowMove: Boolean = false
private var isDragging: Boolean = false private var isDragging: Boolean = false
private var windowDecoratorAnimation: Timeline? = null private var windowDecoratorAnimation: Timeline? = null
private val contentPlaceHolder: StackPane @FXML lateinit var contentPlaceHolder: StackPane
private val titleContainer: BorderPane @FXML lateinit var titleContainer: BorderPane
@FXML lateinit var buttonsContainer: HBox
private val onCloseButtonAction: ObjectProperty<Runnable> private val onCloseButtonAction: ObjectProperty<Runnable>
private val customMaximize: BooleanProperty private val customMaximize: BooleanProperty
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
private val btnMax: JFXButton @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
init { init {
loadFXML("/assets/fxml/decorator.fxml")
this.xOffset = 0.0 this.xOffset = 0.0
this.yOffset = 0.0 this.yOffset = 0.0
this.allowMove = false this.allowMove = false
this.isDragging = false this.isDragging = false
this.contentPlaceHolder = StackPane()
this.onCloseButtonAction = SimpleObjectProperty(Runnable { this.primaryStage.close() }) this.onCloseButtonAction = SimpleObjectProperty(Runnable { this.primaryStage.close() })
this.customMaximize = SimpleBooleanProperty(false) this.customMaximize = SimpleBooleanProperty(false)
this.maximized = false this.maximized = false
this.primaryStage.initStyle(StageStyle.UNDECORATED) this.primaryStage.initStyle(StageStyle.UNDECORATED)
this.isPickOnBounds = false 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)
this.styleClass.add("jfx-decorator")
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)
minus.setSize(12.0, 2.0) minus.setSize(12.0, 2.0)
minus.translateY = 4.0 minus.translateY = 4.0
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) 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) resizeMax.setSize(12.0, 12.0)
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) 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) resizeMin.setSize(12.0, 12.0)
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) 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) close.setSize(12.0, 12.0)
val btnClose = JFXButton()
btnClose.styleClass.add("jfx-decorator-button")
btnClose.cursor = Cursor.HAND
btnClose.setOnAction { action -> (this.onCloseButtonAction.get() as Runnable).run() }
btnClose.graphic = close btnClose.graphic = close
btnClose.ripplerFill = Color.WHITE
val btnMin = JFXButton()
btnMin.styleClass.add("jfx-decorator-button")
btnMin.cursor = Cursor.HAND
btnMin.setOnAction { action -> this.primaryStage.isIconified = true }
btnMin.graphic = minus btnMin.graphic = minus
btnMin.ripplerFill = Color.WHITE
this.btnMax = JFXButton()
this.btnMax.styleClass.add("jfx-decorator-button")
this.btnMax.cursor = Cursor.HAND
this.btnMax.ripplerFill = Color.WHITE
this.btnMax.setOnAction { action ->
if (!this.isCustomMaximize) {
this.primaryStage.isMaximized = !this.primaryStage.isMaximized
this.maximized = this.primaryStage.isMaximized
if (this.primaryStage.isMaximized) {
this.btnMax.graphic = resizeMin
this.btnMax.tooltip = Tooltip("Restore Down")
} else {
this.btnMax.graphic = resizeMax
this.btnMax.tooltip = Tooltip("Maximize")
}
} else {
if (!this.maximized) {
this.originalBox = BoundingBox(primaryStage.x, primaryStage.y, primaryStage.width, primaryStage.height)
val screen = Screen.getScreensForRectangle(primaryStage.x, primaryStage.y, primaryStage.width, primaryStage.height)[0] as Screen
val bounds = screen.visualBounds
this.maximizedBox = BoundingBox(bounds.minX, bounds.minY, bounds.width, bounds.height)
primaryStage.x = this.maximizedBox!!.minX
primaryStage.y = this.maximizedBox!!.minY
primaryStage.width = this.maximizedBox!!.width
primaryStage.height = this.maximizedBox!!.height
this.btnMax.graphic = resizeMin
this.btnMax.tooltip = Tooltip("Restore Down")
} else {
primaryStage.x = this.originalBox!!.minX
primaryStage.y = this.originalBox!!.minY
primaryStage.width = this.originalBox!!.width
primaryStage.height = this.originalBox!!.height
this.originalBox = null
this.btnMax.graphic = resizeMax
this.btnMax.tooltip = Tooltip("Maximize")
}
this.maximized = !this.maximized
}
}
this.btnMax.graphic = resizeMax this.btnMax.graphic = resizeMax
titleContainer = BorderPane()
titleContainer.styleClass += "jfx-decorator-buttons-container"
titleContainer.isPickOnBounds = false
val titleWrapper = HBox()
titleWrapper.style += "-fx-padding: 15;"
titleWrapper.alignment = Pos.CENTER_LEFT
val title = Label("Hello Minecraft! Launcher")
title.alignment = Pos.CENTER_LEFT
title.style += "--fx-background-color: transparent; -fx-text-fill: white; -fx-font-size: 15px;"
title.isMouseTransparent = false
titleWrapper.children.setAll(title)
titleContainer.left = titleWrapper
val buttonsContainer = HBox()
buttonsContainer.styleClass.add("jfx-decorator-buttons-container")
buttonsContainer.background = Background(*arrayOf(BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY))) buttonsContainer.background = Background(*arrayOf(BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)))
buttonsContainer.padding = Insets(4.0)
buttonsContainer.alignment = Pos.CENTER_RIGHT
titleContainer.addEventHandler(MouseEvent.MOUSE_CLICKED) { mouseEvent -> titleContainer.addEventHandler(MouseEvent.MOUSE_CLICKED) { mouseEvent ->
if (mouseEvent.clickCount == 2) { if (mouseEvent.clickCount == 2) {
this.btnMax.fire() this.btnMax.fire()
} }
} }
val btns = ArrayList<Button>()
if (min) { if (!min) buttonsContainer.children.remove(btnMin)
btns.add(btnMin)
}
if (max) { if (!max) buttonsContainer.children.remove(btnMax)
btns.add(this.btnMax)
}
btns.add(btnClose)
buttonsContainer.children.addAll(btns)
titleContainer.addEventHandler(MouseEvent.MOUSE_ENTERED) { enter -> this.allowMove = true } titleContainer.addEventHandler(MouseEvent.MOUSE_ENTERED) { enter -> this.allowMove = true }
titleContainer.addEventHandler(MouseEvent.MOUSE_EXITED) { enter -> titleContainer.addEventHandler(MouseEvent.MOUSE_EXITED) { enter ->
if (!this.isDragging) { if (!this.isDragging) {
@@ -180,130 +109,174 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node:
} }
} }
buttonsContainer.minWidth = 180.0
titleContainer.right = buttonsContainer
this.contentPlaceHolder.styleClass.add("jfx-decorator-content-container")
this.contentPlaceHolder.setMinSize(0.0, 0.0)
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)
VBox.setVgrow(this.contentPlaceHolder, Priority.ALWAYS)
this.contentPlaceHolder.styleClass.add("resize-border")
this.contentPlaceHolder.border = Border(BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderWidths(0.0, 4.0, 4.0, 4.0))) this.contentPlaceHolder.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())
node.setClip(clip) node.setClip(clip)
this.children.addAll(titleContainer, this.contentPlaceHolder) }
this.setOnMouseMoved { mouseEvent ->
if (!this.primaryStage.isMaximized && !this.primaryStage.isFullScreen && !this.maximized) {
if (!this.primaryStage.isResizable) {
this.updateInitMouseValues(mouseEvent)
} else {
val x = mouseEvent.x
val y = mouseEvent.y
val boundsInParent = this.boundsInParent
if (this.contentPlaceHolder.border != null && this.contentPlaceHolder.border.strokes.size > 0) {
val borderWidth = this.contentPlaceHolder.snappedLeftInset()
if (this.isRightEdge(x, y, boundsInParent)) {
if (y < borderWidth) {
this.cursor = Cursor.NE_RESIZE
} else if (y > this.height - borderWidth) {
this.cursor = Cursor.SE_RESIZE
} else {
this.cursor = Cursor.E_RESIZE
}
} else if (this.isLeftEdge(x, y, boundsInParent)) {
if (y < borderWidth) {
this.cursor = Cursor.NW_RESIZE
} else if (y > this.height - borderWidth) {
this.cursor = Cursor.SW_RESIZE
} else {
this.cursor = Cursor.W_RESIZE
}
} else if (this.isTopEdge(x, y, boundsInParent)) {
this.cursor = Cursor.N_RESIZE
} else if (this.isBottomEdge(x, y, boundsInParent)) {
this.cursor = Cursor.S_RESIZE
} else {
this.cursor = Cursor.DEFAULT
}
this.updateInitMouseValues(mouseEvent) fun onMouseMoved(mouseEvent: MouseEvent) {
} if (!this.primaryStage.isMaximized && !this.primaryStage.isFullScreen && !this.maximized) {
if (!this.primaryStage.isResizable) {
} this.updateInitMouseValues(mouseEvent)
} else { } else {
this.cursor = Cursor.DEFAULT val x = mouseEvent.x
} val y = mouseEvent.y
} val boundsInParent = this.boundsInParent
this.setOnMouseReleased { mouseEvent -> this.isDragging = false } if (this.contentPlaceHolder.border != null && this.contentPlaceHolder.border.strokes.size > 0) {
this.setOnMouseDragged { mouseEvent -> val borderWidth = this.contentPlaceHolder.snappedLeftInset()
this.isDragging = true if (this.isRightEdge(x, y, boundsInParent)) {
if (mouseEvent.isPrimaryButtonDown && (this.xOffset != -1.0 || this.yOffset != -1.0)) { if (y < borderWidth) {
if (!this.primaryStage.isFullScreen && !mouseEvent.isStillSincePress && !this.primaryStage.isMaximized && !this.maximized) { this.cursor = Cursor.NE_RESIZE
this.newX = mouseEvent.screenX } else if (y > this.height - borderWidth) {
this.newY = mouseEvent.screenY this.cursor = Cursor.SE_RESIZE
val deltax = this.newX - this.initX } else {
val deltay = this.newY - this.initY this.cursor = Cursor.E_RESIZE
val cursor = this.cursor
if (Cursor.E_RESIZE == cursor) {
this.setStageWidth(this.primaryStage.width + deltax)
mouseEvent.consume()
} else if (Cursor.NE_RESIZE == cursor) {
if (this.setStageHeight(this.primaryStage.height - deltay)) {
this.primaryStage.y = this.primaryStage.y + deltay
} }
} else if (this.isLeftEdge(x, y, boundsInParent)) {
this.setStageWidth(this.primaryStage.width + deltax) if (y < borderWidth) {
mouseEvent.consume() this.cursor = Cursor.NW_RESIZE
} else if (Cursor.SE_RESIZE == cursor) { } else if (y > this.height - borderWidth) {
this.setStageWidth(this.primaryStage.width + deltax) this.cursor = Cursor.SW_RESIZE
this.setStageHeight(this.primaryStage.height + deltay) } else {
mouseEvent.consume() this.cursor = Cursor.W_RESIZE
} else if (Cursor.S_RESIZE == cursor) {
this.setStageHeight(this.primaryStage.height + deltay)
mouseEvent.consume()
} else if (Cursor.W_RESIZE == cursor) {
if (this.setStageWidth(this.primaryStage.width - deltax)) {
this.primaryStage.x = this.primaryStage.x + deltax
} }
} else if (this.isTopEdge(x, y, boundsInParent)) {
mouseEvent.consume() this.cursor = Cursor.N_RESIZE
} else if (Cursor.SW_RESIZE == cursor) { } else if (this.isBottomEdge(x, y, boundsInParent)) {
if (this.setStageWidth(this.primaryStage.width - deltax)) { this.cursor = Cursor.S_RESIZE
this.primaryStage.x = this.primaryStage.x + deltax } else {
} this.cursor = Cursor.DEFAULT
this.setStageHeight(this.primaryStage.height + deltay)
mouseEvent.consume()
} else if (Cursor.NW_RESIZE == cursor) {
if (this.setStageWidth(this.primaryStage.width - deltax)) {
this.primaryStage.x = this.primaryStage.x + deltax
}
if (this.setStageHeight(this.primaryStage.height - deltay)) {
this.primaryStage.y = this.primaryStage.y + deltay
}
mouseEvent.consume()
} else if (Cursor.N_RESIZE == cursor) {
if (this.setStageHeight(this.primaryStage.height - deltay)) {
this.primaryStage.y = this.primaryStage.y + deltay
}
mouseEvent.consume()
} else if (this.allowMove) {
this.primaryStage.x = mouseEvent.screenX - this.xOffset
this.primaryStage.y = mouseEvent.screenY - this.yOffset
mouseEvent.consume()
} }
this.updateInitMouseValues(mouseEvent)
} }
}
} else {
this.cursor = Cursor.DEFAULT
}
}
fun onMouseReleased() {
this.isDragging = false
}
fun onMouseDragged(mouseEvent: MouseEvent) {
this.isDragging = true
if (mouseEvent.isPrimaryButtonDown && (this.xOffset != -1.0 || this.yOffset != -1.0)) {
if (!this.primaryStage.isFullScreen && !mouseEvent.isStillSincePress && !this.primaryStage.isMaximized && !this.maximized) {
this.newX = mouseEvent.screenX
this.newY = mouseEvent.screenY
val deltax = this.newX - this.initX
val deltay = this.newY - this.initY
val cursor = this.cursor
if (Cursor.E_RESIZE == cursor) {
this.setStageWidth(this.primaryStage.width + deltax)
mouseEvent.consume()
} else if (Cursor.NE_RESIZE == cursor) {
if (this.setStageHeight(this.primaryStage.height - deltay)) {
this.primaryStage.y = this.primaryStage.y + deltay
}
this.setStageWidth(this.primaryStage.width + deltax)
mouseEvent.consume()
} else if (Cursor.SE_RESIZE == cursor) {
this.setStageWidth(this.primaryStage.width + deltax)
this.setStageHeight(this.primaryStage.height + deltay)
mouseEvent.consume()
} else if (Cursor.S_RESIZE == cursor) {
this.setStageHeight(this.primaryStage.height + deltay)
mouseEvent.consume()
} else if (Cursor.W_RESIZE == cursor) {
if (this.setStageWidth(this.primaryStage.width - deltax)) {
this.primaryStage.x = this.primaryStage.x + deltax
}
mouseEvent.consume()
} else if (Cursor.SW_RESIZE == cursor) {
if (this.setStageWidth(this.primaryStage.width - deltax)) {
this.primaryStage.x = this.primaryStage.x + deltax
}
this.setStageHeight(this.primaryStage.height + deltay)
mouseEvent.consume()
} else if (Cursor.NW_RESIZE == cursor) {
if (this.setStageWidth(this.primaryStage.width - deltax)) {
this.primaryStage.x = this.primaryStage.x + deltax
}
if (this.setStageHeight(this.primaryStage.height - deltay)) {
this.primaryStage.y = this.primaryStage.y + deltay
}
mouseEvent.consume()
} else if (Cursor.N_RESIZE == cursor) {
if (this.setStageHeight(this.primaryStage.height - deltay)) {
this.primaryStage.y = this.primaryStage.y + deltay
}
mouseEvent.consume()
} else if (this.allowMove) {
this.primaryStage.x = mouseEvent.screenX - this.xOffset
this.primaryStage.y = mouseEvent.screenY - this.yOffset
mouseEvent.consume()
}
} }
} }
} }
fun onMin() {
this.primaryStage.isIconified = true
}
fun onMax() {
if (!max) return
if (!this.isCustomMaximize) {
this.primaryStage.isMaximized = !this.primaryStage.isMaximized
this.maximized = this.primaryStage.isMaximized
if (this.primaryStage.isMaximized) {
this.btnMax.graphic = resizeMin
this.btnMax.tooltip = Tooltip("Restore Down")
} else {
this.btnMax.graphic = resizeMax
this.btnMax.tooltip = Tooltip("Maximize")
}
} else {
if (!this.maximized) {
this.originalBox = BoundingBox(primaryStage.x, primaryStage.y, primaryStage.width, primaryStage.height)
val screen = Screen.getScreensForRectangle(primaryStage.x, primaryStage.y, primaryStage.width, primaryStage.height)[0] as Screen
val bounds = screen.visualBounds
this.maximizedBox = BoundingBox(bounds.minX, bounds.minY, bounds.width, bounds.height)
primaryStage.x = this.maximizedBox!!.minX
primaryStage.y = this.maximizedBox!!.minY
primaryStage.width = this.maximizedBox!!.width
primaryStage.height = this.maximizedBox!!.height
this.btnMax.graphic = resizeMin
this.btnMax.tooltip = Tooltip("Restore Down")
} else {
primaryStage.x = this.originalBox!!.minX
primaryStage.y = this.originalBox!!.minY
primaryStage.width = this.originalBox!!.width
primaryStage.height = this.originalBox!!.height
this.originalBox = null
this.btnMax.graphic = resizeMax
this.btnMax.tooltip = Tooltip("Maximize")
}
this.maximized = !this.maximized
}
}
fun onClose() {
(this.onCloseButtonAction.get() as Runnable).run()
}
private fun updateInitMouseValues(mouseEvent: MouseEvent) { private fun updateInitMouseValues(mouseEvent: MouseEvent) {
this.initX = mouseEvent.screenX this.initX = mouseEvent.screenX
this.initY = mouseEvent.screenY this.initY = mouseEvent.screenY
@@ -371,7 +344,6 @@ class Decorator @JvmOverloads constructor(private val primaryStage: Stage, node:
if (this.maximized != maximized) { if (this.maximized != maximized) {
Platform.runLater { this.btnMax.fire() } Platform.runLater { this.btnMax.fire() }
} }
} }
fun setContent(content: Node) { fun setContent(content: Node) {

View File

@@ -25,10 +25,15 @@ import javafx.event.ActionEvent
import javafx.event.EventHandler import javafx.event.EventHandler
import javafx.fxml.FXMLLoader import javafx.fxml.FXMLLoader
import javafx.scene.Node import javafx.scene.Node
import javafx.scene.Parent
import javafx.scene.Scene
import javafx.scene.control.ListView import javafx.scene.control.ListView
import javafx.scene.control.ScrollBar import javafx.scene.control.ScrollBar
import javafx.scene.image.WritableImage
import javafx.scene.input.MouseEvent import javafx.scene.input.MouseEvent
import javafx.scene.input.ScrollEvent import javafx.scene.input.ScrollEvent
import javafx.scene.layout.Pane
import javafx.scene.shape.Rectangle
import javafx.util.Duration import javafx.util.Duration
fun Node.loadFXML(absolutePath: String) { fun Node.loadFXML(absolutePath: String) {
@@ -86,4 +91,21 @@ fun ListView<*>.smoothScrolling() {
fun runOnUiThread(runnable: () -> Unit) = { fun runOnUiThread(runnable: () -> Unit) = {
JFXUtilities.runInFX(runnable) JFXUtilities.runInFX(runnable)
} }
fun takeSnapshot(node: Parent, width: Double, height: Double): WritableImage {
val scene = Scene(node, width, height)
scene.stylesheets.addAll(*stylesheets)
return scene.snapshot(null)
}
fun setOverflowHidden(node: Pane) {
val rectangle = Rectangle()
rectangle.widthProperty().bind(node.widthProperty())
rectangle.heightProperty().bind(node.heightProperty())
node.clip = rectangle
}
val stylesheets = arrayOf(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())

View File

@@ -43,87 +43,4 @@ import org.jackhuang.hmcl.ui.wizard.Wizard
*/ */
class MainController { class MainController {
/**
* A combo box that allows user to select Minecraft directory.
*/
@FXML lateinit var comboProfiles: JFXComboBox<String> // TODO: JFXComboBox<Profile>
/**
* The button that is to launch the selected game version.
*/
@FXML lateinit var buttonLaunch: JFXButton
/**
* A central pane that contains popups like (global) version settings, app settings, game installations and so on.
*/
@FXML lateinit var page: StackPane
@FXML lateinit var listVersions: JFXListView<VersionListItem> // TODO: JFXListView<Version> including icon, title, game version(if equals to title, hidden)
lateinit var animationHandler: TransitionHandler
// TODO: implementing functions.
fun initialize() {
Controllers.mainController = this
animationHandler = TransitionHandler(page)
EVENT_BUS.channel<RefreshedVersionsEvent>() += this::loadVersions
EVENT_BUS.channel<ProfileLoadingEvent>() += this::onProfilesLoading
EVENT_BUS.channel<ProfileChangedEvent>() += this::onProfileChanged
listVersions.setOnMouseClicked {
if (it.clickCount == 2) {
setContentPage(Controllers.versionPane)
val id = listVersions.selectionModel.selectedItem.id
Controllers.versionController.loadVersionSetting(id, Settings.getLastProfile().getVersionSetting(id))
} else
it.consume()
}
listVersions.smoothScrolling()
Settings.onProfileLoading()
}
private val empty = Pane()
fun setContentPage(node: Node?) {
animationHandler.setContent(node ?: empty, ContainerAnimations.FADE.animationProducer)
}
fun installNewVersion() {
setContentPage(Wizard.createWizard("Install New Game", DownloadWizardProvider()))
}
fun onProfilesLoading() {
// TODO: Profiles
}
fun onProfileChanged(event: ProfileChangedEvent) {
val profile = event.value
profile.selectedVersionProperty.addListener { _, _, newValue ->
versionChanged(newValue)
}
}
val versionListItems = mutableMapOf<String, VersionListItem>()
fun loadVersions() {
val profile = Settings.getLastProfile()
val list = mutableListOf<VersionListItem>()
versionListItems.clear()
profile.repository.getVersions().forEach {
val item = VersionListItem(it.id, minecraftVersion(Settings.getLastProfile().repository.getVersionJar(it.id)) ?: "Unknown")
list += item
versionListItems += it.id to item
}
listVersions.items = FXCollections.observableList(list)
}
fun versionChanged(selectedVersion: String) {
listVersions.selectionModel.select(versionListItems[selectedVersion])
}
} }

View File

@@ -21,12 +21,17 @@ import javafx.animation.KeyFrame
import javafx.animation.Timeline import javafx.animation.Timeline
import javafx.event.EventHandler import javafx.event.EventHandler
import javafx.scene.Node import javafx.scene.Node
import javafx.scene.Parent
import javafx.scene.SnapshotParameters import javafx.scene.SnapshotParameters
import javafx.scene.image.ImageView import javafx.scene.image.ImageView
import javafx.scene.image.WritableImage import javafx.scene.image.WritableImage
import javafx.scene.layout.StackPane import javafx.scene.layout.StackPane
import javafx.util.Duration import javafx.util.Duration
import org.jackhuang.hmcl.ui.takeSnapshot
/**
* @param view A stack pane that contains another control that is [Parent]
*/
class TransitionHandler(override val view: StackPane): AnimationHandler { class TransitionHandler(override val view: StackPane): AnimationHandler {
private var animation: Timeline? = null private var animation: Timeline? = null
@@ -62,13 +67,16 @@ class TransitionHandler(override val view: StackPane): AnimationHandler {
private fun updateContent(newView: Node) { private fun updateContent(newView: Node) {
if (view.width > 0 && view.height > 0) { if (view.width > 0 && view.height > 0) {
val image = view.snapshot(SnapshotParameters(), null) val content = view.children.firstOrNull()
val x = (image.width - view.width) / 2 val image: WritableImage
val y = image.height - view.height if (content != null && content is Parent) {
val newImage = WritableImage(image.pixelReader, x.toInt(), y.toInt(), view.width.toInt(), view.height.toInt()) view.children.setAll()
snapshot.image = newImage image = takeSnapshot(content, view.width, view.height)
snapshot.fitWidth = newImage.width view.children.setAll(content)
snapshot.fitHeight = newImage.height } else image = view.snapshot(SnapshotParameters(), WritableImage(view.width.toInt(), view.height.toInt()))
snapshot.image = image
snapshot.fitWidth = view.width
snapshot.fitHeight = view.height
} else } else
snapshot.image = null snapshot.image = null

View File

@@ -31,8 +31,8 @@ class InstallTypePage(private val controller: WizardController): StackPane(), Wi
init { init {
loadFXML("/assets/fxml/download/dltype.fxml") loadFXML("/assets/fxml/download/dltype.fxml")
list.selectionModel.selectedIndexProperty().addListener { _, _, newValue -> list.setOnMouseClicked {
controller.settings[INSTALL_TYPE] = newValue controller.settings[INSTALL_TYPE] = list.selectionModel.selectedIndex
controller.onNext() controller.onNext()
} }
} }

View File

@@ -21,6 +21,7 @@ import com.jfoenix.concurrency.JFXUtilities
import com.jfoenix.controls.JFXButton import com.jfoenix.controls.JFXButton
import com.jfoenix.controls.JFXProgressBar import com.jfoenix.controls.JFXProgressBar
import com.jfoenix.controls.JFXToolbar import com.jfoenix.controls.JFXToolbar
import com.jfoenix.effects.JFXDepthManager
import javafx.fxml.FXML import javafx.fxml.FXML
import javafx.scene.Node import javafx.scene.Node
import javafx.scene.control.Label import javafx.scene.control.Label

View File

@@ -43,7 +43,7 @@
.jfx-decorator .resize-border { .jfx-decorator .resize-border {
-fx-border-color: -fx-decorator-color; -fx-border-color: -fx-decorator-color;
-fx-border-width: 0 4 4 4; -fx-border-width: 0 1 1 1;
} }
.jfx-text-area, .text-area { .jfx-text-area, .text-area {

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import com.jfoenix.controls.JFXButton?>
<?import javafx.scene.Cursor?>
<?import javafx.geometry.Insets?>
<?import java.lang.String?>
<fx:root xmlns="http://javafx.com/javafx"
type="VBox"
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">
<styleClass>
<String fx:value="jfx-decorator-content-container" />
<String fx:value="resize-border" />
</styleClass>
<!-- Node -->
</StackPane>
</fx:root>

View File

@@ -3,6 +3,7 @@
<?import javafx.scene.control.*?> <?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"
@@ -20,7 +21,7 @@
<StackPane> <StackPane>
<JFXListView fx:id="listVersions" styleClass="mylistview" style="-fx-background-color: #F1F1F1;" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" /> <JFXListView fx:id="listVersions" styleClass="mylistview" style="-fx-background-color: #F1F1F1;" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
<AnchorPane pickOnBounds="false"> <AnchorPane pickOnBounds="false">
<JFXButton onMouseClicked="#installNewVersion" 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;"> <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> <graphic>
<fx:include source="/assets/svg/plus.fxml" /> <fx:include source="/assets/svg/plus.fxml" />
</graphic> </graphic>
@@ -30,6 +31,41 @@
</center> </center>
</BorderPane> </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

@@ -8,31 +8,11 @@
<StackPane xmlns="http://javafx.com/javafx" <StackPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml" xmlns:fx="http://javafx.com/fxml"
fx:controller="org.jackhuang.hmcl.ui.VersionController"> fx:controller="org.jackhuang.hmcl.ui.VersionController">
<VBox spacing="20" GridPane.rowIndex="0"> <BorderPane>
<JFXToolbar maxHeight="20" styleClass="jfx-tool-bar"> <center>
<leftItems>
<JFXButton fx:id="backButton" maxHeight="20" styleClass="toggle-icon3"
StackPane.alignment="CENTER_LEFT">
<graphic>
<fx:include source="/assets/svg/arrow-left.fxml" />
</graphic>
<StackPane.margin>
<Insets left="20" />
</StackPane.margin>
</JFXButton>
<Label fx:id="titleLabel" style="-fx-text-fill:WHITE; -fx-font-size: 20;"
StackPane.alignment="CENTER_LEFT"/>
</leftItems>
<rightItems>
</rightItems>
</JFXToolbar>
<ScrollPane fx:id="scroll" <ScrollPane fx:id="scroll"
style="-fx-margin: 10; -fx-background-color: white; -fx-font-size: 14; -fx-pref-width: 100%; " style="-fx-background-color: white; -fx-font-size: 14; -fx-pref-width: 100%; "
fitToHeight="true" fitToWidth="true"> fitToHeight="true" fitToWidth="true">
<VBox.margin>
<Insets left="10" right="10"/>
</VBox.margin>
<VBox> <VBox>
<GridPane fx:id="settingsPane" style="-fx-margin-left: 10; -fx-background-color: white; " hgap="5" vgap="10"> <GridPane fx:id="settingsPane" style="-fx-margin-left: 10; -fx-background-color: white; " hgap="5" vgap="10">
<Label GridPane.rowIndex="0" GridPane.columnIndex="0">Java Directory</Label> <Label GridPane.rowIndex="0" GridPane.columnIndex="0">Java Directory</Label>
@@ -105,5 +85,26 @@
</VBox> </VBox>
</VBox> </VBox>
</ScrollPane> </ScrollPane>
</VBox> </center>
<top>
<JFXToolbar maxHeight="20" styleClass="jfx-tool-bar">
<leftItems>
<JFXButton fx:id="backButton" maxHeight="20" styleClass="toggle-icon3"
StackPane.alignment="CENTER_LEFT">
<graphic>
<fx:include source="/assets/svg/arrow-left.fxml" />
</graphic>
<StackPane.margin>
<Insets left="20" />
</StackPane.margin>
</JFXButton>
<Label fx:id="titleLabel" style="-fx-text-fill:WHITE; -fx-font-size: 20;"
StackPane.alignment="CENTER_LEFT"/>
</leftItems>
<rightItems>
</rightItems>
</JFXToolbar>
</top>
</BorderPane>
</StackPane> </StackPane>

View File

@@ -9,7 +9,7 @@
type="StackPane" type="StackPane"
xmlns:fx="http://javafx.com/fxml"> xmlns:fx="http://javafx.com/fxml">
<VBox> <VBox>
<JFXToolbar fx:id="toolbar" maxHeight="20" styleClass="jfx-tool-bar"> <JFXToolbar fx:id="toolbar" styleClass="jfx-tool-bar">
<leftItems> <leftItems>
<JFXButton fx:id="closeButton" maxHeight="20" styleClass="toggle-icon3" <JFXButton fx:id="closeButton" maxHeight="20" styleClass="toggle-icon3"
StackPane.alignment="CENTER_RIGHT" onMouseClicked="#close"> StackPane.alignment="CENTER_RIGHT" onMouseClicked="#close">

View File

@@ -52,7 +52,10 @@ interface Scheduler {
override fun schedule(block: Runnable) = IO_EXECUTOR.submit(block) override fun schedule(block: Runnable) = IO_EXECUTOR.submit(block)
} }
val DEFAULT = NEW_THREAD val DEFAULT = NEW_THREAD
private val CACHED_EXECUTOR = Executors.newCachedThreadPool() private val CACHED_EXECUTOR: ExecutorService by lazy {
Executors.newCachedThreadPool()
}
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)