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.0
18 import Ubuntu.Components 0.1
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 
34  Flickable {
35  id: flickable
36  objectName: "flickable"
37  clip: true // FIXME maybe we can remove this, or just not clip in few cases
38  contentHeight: imageContainer.height
39  contentWidth: imageContainer.width
40 
41  onHeightChanged: image.resetScale()
42  onWidthChanged: image.resetScale()
43  anchors.fill: parent
44 
45  Item {
46  id: imageContainer
47  objectName: "imageContainer"
48  width: Math.max(image.width * image.scale, flickable.width)
49  height: Math.max(image.height * image.scale, flickable.height)
50 
51  Item {
52  id: image
53  objectName: "image"
54  property alias imageStatus: imageRenderer.status
55  property var prevScale
56  anchors.centerIn: parent
57 
58  signal imageReloaded
59 
60  Image {
61  id: imageRenderer
62  objectName: "imageRenderer"
63  smooth: !flickable.movingVertically
64  anchors.fill: parent
65  fillMode: Image.PreserveAspectFit
66 
67  readonly property int sourceSizeMultiplier: 3
68 
69  sourceSize.width: root.width * sourceSizeMultiplier <= root.height * sourceSizeMultiplier ? root.width * sourceSizeMultiplier : 0
70  sourceSize.height: root.height * sourceSizeMultiplier <= root.width * sourceSizeMultiplier ? root.height * sourceSizeMultiplier : 0
71 
72  onStatusChanged: {
73  if (status === Image.Ready) {
74  image.imageReloaded();
75  }
76  }
77  }
78 
79  onImageReloaded: {
80  image.height = imageRenderer.implicitHeight
81  image.width = imageRenderer.implicitWidth
82  image.resetScale();
83  }
84 
85  function resetScale() {
86  image.scale = Math.min(flickable.width / image.width, flickable.height / image.height);
87  pinchArea.minScale = image.scale;
88  prevScale = Math.min(image.scale, 1);
89  }
90 
91  onScaleChanged: {
92  var currentWidth = width * scale
93  var currentHeight = height * scale
94  var scaleRatio = scale / prevScale
95  if (currentWidth > flickable.width) {
96  var xpos = flickable.width / 2 + flickable.contentX;
97  var xoff = xpos * scaleRatio;
98  flickable.contentX = xoff - flickable.width / 2;
99  }
100  if (currentHeight > flickable.height) {
101  var ypos = flickable.height / 2 + flickable.contentY;
102  var yoff = ypos * scaleRatio;
103  flickable.contentY = yoff - flickable.height / 2;
104  }
105  prevScale = scale;
106  }
107  }
108  }
109 
110  PinchArea {
111  id: pinchArea
112  objectName: "pinchArea"
113  property real minScale: 1.0
114  anchors.fill: parent
115  enabled: zoomable ? zoomable : false
116 
117  pinch.target: image
118  pinch.minimumScale: minScale
119  pinch.maximumScale: 10
120 
121  onPinchFinished: flickable.returnToBounds()
122  }
123 
124  MouseArea {
125  id: mouseArea
126  objectName: "mouseArea"
127 
128  anchors.fill: parent
129  enabled: zoomable ? zoomable : false
130 
131  onWheel: {
132  var startScale = image.scale;
133  if (wheel.angleDelta.y > 0) {
134  image.scale = startScale + 0.1;
135  } else if (wheel.angleDelta.y < 0) {
136  if (image.scale > 0.1 && image.scale > pinchArea.minScale) {
137  image.scale = startScale - 0.1;
138  }
139  }
140  wheel.accepted = true;
141  }
142 
143  onPressed: {
144  mouse.accepted = false;
145  }
146 
147  onReleased: {
148  mouse.accepted = false;
149  }
150 
151  onClicked: {
152  mouse.accepted = false;
153  }
154  }
155  }
156 }