Unity 8
ZoomableImage.qml
1 /*
2  * Copyright (C) 2014 Canonical, Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 3.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 import QtQuick 2.4
18 import Ubuntu.Components 1.3
19 import "../Components"
20 
21 /*! \brief Zoomable for image.
22 
23  This widget shows image contained in source,
24  can be zoomable accordingly with zoomable.
25  */
26 
27 Item {
28  id: root
29  property alias source: imageRenderer.source
30  property var zoomable: false
31  property alias imageStatus: imageRenderer.status
32  property alias asynchronous: imageRenderer.asynchronous
33  readonly property alias status: imageRenderer.status
34 
35  Flickable {
36  id: flickable
37  objectName: "flickable"
38  clip: true // FIXME maybe we can remove this, or just not clip in few cases
39  contentHeight: imageContainer.height
40  contentWidth: imageContainer.width
41 
42  onHeightChanged: image.resetScale()
43  onWidthChanged: image.resetScale()
44  anchors.fill: parent
45 
46  Item {
47  id: imageContainer
48  objectName: "imageContainer"
49  width: Math.max(image.width * image.scale, flickable.width)
50  height: Math.max(image.height * image.scale, flickable.height)
51 
52  Item {
53  id: image
54  objectName: "image"
55  property alias imageStatus: imageRenderer.status
56  property var prevScale
57  anchors.centerIn: parent
58 
59  signal imageReloaded
60 
61  Image {
62  id: imageRenderer
63  objectName: "imageRenderer"
64  smooth: !flickable.movingVertically
65  anchors.fill: parent
66  fillMode: Image.PreserveAspectFit
67 
68  readonly property int sourceSizeMultiplier: 3
69 
70  sourceSize.width: root.width * sourceSizeMultiplier <= root.height * sourceSizeMultiplier ? root.width * sourceSizeMultiplier : 0
71  sourceSize.height: root.height * sourceSizeMultiplier <= root.width * sourceSizeMultiplier ? root.height * sourceSizeMultiplier : 0
72 
73  onStatusChanged: {
74  if (status === Image.Ready) {
75  image.imageReloaded();
76  }
77  }
78  }
79 
80  onImageReloaded: {
81  image.height = imageRenderer.implicitHeight
82  image.width = imageRenderer.implicitWidth
83  image.resetScale();
84  }
85 
86  function resetScale() {
87  image.scale = Math.min(flickable.width / image.width, flickable.height / image.height);
88  pinchArea.minScale = image.scale;
89  prevScale = Math.min(image.scale, 1);
90  }
91 
92  onScaleChanged: {
93  var currentWidth = width * scale
94  var currentHeight = height * scale
95  var scaleRatio = scale / prevScale
96  if (currentWidth > flickable.width) {
97  var xpos = flickable.width / 2 + flickable.contentX;
98  var xoff = xpos * scaleRatio;
99  flickable.contentX = xoff - flickable.width / 2;
100  }
101  if (currentHeight > flickable.height) {
102  var ypos = flickable.height / 2 + flickable.contentY;
103  var yoff = ypos * scaleRatio;
104  flickable.contentY = yoff - flickable.height / 2;
105  }
106  prevScale = scale;
107  }
108  }
109  }
110 
111  PinchArea {
112  id: pinchArea
113  objectName: "pinchArea"
114  property real minScale: 1.0
115  anchors.fill: parent
116  enabled: zoomable ? zoomable : false
117 
118  pinch.target: image
119  pinch.minimumScale: minScale
120  pinch.maximumScale: 10
121 
122  onPinchFinished: flickable.returnToBounds()
123  }
124 
125  MouseArea {
126  id: mouseArea
127  objectName: "mouseArea"
128 
129  anchors.fill: parent
130  enabled: zoomable ? zoomable : false
131 
132  onWheel: {
133  var startScale = image.scale;
134  if (wheel.angleDelta.y > 0) {
135  image.scale = startScale + 0.1;
136  } else if (wheel.angleDelta.y < 0) {
137  if (image.scale > 0.1 && image.scale > pinchArea.minScale) {
138  image.scale = startScale - 0.1;
139  }
140  }
141  wheel.accepted = true;
142  }
143 
144  onPressed: {
145  mouse.accepted = false;
146  }
147 
148  onReleased: {
149  mouse.accepted = false;
150  }
151 
152  onClicked: {
153  mouse.accepted = false;
154  }
155  }
156  }
157 }