Created
May 15, 2012 22:40
-
-
Save torarnv/2705676 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
doesn't seem to work in QtQuick 2.0