Allow user to resize an undecorated Stage

后端 未结 12 1435
悲&欢浪女
悲&欢浪女 2020-12-24 02:21

I am working on making a screen recorder in JavaFX and one utility that is mandatory in the screen recorder is to let the user define how much area to record.

I m

12条回答
  •  囚心锁ツ
    2020-12-24 03:20

    Kotlin Version

    Inspired by the many answers here, I wrote my own implementation that has all the necessary features that I could think of:

    Features

    • Draggability

      • Window is dragable
      • Customizable interaction areas (top, right, bottom, left)
      • Custom cursor when hovering over a drag area
    • Fullscreen

      • Window can be set into fullscreen
      • Window restores back to its previous size and position
      • Customizable interaction areas (top, right, bottom, left)
    • Resizeable

      • Window can be resized by the window border in any direction (n, ne, e, se, s, se, w, nw)
      • Customizable interaction areas (n, e, w, s)
      • Double clicking on a window border will resize the window and take up all the available space in that direction
      • Custom cursor when hovering over a resize area

    Usage

    Quick setup:

    StageInteractor(this)
        .makeDraggable()
        .makeFullscreenable()
        .makeResizable()
    

    Custom interaction areas:

    StageInteractor(this)
    
        // custom width / height for drag areas (top, right, bottom, left)
        .makeDraggable(50, 20, 50, 20) 
    
        // custom width / height for double-clickable areas (top, right, bottom, left)
        .makeFullscreenable(50, 20, 50, 20)
    
        // custom width / height for resize areas (top, right, bottom, left)
        .makeResizable(20, 20, 20, 20)
    

    Code

    import javafx.scene.Cursor
    import javafx.scene.Scene
    import javafx.scene.input.MouseButton
    import javafx.scene.input.MouseEvent
    import javafx.stage.Screen
    import javafx.stage.Stage
    
    class StageInteractor {
        private val stage: Stage
        private val scene: Scene
    
        private var isDraggable = false
        private var isDragging = false
        private var allowDragging = true
        private var dragMarginTop = 0.0
        private var dragMarginRight = 0.0
        private var dragMarginBottom = 0.0
        private var dragMarginLeft = 0.0
    
        private var isFullscreenable = false
        private var isFullscreen = false
        private var allowFullscreen = true
        private var fullscreenMarginTop = 0.0
        private var fullscreenMarginRight = 0.0
        private var fullscreenMarginBottom = 0.0
        private var fullscreenMarginLeft = 0.0
        private var stageWidthBeforeFullscreen = 0.0
        private var stageHeightBeforeFullscreen = 0.0
        private var stageXBeforeFullscreen = 0.0
        private var stageYBeforeFullscreen = 0.0
    
        private var isResizeable = false
        private var isResizing = false
        private var allowResizing = true
        private var resizeDirection: ResizeDirection? = null
        private var resizeMarginTop = 0.0
        private var resizeMarginRight = 0.0
        private var resizeMarginBottom = 0.0
        private var resizeMarginLeft = 0.0
    
    
        constructor(stage: Stage) {
            this.stage = stage
            this.scene = stage.scene
        }
    
        fun makeDraggable(
            marginTop: Double = 50.0,
            marginRight: Double = 0.0,
            marginBottom: Double = 0.0,
            marginLeft: Double = 0.0
        ): StageInteractor {
            dragMarginTop = marginTop
            dragMarginRight = marginRight
            dragMarginBottom = marginBottom
            dragMarginLeft = marginLeft
    
            if (!isDraggable) {
                isDraggable = true
    
                var dragStartOffsetX = 0.0
                var dragStartOffsetY = 0.0
    
                scene.addEventHandler(MouseEvent.MOUSE_MOVED) {
                    val isWithinBounds = detectDraggingBounds(it)
    
                    if (isDraggable && allowDragging && isWithinBounds) {
                        scene.cursor = Cursor.OPEN_HAND
                    } else {
                        if (scene.cursor == Cursor.OPEN_HAND) {
                            scene.cursor = Cursor.DEFAULT
                        }
                    }
                }
    
                scene.addEventHandler(MouseEvent.MOUSE_PRESSED) {
                    dragStartOffsetX = stage.x - it.screenX
                    dragStartOffsetY = stage.y - it.screenY
                }
    
                scene.addEventHandler(MouseEvent.MOUSE_DRAGGED) {
                    val isWithinBounds = detectDraggingBounds(it)
    
                    if (isDraggable && allowDragging && isWithinBounds) {
                        isDragging = true
                        scene.cursor = Cursor.CLOSED_HAND
                    }
    
                    if (isDragging) {
                        stage.x = it.screenX + dragStartOffsetX
                        stage.y = it.screenY + dragStartOffsetY
                    }
                }
    
                scene.addEventHandler(MouseEvent.MOUSE_RELEASED) {
                    if (isDragging) {
                        isDragging = false
                        scene.cursor = Cursor.DEFAULT
                    }
                }
            }
    
            return this
        }
    
        private fun detectDraggingBounds(event: MouseEvent): Boolean {
            return event.sceneY <= dragMarginTop
                    || scene.height - event.sceneY <= dragMarginBottom
                    || event.sceneX <= dragMarginLeft
                    || scene.width - event.sceneX <= dragMarginRight
        }
    
        fun makeFullscreenable(
            marginTop: Double = 50.0,
            marginRight: Double = 0.0,
            marginBottom: Double = 0.0,
            marginLeft: Double = 0.0
        ): StageInteractor {
            fullscreenMarginTop = marginTop
            fullscreenMarginRight = marginRight
            fullscreenMarginBottom = marginBottom
            fullscreenMarginLeft = marginLeft
    
            if (!isFullscreenable) {
                isFullscreenable = true
    
                scene.addEventHandler(MouseEvent.MOUSE_PRESSED) {
                    val isDoubleClick = it.button == MouseButton.PRIMARY && it.clickCount >= 2
    
                    if (isFullscreenable && allowFullscreen && isDoubleClick && detectFullscreenBounds(it)) {
                        if (isFullscreen) {
                            isFullscreen = false
                            allowDragging = true
                            allowResizing = true
    
                            stage.x = stageXBeforeFullscreen
                            stage.y = stageYBeforeFullscreen
                            stage.width = stageWidthBeforeFullscreen
                            stage.height = stageHeightBeforeFullscreen
                        } else {
                            isFullscreen = true
                            allowDragging = false
                            allowResizing = false
                            stageWidthBeforeFullscreen = stage.width
                            stageHeightBeforeFullscreen = stage.height
                            stageXBeforeFullscreen = stage.x
                            stageYBeforeFullscreen = stage.y
    
                            val screenBounds = Screen.getPrimary().visualBounds
                            val newWidth = if (stage.maxWidth < screenBounds.width) {
                                stage.maxWidth
                            } else {
                                screenBounds.width
                            }
                            val newHeight = if (stage.maxHeight < screenBounds.height) {
                                stage.maxHeight
                            } else {
                                screenBounds.height
                            }
    
                            stage.width = newWidth
                            stage.height = newHeight
                            stage.x = screenBounds.minX
                            stage.y = screenBounds.minY
                        }
                    }
                }
    
            }
    
            return this
        }
    
        private fun detectFullscreenBounds(event: MouseEvent): Boolean {
            val isWithinBounds = event.sceneY <= fullscreenMarginTop
                    || scene.height - event.sceneY <= fullscreenMarginBottom
                    || event.sceneX <= fullscreenMarginLeft
                    || scene.width - event.sceneX <= fullscreenMarginRight
    
            val resizeDirection = detectResizeDirection(event)
    
            return isWithinBounds && resizeDirection == null
        }
    
        fun makeResizable(
            marginTop: Double = 10.0,
            marginRight: Double = 10.0,
            marginBottom: Double = 10.0,
            marginLeft: Double = 10.0
        ): StageInteractor {
            resizeMarginTop = marginTop
            resizeMarginRight = marginRight
            resizeMarginBottom = marginBottom
            resizeMarginLeft = marginLeft
    
            if (!isResizeable) {
                isResizeable = true
    
                scene.addEventHandler(MouseEvent.MOUSE_MOVED) {
                    if (isResizeable && allowResizing && !isResizing) {
                        when (detectResizeDirection(it)) {
                            ResizeDirection.NORTH_WEST -> scene.cursor = Cursor.NW_RESIZE
                            ResizeDirection.NORTH_EAST -> scene.cursor = Cursor.NE_RESIZE
                            ResizeDirection.SOUTH_WEST -> scene.cursor = Cursor.SW_RESIZE
                            ResizeDirection.SOUTH_EAST -> scene.cursor = Cursor.SE_RESIZE
                            ResizeDirection.NORTH -> scene.cursor = Cursor.N_RESIZE
                            ResizeDirection.SOUTH -> scene.cursor = Cursor.S_RESIZE
                            ResizeDirection.WEST -> scene.cursor = Cursor.W_RESIZE
                            ResizeDirection.EAST -> scene.cursor = Cursor.E_RESIZE
                            else -> {
                                val cursors = listOf(
                                    Cursor.NW_RESIZE,
                                    Cursor.NE_RESIZE,
                                    Cursor.SW_RESIZE,
                                    Cursor.SE_RESIZE,
                                    Cursor.N_RESIZE,
                                    Cursor.S_RESIZE,
                                    Cursor.W_RESIZE,
                                    Cursor.E_RESIZE
                                )
    
                                if (cursors.contains(scene.cursor)) {
                                    scene.cursor = Cursor.DEFAULT
                                }
                            }
                        }
                    }
                }
    
                var resizeStartFromSceneX = 0.0
                var resizeStartFromSceneY = 0.0
                var resizeStartFromScreenX = 0.0
                var resizeStartFromScreenY = 0.0
                var resizeStartStageWidth = 0.0
                var resizeStartStageHeight = 0.0
    
                scene.addEventHandler(MouseEvent.MOUSE_PRESSED) {
                    if (isResizeable && allowResizing && !isResizing) {
                        resizeDirection = detectResizeDirection(it)
    
                        if (resizeDirection != null) {
                            if (it.button == MouseButton.PRIMARY && it.clickCount >= 2) {
                                val screenBounds = Screen.getPrimary().visualBounds
    
                                if (resizeDirection == ResizeDirection.NORTH || resizeDirection == ResizeDirection.NORTH_WEST || resizeDirection == ResizeDirection.NORTH_EAST) {
                                    stage.height = ensureStageHeightIsWithinLimits(
                                        stage.height + stage.y - screenBounds.minY
                                    )
                                    stage.y = 0.0
                                }
    
                                if (resizeDirection == ResizeDirection.SOUTH || resizeDirection == ResizeDirection.SOUTH_WEST || resizeDirection == ResizeDirection.SOUTH_EAST) {
                                    stage.height = ensureStageHeightIsWithinLimits(
                                        screenBounds.height - stage.y + screenBounds.minY
                                    )
    
                                    if (stage.height == screenBounds.height) {
                                        stage.y = 0.0
                                    }
                                }
    
                                if (resizeDirection == ResizeDirection.WEST || resizeDirection == ResizeDirection.NORTH_WEST || resizeDirection == ResizeDirection.SOUTH_WEST) {
                                    stage.width = ensureStageWidthIsWithinLimits(
                                        stage.width + stage.x
                                    )
                                    stage.x = 0.0
                                }
    
                                if (resizeDirection == ResizeDirection.EAST || resizeDirection == ResizeDirection.NORTH_EAST || resizeDirection == ResizeDirection.SOUTH_EAST) {
                                    stage.width = ensureStageWidthIsWithinLimits(
                                        screenBounds.width - stage.x
                                    )
    
                                    if (stage.width == screenBounds.width) {
                                        stage.x = 0.0
                                    }
                                }
                            } else {
                                isResizing = true
                                isDraggable = false
                                isFullscreenable = false
    
                                resizeStartFromScreenX = it.screenX
                                resizeStartFromScreenY = it.screenY
                                resizeStartFromSceneX = it.sceneX
                                resizeStartFromSceneY = it.sceneY
                                resizeStartStageWidth = stage.width
                                resizeStartStageHeight = stage.height
                            }
                        }
                    }
                }
    
                scene.addEventHandler(MouseEvent.MOUSE_DRAGGED) {
                    if (isResizing) {
                        if (resizeDirection == ResizeDirection.NORTH || resizeDirection == ResizeDirection.NORTH_WEST || resizeDirection == ResizeDirection.NORTH_EAST) {
                            val newHeight = ensureStageHeightIsWithinLimits(
                                resizeStartStageHeight + (resizeStartFromScreenY - it.screenY)
                            )
                            val newY = when (newHeight) {
                                stage.maxHeight, stage.minHeight -> stage.y
                                else -> it.screenY - resizeStartFromSceneY
                            }
    
                            stage.height = newHeight
                            stage.y = newY
                        }
    
                        if (resizeDirection == ResizeDirection.SOUTH || resizeDirection == ResizeDirection.SOUTH_WEST || resizeDirection == ResizeDirection.SOUTH_EAST) {
                            val newHeight = ensureStageHeightIsWithinLimits(
                                resizeStartStageHeight + (it.screenY - resizeStartFromScreenY)
                            )
    
                            stage.height = newHeight
                        }
    
                        if (resizeDirection == ResizeDirection.WEST || resizeDirection == ResizeDirection.NORTH_WEST || resizeDirection == ResizeDirection.SOUTH_WEST) {
                            val newWidth = ensureStageWidthIsWithinLimits(
                                resizeStartStageWidth + (resizeStartFromScreenX - it.screenX)
                            )
                            val newX = when (newWidth) {
                                stage.maxWidth, stage.minWidth -> stage.x
                                else -> it.screenX - resizeStartFromSceneX
                            }
    
                            stage.width = newWidth
                            stage.x = newX
                        }
    
                        if (resizeDirection == ResizeDirection.EAST || resizeDirection == ResizeDirection.NORTH_EAST || resizeDirection == ResizeDirection.SOUTH_EAST) {
                            val newWidth = ensureStageWidthIsWithinLimits(
                                resizeStartStageWidth + (it.screenX - resizeStartFromScreenX)
                            )
    
                            stage.width = newWidth
                        }
                    }
                }
    
                scene.addEventHandler(MouseEvent.MOUSE_RELEASED) {
                    if (isResizing) {
                        isResizing = false
                        isDraggable = true
                        isFullscreenable = true
                    }
                }
            }
    
            return this
        }
    
        private fun detectResizeDirection(event: MouseEvent): ResizeDirection? {
            val isNorthResize = event.sceneY <= resizeMarginTop
            val isSouthResize = scene.height - event.sceneY <= resizeMarginBottom
            val isWestResize = event.sceneX <= resizeMarginLeft
            val isEastResize = scene.width - event.sceneX <= resizeMarginRight
            val isNorthWestResize = isNorthResize && isWestResize
            val isNorthEastResize = isNorthResize && isEastResize
            val isSouthWestResize = isSouthResize && isWestResize
            val isSouthEastResize = isSouthResize && isEastResize
    
            return when {
                isNorthWestResize -> ResizeDirection.NORTH_WEST
                isNorthEastResize -> ResizeDirection.NORTH_EAST
                isSouthWestResize -> ResizeDirection.SOUTH_WEST
                isSouthEastResize -> ResizeDirection.SOUTH_EAST
                isNorthResize -> ResizeDirection.NORTH
                isSouthResize -> ResizeDirection.SOUTH
                isWestResize -> ResizeDirection.WEST
                isEastResize -> ResizeDirection.EAST
                else -> null
            }
        }
    
        private fun ensureStageWidthIsWithinLimits(width: Double): Double {
            val screenBounds = Screen.getPrimary().visualBounds
    
            return when {
                width > stage.maxWidth -> stage.maxWidth
                width < stage.minWidth -> stage.minWidth
                width > screenBounds.width -> screenBounds.width
                else -> width
            }
        }
    
        private fun ensureStageHeightIsWithinLimits(height: Double): Double {
            val screenBounds = Screen.getPrimary().visualBounds
    
            return when {
                height > stage.maxHeight -> stage.maxHeight
                height < stage.minHeight -> stage.minHeight
                height > screenBounds.height -> screenBounds.height
                else -> height
            }
        }
    
        enum class ResizeDirection {
            NORTH, NORTH_EAST, NORTH_WEST,
            SOUTH, SOUTH_EAST, SOUTH_WEST,
            EAST, WEST;
        }
    }
    

提交回复
热议问题