본문 바로가기
JavaFX

javafx node translate(노드의 drag 창 이동)

by 루에 2020. 6. 24.
반응형

작업하다보니 javafx의 drag 이벤트 대응 수준이 안드로이드 2.x급인지라... 실무에서 사용하려면 여러가지 고려해야될 것들이 많아 못쓸 것 같지만, 기록으로 남긴다.

 

아래 예제는 stage안에 popup으로 띄우는 작은 툴팁에 적용한 것이다.

root에 setOnMousePressed 이벤트에 클릭한 현재 좌표(screen.x, screen.y)를 저장하고

setOnMouseDragged 이벤트에 translateX로 마우스의 이동 좌표값을 따라간다.

 

마지막에 오프셋을 갱신하는 이유는, translate가 현재 위치에서의 +- 값으로 이동 좌표가 결정되기 때문에 갱신하지 않으면 it.scrrenX - offsetX의 값이 x^n 이 되어 값이 너무 커져 순간이동이 발생하기 때문이다.

 

다만, 적용해보니 아래 정도로는 안되는게 가령 owner가 되는 아래 창의 범위를 넘어가면 컴포넌트가 아래 가려 사라진 것처럼 보일 수도 있으므로 범위를 나가지 않게도 해야하며(터치 스크린에서는 상관 없을 거다) 기타 여러가지 설정이나 정책이 필요하다.

override val root: Parent by fxml2("/fxml/popup/CommonInformationImagePopup.fxml")

    private val infoImagePopup: AnchorPane by fxid()
    private val close: Button by fxid()
    private val img: ImageView by fxid()
    private var offsetX = 0.0
    private var offsetY = 0.0
    init {
        val targetImage = ImageCache.loadImage(imgPath)

        with(infoImagePopup) {
            this.prefWidth = popupWidth.width
        }
        with(root) {
            prefWidth(targetImage.width)
            prefHeight(targetImage.height)
            AnchorPane.setLeftAnchor(this, xLocation.toDouble())
            AnchorPane.setTopAnchor(this, yLocation.toDouble())

            setOnMousePressed {
                    offsetX = it.screenX
                    offsetY = it.screenY
            }
            setOnMouseDragged {
                translateX += (it.screenX - offsetX)
                translateY += (it.screenY - offsetY)
                offsetX = it.screenX
                offsetY = it.screenY
            }
        }

 

6/26

 

화면 밖으로 포인트가 이동되는 것을 막았다.

어차피 프로그램에 적용할 것 같진 않고... 이 쯤에서 멈출지도, 고도화할지도 모르겠다.

 

우선 코드는 아래와 같다.


	private var offsetX = 0.0
    private var offsetY = 0.0
    val limitLeft = -xLocation.toDouble()
    val limitTop = -yLocation.toDouble()
    val limitRight = 355.0
    val limitBottom = 315.0
    init {
        val targetImage = ImageCache.loadImage(imgPath)

        with(infoImagePopup) {
            this.prefWidth = popupWidth.width
        }
        with(root) {
            prefWidth(targetImage.width)
            prefHeight(targetImage.height)
            AnchorPane.setLeftAnchor(this, xLocation.toDouble())
            AnchorPane.setTopAnchor(this, yLocation.toDouble())

            setOnMousePressed {
                    offsetX = it.screenX
                    offsetY = it.screenY
            }
            setOnMouseDragged {
                println("setOnMouseDragged")

                println("infoImagePopup.width ${infoImagePopup.width} infoImagePopup.height ${infoImagePopup.height}")
                println("translateX $translateX translateY $translateY")
                println("offsetx ${offsetX} offsetY $offsetY")
                println("xLocation ${xLocation}, scene.x ${scene.x}, it.sceneX ${it.sceneX} it.screenX ${it.screenX} layoutX ${layoutX}, windowX ${scene.window.x} ${scene.window.width}")
                println("yLocation ${yLocation}, scene.y ${scene.y}, it.sceneY ${it.sceneY} it.screenX ${it.screenX} layoutY ${layoutY}, windowY ${scene.window.y} ${scene.window.height}")


                if(translateX in limitLeft..limitRight  // 화면 안에서 팝업을 움직임
                    || (translateX < limitLeft && (it.screenX - offsetX) > 0)   // 왼쪽 바깥으로 빠져나간 상태에서 오른쪽으로 움직임
                    || (translateX > limitRight && (it.screenX - offsetX) < 0)  // 오른쪽 바깥으로 빠져나간 상태에서 왼쪽으로 움직임
                ) {
                    translateX += (it.screenX - offsetX)
                    offsetX = it.screenX
                }
                if(translateY in limitTop..limitBottom  // 화면 안에서 팝업을 움직임
                    || (translateY < limitTop && (it.screenY - offsetY) > 0)    // 위쪽 바깥으로 빠져나간 상태에서 아래로 움직임
                    || (translateY > limitBottom && (it.screenY - offsetY) < 0) // 아래쪽 바깥으로 빠져나간 상태에서 위로 움직임
                ) {
                    translateY += (it.screenY - offsetY)
                    offsetY = it.screenY
                }
            }

            setOnMouseReleased {
                println("released")
                if(translateX < limitLeft) {
                    translateX = limitLeft
                }
                if(translateX > limitRight) {
                    translateX = limitRight
                }
                if(translateY < limitTop) {
                    translateY = limitTop
                }
                if(translateY > limitBottom) {
                    translateY = limitBottom
                }
            }
        }

상하좌우에 대한 offset에 대응하는 제한값을 설정했다. right, bottom은 임의의 상수값이 들어가 있는데 내 경우는 아래 stage가 될 놈에 대한 정보를 얻을 수 없는 곳에 띄우는 팝업으로 코드를 작성하다보니 저렇게 상수를 넣은 것이고, 보통은 stage의 값을 참조하여 동적으로 값을 설정해야 할 것이다.(어차피 시작점이 0,0 이니 아닐수도 있고...)

 

화면 밖으로 마우스 포인터가 벗어나있는 경우는 exception상황으로 가정하고 translate하지 않고 offset도 갱신하지 않는다. 하지만 마우스가 클릭한 채로 다시 화면 안으로 들어왔을 경우가 있기 때문에 해당 케이스에 대응하기 위해 3개의 if 조건으로 translate를 하도록 설정하며, x이동과 y이동에 대응하여 같은 맥락의 조건으로 두 개를 설정한다.

 

setOnMouseDragExit , setOnMouseDragRelease 등의 리스너가 존재하나 이 경우는 동작에 해당하지 않아 사용할 수가 없다. 대신 MouseRelease 리스너를 이용해 빠른 이동으로 팝업이 사각범위에서 벗어나있을 경우 제한선으로 이동시켜준다. 마치 스프링같은 효과가 난다.

반응형

댓글