Sequential movement of an object by coordinates

Viewed 43

I need to move an object by the clock and counterclockwise sequentially. But the for loop works differently, it only moves in the latter direction. When you click on the button, the object must first turn clockwise, and then counterclockwise. Maybe there is some kind of delay when performing the animation? How can I do it?

import QtQuick 2.15
import QtQuick.Window 2.14
import QtQuick3D 1.15
import QtQuick.Controls 2.14


Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")
    visibility: "Maximized"
    property int scl: 5
    property int angle: 360
    Node{
        id: standAloneScene
        DirectionalLight {
            ambientColor: Qt.rgba(1.0, 1.0, 1.0, 1.0)
        }
        Node {
            id: sphere
            Model {
                id: model_sphere
                source: "#Cube"
                x: 200
                y: 100
                z: 0
                materials: [
                    DefaultMaterial {
                        diffuseColor: Qt.rgba(0.053, 0.130, 0.219, 0.75)
                    }
                ]
            }
        }
        ParallelAnimation{
            id: start
            running: false
            NumberAnimation {
                target: sphere
                property: "eulerRotation.y"
                to: angle
                duration: 2000
                easing.type: Easing.InOutQuad
            }
            NumberAnimation {
                target: model_sphere
                property: "eulerRotation.y"
                to: 2*angle
                duration: 2000
                easing.type: Easing.InOutQuad
            }
        }

        OrthographicCamera {
            id: cameraOrthographicFront
            eulerRotation.y: 45
            eulerRotation.x: -45
            x: 600
            y: 800
            z: 600
        }
    }
    Rectangle {
        id: view
        anchors.top: parent.top
        anchors.left: parent.left
        width: parent.width
        height: parent.height
        color: "#848895"
        border.color: "black"

        View3D {
            id: topLeftView
            anchors.fill: parent
            importScene: standAloneScene
            camera: cameraOrthographicFront
        }
        Button {
            id: posmoveZ
            width: view.width/8
            height: view.height/16
            anchors.top: view.top
            anchors.right: view.right
            text: "start"
            font.pixelSize: height
            onClicked: {
                for(var i=0; i<6; i++){
                    if(i % 2 == 0){
                        angle = 360
                    }
                    else{
                        angle = -360
                    }
                    start.restart();
                }
            }
        }
    }
}
1 Answers

I present two solutions to your problem.

Both solutions involve running multiple instances of your ParallelAnimation. Both solutions have a rewrite of your Button onClicked event removing the synchronous for loop for something that's asynchronous, either (1) event-driven, or (2) Promise-chain driven. The idea is to wait for the clockwise animation to finish before starting the counterclockwise animation.

The first solution uses the following pattern to animate:

ParallelAnimation {
    id: clockwise
    // ...
    onFinished: Qt.callLater(nextJob)
}
ParallelAnimation {
    id: counterClockwise
    // ...
    onFinished: Qt.callLater(nextJob)
}
Button {
    onClicked: {
         jobs = [
             clockwise.start,
             counterClockwise.start,
             clockwise.start,
             counterClockwise.start,
             clockwise.start,
             counterClockwise.start
         ];
         Qt.callLater(nextJob);
    }
}
property var jobs: []
function nextJob() {
    if (!jobs || !jobs.length) return;
    let job = jobs.shift();
    Qt.callLater(job);
}

The jobs is declared to be an array of callable functions. Note that we write clockwise.start and not clockwise.start() since we do not want invoke the animations at the time the are entered into the array. The functions will get called when they are retrieved from the jobs array in the nextJob() function with with let job = jobs.shift();. Everytime an animation is finish, it calls nextJob(). This repeats until all jobs are processed. Clicking the Start button initializes all jobs.

Here's your entire program with the appropriate modifications:

import QtQuick 2.15
import QtQuick.Window 2.14
import QtQuick3D 1.15
import QtQuick.Controls 2.14

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")
    visibility: "Maximized"
    property int scl: 5
    property int angle: 360

    Node{
        id: standAloneScene
        DirectionalLight {
            ambientColor: Qt.rgba(1.0, 1.0, 1.0, 1.0)
        }
        Node {
            id: sphere
            Model {
                id: model_sphere
                source: "#Cube"
                x: 200
                y: 100
                z: 0
                materials: [
                    DefaultMaterial {
                        diffuseColor: Qt.rgba(0.053, 0.130, 0.219, 0.75)
                    }
                ]
            }
        }

        ParallelAnimation{
            id: clockwise
            running: false
            property int count: 3
            NumberAnimation {
                target: sphere
                property: "eulerRotation.y"
                from: 360
                to: 0
                duration: 2000
                easing.type: Easing.InOutQuad
            }
            NumberAnimation {
                target: model_sphere
                property: "eulerRotation.y"
                from: 720
                to: 0
                duration: 2000
                easing.type: Easing.InOutQuad
            }
            onFinished: Qt.callLater(nextJob)
        }

        ParallelAnimation{
            id: counterClockwise
            running: false
            NumberAnimation {
                target: sphere
                property: "eulerRotation.y"
                from: 0
                to: 360
                duration: 2000
                easing.type: Easing.InOutQuad
            }
            NumberAnimation {
                target: model_sphere
                property: "eulerRotation.y"
                from: 0
                to: 720
                duration: 2000
                easing.type: Easing.InOutQuad
            }
            onFinished: Qt.callLater(nextJob)
        }

        OrthographicCamera {
            id: cameraOrthographicFront
            eulerRotation.y: 45
            eulerRotation.x: -45
            x: 600
            y: 800
            z: 600
        }
    }
    Rectangle {
        id: view
        anchors.top: parent.top
        anchors.left: parent.left
        width: parent.width
        height: parent.height
        color: "#848895"
        border.color: "black"

        View3D {
            id: topLeftView
            anchors.fill: parent
            importScene: standAloneScene
            camera: cameraOrthographicFront
        }
        Button {
            id: posmoveZ
            width: view.width/8
            height: view.height/16
            anchors.top: view.top
            anchors.right: view.right
            text: "start"
            font.pixelSize: height
            enabled: !clockwise.running && !counterClockwise.running
            onClicked: {
                jobs = [
                            clockwise.start,
                            counterClockwise.start,
                            clockwise.start,
                            counterClockwise.start,
                            clockwise.start,
                            counterClockwise.start
                        ];
                Qt.callLater(nextJob);
            }
        }
    }
    property var jobs: []
    function nextJob() {
        if (!jobs || !jobs.length) return;
        let job = jobs.shift();
        Qt.callLater(job);
    }
}

The second solution is based on Promises and is more scalable it supports more complex animation sequences via Promise chaining.

ParallelAnimation {
    id: parallelAnimation
    property var resolve
    property int from
    property int to
    // ...
    function animate(from, to) {
        return new Promise(function (resolve, reject) {
            parallelAnimation.resolve = resolve;
            parallelAnimation.reject = reject;
            parallelAnimation.from = from;
            parallelAnimation.to = to;
            start();
        } );
    }
    onFinished: resolve()
}
Button {
    onClicked: {
        parallelAnimation.animate(360, 0)
        then( () => parallelAnimation.animate(0, 360))
        then( () => parallelAnimation.animate(360, 0))
        then( () => parallelAnimation.animate(0, 360))
        then( () => parallelAnimation.animate(360, 0))
        then( () => parallelAnimation.animate(0, 360))
        catch( (err) => { console.error(err.message); throw err; } )
        ;
    }
}

Below is a complete implementation of running NumberAnimation with Promise:

import QtQuick 2.15
import QtQuick.Window 2.14
import QtQuick3D 1.15
import QtQuick.Controls 2.14

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")
    visibility: "Maximized"
    property int scl: 5
    property int angle: 360
    Node{
        id: standAloneScene
        DirectionalLight {
            ambientColor: Qt.rgba(1.0, 1.0, 1.0, 1.0)
        }
        Node {
            id: sphere
            Model {
                id: model_sphere
                source: "#Cube"
                x: 200
                y: 100
                z: 0
                materials: [
                    DefaultMaterial {
                        diffuseColor: Qt.rgba(0.053, 0.130, 0.219, 0.75)
                    }
                ]
            }
        }

        ParallelAnimation{
            id: parallelAnimation
            running: false
            property var resolve: null
            property var reject: null
            property int from: 360
            property int to: 0
            NumberAnimation {
                target: sphere
                property: "eulerRotation.y"
                from: parallelAnimation.from
                to: parallelAnimation.to
                duration: 2000
                easing.type: Easing.InOutQuad
            }
            NumberAnimation {
                target: model_sphere
                property: "eulerRotation.y"
                from: parallelAnimation.from * 2
                to: parallelAnimation.to * 2
                duration: 2000
                easing.type: Easing.InOutQuad
            }
            function animate(from, to) {
                return new Promise(function (resolve, reject) {
                    parallelAnimation.resolve = resolve;
                    parallelAnimation.reject = reject;
                    parallelAnimation.from = from;
                    parallelAnimation.to = to;
                    parallelAnimation.restart();
                } );
            }
            onFinished: resolve()
        }

        OrthographicCamera {
            id: cameraOrthographicFront
            eulerRotation.y: 45
            eulerRotation.x: -45
            x: 600
            y: 800
            z: 600
        }
    }
    Rectangle {
        id: view
        anchors.top: parent.top
        anchors.left: parent.left
        width: parent.width
        height: parent.height
        color: "#848895"
        border.color: "black"

        View3D {
            id: topLeftView
            anchors.fill: parent
            importScene: standAloneScene
            camera: cameraOrthographicFront
        }
        Button {
            id: posmoveZ
            width: view.width/8
            height: view.height/16
            anchors.top: view.top
            anchors.right: view.right
            text: "start"
            font.pixelSize: height
            onClicked: {
                parallelAnimation.animate(360, 0)
                .then( () => parallelAnimation.animate(0, 360))
                .then( () => parallelAnimation.animate(360, 0))
                .then( () => parallelAnimation.animate(0, 360))
                .then( () => parallelAnimation.animate(360, 0))
                .then( () => parallelAnimation.animate(0, 360))
                .catch( (err) => { console.error(err.message); throw err; } )
                ;
            }
        }
    }
}
Related