I am unsure what the markdirty() function does in QML. The QT documentation of that function does not seem clear to me.
My interpretation of this is that it allows us to track changes to a small subset of the canvas, so that any changes to that part redraws everything only in that part, using paint()
Doing requestPaint() on the other hand would be far more inefficient because it would redraw the whole canvas.
Is this correct? Some simple example codes would be quite helpful in understanding the usecase of markDirty()
That's a widely used term in programming especially GUI. Using that you can mark part of the canvas as in need of updating. So if this part is visible the render engine will fire paint(rect region) as soon as possible. In the onPaint handler you should repaint only items inside this region. requestPaint() does almost the same but for all the visible region.
Check the output in the example below:
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
width: 800
height: 800
title: qsTr("QML Test")
Canvas {
property var shapes: [20,20,220,20,20,220,220,220]
id: canvas
width: 400
height: 400
anchors.centerIn: parent
onPaint: {
console.log(region);
var ctx = getContext("2d");
ctx.beginPath();
ctx.fillStyle = Qt.rgba(Math.random(),Math.random(),Math.random(),1);
// draw 4 circles
for(var i = 0;i < canvas.shapes.length;i +=2) {
var x = canvas.shapes[i];
var y = canvas.shapes[i + 1];
var width = 160;
var height = 160;
// check the circle is inside the region
if(!( (x + width) < region.x || x > (region.x + region.width) || (y + height) < region.y || y > (region.y + region.height) ) ) {
ctx.ellipse(x, y, width, height);
}
}
ctx.fill();
}
}
Timer {
id: timer
property int step: 0
interval: 2000
repeat: true
running: true
onTriggered: {
switch(step++) {
case 0: canvas.markDirty(Qt.rect(0, 0, 200, 200)); break;
case 1: canvas.requestPaint(); break;
case 2: timer.stop(); break;
}
}
}
}
Related
I have 2 QQuickItems like below which I can fetch on C++ side using the QMLEngine like this.
QQuickItem * quick_item_1 = m_qml_engine->rootObjects()[0]->findChild<QQuickItem*>("quickitem1");
QQuickItem * quick_item_2 = m_qml_engine->rootObjects()[0]->findChild<QQuickItem*>("quickitem2");
Note: quick_item_1's immediate parent is different & quick_item_2's immediate parent is also different. But they both are drawn on the same application window into different immediate parents.
I am drawing both of them offscreen on a different QQuickItem. Let's call it new_parent_surface. I draw both these items on new_parent_surface by changing their parent to new_parent_surface like this.
quick_item_1->setParentItem(new_parent_surface);
quick_item_2->setParentItem(new_parent_surface);
This works fine for the objective of drawing them on a new parent QQuickItem. I get the both quick_item_1 & quick_item_2 drawn on new_parent_surface. Even though new_parent_surface is not drawn on UI, but if I take a snapshot using grabToImage of new_parent_surface, I can see the 2 items drawn on them. Fine till here.
However the positioning of quick_item_1 & quick_item_2 is not correct. I want to position them similar to the way they were positioned their original parent item. I can do some percentage math & try positioning them the same way as they were drawn on their original parent but isn't there a QQQuickItem or Qt API to translate this positioning to a new parent?
I tried to look into QQuickItem's mapping APIs like mapToItem & trying them out like this.
quick_item_2->mapToItem(new_parent_surface, quick_item_2->position());
But the positioning is still not correct.
So, how can I map a QQuickItem's position into its new parent QQuickItem after doing a setParentItem?
Items position is always relative to its parent. And position of the parent is relative to its parent and and so on. But you always can get both relative or global position. QML has lots of coordination translation function. Here is small example that could explain the issue:
import QtQuick 2.7
import QtQuick.Window 2.0
import QtQuick.Controls 2.1
Window {
id:container
width: 800
height: 800
visible: true
Component {
id: rect
Rectangle {
property bool imParent: false
x: 50 + Math.round(Math.random() * 550)
y: 50 + Math.round(Math.random() * 550)
width: 100 + Math.round(Math.random() * 100)
height: 100 + Math.round(Math.random() * 100)
color: Qt.rgba(Math.random(),Math.random(),Math.random(),1);
Drag.active: dragArea.drag.active
MouseArea {
id: dragArea
anchors.fill: parent
drag.target: parent
}
Text {
anchors.centerIn: parent
text: imParent ? "I'm parent" : "Drag me"
color: "white"
}
}
}
Rectangle {
id: blk
x: 10
y: 10
z: 100
parent: null
height: 50
width: 50
radius: 5
border.color: "white"
color: "black"
}
Repeater {
id: reptr
model: 5
property int pos: 0
Loader {
id: loader
sourceComponent: rect
onLoaded: {
if(blk.parent == null) {
blk.parent = loader.item;
loader.item.imParent = true;
}
}
}
}
Row {
anchors.horizontalCenter: container.contentItem.horizontalCenter
spacing: 2
Button {
text: "Reparent relative to parent"
onClicked: {
reptr.pos ++;
if(reptr.pos >= reptr.model) {
reptr.pos = 0;
}
var item = reptr.itemAt(reptr.pos).item;
blk.parent.imParent = false;
blk.parent = item;
blk.parent.imParent = true;
}
}
Button {
text: "Reparent relative to scene"
onClicked: {
reptr.pos ++;
if(reptr.pos >= reptr.model) {
reptr.pos = 0;
}
var item = reptr.itemAt(reptr.pos).item;
var coord = blk.mapToGlobal(blk.x, blk.y);
blk.parent.imParent = false;
blk.parent = item;
blk.parent.imParent = true;
coord = blk.mapFromGlobal(coord.x, coord.y);
blk.x = coord.x;
blk.y = coord.y;
}
}
}
}
AKA: Canvas requestPaint() too slow; requestAnimationFrame() too fast
I'm trying to create a QML Canvas that repaints as fast as possible—once per update in the main UI render loop—in order to create an FPS timer.
I initially wrote this simple test:
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
visible:true; width:100; height:100
Canvas {
anchors.fill:parent
onPaint: console.log(+new Date)
}
}
I only get the callback once. So I added requestPaint():
onPaint: {
console.log(+new Date)
requestPaint()
}
No change: I still only get one callback. Same if I use markDirty(). Same if I actually paint something on the canvas each callback.
So I moved to requestAnimationFrame():
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
visible:true; width:100; height:100
Canvas {
anchors.fill:parent
Component.onCompleted: crank()
function crank(){
console.log(+new Date)
requestAnimationFrame(crank)
}
}
}
Now I get callbacks, but way too many. On average, I get 77 callbacks per millisecond, some times as many as 127 callbacks in a single millisecond. So many callbacks that nothing else in the application displays, not even initially. Even if I remove the console.log(), to prove that I'm not i/o bound).
How can I get my canvas to repaint once "per frame", so that I can measure the FPS semi-accurately? Any why does requestPaint() not actually work? And why is requestAnimationFrame() apparently useless?
The problem with your approach is that you are requesting paint from onPaint, this is not going to work,
because onPaint event is triggered from within
QQuickItem::polish()
void QQuickItem::polish()
{
Q_D(QQuickItem);
if (!d->polishScheduled) {
d->polishScheduled = true;
if (d->window) {
QQuickWindowPrivate *p = QQuickWindowPrivate::get(d->window);
bool maybeupdate = p->itemsToPolish.isEmpty();
p->itemsToPolish.append(this);
if (maybeupdate) d->window->maybeUpdate();
}
}
}
During this call d->polishScheduled is set to true and if you call requestPaint() again, nothing happens. You need to trigger it asynchronously. For example, use Timer with interval 0.
import QtQuick 2.0
Canvas {
id: canvas
width: 200
height: 200
property real angle
property int fps
Timer {
id: repaintTimer
running: false
interval: 0
onTriggered: {
angle += 0.01
canvas.requestPaint()
}
}
Timer {
interval: 1000
running: true
repeat: true
onTriggered: {
console.log(fps)
fps = 0
}
}
onPaint: {
var ctx = getContext("2d")
ctx.save()
ctx.clearRect(0, 0, width, height)
ctx.moveTo(100, 100)
ctx.translate(100,100)
ctx.rotate(angle)
ctx.beginPath()
ctx.lineTo(40, 10)
ctx.lineTo(40, 40)
ctx.lineTo(10, 40)
ctx.lineTo(10, 10)
ctx.closePath()
ctx.stroke()
ctx.restore()
fps += 1
repaintTimer.start()
}
}
Another Timer is here to record fps. When I run this code in qmlscene, I get 60 fps.
There was a bug with requestAnimationFrame() prior to Qt 5.9. This bug has been fixed.
The following code works as expected and desired to keep the canvas continuously redrawing:
Canvas {
width:100; height:100;
property var ctx
onAvailableChanged: if (available) ctx = getContext('2d');
onPaint: {
if (!ctx) return;
ctx.clearRect(0, 0, width, height);
// draw here
requestAnimationFrame(paint);
}
}
Update: Isn't it often the way: you ask a question and then discover the answer on your own a short time later.
It seems I have some confusion between referencing mouseX and mouseY in a MouseArea and getting the mouse data from the MouseEvent
If I change my code below to:
var pt = Qt.point( mouse.x, mouse.y)
from what I had:
var pt = Qt.point(mouseX, mouseY)
Then the newly created sprites are located at the click point. That's great but I am still not understanding the difference, particularly in this case since the MouseArea fills the entire window (parent).
The mouse data is the same in either approach, unless the mouse event has been propagated – then the mouse approach gives the correct data while mouseX, mouseY does not. That is the part that is confusing me.
Can anyone explain the difference in what is going on here?
I have made a reusable QML component which can load .png images with an alpha channel and handle clicks on transparent pixels by propagating the mouse event. I've got it working in the code below (any suggestions for improvement much welcomed) but it seems the mouse data is wrong.
In the image below I have marked the order and location of 3 clicks. In the log statements even though the mouse click position has changed, the reported position stays the same. So what is occurring is that although 3 sprites have been created, they are stacking on top of each other
Am I missing something about how a MouseArea reports the mouse position or how propagateComposedEvents works?
main clicked. Creating sprite at: 598.01953125 492.953125
graphic alpha clicked: 5.69921875 103.41015625 <----- EVENT PASSED THROUGH
main clicked. Creating sprite at: 598.01953125 492.953125
graphic alpha clicked: 121.953125 103.01953125 <----- EVENT PASSED THROUGH
graphic alpha clicked: 5.69921875 103.41015625 <----- EVENT PASSED THROUGH
main clicked. Creating sprite at: 598.01953125 492.953125
// main.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
ApplicationWindow {
id: appWindow
visible: true
width: 1024
height: 768
title: qsTr("QtQuick")
Item {
id: container
anchors.fill: parent
property var events: new Array()
property int counter: 0;
MouseArea {
anchors.fill: parent
onClicked: {
console.log("---------main clicked. Creating sprite at:", mouseX, mouseY);
var pt = Qt.point(mouseX, mouseY)
var component = Qt.createComponent("graphicAsset.qml");
var imageName = "Earth-icon.png";
var sprite = component.createObject(container, {"x": pt.x, "y": pt.y, "imageSource": imageName});
}
}
}
}
//graphicAsset.qml
import QtQuick 2.5
Canvas {
id: graphicAsset
width: 50
height: 50
property string imageSource;
onImageSourceChanged:loadImage(imageSource)
onImageLoaded: {
var ctx = getContext("2d")
var imageData = ctx.createImageData(imageSource)
graphicAsset.width = imageData.width;
graphicAsset.height = imageData.height;
x = x - (imageData.width /2);
y = y - (imageData.height /2);
requestPaint();
}
onPaint: {
if (isImageLoaded(imageSource)){
var ctx = getContext("2d")
var imageData = ctx.createImageData(imageSource)
ctx.drawImage(imageData, 0, 0)
}
}
MouseArea {
anchors.fill: parent
drag.target: parent
propagateComposedEvents: true
onClicked: {
var ctx = parent.getContext("2d")
var imageData = ctx.getImageData(mouseX, mouseY, 1, 1)
if (imageData.data[3] == 0 ){
console.log("graphic alpha clicked:", mouseX, mouseY);
mouse.accepted = false;
} else {
mouse.accepted = true;
}
}
}
}
Mouse events' x and y positions are relative to the MouseArea that generated the event, as well as the coordinates of the mouse cursor within the same area (named mouseX and mouseY).
I am trying to use QML Canvas.requestAnimationFrame to draw some custom animation. I expected that the provided callback is called once for each frame, about 60 times per second. The code I have is:
Canvas {
id: canvas
width: 600
height: 600
function draw() {
}
Component.onCompleted: {
var i = 1;
function drawFrame() {
requestAnimationFrame(drawFrame)
console.log("Frame callback: " + i++)
draw()
}
drawFrame()
}
onPaint: {
draw()
}
}
What I see is that the callback is called way more often. The counter reaches 70000 in a few seconds, after which the application becomes entirely unresponsive.
What am I doing wrong?
Your drawFrame() function passes itself as callback function for rendering and you're caught in a loop. You either want to render on demand only like for example after user input to keep resources at a minimum, or you have some logic that changes every frame or you just need continuous rendering.
If time-based rendering is what you want, just use a Timer:
import QtQuick 2.4
Canvas {
id: cvs
width: 600; height: 600
contextType: "2d"
property real i : 0
onPaint: {
console.timeEnd("t")
if (context) {
context.clearRect (0, 0, width, height)
context.fillRect(i, 50, 50, 50 + i)
}
console.time("t")
}
Timer {
interval: 1
repeat: true
running: true
onTriggered: {
cvs.i = (cvs.i + 0.1) % cvs.width
cvs.requestPaint()
}
}
}
Edit:
Just updated the code:
onPaint calls are synced to the display frame rate even though the timer interval is set to 1ms as can be seen from the log when running the sample above.
In fact the whole block assigned to the onTriggered signal is executed every millisecond but requestPaint() makes sure to synchronize rendering calls for best performance just like requestAnimationFrame() does for the HTML canvas.
Apparently, requestAnimationFrame() inside the QML.Canvas doesn't work as expected and there's not much documentation...
Hope this helps!
Just a small update on this topic. I've encountered same problem with the Qt qml Canvas and requestAnimationFrame while I was working on my project. The solution I've found is to switch the render strategy to Threaded and use onPainted signal. The example of qCring's code with my updates looks like this:
import QtQuick 2.4
Canvas {
id: cvs
width: 600; height: 600
//renderStrategy: Canvas.Cooperative // Will work as well but animation chops on my computer from time to time
renderStrategy: Canvas.Threaded
contextType: "2d"
property real i : 0
function animate() {
cvs.i = (cvs.i + 0.1) % cvs.width;
}
onPaint: {
console.timeEnd( "t" )
if ( context ) {
context.clearRect( 0, 0, width, height )
context.fillRect( i, 50, 50, 50 + i )
}
console.time("t")
cvs.requestAnimationFrame(animate);
}
onPainted: {
cvs.requestPaint();
}
}
There was a bug with requestAnimationFrame() prior to Qt 5.9. This bug has been fixed.
This code works as expected and desired to keep the canvas continuously redrawing.
Canvas {
width:100; height:100;
property var ctx
onAvailableChanged: if (available) ctx = getContext('2d');
onPaint: {
if (!ctx) return;
ctx.clearRect(0, 0, width, height);
// draw here
requestAnimationFrame(paint);
}
}
I would like to track a global position of an object (or relative to one of it's ancestors) and bind it to some other item's position.
I was thinking about using mapFromItem as follows:
SomeObject {
x: ancestor.mapFromItem(trackedObject, trackedObject.x, 0).x
y: ancestor.mapFromItem(trackedObject, 0, trackedObject.y).y
}
The problem with this approach is that the mapFromItem is evaluated once and doesn't update as one of it's arguments gets updated. Moreover the mapping sometimes returns the new position altered by an offset I'm unable to track in the code (but that's not the matter at hand).
My second idea was to calculate the global position by implementing a function that would recursively sum the offsets, stopping at the provided ancestor (something like calculateOffsetFrom(ancestor)). Still this is just a function and as far as I'm concerned it won't get re-evaluated as one of the ancestors position changes (unless, in that function, I'll bind calling it to the onXChanged signal for each one of the ancestors along the way, which seems like a dirty solution).
So in the end I've added properties to the object I intend to track and then I bind to them:
TrackedObject {
property real offsetX: x + parent.x + parent.parent.x + parent.parent.parent.x ...
property real offsetY: y + parent.y + parent.parent.y + parent.parent.parent.y ...
}
SomeObject {
x: trackedObject.globalX
y: trackedObject.globalY
}
But well... yeah... this one doesn't scale at all and is as ugly as it gets.
Does anyone have any idea how this problem might be solved in a cleaner way?
Edit:
As far as I'm concerned I can't use anchors in this case. The SomeObject component is a custom component drawing a bezier curve from one point to another (it will connect two TrackedObjects). For that I need the difference between the coordinates. If I'm correct anchors don't provide any way of calculating the distance between them.
This is a hard point, but here is the hack i used in one of my projects : to make blue rect which is in another parent than green rect move, to stay aligned with it, when green rect moves but also when yellow rect (green rect parent) moves :
import QtQuick 2.0;
Rectangle {
id: window;
width: 800;
height: 480;
property bool globalBit : true;
function updatePos (item_orig, item_dest, bit) {
var pos_abs = window.mapFromItem (item_orig.parent, item_orig.x, item_orig.y);
return window.mapToItem (item_dest.parent, pos_abs.x, pos_abs.y);
}
Rectangle {
id: rectYellow;
width: 400;
height: 300;
x: 300;
y: 200;
color: "yellow";
onXChanged: { globalBit = !globalBit; }
onYChanged: { globalBit = !globalBit; }
MouseArea {
drag {
target: rectYellow;
minimumX: 0;
minimumY: 0;
maximumX: (rectYellow.parent.width - rectYellow.width);
maximumY: (rectYellow.parent.height - rectYellow.height);
}
anchors.fill: parent;
}
Rectangle {
id: rectGreen;
x: 100;
y: 100;
width: 50;
height: 50;
color: "green";
MouseArea {
drag {
target: rectGreen;
minimumX: 0;
minimumY: 0;
maximumX: (rectGreen.parent.width - rectGreen.width);
maximumY: (rectGreen.parent.height - rectGreen.height);
}
anchors.fill: parent;
}
}
}
Rectangle {
id: rectBlue;
x: pos.x + 50;
y: pos.y + 50;
width: 50;
height: 50;
color: "blue";
property var pos : updatePos (rectGreen, rectBlue, globalBit);
}
}
The trick is to bring all coordinates back to the first common ancestor, using both mapfromItem and mapToItem, and to force the function to be re-evaluated, just put a global boolean flag that you pass to the computing function, and that you invert each time a movable element on your map moves... You don't have to put it every where, just on parents of items that can move and are inside the ancestor item.
So it works, your positions will always be right, and it's quite scalable and doesn't add much code.
The solution below will trigger the qmlElementToTrack.onPropertyNameXChanged() and qmlElementToTrack.onPropertyNameYChanged() events each time one of its parents 'x' or 'y' values change.
It does this by attaching to each parent's onXChanged() and onYChanged() signals. When one of those values changes, it recalculates the propertyNameX or propertyNameY values by traversing all of qmlElementToTrack's parents.
To make it 'relative' positioned (instead of 'absolute'), add current !== qmlElementToStopAt to each while() condition.
ElementToTrack {
id: qmlElementToTrack
property real propertyNameX: 0
property real propertyNameY: 0
}
setPositionChangedToParents(qmlElementToTrack);
/**
Connect to each parent's 'onXChanged' and 'onYChanged' signals.
*/
setPositionChangedToParents = function(current) {
while (current && current.parent) {
current.onXChanged.connect(calculatePropertyNameX);
current.onYChanged.connect(calculatePropertyNameY);
current = current.parent;
}
};
/**
Disconnects the signals set to all parents.
*/
removePositionChangedFromParents = function(current) {
while (current && current.parent) {
current.onXChanged.disconnect(calculatePropertyNameX);
current.onYChanged.disconnect(calculatePropertyNameY);
current = current.parent;
}
};
/**
When any parent's 'x' changes, recalculate the 'x' value for the 'property name'.
*/
calculatePropertyNameX = function() {
var calculatedX, current;
calculatedX = 0;
current = qmlElementToTrack;
while (current && current.parent) {
calculatedX += current.x;
current = current.parent;
}
propertyNameX = calculatedX;
};
/**
When any parent's 'y' changes, recalculate the 'y' value for the 'property name'.
*/
calculatePropertyNameY = function() {
var calculatedY, current;
calculatedY = 0;
current = qmlElementToTrack;
while (current && current.parent) {
calculatedY += current.y;
current = current.parent;
}
propertyNameY = calculatedY;
};
I don't know whether this will help, but for the above yellow rect,blue rect and green rect problem mentioned by TheBootroo, I used the below code to solve the problem
import QtQuick 2.0;
Rectangle {
id: window;
width: 800;
height: 480;
Rectangle {
id: rectYellow;
width: 400;
height: 300;
x: 300;
y: 200;
color: "yellow";
MouseArea {
drag {
target: rectYellow;
minimumX: 0;
minimumY: 0;
maximumX: (rectYellow.parent.width - rectYellow.width);
maximumY: (rectYellow.parent.height - rectYellow.height);
}
anchors.fill: parent;
}
Rectangle {
id: rectGreen;
x: 100;
y: 100;
width: 50;
height: 50;
color: "green";
MouseArea {
drag {
target: rectGreen;
minimumX: 0;
minimumY: 0;
maximumX: (rectGreen.parent.width - rectGreen.width);
maximumY: (rectGreen.parent.height - rectGreen.height);
}
anchors.fill: parent;
}
}
}
Rectangle {
id: rectBlue;
//Need to acheive the below behvior(commented)
//x: window.x+rectYellow.x+rectGreen.x+50
//y: window.y + rectYellow.y +rectGreen.y+50
width: 50;
height: 50;
color: "blue";
}
Component.onCompleted: {
rectBlue.x =Qt.binding(
function()
{
//Returns window.x+rectYellow.x+rectGreen.x+rectGreen.width
var docRoot = null;
var x=rectGreen.x;
if(!docRoot)
{
docRoot = rectGreen.parent;
x+=docRoot.x;
while(docRoot.parent)
{
docRoot = docRoot.parent;
x+=docRoot.x
}
}
return x+rectGreen.width;
}
)
rectBlue.y = Qt.binding(
function()
{
//Returns window.y+rectYellow.y+rectGreen.y+rectGreen.height
var docRoot = null;
var y=rectGreen.y
if(!docRoot)
{
docRoot = rectGreen.parent;
y+=docRoot.y;
while(docRoot.parent)
{
docRoot = docRoot.parent;
y+=docRoot.y
}
}
return y+rectGreen.height;
}
)
}
}
The idea is to calculate the position of blue rectangle relative to the green rectangle, by
calculating the position of green rectangle and its visual ancestors.
The inspiration behind this solution is -> http://developer.nokia.com/Community/Wiki/How_to_create_a_Context_Menu_with_QML
Tracking certain Item's global positions seems like an important problem if developing some complex graphics interaction. I came up with a relatively simple & graceful solution. Here is my core codes:
Item{
id: globalRoot
signal globalPositionChanged(Item item, real newX, real newY);
function tracking(item){
var obj = item;
var objN;
function onGlobalXYChanged(){
var pt = mapFromItem(item, item.x, item.y);
globalRoot.globalPositionChanged(item, pt.x, pt.y);
}
do{
objN = obj.objectName;
obj.xChanged.connect(onGlobalXYChanged);
obj.yChanged.connect(onGlobalXYChanged);
obj = obj.parent;
}while(objN !== "furthestAncestorObjectName");
}
}
The core idea is: what essentially makes an Item's global position change? It maybe itself, its parent or its parent's parent etc. So make a traverse back to its furthest parent and connect each of its ancestor's x/y change signal to a function, within which we get the item's global position and broadcast outside.
I have tried to improve on #Shubhanga's answer a bit by moving the code into its own ItemPositionTracker.qml file:
import QtQuick 2.3
Item {
id: root
property Item trackedItem
property Item movedItem
Component.onCompleted: {
movedItem.x =
Qt.binding(
function()
{
if (trackedItem === null) return 0;
var docRoot = trackedItem;
var x = trackedItem.x;
while(docRoot.parent)
{
docRoot = docRoot.parent;
x += docRoot.x
}
return x;
}
)
movedItem.y =
Qt.binding(
function()
{
if (trackedItem === null) return 0;
var docRoot = trackedItem;
var y = trackedItem.y
while(docRoot.parent)
{
docRoot = docRoot.parent;
y += docRoot.y
}
return y;
}
)
}
}
The code can now be added to any QML object like this:
ItemPositionTracker {
trackedItem: rectGreen
movedItem: rectBlue
}
Which makes rectBlue follow rectGreen.