Make ScalaFX Scene Drag and Drop
I am actually working on a ScalaFX application without window decoration. What I want to do is make it draggable so that the user can move it.
I was able to move the scene by simply writing the coordinates of the drag mouse to the X / Y coordinates of the scene. However, this resulted in a lagging and flickering window.
How can you drag a scene smoothly in ScalaFX?
source to share
Here's an example that works well for me. It was adapted from one example in the JavaFX 8 Introduction by Example book. How does this compare to your attempt?
import scalafx.Includes._ import scalafx.application.JFXApp import scalafx.application.JFXApp.PrimaryStage import scalafx.geometry.Point2D import scalafx.scene.Scene import scalafx.scene.input.MouseEvent import scalafx.scene.paint.Color import scalafx.stage.{WindowEvent, StageStyle} object DraggableApp extends JFXApp { private var anchorPt: Point2D = null private var previousLocation: Point2D = null stage = new PrimaryStage { initStyle(StageStyle.TRANSPARENT) scene = new Scene { fill = Color.rgb(0, 0, 0, 1.0) } } // Initialize stage to be movable via mouse initMovablePlayer() /** * Initialize the stage to allow the mouse cursor to move the application * using dragging. */ private def initMovablePlayer(): Unit = { val scene = stage.getScene scene.onMousePressed = (event: MouseEvent) => anchorPt = new Point2D(event.screenX, event.screenY) scene.onMouseDragged = (event: MouseEvent) => if (anchorPt != null && previousLocation != null) { stage.x = previousLocation.x + event.screenX - anchorPt.x stage.y = previousLocation.y + event.screenY - anchorPt.y } scene.onMouseReleased = (event: MouseEvent) => previousLocation = new Point2D(stage.getX, stage.getY) stage.onShown = (event: WindowEvent) => previousLocation = new Point2D(stage.getX, stage.getY) } }
Change: . About you question about resizing the scene, I tried the following option which resizes the scene on right click and drag; I don't see flickering (on OS X).
import javafx.scene.{input => jfxsi} // (...) object DraggableApp extends JFXApp { private var previousHeight = 0.0 private var previousWidth = 0.0 // (...) private def initMovablePlayer(): Unit = { val scene = stage.getScene scene.onMousePressed = (event: MouseEvent) => anchorPt = new Point2D(event.screenX, event.screenY) scene.onMouseDragged = (event: MouseEvent) => if (anchorPt != null && previousLocation != null) { if (event.getButton == jfxsi.MouseButton.PRIMARY) { stage.x = previousLocation.x + event.screenX - anchorPt.x stage.y = previousLocation.y + event.screenY - anchorPt.y } else if (event.getButton == jfxsi.MouseButton.SECONDARY) { stage.width = previousWidth + event.screenX - anchorPt.x stage.height = previousHeight + event.screenY - anchorPt.y } } scene.onMouseReleased = (_: MouseEvent) => reset() stage.onShown = (_: WindowEvent) => reset() def reset (): Unit = { previousLocation = new Point2D(stage.getX, stage.getY) previousHeight = stage.getHeight previousWidth = stage.getWidth } } }
source to share
I know this has already been answered, but for the sake of completeness I would like to include how I have done this in the past.
Credits:
Draggable Window: Pro JavaFX 8 using
Resizable Window as an example : Little Child - Allow user to resize undeclared stage
Restore up / down: Undecorator - UndecoratorController.java
Get ready for a wall of (dirty) code
class UndecoratedWindowHelper(val target: Stage) {
var anchorPt: Point2D = null
var prevLoc: Point2D = null
private var savedBounds: BoundingBox = _
private val _maximized: ReadOnlyBooleanWrapper = new ReadOnlyBooleanWrapper {value = false}
// begin init
val resizeListener = new ResizeListener(target, this)
target.scene.get.addEventHandler(jfxme.MOUSE_MOVED, resizeListener)
target.scene.get.addEventHandler(jfxme.MOUSE_PRESSED, resizeListener)
target.scene.get.addEventHandler(jfxme.MOUSE_DRAGGED, resizeListener)
// end init
def addWindowDragPoint(dragNode: Node): Unit = {
dragNode.onMousePressed = (event: MouseEvent) => {
anchorPt = new Point2D(event.screenX, event.screenY)
prevLoc = new Point2D(target.getX, target.getY)
}
dragNode.onMouseClicked = (event: MouseEvent) =>
if (event.clickCount == 2)
maximizeOrRestore()
dragNode.onMouseDragged = (event: MouseEvent) =>
if (resizeListener.cursorEvent == Cursor.DEFAULT && !maximized) {
target.x = prevLoc.x + event.screenX - anchorPt.x
target.y = prevLoc.y + event.screenY - anchorPt.y
}
target.onShown = (event: WindowEvent) => prevLoc = new Point2D(target.getX, target.getY)
}
def maximizeOrRestore() {
if (maximized) {
restoreSavedBounds()
savedBounds = null
_maximized set false
} else {
val screensForRectangle = Screen.screensForRectangle(target.getX, target.getY, target.getWidth, target.getHeight)
val screen = screensForRectangle.get(0)
val visualBounds = screen.visualBounds
savedBounds = new BoundingBox(target.getX, target.getY, target.getWidth, target.getHeight)
target.setX(visualBounds.getMinX)
target.setY(visualBounds.getMinY)
target.setWidth(visualBounds.getWidth)
target.setHeight(visualBounds.getHeight)
_maximized set true
}
}
def saveBounds() {
savedBounds = new BoundingBox(target.getX, target.getY, target.getWidth, target.getHeight)
}
def restoreSavedBounds() {
target.setX(savedBounds.getMinX)
target.setY(savedBounds.getMinY)
target.setWidth(savedBounds.getWidth)
target.setHeight(savedBounds.getHeight)
savedBounds = null
}
def maximized = _maximized.getValue
def maximizedProp = _maximized.getReadOnlyProperty
}
//formatter:off
protected class ResizeListener(
val stage: Stage, owner: UndecoratedWindowHelper) extends EventHandler[jfxme] {
var cursorEvent = Cursor.DEFAULT
val border = 4
var startX = 0d
var startY = 0d
//formatter:on
def handle(mouseEvent: jfxme) {
val mouseEventType = mouseEvent.getEventType
val scene = stage.getScene
val mouseEventX = mouseEvent.getSceneX
val mouseEventY = mouseEvent.getSceneY
val sceneWidth = scene.getWidth
val sceneHeight = scene.getHeight
if (jfxme.MOUSE_MOVED == mouseEventType) {
//@formatter:off
cursorEvent =
if (mouseEventX < border && mouseEventY < border) Cursor.NW_RESIZE
else if (mouseEventX < border && mouseEventY > sceneHeight - border) Cursor.SW_RESIZE
else if (mouseEventX > sceneWidth - border && mouseEventY < border) Cursor.NE_RESIZE
else if (mouseEventX > sceneWidth - border && mouseEventY > sceneHeight - border) Cursor.SE_RESIZE
else if (mouseEventX < border) Cursor.W_RESIZE
else if (mouseEventX > sceneWidth - border) Cursor.E_RESIZE
else if (mouseEventY < border) Cursor.N_RESIZE
else if (mouseEventY > sceneHeight - border) Cursor.S_RESIZE
else Cursor.DEFAULT
//@formatter:on
if (owner.maximized)
cursorEvent = Cursor.DEFAULT
scene.setCursor(cursorEvent)
} else if (mouseEventType == jfxme.MOUSE_PRESSED) {
startX = stage.getWidth - mouseEventX
startY = stage.getHeight - mouseEventY
} else if (mouseEventType == jfxme.MOUSE_DRAGGED) {
if (Cursor.DEFAULT != cursorEvent) {
if (Cursor.W_RESIZE != cursorEvent && Cursor.E_RESIZE != cursorEvent) {// not west or east
val minHeight = Math.max(stage.getMinHeight, border * 2)
if (Cursor.NW_RESIZE == cursorEvent || Cursor.N_RESIZE == cursorEvent || Cursor.NE_RESIZE == cursorEvent) {// NW or N or NE
val attemptedSize = stage.getY - mouseEvent.getScreenY + stage.getHeight
val actSize = Math.max(minHeight, attemptedSize)
val diff = actSize - attemptedSize
stage.setHeight(actSize)
stage.setY(mouseEvent.getScreenY - diff)
} else {// SW or S or SE
val attemptedSize = mouseEventY + startY
val actSize = Math.max(minHeight, attemptedSize)
stage.setHeight(actSize)
}
}
if (Cursor.N_RESIZE != cursorEvent && Cursor.S_RESIZE != cursorEvent) {
val minWidth = Math.max(stage.getMinWidth, border * 2)
if (Cursor.NW_RESIZE == cursorEvent || Cursor.W_RESIZE == cursorEvent || Cursor.SW_RESIZE == cursorEvent) {
val attemptedSize = stage.getX - mouseEvent.getScreenX + stage.getWidth
val actSize = Math.max(minWidth, attemptedSize)
val diff = actSize - attemptedSize
stage.setWidth(actSize)
stage.setX(mouseEvent.getScreenX - diff)
} else {
val attemptedSize = mouseEventX + startX
val actSize = Math.max(minWidth, attemptedSize)
stage.setWidth(actSize)
}
}
owner.saveBounds()
}
}
}
}
source to share