Home > database >  How to make an Item draggable without flicker when parent's position changes during drag
How to make an Item draggable without flicker when parent's position changes during drag

Time:06-07

I'm trying to make an item that can be resized by its edges.

For showing a minimal testcase of the problem it is enough to have its left edge draggable, so here it is:

Rectangle {
    id: root
    border.width: 1
    border.color: 'black'
    color: 'red'

    // save original position and size at drag start
    property real origX: 0
    property real origWidth: 0

    // drag this item:
    Item {
        id: dragDummy
        x: 0
        onXChanged: {
            root.x = root.origX   x
            root.width = root.origWidth - x
        }
    }

    MouseArea {
        anchors.fill: root
        drag.target: dragDummy
        drag.axis: Drag.XAxis
        drag.onActiveChanged: {
            // onDragStarted -> Cannot assign to non-existent property "onDragStarted" ???
            if(!active) return
            root.origX = root.x
            root.origWidth = root.width
        }
    }
}

the problem seems to be that if drag causes parent position to change, that triggers another drag event, causing this flicker:

screenshot

I'm guessing MouseArea can't help here? Then low level mouse events should be used like in "old-school" apps (i.e. capturing events at root Item, manually compute offset with respect to initial mouse down position, etc...)?

(or I have to move the MouseArea to an ancestor that won't move during drag, which is almost the same...)

CodePudding user response:

The solution I come up with consists of having two MouseAreas:

  • a MouseArea moves with the item to drag, that is used only for hit-testing, so its onPressed handler is something like this:
    onPressed: (mouse) => {
        mouse.accepted = false
        root.container.myDragTarget = root
    }
    onReleased: root.container.myDragTarget = null
  • another MouseArea, stacked below the others and not moving, handles the mouse position change and the dragging:
    onPressed: _start = Qt.point(mouseX, mouseY)
    onPositionChanged: {
        if(myDragTarget) {
            var delta = Qt.point(mouseX - _start.x, mouseY - _start.y)
            // do any rounding/snapping of delta here...
            _start.x  = delta.x
            _start.y  = delta.y
            myDragTarget.x  = delta.x
            myDragTarget.y  = delta.y
        }
    }

This is able to drag the item reliably.

This is also what I wanted to avoid, because it reinvents mouse drag, but in absence of a better solution it is what I am going to use.

I won't accept this answer as I'm curious to see other ways to approach this problem.

CodePudding user response:

There is a nice QML Item type called DragHandler which people often overlook, but I find that it works very well.

This solution is a little more idiomatic than other suggestions in that it uses a declarative style rather than imperative:

import QtQuick 2.15

Item {
    id: root
    width: 500
    height: 100
    Item {
        height: 100
        width: handle.x   handle.width / 2
    }
    Rectangle {
        x: handle.x   handle.width / 2
        width: root.width - (handle.x - handle.width/2)
        height: 100
        border{
            width: 1
            color: 'black'
        }
        color: 'red'
    }
    Item {
        id: handle
        x: -width / 2
        width: 50
        height: 100
        DragHandler {
            yAxis.enabled: false
            xAxis{
                minimum: -handle.width
                maximum: root.width
            }
        }
    }
}
  • Related