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.