[Interest] Best practices on pinch-zoomable item container with non-zoomable item attached
Alexander Ivash
elderorb at gmail.com
Thu Sep 13 16:13:34 CEST 2018
Hi Shawn,
I've realized it might be faster to provide some code than try to
explain better.
Original.txt - is the code where scaling happens relatively top/left
Approach_1.txt - adjusting x/y
Approach_2.txt - scaling top level item & scaling non-zoomable to compensate
>> anchors.centerIn: parent
What I meant is rather anchors.centerIn: child (so that child would
free-zoom relatively center, and parent would center itself relatively
center of the child)
чт, 13 сент. 2018 г. в 16:15, Shawn Rutledge <Shawn.Rutledge at qt.io>:
>
>
> > On 13 Sep 2018, at 14:41, Alexander Ivash <elderorb at gmail.com> wrote:
> >
> > I need pinch-zoomable item container with non-zoomable item container
> > attached to the bottom. Like this:
> >
> > Item {
> > id: item
> > Item {
> > id: zoomable
> > }
> >
> > Item {
> > id: nonzoomable
> > }
> > }
> >
> > (anchoring, geometry omitted for simplicity)
> >
> > I see at least the following solutions:
> >
> > 1. Change 'scale' of the whole item (zoomable + non-zoomable), then
> > change 'scale' of non-zoomable to compensate parent scaling. It feels
> > a bit wrong so I'd like to avoid it. Also doing in this way leads to
> > some issues with anchoring non-zoomable item to zoomable one properly.
> >
> > 2. Change 'scale' of the zoomable part only. But in this case I need
> > to adjust x/y of 'item', otherwise scaling will always be happening
> > relatively top/left (and I need relatively to the center)
>
> PinchArea only sets the scale property of the zoomable item directly, and that is relative to the Item transformOrigin which defaults to center.
>
> PinchHandler zooms relative to the centroid between the touchpoints, which is more typical behavior like what you get in most non-Qt interfaces (you need to be able to zoom into a particular geographical area of a map, or an interesting part of some drawing, for example). It normally does that by setting the scale and the x and y properties at the same time. But it’s not overriding anchors.centerIn: parent, so you can still use that to keep your item centered.
>
> If yours is always zooming toward the top left, it must be because of your anchors or bindings, or because of setting transformOrigin to TopLeft.
>
> You could use another wrapper item to hold the place in the layout, and then add your zoomable item to that. If it has no bindings or anchors, it can be free to zoom and move around within that space. If you want to disable movement, or allow movement only within certain confines, you can try the minimumX/Y and maximumX/Y properties.
>
> > Is there better way to do it?
> >
> > If there would be a way to center parent in child - I could anchor
> > 'item' to the center of 'zoomable' and everything would work
> > magically. But such kinds of anchors are not supported.
>
> anchors.centerIn: parent
>
> What I sometimes wish we had is _bindable_ centerX/Y properties. But at least we have anchors.
>
-------------- next part --------------
import QtQuick 2.0
import QtQuick.Controls 2.2
import QtQuick.Shapes 1.0
import Qt.labs.handlers 1.0
Item {
id: draggableObject
property int index: -1
default property alias content: content.children
signal grabChanged(var index);
signal tapped(var index);
width: contentContainer.width
onWidthChanged: {
console.debug('draggable width: ', width);
}
height: contentContainer.height
onHeightChanged: {
console.debug('draggable height: ', height);
}
Item {
id: contentContainer
width: content.width * content.scale
property int prevWidth: width
onWidthChanged: {
console.debug('content width: ', width);
draggableObject.x -= (width - prevWidth) / 2
prevWidth = width;
}
height: content.height * content.scale
property int prevHeight: height
onHeightChanged: {
console.debug('content height: ', height);
draggableObject.y -= (height - prevHeight) / 2
prevHeight = height;
}
DragHandler {
onGrabChanged: {
draggableObject.grabChanged(index);
}
}
PinchHandler {
target: content
}
TapHandler {
onTapped: {
draggableObject.tapped(index);
}
}
Item {
id: content
anchors.centerIn: parent
clip: true
width: childrenRect.width
onWidthChanged: {
console.debug('content width: ', width);
}
height: childrenRect.height
onHeightChanged: {
console.debug('content height: ', height);
}
onScaleChanged: {
}
}
Shape {
anchors.centerIn: parent
width: contentContainer.width
height: contentContainer.height
ShapePath {
fillColor: 'transparent'
strokeColor: 'green'
strokeWidth: 4
strokeStyle: ShapePath.DashLine
startX: 0
startY: 0
dashPattern: [ 1, 4 ]
PathLine { x: contentContainer.width; y: 0 }
PathLine { x: contentContainer.width; y: contentContainer.height }
PathLine { x: 0; y: contentContainer.height }
PathLine { x: 0; y: 0 }
}
}
MouseArea {
id: mouseZoomer
anchors.fill: parent
hoverEnabled: false
onPressed: {
mouse.accepted = false
}
onReleased: {
mouse.accepted = false
}
onWheel: {
if(wheel.modifiers & Qt.ControlModifier) {
console.debug('onWheel');
if(wheel.angleDelta.y > 1.0) {
content.scale *= 1.1
} else {
content.scale /= 1.1
}
} else {
wheel.accepted = false
}
}
}
}
Button {
anchors.top: contentContainer.bottom
anchors.right: contentContainer.right
width: 50
height: 50
text: 'button: ' + index
}
}
-------------- next part --------------
import QtQuick 2.0
import QtQuick.Controls 2.2
import QtQuick.Shapes 1.0
import Qt.labs.handlers 1.0
Item {
id: draggableObject
property int index: -1
default property alias content: content.children
signal grabChanged(var index);
signal tapped(var index);
width: contentContainer.width
onWidthChanged: {
console.debug('draggable width: ', width);
}
height: contentContainer.height
onHeightChanged: {
console.debug('draggable height: ', height);
}
Item {
id: contentContainer
width: content.width * content.scale
property int prevWidth: width
onWidthChanged: {
console.debug('content width: ', width);
}
height: content.height * content.scale
property int prevHeight: height
onHeightChanged: {
console.debug('content height: ', height);
}
DragHandler {
onGrabChanged: {
draggableObject.grabChanged(index);
}
}
PinchHandler {
target: draggableObject
}
TapHandler {
onTapped: {
draggableObject.tapped(index);
}
}
Item {
id: content
anchors.centerIn: parent
clip: true
width: childrenRect.width
onWidthChanged: {
console.debug('content width: ', width);
}
height: childrenRect.height
onHeightChanged: {
console.debug('content height: ', height);
}
onScaleChanged: {
}
}
Shape {
anchors.centerIn: parent
width: contentContainer.width
height: contentContainer.height
ShapePath {
fillColor: 'transparent'
strokeColor: 'green'
strokeWidth: 4
strokeStyle: ShapePath.DashLine
startX: 0
startY: 0
dashPattern: [ 1, 4 ]
PathLine { x: contentContainer.width; y: 0 }
PathLine { x: contentContainer.width; y: contentContainer.height }
PathLine { x: 0; y: contentContainer.height }
PathLine { x: 0; y: 0 }
}
}
MouseArea {
id: mouseZoomer
anchors.fill: parent
hoverEnabled: false
onPressed: {
mouse.accepted = false
}
onReleased: {
mouse.accepted = false
}
onWheel: {
if(wheel.modifiers & Qt.ControlModifier) {
console.debug('onWheel');
if(wheel.angleDelta.y > 1.0) {
draggableObject.scale *= 1.1
} else {
draggableObject.scale /= 1.1
}
} else {
wheel.accepted = false
}
}
}
}
Button {
anchors.top: contentContainer.bottom
anchors.right: contentContainer.right
scale: 1 / draggableObject.scale
width: 50
height: 50
text: 'button: ' + index
}
}
-------------- next part --------------
import QtQuick 2.0
import QtQuick.Controls 2.2
import QtQuick.Shapes 1.0
import Qt.labs.handlers 1.0
Item {
id: draggableObject
property int index: -1
default property alias content: content.children
signal grabChanged(var index);
signal tapped(var index);
width: contentContainer.width
onWidthChanged: {
console.debug('draggable width: ', width);
}
height: contentContainer.height
onHeightChanged: {
console.debug('draggable height: ', height);
}
Item {
id: contentContainer
width: content.width * content.scale
height: content.height * content.scale
DragHandler {
onGrabChanged: {
draggableObject.grabChanged(index);
}
}
PinchHandler {
target: content
}
TapHandler {
onTapped: {
draggableObject.tapped(index);
}
}
Item {
id: content
anchors.centerIn: parent
clip: true
width: childrenRect.width
onWidthChanged: {
console.debug('content width: ', width);
}
height: childrenRect.height
onHeightChanged: {
console.debug('content height: ', height);
}
onScaleChanged: {
}
}
Shape {
anchors.centerIn: parent
width: contentContainer.width
height: contentContainer.height
ShapePath {
fillColor: 'transparent'
strokeColor: 'green'
strokeWidth: 4
strokeStyle: ShapePath.DashLine
startX: 0
startY: 0
dashPattern: [ 1, 4 ]
PathLine { x: contentContainer.width; y: 0 }
PathLine { x: contentContainer.width; y: contentContainer.height }
PathLine { x: 0; y: contentContainer.height }
PathLine { x: 0; y: 0 }
}
}
MouseArea {
id: mouseZoomer
anchors.fill: parent
hoverEnabled: false
onPressed: {
mouse.accepted = false
}
onReleased: {
mouse.accepted = false
}
onWheel: {
if(wheel.modifiers & Qt.ControlModifier) {
console.debug('onWheel');
if(wheel.angleDelta.y > 1.0) {
content.scale *= 1.1
} else {
content.scale /= 1.1
}
} else {
wheel.accepted = false
}
}
}
}
Button {
anchors.top: contentContainer.bottom
anchors.right: contentContainer.right
width: 50
height: 50
text: 'button: ' + index
}
}
More information about the Interest
mailing list