0

I want to create a button that can be dragged around by the user, and when released will return to its original position.

During the movement the button must be animated following some easing curve.

What I have until now is this:

import QtQuick 2.2
import QtGraphicalEffects 1.0

Item {
    property bool wasDragged: false
    property real basePosX: (menuButton.width / 2) - (menuButtonIcon.width / 2) //menuButton.horizontalCenter
    property real basePosY: (menuButton.height / 2) - (menuButtonIcon.height / 2) //menuButton.verticalCenter

    id: menuButton
    x: parent.width - 55
    y: parent.height - 60
    state: "Closed"

    Rectangle {
        id: menuButtonIcon
        x: basePosX
        y: basePosY
        color: "#D23F31"
        width: 65;
        height: 65;
        radius: width * 0.5
        antialiasing: true
        smooth: true
    }

    Rectangle {
        id: menuButtonIconBar1
        anchors.centerIn: menuButtonIcon
        width: 17
        height: 3
        antialiasing: true
    }

    Rectangle {
        id: menuButtonIconBar2
        anchors.centerIn: menuButtonIcon
        width: 17
        height: 3
        antialiasing: true
        rotation: 90
    }

    MouseArea {
        id: mouseArea
        anchors.fill: menuButtonIcon
        onPressed: menuButton.state = menuButton.state === "Open" ? "Closed" : "Open"
        onReleased: {
            if (wasDragged) {
                wasDragged = false
                menuButton.state = "Closed"
            }
        }

        onPositionChanged:
        {
            wasDragged = true
            var currentPosition = mapToItem(parent, mouse.x, mouse.y)
            updateButtonPosition(currentPosition.x, currentPosition.y)
        }
    }

    function updateButtonPosition(mouseX, mouseY)
    {
        console.log("pos change")
        menuButtonIcon.x = mouseX - (menuButtonIcon.width / 2);
        menuButtonIcon.y = mouseY - (menuButtonIcon.height / 2);
    }

    states: [
        State {
            name: "Closed"
            PropertyChanges { target: menuButtonIcon; x: basePosX; y: basePosY}
        },
        State {
            name: "Open"
            PropertyChanges { target: menuButtonIconBar1; rotation: 135;}
            PropertyChanges { target: menuButtonIconBar2; rotation: 225;}
            PropertyChanges { target: menuButtonIcon; x: basePosX; y: basePosY}
        }
    ]

    transitions: [
        Transition {
            SpringAnimation { target: menuButtonIcon; properties: "x, y"; spring: 3; damping: 0.25; epsilon: 0.2; mass: 1; modulus: 0; velocity: 0 }
            SpringAnimation { target: menuButtonIconBar1; properties: "rotation, scale"; spring: 3; damping: 0.25; epsilon: 0.5; mass: 1; modulus: 0; velocity: 0 }
            SpringAnimation { target: menuButtonIconBar2; properties: "rotation, scale"; spring: 3; damping: 0.25; epsilon: 0.5; mass: 1; modulus: 0; velocity: 0 }
        }
    ]
}

But the issue with this is that if you drag it and hold still before the rotation animation ends, the button will return to its original position, even though you never released it.

If you remove the two Rectangles forming the cross, as well as their animations, then it works as intended.

Why is the button returning to its original positions by itself, and how can that be fixed?

BaCaRoZzo
  • 7,502
  • 6
  • 51
  • 82
ealiaj
  • 1,525
  • 1
  • 15
  • 25
  • You can approach the problem in a different way if you use [`drag`](http://doc.qt.io/qt-5/qml-qtquick-mousearea.html#drag-prop) property. See [this](http://stackoverflow.com/a/30991733/2538363) example. – BaCaRoZzo Jul 07 '15 at 22:53
  • I do know about the drag property, and tested it. It works fine but it will not do for me, because I need to directly have control over the buttons position, and not simply have it hollow the mouse. Besides I really want to know why my approach is not working. I believe this is a good chance to learn something new. – ealiaj Jul 08 '15 at 08:28
  • I had just the opportunity to skim your code so it's hard to say why is it not working. I agree that it would be interesting to check out and I hope someone is going to provide an answer. :) That said, I think `drag` gives you control over the position by the means of the `MouseArea` position. – BaCaRoZzo Jul 08 '15 at 09:30
  • 1
    @BaCaRoZzo even in the example you provided though, if tested in Qt5.3 it will not work as intended, behaving almost like I described above. Trying it with the latest version of Qt seems to be fine, unless you drag a rectangle very fast and release it almost immediately. Then again that rectangle starts behaving oddly. So there is a slight chance that this is not a problem with my code. – ealiaj Jul 08 '15 at 11:27
  • 1
    Bugs appears here and there and quick is not perfect, at all. Hence, it could easily be a bug. – BaCaRoZzo Jul 08 '15 at 12:42

1 Answers1

1

try this:

import QtQuick 2.0
import QtGraphicalEffects 1.0

Item {
    property bool wasDragged: false
    property real basePosX: (menuButton.width / 2) - (menuButtonIcon.width / 2)
    property real basePosY: (menuButton.height / 2) - (menuButtonIcon.height / 2)
    property real currentPosX: (menuButton.width / 2) - (menuButtonIcon.width / 2)
    property real currentPosY: (menuButton.height / 2) - (menuButtonIcon.height / 2)

    id: menuButton
    x: parent.width - 55
    y: parent.height - 60
    state: "Closed"

    Rectangle {
        id: menuButtonIcon
        x: basePosX
        y: basePosY
        color: "#D23F31"
        width: 65;
        height: 65;
        radius: width * 0.5
        antialiasing: true
        smooth: true
    }

    Rectangle {
        id: menuButtonIconBar1
        anchors.centerIn: menuButtonIcon
        width: 17
        height: 3
        antialiasing: true
    }

    Rectangle {
        id: menuButtonIconBar2
        anchors.centerIn: menuButtonIcon
        width: 17
        height: 3
        antialiasing: true
        rotation: 90
    }

    MouseArea {
        id: mouseArea
        anchors.fill: menuButtonIcon
        onPressed: menuButton.state = menuButton.state === "Open" ? "Closed" : "Open"
        onReleased: {
            if (wasDragged) {
                wasDragged = false
                menuButton.state = "Closed"
            }
        }

        onPositionChanged:
        {
            wasDragged = true
            var currentPosition = mapToItem(parent, mouse.x, mouse.y)
            updateButtonPosition(currentPosition.x, currentPosition.y)
        }
    }

    function updateButtonPosition(mouseX, mouseY)
    {
        console.log("pos change")
        menuButtonIcon.x = mouseX - (menuButtonIcon.width / 2);
        menuButtonIcon.y = mouseY - (menuButtonIcon.height / 2);
        currentPosX = mouseX - (menuButtonIcon.width / 2);
        currentPosY = mouseY - (menuButtonIcon.height / 2);
    }

    states: [
        State {
            name: "Closed"
            PropertyChanges { target: menuButtonIcon; x: currentPosX; y: currentPosY}
        },
        State {
            name: "Open"
            PropertyChanges { target: menuButtonIconBar1; rotation: 135;}
            PropertyChanges { target: menuButtonIconBar2; rotation: 225;}
            PropertyChanges { target: menuButtonIcon; x: currentPosX; y: currentPosY}
        }
    ]

    onStateChanged: if(state === "Closed"){
                        currentPosX = basePosX
                        currentPosY = basePosY
                    }

    transitions: [
        Transition {
            SpringAnimation {
                target: menuButtonIcon;
                properties: "x, y";
                spring: 3;
                damping: 0.25;
                epsilon: 0.2;
                mass: 1;
                modulus: 0;
                velocity: 0
            }
            SpringAnimation {
                id:tr1;
                target: menuButtonIconBar1;
                properties: "rotation, scale";
                spring: 3;
                damping: 0.25;
                epsilon: 0.5;
                mass: 1;
                modulus: 0;
                velocity: 0
            }
            SpringAnimation {
                id:tr2;
                target: menuButtonIconBar2;
                properties: "rotation, scale";
                spring: 3;
                damping: 0.25;
                epsilon: 0.5;
                mass: 1;
                modulus: 0;
                velocity: 0
            }
        }
    ]
}

in the state open you should do a propertyChanges of x and y on the current position, the bind on the base position cause of the reset of x and y. you'll need to other properties that take the current x and the current y. with that 90% of the issue is solved, you'll see a small decalage for about 1 s and the button will return to the good place. to solve it I putted the property change in the close state , the x and y change to the currentPosition and added a on StateChanged slot within it I reset the position. I dont know the root cause of this strange behavior. but with that workaround it's working fine. regards

Mido
  • 1,092
  • 1
  • 9
  • 14
  • Even though it solves the button returning to its default position if the drag had ended before the rotation animation ended, it introduces a new one. Now if you release the button very fast (before the rotation animation has ended) then it will not return to its default position using the spring animation. Still this is a good solution. – ealiaj Jul 21 '15 at 14:16