Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use QML Scale Element for incremental scaling with different origin

Tags:

qt

qt-quick

qml

I'm trying to use a QML Scale Element to perform view scaling around a point clicked by the user, but it's not always working as documented.

To reproduce the problem, run the minimal QML example below (I'm using Qt 5.3.1 on Ubuntu 14.04 x86_64) and then:

  • Click in the center of the blue rectangle at the top left.
  • See that everything is scaled up, but the center of the blue rectangle remains at your click location. This is as documented in http://doc.qt.io/qt-5/qml-qtquick-scale.html - "[The origin] holds the point that the item is scaled from (that is, the point that stays fixed relative to the parent as the rest of the item grows)."
  • Now click in the center of the red rectangle.
  • See that everything is scaled up, but the center of the red rectangle did not remain at your click point, it was translated up and to the left. This is not as documented.

My goal is to have it always zoom correctly maintaining the click point as the origin, as stated in the documentation.

P.S. Interestingly, if you now click again in the center of the red rectangle, it scales up around that point as promised. Clicking again now on the center of the blue rectangle, you see the same unexpected translation behaviour.

P.P.S. I'm working on an application where the user can mouse-wheel / pinch anywhere on the containing rectangle, and everything inside should scale up or down around the mouse / pinch position. Many applications have exactly this behaviour. See for example inkscape.

import QtQuick 2.2
import QtQuick.Controls 1.1

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    Rectangle {
        x: 100
        y: 100
        width: 300
        height: 300

        transform: Scale {
            id: tform
        }

        MouseArea {
            anchors.fill: parent
            onClicked: {
                console.log(mouse.x + " " + mouse.y)
                tform.xScale += 0.5
                tform.yScale += 0.5
                tform.origin.x = mouse.x
                tform.origin.y = mouse.y
            }
        }

        Rectangle {
            x: 50
            y: 50
            width: 50
            height: 50
            color: "blue"
        }

        Rectangle {
            x: 100
            y: 100
            width: 50
            height: 50
            color: "red"
        }

    }

}

(I filed this as a Qt bug, because the behaviour does not follow the documentation. At the moment of writing, the bug seems to have been triaged as "important". https://bugreports.qt.io/browse/QTBUG-40005 - I'm still very much open to suggestions of work-arounds / fixes over here)

like image 509
Charl Botha Avatar asked Jun 29 '14 17:06

Charl Botha


1 Answers

In fact it is not a deviant behavior, just a different one from what you may expect after reading the doc.

When you change the Scale transform of your rectangle, the transformation is applied on the original rectangle. The point you click on stay in the same place from the original rectangle point of view. That's why your rectangle "moves" so much when you click on one corner then the opposite corner.

In order to achieve what you want, you can't rely on transform origin. You have to set the actual x y coordinates of your rectangle.

Here's a working example:

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    Rectangle {
        id: rect
        x: 100
        y: 100
        width: 300
        height: 300

        Rectangle {
                x: 50
                y: 50
                width: 50
                height: 50
                color: "blue"
            }

            Rectangle {
                x: 100
                y: 100
                width: 50
                height: 50
                color: "red"
            }


        transform: Scale {
            id: tform
        }

        MouseArea {
            anchors.fill: parent
            property double factor: 2.0
            onWheel:
            {
                if(wheel.angleDelta.y > 0)  // zoom in
                    var zoomFactor = factor
                else                        // zoom out
                    zoomFactor = 1/factor   

                var realX = wheel.x * tform.xScale
                var realY = wheel.y * tform.yScale
                rect.x += (1-zoomFactor)*realX
                rect.y += (1-zoomFactor)*realY
                tform.xScale *=zoomFactor
                tform.yScale *=zoomFactor

            }
        }
    }
}
like image 89
Yoann Quenach de Quivillic Avatar answered Oct 23 '22 11:10

Yoann Quenach de Quivillic