Phrogz
Phrogz

Reputation: 303188

Offset rotation for QML Item

In a QML application I have an item that is moving around the screen (not rotating). I want to display an indicator that rotates around this item, pointing away from the center of the screen, a fixed distance away from the center of the item.

The following simplified QML application performs this goal, by making the indicator a child of the item, and translating it to the desired location. However, when I try to rotate the indicator (the commented-out code) I cannot find any values for origin.x and .y that work. It feels like the QML scene graph calculates X/Y positioning in a way unlike any I've experienced.

import QtQuick 2.7
import QtQuick.Window 2.2

Window {
    id: win
    visible:true; width:600; height:300
    property real padding: 50
    property real angle: 0
    property real _rads: angle * Math.PI/180

    Timer {
      interval:50; running:true; repeat:true
      onTriggered:win.angle = (new Date/50) % 360
    }

    Rectangle {
        id:object; color:'blue'
        width:50; height:width
        property real xOffset: Math.cos(_rads)
        property real yOffset: Math.sin(_rads)
        x: win.width/2  + xOffset * (win.width/2 - padding*2)
        y: win.height/2 + yOffset * (win.height/2 - padding*2)

        Rectangle {
            id:indicator; color:'red'
            property real centerOffset: 40
            width:10; height:width*2
            x: object.width/2  + object.xOffset * centerOffset - width/2
            y: object.height/2 + object.yOffset * centerOffset - height/2
//          transform: Rotation { origin.x:0; origin.y:0; angle:win.angle }
        }
    }
}

I've tried making the indicator not be a child of the item. I've tried using Translate in the transform stack instead of X/Y positions. All of them result in amusing-but-incorrect rotations.

How can I simply rotate the indicator around its own center, or otherwise achieve my goal?

Upvotes: 2

Views: 2209

Answers (2)

Stephen Quan
Stephen Quan

Reputation: 25906

Similar to @derM I have a solution that makes use of Item.rotation. However, with regard to the rest, I have generalized it by avoiding cos and sin since they are not required.

To demonstrate I create 3 SVG images crosshairs.svg, marker.svg, and triangle.svg. I placed crosshairs.svg in the center of the screen. I animate marker.svg by making it bounce around the screen "pong" style. Now, the secret sauce is the placement and orientation of triangle.svg.

To place an offset a triangle relative to the marker. I put the triangle Image inside an Item component. The Item component has no area, it merely has x, y, and rotation set. The Image component is placed relative to the Item and we need to compute its relative placement.

Because the triangle.svg is 16x16, I placed it at (20, -8) relative to the marker. If I had chosen (-8, -8) the SVG would sit on top of the marker. Because I put it at (20, -8) it puts it beyond the marker. Lastly, I compute the rotation using Math.atan2() on the vector between the marker and the crosshairs:

        Item {
            x: marker.x
            y: marker.y
            rotation: Math.atan2(
                marker.y - crosshairs.y,
                marker.x - crosshairs.x
            ) * 180 / Math.PI
            Image {
                source: "triangle.svg"
                x: 20
                y: -8
                //cache: false
            }
        }

Here's a full working demo:

import QtQuick 2.15
import QtQuick.Controls 2.15

Page {
    Timer {
        interval: 50
        running: true
        repeat: true
        onTriggered: marker.animate()
    }

    Rectangle {
        id: frame
        anchors.centerIn: parent
        width: parent.width / 2
        height: parent.height / 2
        color: "#ffe"
        border.color: "grey"
        clip: true

        Image {
            id: crosshairs
            anchors.centerIn: parent
            source: "crosshair.svg"
            //cache: false
        }

        Item {
            id: marker
            x: parent.width/2
            y: parent.height/2
            property int dx: 2
            property int dy: 2
            property int size: 20
            Image {
                anchors.centerIn: parent
                source: "marker.svg"
                //cache: false
            }
            function animate() {
                x += dx;
                y += dy;
                if (x + size / 2 >= parent.width || x - size / 2 <= 0) {
                    dx = -dx;
                    x += dx;
                    x += dx;
                }
                if (y + size / 2 >= parent.height || y - size / 2 <= 0) {
                    dy = -dy;
                    py += dy;
                    py += dy;
                }
            }
        }

        Item {
            x: marker.x
            y: marker.y
            rotation: Math.atan2(
                marker.y - crosshairs.y,
                marker.x - crosshairs.x
            ) * 180 / Math.PI
            Image {
                source: "triangle.svg"
                x: 20
                y: -8
                //cache: false
            }
        }
    }
}

//crosshair.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path stroke="grey" d="M6 0L6 12M 0 6L 12 6"/></svg>

//marker.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path stroke="grey" fill="#ffe" d="M0 0 L20 0 L20 20 L0 20 z"/></svg>

//triangle.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path stroke="grey" fill="red" d="M16 8 L0 0 L 0 16z"/></svg>

You can Try it Online!

Upvotes: 0

derM
derM

Reputation: 13691

You might think of it as a clock and build yourself a clockhand.

import QtQuick 2.7
import QtQuick.Window 2.2

Window {
    id: win
    visible:true; width:600; height:300
    property real padding: 50
    property real angle: 0
    property real _rads: angle * Math.PI/180

    Timer {
        interval:50; running:true; repeat:true
        onTriggered:win.angle = (new Date/50) % 360
    }

    Rectangle {
        id:object; color:'blue'
        width:50; height:width
        property real xOffset: Math.cos(_rads)
        property real yOffset: Math.sin(_rads)
        x: win.width/2  + xOffset * (win.width/2 - padding*2)
        y: win.height/2 + yOffset * (win.height/2 - padding*2)


        Text {
            width: 250
            height: 250
            x: -100
            y: -100
            text: '▲'
            color: 'red'
            font.pixelSize: 20
            horizontalAlignment: Qt.AlignHCenter
            verticalAlignment: Qt.AlignTop

            transform: Rotation {
                angle: win.angle + 90
                origin.x: 125
                origin.y: 125
            }
        }

        Text {
            x: 15
            y: -125
            width: 20
            height: 20

            text: '▲'
            color: 'red'
            font.pixelSize: 20
            horizontalAlignment: Qt.AlignHCenter
            verticalAlignment: Qt.AlignVCenter

            transform: Rotation {
                angle: win.angle + 90
                origin.x: 10
                origin.y: 150
            }
        }


        Rectangle {
            id: clockhand
            width: 1
            height: 100
            color: 'black'
            anchors {
                centerIn: parent
            }

            rotation: win.angle + 90

            Text {
                text: '▲'
                color: 'red'
                anchors {
                    horizontalCenter: parent.horizontalCenter
                    bottom: parent.top
                    bottomMargin: -5
                }
                font.pixelSize: 20
            }
        }
    }
}

Just turn the Clockhand into an Item and remove the color, to make it invisible.

Upvotes: 1

Related Questions