Skip to content

Instantly share code, notes, and snippets.

@torarnv
Created May 15, 2012 22:40
Show Gist options
  • Save torarnv/2705676 to your computer and use it in GitHub Desktop.
Save torarnv/2705676 to your computer and use it in GitHub Desktop.
import QtQuick 1.1
Flickable {
id: flickable
// Updates to the contentWidth and contentHeight of the regular
// flickable will trigger an instant relocation of the content
// item to the bounds of the flickable. We want to controll this
// behavior, so we shadow the properties and ensure they are set
// through the contentItem's own width and hight properties,
// which don't exhibit this behavior.
property real contentWidth
property real contentHeight
onContentWidthChanged: {
resizeContent(contentWidth, contentHeight, "0x0")
}
onContentHeightChanged: {
resizeContent(contentWidth, contentHeight, "0x0")
}
property Item target
onTargetChanged: target.parent = wrapper
property alias effectiveScale: pinchArea.effectiveScale
Item {
id: wrapper
// The wrapper makes sure that the item's bounds appear
// to change during scaling, so that if you anchor other
// items to it they will behave as expected during scaling.
// FIXME: This requires you to anchor to the real item's
// parent. Ideally this would be completely transparent.
width: target.width * scaleTransform.scale
height: target.height * scaleTransform.scale
property alias effectiveScale: pinchArea.effectiveScale
PinchArea {
id: pinchArea
// We let the interaction area cover the flickable instead of
// the item, so it does not matter where or how small the
// real item is.
x: flickable.contentX
y: flickable.contentY
width: flickable.width
height: flickable.height
// FIXME: Add support for these properties
pinch {
minimumScale: 0.5
maximumScale: 2.0
}
property Item target: flickable.target
property real effectiveScale: 1
// FIXME: Do we really need this now that we don't care about the origin?
Scale {
id: scaleTransform
property real scale: 1
origin.x: 0; origin.y: 0
xScale: scale; yScale: scale
}
onTargetChanged: {
target.transform = scaleTransform;
}
// FIXME: Clean up
property real originX
property real originY
property variant intitialContentPosition: Qt.point(flickable.contentItem.x, flickable.contentItem.y)
onPinchStarted: {
console.log("pinch started " + scaleTransform.scale)
if (scaleTransform.scale != 1) {
console.log("Started pinch before previous pinch finished!!!!")
return
}
intitialContentPosition = flickable.contentItem.pos
// If the flickable is interactive while we're pinching
// it will somehow figure out that it should flick if we
// move the touch points too much, and we want full control
// over that behavior.
flickable.interactive = false
//console.log("startCenter: " + pinch.startCenter.x + "," + pinch.startCenter.y)
updateOrigin(pinch.startCenter)
}
function updateOrigin(origin) {
// The pinch area covers the flickable, not the target
// item, so we have to map the origin from our coordinates
// to the target item.
origin = mapToItem(target, origin.x, origin.y)
// But if the target is smaller than the pinch area, we
// don't want to pinch with the center outside the target.
origin.x = Math.max(0, Math.min(origin.x, target.width))
origin.y = Math.max(0, Math.min(origin.y, target.height))
originX = origin.x
originY = origin.y
}
onPinchUpdated: {
if (false) {
console.log("pinch updated: " + pinch.point1.x + "," + pinch.point1.y
+ " " + pinch.point2.x + "," + pinch.point2.y + " scale: " + pinch.scale
+ " transformScale: " + scaleTransform.scale)
}
if (pinch.point1.x == pinch.point2.x && pinch.point1.y == pinch.point2.y) {
if (scaleTransform.scale != 1) {
// The user has released one finger, at which point we want to
// commit the scale, but not return the item to the bounds in
// case the user wants to continue pinching or panning.
commitScale();
}
return;
}
// FIXME: Support panning while pinching (dragAxis support)
// We can do this by using pinch.center, or manually moving the content item
// based on previousCenter. Dunno what the best approach is.
scaleTransform.scale = pinch.scale
// FIXME: For some reason the mapping inside updateOrigin will
// produce jittering values for the same input value, during
// pinching. And if making a sudden flick movement the values
// will be way outside the item. One fix would be to cap the
// origin values to be within the items bounds. But since we
// don't support panning while pinching right now anyways, we
// don't need to continousuly update the center.
// updateOrigin(pinch.startCenter)
flickable.contentItem.pos = Qt.point(
intitialContentPosition.x - originX * (scaleTransform.scale - 1),
intitialContentPosition.y - originY * (scaleTransform.scale - 1))
}
onPinchFinished: {
console.log("pinch finished")
commitScale();
// Returning to bounds takes 400ms, and if the user starts
// another pinch in that time window the pinch gesture will
// conflict with the moving of the content item. To solve this
// we should probably have our own animation to return to
// bounds, and make sure to stop it if we detect another
// scale change starting.
flickable.returnToBounds()
flickable.interactive = true
}
function commitScale() {
if (scaleTransform.scale == 1)
return
console.log("committing...")
flickable.contentX = -flickable.contentItem.x
flickable.contentY = -flickable.contentItem.y
target.width *= scaleTransform.scale
target.height *= scaleTransform.scale
effectiveScale *= scaleTransform.scale
scaleTransform.scale = 1;
}
MouseArea {
// FIXME: If the scaleable item has it's own mouse handling
// it will block this mouse area, and the user has to call
// zoomAtPosition manually.
anchors.fill: parent
onDoubleClicked: flickable.zoomAtPosition(mouse)
}
}
}
SequentialAnimation {
id: zoomAnimation
property double targetScale: 1.0
property variant targetPosition: Qt.point(0, 0)
property int duration: 250
property variant easing: Easing.InOutQuad
ParallelAnimation {
NumberAnimation {
target: scaleTransform; property: "scale";
duration: zoomAnimation.duration; easing.type: zoomAnimation.easing
to: zoomAnimation.targetScale
}
NumberAnimation {
target: flickable.contentItem; property: "x"
duration: zoomAnimation.duration; easing.type: zoomAnimation.easing
to: zoomAnimation.targetPosition.x
}
NumberAnimation {
target: flickable.contentItem; property: "y"
duration: zoomAnimation.duration; easing.type: zoomAnimation.easing
to: zoomAnimation.targetPosition.y
}
}
ScriptAction {
script: {
pinchArea.commitScale()
flickable.returnToBounds()
}
}
}
function zoomAtPosition(position) {
pinchArea.updateOrigin(position)
if (Math.round(pinchArea.effectiveScale) === 1)
zoomAnimation.targetScale = pinchArea.pinch.maximumScale
else
zoomAnimation.targetScale = 1 / pinchArea.effectiveScale;
zoomAnimation.targetPosition = Qt.point(
flickable.contentItem.pos.x - pinchArea.originX * (zoomAnimation.targetScale - 1),
flickable.contentItem.pos.y - pinchArea.originY * (zoomAnimation.targetScale - 1))
zoomAnimation.restart()
}
}
import QtQuick 1.1
Rectangle {
width: rect.width + rect.anchors.margins * 2
height: rect.height + rect.anchors.margins * 2
Rectangle {
id: rect
clip: true
color: "#444"
width: 500; height: 500
anchors {
fill: parent
margins: 10
}
ScaleArea {
id: scaleArea
anchors.fill: parent
contentWidth: scalableCat.width
contentHeight: scalableCat.height
target: scalableCat
Image {
id: scalableCat
source: "http://placekitten.com/300/300"
onWidthChanged: console.log(width)
MouseArea {
enabled: false
anchors.fill: parent
onClicked: console.log("meow!")
// We have to manually trigger a zoom since we have
// our own mouse area.
onDoubleClicked: scaleArea.zoomAtPosition(mouse)
}
Rectangle {
anchors.fill: parent
radius: width
color: "magenta"
opacity: 0.3
}
Rectangle {
radius: width
anchors.fill: parent
border.width: 5 * scaleArea.targetScale
color: "transparent"
}
Text {
anchors.centerIn: parent
font.pointSize: 40 * scaleArea.targetScale
text: "Meow!"
}
}
Image {
source: "http://placekitten.com/g/500/50"
anchors {
horizontalCenter: scalableCat.parent.horizontalCenter
bottom: scalableCat.parent.top
}
}
Image {
source: "http://placekitten.com/g/400/200"
anchors.top: scalableCat.parent.bottom
}
Image {
source: "http://placekitten.com/g/100/500"
anchors.right: scalableCat.parent.left
}
Image {
source: "http://placekitten.com/g/100/300"
anchors.left: scalableCat.parent.right
}
}
}
Rectangle {
anchors.fill: rect
border.width: 1
color: "transparent"
}
Component.onCompleted: {
console.log("Reload")
}
}
@fferri
Copy link

fferri commented Oct 8, 2017

doesn't seem to work in QtQuick 2.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment