comp1201
comp1201

Reputation: 415

How to implement "Snap While Dragging" behavior in QML?

I'm looking to achieve a "snap while dragging" behavior in QML. Specifically, within my example below, when the element being dragged (red column) reaches a location on the x axis that is inside the "snap area" (orange column), it snaps to the middle of the snap area and sticks there until the mouse pointer is again outside the snap area, at which point the dragged element should snap to the current position of the mouse pointer and the drag continues on it's way. Here is what I have so far:

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: main_window
    width: 640
    height: 480
    visible: true
    visibility: Window.Maximized

    property double x_location: square.x
    property double y_location: square.y

    property double start_x_of_snap_area: snap_area.x
    property double end_x_of_snap_area: snap_area.x + snap_area.width

    property bool is_handlebar_inside_snap_area: false

    onX_locationChanged: function () {
        if (x_location >= start_x_of_snap_area
                && square.x + square.width <= end_x_of_snap_area) {
            is_handlebar_inside_snap_area = true
        } else {
            is_handlebar_inside_snap_area = false
        }
    }

    onIs_handlebar_inside_snap_areaChanged: function () {
        if (is_handlebar_inside_snap_area === true) {
            console.log(dragArea.drag.target)
            dragArea.drag.target = null
            square.anchors.horizontalCenter = snap_area.horizontalCenter
        }
    }

    Text {
        y: 0
        text: "start_x_of_snap_area: " + start_x_of_snap_area
        font.pixelSize: 30
    }

    Text {
        y: 50
        text: "end_x_of_snap_area: " + end_x_of_snap_area
        font.pixelSize: 30
    }

    Text {
        y: 100
        text: "is_handlebar_inside_snap_area: " + is_handlebar_inside_snap_area
        font.pixelSize: 30
    }

    Rectangle {
        id: snap_area
        height: parent.height
        width: 200
        anchors.centerIn: parent
        color: 'orange'

        Text {
            text: "snap area"
            font.pixelSize: 30
            anchors.centerIn: parent
        }
    }

    Rectangle {
        id: square
        width: 50
        height: main_window.height
        color: 'red'
        opacity: .7
        Drag.active: dragArea.drag.active

        Text {
            text: 'x: ' + x_location + ' y: ' + y_location
            font.pixelSize: 10
            color: 'white'
            anchors.centerIn: parent
            font.family: 'Arial'
        }

        MouseArea {
            id: dragArea
            drag {
                target: parent
                axis: "XAxis"
                minimumX: 0
                maximumX: main_window.width - square.width
            }
            anchors.fill: parent
            cursorShape: Qt.SizeAllCursor
        }
    }
}

Some problems with the above code is once the snap happens, its stuck. Also, if you move the mouse pointer really fast over the snap area the function doesn't fire quick enough to give satisfactory results (it doesn't work at all).

Upvotes: 0

Views: 919

Answers (1)

Muhammet Ali Asan
Muhammet Ali Asan

Reputation: 1514

When you enter snap area you are deactivating mouse drag so it is stuck to there. In order to show a prospective location and actual moving object you should have at least one place holder. You can simply bind is_handlebar_inside_snap_area to x location. Here the solution should work for your case.

import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: main_window
    width: 640
    height: 480
    visible: true

    property int end_x_of_snap_area: snap_area.x + snap_area.width
    property bool is_handlebar_inside_snap_area: square.x >= snap_area.x &&
                                                 square.x+square.width <= end_x_of_snap_area
    property bool is_dragging: dragArea.drag.active

    Rectangle {
        id: snap_area
        height: parent.height
        width: 200
        anchors.centerIn: parent
        color: 'orange'
    }

    Rectangle {
        id: square
        width: 50
        height: main_window.height
        color: 'red'
        opacity: is_handlebar_inside_snap_area && is_dragging ? 0.5 : 0.9
        Drag.active: dragArea.drag.active

        MouseArea {
            id: dragArea
            drag {
                target: parent
                axis: "XAxis"
                minimumX: 0
                maximumX: main_window.width - square.width
            }
            anchors.fill: parent
            cursorShape: Qt.SizeAllCursor
            onReleased:
            {
                if (is_handlebar_inside_snap_area)
                {
                    square.x = placeHolderRect.x
                }
            }
        }
    }

    Rectangle
    {
        id:placeHolderRect
        color: square.color
        width: square.width
        height: square.height
        opacity: 0.3
        visible: is_handlebar_inside_snap_area
        anchors.horizontalCenter: snap_area.horizontalCenter
    }
}

Upvotes: 2

Related Questions