3

I decided to rewrite this question. I tried to make it clear and short but it didn't work out.

What I am trying to achieve is an object I can put on anything (Rectangle, Image and so on) and make it respond to touch gestures. Unfortunately I ran into a problem I cannot solve. I couldn't find help so I try here.

Here is simple main.qml:

import QtQuick 2.5
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2

ApplicationWindow {
    title: qsTr("Touch surface test")
    width: 640
    height: 480
    visible: true


    Rectangle {
        width: 200
        height: 60
        color: "blue"

        MyTouchSurface {
            id: myTouchSurface
            anchors.fill: parent
        }
    }

}

and here is MyTouchSurface.qml

import QtQuick 2.5

MultiPointTouchArea {
    id: myTouchSurface

    // this is object I want to respond to touch gestures
    property Item target: parent

    // this is object I want to keep target in. I won't let target get out of it
    property Item container: parent.parent

    property double targetX: target.x
    property double targetY: target.y
    property double centerX: target.width / 2
    property double centerY: target.height / 2
    property double lastTouchX
    property double lastTouchY
    property double lastXDrag
    property double lastYDrag

    // here I calculate received touches and move target
    onPressed: {
        lastTouchX = touchPoints[0].sceneX
        lastTouchY = touchPoints[0].sceneY
        if (slidingAnimation.running)
            slidingAnimation.stop()
    }
    onTouchUpdated: {
        if (touchPoints.length) {
            target.x += touchPoints[0].sceneX - lastTouchX
            target.y += touchPoints[0].sceneY - lastTouchY
            lastXDrag = touchPoints[0].sceneX - lastTouchX
            lastYDrag = touchPoints[0].sceneY - lastTouchY
            lastTouchX = touchPoints[0].sceneX
            lastTouchY = touchPoints[0].sceneY
        }
        else
            startSliding()
    }

    // when lifting fingers off screen I want to slide target across it's container
    function startSliding() {
        slidingAnimation.toX = target.x + lastXDrag * 10
        slidingAnimation.toY = target.y + lastYDrag * 10
        slidingAnimation.restart()
    }

    // This is animation responsible for sliding the target
    ParallelAnimation {
        id: slidingAnimation
        property double toX
        property double toY
        property int animationDuration: 250
        NumberAnimation {
            target: myTouchSurface.target
            property: "x"
            to: slidingAnimation.toX
            duration: slidingAnimation.animationDuration
            easing.type: Easing.OutQuad
        }
        NumberAnimation {
            target: myTouchSurface.target
            property: "y"
            to: slidingAnimation.toY
            duration: slidingAnimation.animationDuration
            easing.type: Easing.OutQuad
        }
    }

    // this is how I keep target object within container
    onTargetXChanged: {
        if (container != null) {
            if (target.x + centerX < 0)
                target.x = -centerX
            else if (target.x + centerX > container.width)
                target.x = container.width - centerX
        }
    }
    onTargetYChanged: {
        if (container != null) {
            if (target.y + centerY < 0)
                target.y = -centerY
            else if (target.y + centerY > container.height)
                target.y = container.height - centerY
        }
    }
}

I want to have all calculating and functions within MyTouchSurface so it is easy to apply on other object and to use it in another project.

The problem I have is I don't know how to prevent target object from moving out of container. Well, the code here is working very nice but it also generates very ugly errors about binding loop.

What is going on is:

  • I check target's x and y when they changes
  • If the x or y is out of specified area I move them back
  • I receive signal "x changed" or "y changed" again
  • I receive error about loop because my function caused itself to execute again

So I am asking: Is there any other simple way to prevent object from flying off screen and not receive binding loop errors at the same time?

I know I can use Timer to check target's x and y from time to time, and bring them back.

I know I can change the easing and duration of the animation so it stops on the container bounds itself but looks like it was stopped.

Seriously. Is there no simple way?


Things that won't work:

  • stopping animation after object was detected outside container (if it would move really fast it will stop out of the screen )
  • stopping animation and then changing target's x or y

Thanks everyone helping me so far. It is nice to know I am not alone here :)

scopchanov
  • 7,966
  • 10
  • 40
  • 68
Filip Hazubski
  • 1,668
  • 1
  • 17
  • 33
  • Just ignoring the warnings is probably the worst decision. Binding loops give you a hint that you're doing something wrong and you should further investigate what the qml engine is complaining about. Your goal should be to design your qml files properly, meaning: change the way how you transform the item's position based on drag interaction. In order to help you, we need some more than those few lines of code. – qCring Jul 28 '15 at 08:35
  • @qCring I attached simple example which you can copy-paste to reproduce my problem. – Filip Hazubski Jul 28 '15 at 08:53
  • You do have a binding loop. Fix it. Let me paraphrase your question: "I know I have a problem, but I don't want to fix it". If so, what's your question? (No, the warnings can't be removed, for a good reason - QML is protecting your feet from yourself.) – Kuba hasn't forgotten Monica Jul 28 '15 at 12:21
  • @KubaOber I just don't get it. The algorythm goes: If the variable changes check if it stays between wanted scope, if not - change it. Changing it causes the same if operation to fire again. Then the warning appears. How do I fix that? That is my question. If it is a loop then fine, whatever, but I don't really see any simple way to make it differently. If we talk about removing warnings - look at the example code. The same operation in one place causes error and in another not. For me everything is not that obvious. – Filip Hazubski Jul 28 '15 at 12:36

3 Answers3

2

I think your problem is that you update either targetX or target.x (resp. for y) inside of the property update handler (onTargetXChanged()).

This might be picked up as a biding loop by the QML engine as if you change target.x from onTargetXChanged() the signal onTargetXChanged() will trigger again as it's bound to it in property double targetX: target.x.

You can try something like this:

TouchArea {
  property Item target  //  target is the Image object being moved
  property double targetX: target.x
  property double targetY: target.y

  onTargetXChanged: {
    /* if object is out of specified area move x respectively */
    performPropertyUpdate(args)
  }
  onTargetYChanged: {
    /* if object is out of specified area move y respectively */
    performPropertyUpdate(args)
  }

  function performPropertyUpdate(args) {
    /* actual setting of target.x or target.y */
  }
}
BaCaRoZzo
  • 7,502
  • 6
  • 51
  • 82
ramses728
  • 323
  • 2
  • 12
  • Thak you. Unfortunately this does not solve the problem. – Filip Hazubski Jul 28 '15 at 09:00
  • Have you tried making `property double myX: x` to `property alias myX: x` or just use `onXChanged` if avilable? – ramses728 Jul 28 '15 at 09:38
  • I can't use `onXChanged` unfortunately. Trying to use alias gives this error: `Invalid alias reference. Unable to find id "target"`. `target` is only reference to object from another file. In my file there is a `MultiPointTouchArea` and nothing else. Then I apply object from this file on another object. – Filip Hazubski Jul 28 '15 at 09:44
  • @Mertanian Invalid alias reference, cause you'll need to add the id of the object having the property target, beore target, example instance.target. see my answer – Mido Jul 28 '15 at 18:10
2

The solution is simply use Connections:

import QtQuick 2.5

MultiPointTouchArea {
    id: myTouchSurface

    // this is object I want to respond to touch gestures
    property Item target: parent

    // this is object I want to keep target in. I won't let target get out of it
    property Item container: parent.parent

    property double targetX: target.x
    property double targetY: target.y
    property double centerX: target.width / 2
    property double centerY: target.height / 2
    property double lastTouchX
    property double lastTouchY
    property double lastXDrag
    property double lastYDrag

    // here I calculate received touches and move target
    onPressed: {
        lastTouchX = touchPoints[0].sceneX
        lastTouchY = touchPoints[0].sceneY
        if (slidingAnimation.running)
            slidingAnimation.stop()
    }
    onTouchUpdated: {
        if (touchPoints.length) {
            target.x += touchPoints[0].sceneX - lastTouchX
            target.y += touchPoints[0].sceneY - lastTouchY
            lastXDrag = touchPoints[0].sceneX - lastTouchX
            lastYDrag = touchPoints[0].sceneY - lastTouchY
            lastTouchX = touchPoints[0].sceneX
            lastTouchY = touchPoints[0].sceneY
        }
        else
            startSliding()
    }

    // when lifting fingers off screen I want to slide target across it's container
    function startSliding() {
        slidingAnimation.toX = target.x + lastXDrag * 10
        slidingAnimation.toY = target.y + lastYDrag * 10
        slidingAnimation.restart()
    }

    // This is animation responsible for sliding the target
    ParallelAnimation {
        id: slidingAnimation
        property double toX
        property double toY
        property int animationDuration: 250
        NumberAnimation {
            target: myTouchSurface.target
            property: "x"
            to: slidingAnimation.toX
            duration: slidingAnimation.animationDuration
            easing.type: Easing.OutQuad
        }
        NumberAnimation {
            target: myTouchSurface.target
            property: "y"
            to: slidingAnimation.toY
            duration: slidingAnimation.animationDuration
            easing.type: Easing.OutQuad
        }
    }

    Connections{
        target:myTouchSurface.target
        onXChanged:{
            if (container != null) {
                if (target.x + centerX < 0)
                    target.x = -centerX
                else if (target.x + centerX > container.width)
                    target.x = container.width - centerX
            }
        }

        onYChanged:{
            if (container != null) {
                if (target.y + centerY < 0)
                    target.y = -centerY
                else if (target.y + centerY > container.height)
                    target.y = container.height - centerY
            }
        }
    }

    // this is how I keep target object within container
//    onTargetXChanged: {

//    }
//    onTargetYChanged: {

//    }
}
BaCaRoZzo
  • 7,502
  • 6
  • 51
  • 82
Mido
  • 1,092
  • 1
  • 9
  • 14
1

To solve the issue myX should be an alias of x,

property alias myX: rectangle2.x

You're getting this problem cause myX is changing the value of x and x is changing the value of myX: a loop.

BaCaRoZzo
  • 7,502
  • 6
  • 51
  • 82
Mido
  • 1,092
  • 1
  • 9
  • 14
  • Sorry. Perhaps I messed up things a little. The sample code is to show an error. In real situation the touch area is in a seperate file and I don't have an ID of a target I want to move across screen. Therefore I can't use alias. Soon I will modify my example code to assure everything is as I use it in my application. – Filip Hazubski Jul 28 '15 at 18:16
  • Ok waiting your example :) – Mido Jul 28 '15 at 18:18
  • Should this really be a separate answer of yours? – scopchanov Sep 27 '20 at 23:26