149 lines
5.2 KiB
QML
149 lines
5.2 KiB
QML
|
import QtQuick 2.6
|
||
|
import Sailfish.Silica 1.0
|
||
|
|
||
|
SilicaFlickable {
|
||
|
// id
|
||
|
id: flickable
|
||
|
// property declarations
|
||
|
property real zoom
|
||
|
property bool zoomed
|
||
|
// override if needed
|
||
|
property bool zoomEnabled: true
|
||
|
property real minimumZoom: fitZoom
|
||
|
property real maximumZoom: 4 //Math.max(fitZoom, 1) * 3
|
||
|
|
||
|
default property alias zoomContentItem: zoomContentItem.data
|
||
|
property alias implicitContentWidth: zoomContentItem.implicitWidth
|
||
|
property alias implicitContentHeight: zoomContentItem.implicitHeight
|
||
|
// factor for "PreserveAspectFit"
|
||
|
readonly property real fitZoom: implicitContentWidth > 0 && implicitContentHeight > 0
|
||
|
? Math.min(maximumZoom, width / implicitContentWidth, height / implicitContentHeight)
|
||
|
: 1.0
|
||
|
readonly property int minimumBoundaryAxis: (implicitContentWidth / implicitContentHeight) > (width / height) ? Qt.Horizontal : Qt.Vertical
|
||
|
|
||
|
// JavaScript functions
|
||
|
function zoomOut(animated) {
|
||
|
if (zoomed) {
|
||
|
if(animated) { zoomOutAnimation.start() }
|
||
|
else {
|
||
|
zoom = fitZoom
|
||
|
zoomed = false
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// object properties
|
||
|
contentWidth: Math.max(width, zoomContentItem.width)
|
||
|
contentHeight: Math.max(height, zoomContentItem.height)
|
||
|
enabled: !zoomOutAnimation.running && implicitContentWidth > 0 && implicitContentHeight > 0
|
||
|
flickableDirection: Flickable.HorizontalAndVerticalFlick
|
||
|
interactive: zoomed
|
||
|
// According to Jolla, otherwise pinching would sometimes not work:
|
||
|
pressDelay: 0
|
||
|
Binding { // Update zoom on orientation changes and set as default
|
||
|
target: flickable
|
||
|
when: !zoomed
|
||
|
property: "zoom"
|
||
|
value: minimumZoom
|
||
|
}
|
||
|
// child objects
|
||
|
|
||
|
PinchArea {
|
||
|
id: pinchArea
|
||
|
parent: flickable.contentItem
|
||
|
width: flickable.contentWidth
|
||
|
height: flickable.contentHeight
|
||
|
enabled: zoomEnabled && minimumZoom !== maximumZoom && flickable.enabled
|
||
|
onPinchUpdated: {
|
||
|
scrollDecoratorTimer.restart()
|
||
|
var f = flickable;
|
||
|
var requestedZoomFactor = 1.0 + pinch.scale - pinch.previousScale;
|
||
|
var previousWidth = f.contentWidth
|
||
|
var previousHeight = f.contentHeight
|
||
|
var targetWidth
|
||
|
var targetHeight
|
||
|
var targetZoom = requestedZoomFactor * f.zoom;
|
||
|
if (targetZoom < f.minimumZoom) {
|
||
|
f.zoom = f.minimumZoom;
|
||
|
f.zoomed = false;
|
||
|
f.contentX = 0;
|
||
|
f.contentY = 0;
|
||
|
return
|
||
|
} else if(targetZoom >= f.maximumZoom) {
|
||
|
f.zoom = f.maximumZoom;
|
||
|
targetHeight = f.implicitContentHeight * f.zoom
|
||
|
targetWidth = f.implicitContentWidth * f.zoom
|
||
|
}
|
||
|
else if(targetZoom < f.maximumZoom) {
|
||
|
if (f.minimumBoundaryAxis == Qt.Horizontal) {
|
||
|
targetWidth = f.contentWidth * requestedZoomFactor
|
||
|
f.zoom = targetWidth / f.implicitContentWidth
|
||
|
targetHeight = f.implicitContentHeight * f.zoom
|
||
|
} else {
|
||
|
targetHeight = f.contentHeight * requestedZoomFactor
|
||
|
f.zoom = targetHeight / f.implicitContentHeight
|
||
|
targetWidth = f.implicitContentWidth * f.zoom
|
||
|
}
|
||
|
}
|
||
|
// calculate center difference
|
||
|
f.contentX += pinch.previousCenter.x - pinch.center.x
|
||
|
f.contentY += pinch.previousCenter.y - pinch.center.y
|
||
|
// move to new (zoomed) center. this jumps a tiny bit, but is bearable:
|
||
|
if (targetWidth > f.width)
|
||
|
f.contentX -= (previousWidth - targetWidth)/(previousWidth/pinch.previousCenter.x)
|
||
|
if (targetHeight > f.height)
|
||
|
f.contentY -= (previousHeight - targetHeight)/(previousHeight/pinch.previousCenter.y)
|
||
|
|
||
|
f.zoomed = true
|
||
|
}
|
||
|
onPinchFinished: {
|
||
|
returnToBounds()
|
||
|
}
|
||
|
Item {
|
||
|
id: zoomContentItem
|
||
|
anchors.centerIn: parent
|
||
|
implicitWidth: flickable.width
|
||
|
implicitHeight: flickable.height
|
||
|
width: Math.ceil(implicitWidth * zoom)
|
||
|
height: Math.ceil(implicitHeight * zoom)
|
||
|
}
|
||
|
}
|
||
|
// enable zoom to minimumZoom on click
|
||
|
ParallelAnimation {
|
||
|
id: zoomOutAnimation
|
||
|
NumberAnimation {
|
||
|
target: flickable
|
||
|
properties: "contentX, contentY"
|
||
|
to: 0
|
||
|
}
|
||
|
NumberAnimation {
|
||
|
target: flickable
|
||
|
property: "zoom"
|
||
|
to: fitZoom
|
||
|
}
|
||
|
onRunningChanged: {
|
||
|
if(!running) {
|
||
|
zoomed = false
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// show scroll decorators when scrolling OR zooming
|
||
|
Timer {
|
||
|
id: scrollDecoratorTimer
|
||
|
readonly property bool moving: flickable.moving
|
||
|
readonly property bool showing: moving || running
|
||
|
onMovingChanged: restart()
|
||
|
interval: 300
|
||
|
}
|
||
|
|
||
|
VerticalScrollDecorator {
|
||
|
flickable: flickable
|
||
|
opacity: scrollDecoratorTimer.showing ? 1.0 : 0.0
|
||
|
}
|
||
|
HorizontalScrollDecorator {
|
||
|
flickable: flickable
|
||
|
opacity: scrollDecoratorTimer.showing ? 1.0 : 0.0
|
||
|
}
|
||
|
}
|