MauiKit Controls

ImageViewer.qml
1/*
2 * SPDX-FileCopyrightText: (C) 2015 Vishesh Handa <vhanda@kde.org>
3 * SPDX-FileCopyrightText: (C) 2017 Atul Sharma <atulsharma406@gmail.com>
4 * SPDX-FileCopyrightText: (C) 2017 Marco Martin <mart@kde.org>
5 *
6 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
7 */
8
9import QtQuick
10import QtQuick.Controls
11import org.mauikit.controls as Maui
12
13/**
14 * @inherit QtQuick.Flickable
15 * @brief A view container for displaying images.
16 *
17 * <a href="https://doc.qt.io/qt-6/qml-qtquick-controls-flickable.html">This controls inherits from QQC2 Flickable, to checkout its inherited properties refer to the Qt Docs.</a>
18 *
19 * This control along with the AnimatedImageViewer are meant to display images, with support for zooming in and out with touch or mouse gestures, and keyboard shortcuts.
20 *
21 */
22Flickable
23{
24 id: flick
25
26 contentWidth: width
27 contentHeight: height
28 boundsBehavior: Flickable.StopAtBounds
29 boundsMovement: Flickable.StopAtBounds
30 interactive: contentWidth > width || contentHeight > height
31 clip: false
32
33 ScrollBar.vertical: ScrollBar
34 {
35 visible: false
36 }
37
38 ScrollBar.horizontal: ScrollBar
39 {
40 visible: false
41 }
42
43 /**
44 * @brief This an alias to the actual control painting the image.
45 * This control is handled by a QQC2 Image.
46 * @note See Qt documentation for more information about the Image control.
47 * @property Image ImageViewer::image
48 */
49 property alias image: image
50
51 /**
52 * @brief The painted size of the image.
53 * As taken from Qt documentation: This property holds the scaled width and height of the full-frame image.
54 * Unlike the width and height properties, which scale the painting of the image, this property sets the maximum number of pixels stored for the loaded image so that large images do not use more memory than necessary.
55 * @property size ImageViewer::sourceSize
56 */
57 property alias sourceSize : image.sourceSize
58
59 /**
60 * @brief The fill mode of the image. The possible values can be found on the Image control documentation from Qt.
61 * By default this is set to `Image.PreserveAspectFit`.
62 * @property enumaration ImageViewer::fillMode
63 */
64 property alias fillMode: image.fillMode
65
66 /**
67 * @brief Whether the image should be loaded asynchronously.
68 * By default this is set to `true`.
69 * @property bool ImageViewer::asynchronous
70 */
71 property alias asynchronous : image.asynchronous
72
73 /**
74 * @brief If the image should be cached in memory.
75 * The default value is set to `true`
76 * @property bool ImageViewer::cache
77 */
78 property alias cache: image.cache
79
80 /**
81 * @brief The painted width of the image. This the same as using the image `sourceSize` property to set the width.
82 * @property int ImageViewer::imageWidth
83 */
84 property alias imageWidth: image.sourceSize.width
85
86 /**
87 * @brief The painted height of the image. This the same as using the image `sourceSize` property to set the height.
88 * @property int ImageViewer::imageHeight
89 */
90 property alias imageHeight: image.sourceSize.height
91
92 /**
93 * @brief The source of the image. Can be a remote or local file URL.
94 * @property url ImageViewer::source
95 */
96 property alias source : image.source
97
98 /**
99 * @brief Emitted when the image area has been right clicked with a mouse event.
100 */
101 signal rightClicked()
102
103 /**
104 * @brief Emitted when the image area has been pressed for a few seconds.
105 */
106 signal pressAndHold()
107
108 PinchArea
109 {
110 width: Math.max(flick.contentWidth, flick.width)
111 height: Math.max(flick.contentHeight, flick.height)
112
113 property real initialWidth
114 property real initialHeight
115
116 onPinchStarted: {
117 initialWidth = flick.contentWidth
118 initialHeight = flick.contentHeight
119 }
120
121 onPinchUpdated: (pinch) => {
122 // adjust content pos due to drag
123 flick.contentX += pinch.previousCenter.x - pinch.center.x
124 flick.contentY += pinch.previousCenter.y - pinch.center.y
125
126 // resize content
127 flick.resizeContent(Math.max(flick.width*0.7, initialWidth * pinch.scale), Math.max(flick.height*0.7, initialHeight * pinch.scale), pinch.center)
128 }
129
130 onPinchFinished: {
131 // Move its content within bounds.
132 if (flick.contentWidth < flick.width ||
133 flick.contentHeight < flick.height) {
134 zoomAnim.x = 0;
135 zoomAnim.y = 0;
136 zoomAnim.width = flick.width;
137 zoomAnim.height = flick.height;
138 zoomAnim.running = true;
139 } else {
140 flick.returnToBounds();
141 }
142 }
143
144 ParallelAnimation {
145 id: zoomAnim
146 property real x: 0
147 property real y: 0
148 property real width: flick.width
149 property real height: flick.height
150 NumberAnimation {
151 target: flick
152 property: "contentWidth"
153 from: flick.contentWidth
154 to: zoomAnim.width
155 duration: Maui.Style.units.longDuration
156 easing.type: Easing.InOutQuad
157 }
158 NumberAnimation {
159 target: flick
160 property: "contentHeight"
161 from: flick.contentHeight
162 to: zoomAnim.height
163 duration: Maui.Style.units.longDuration
164 easing.type: Easing.InOutQuad
165 }
166 NumberAnimation {
167 target: flick
168 property: "contentY"
169 from: flick.contentY
170 to: zoomAnim.y
171 duration: Maui.Style.units.longDuration
172 easing.type: Easing.InOutQuad
173 }
174 NumberAnimation {
175 target: flick
176 property: "contentX"
177 from: flick.contentX
178 to: zoomAnim.x
179 duration: Maui.Style.units.longDuration
180 easing.type: Easing.InOutQuad
181 }
182 }
183
184 Image
185 {
186 id: image
187 width: flick.contentWidth
188 height: flick.contentHeight
189 fillMode: Image.PreserveAspectFit
190 autoTransform: true
191 asynchronous: true
192
193 Maui.ProgressIndicator
194 {
195 width: parent.width
196 anchors.bottom: parent.bottom
197 visible: image.status === Image.Loading
198 }
199
200 Maui.Holder
201 {
202 anchors.fill: parent
203 visible: image.status === Image.Error || image.status === Image.Null
204 title: i18nd("mauikit", "Oops!")
205 body: i18nd("mauikit", "The image could not be loaded.")
206 emoji: "qrc:/assets/dialog-information.svg"
207 }
208
209 MouseArea {
210 anchors.fill: parent
211 acceptedButtons: Qt.RightButton | Qt.LeftButton
212 onClicked: (mouse) =>
213 {
214 if(!Maui.Handy.isMobile && mouse.button === Qt.RightButton)
215 {
216 flick.rightClicked()
217 }
218 }
219
220 onPressAndHold: flick.pressAndHold()
221
222 onDoubleClicked: (mouse) =>
223 {
224 if (flick.interactive)
225 {
226 zoomAnim.x = 0;
227 zoomAnim.y = 0;
228 zoomAnim.width = flick.width;
229 zoomAnim.height = flick.height;
230 zoomAnim.running = true;
231 flick.interactive = !flick.interactive
232 } else
233 {
234 zoomAnim.x = mouse.x * 2;
235 zoomAnim.y = mouse.y *2;
236 zoomAnim.width = flick.width * 3;
237 zoomAnim.height = flick.height * 3;
238 zoomAnim.running = true;
239 flick.interactive = !flick.interactive
240 }
241 }
242
243 onWheel: (wheel) =>
244 {
245 if (wheel.modifiers & Qt.ControlModifier) {
246 if (wheel.angleDelta.y != 0) {
247 var factor = 1 + wheel.angleDelta.y / 600;
248 zoomAnim.running = false;
249
250 zoomAnim.width = Math.min(Math.max(flick.width, zoomAnim.width * factor), flick.width * 4);
251 zoomAnim.height = Math.min(Math.max(flick.height, zoomAnim.height * factor), flick.height * 4);
252
253 //actual factors, may be less than factor
254 var xFactor = zoomAnim.width / flick.contentWidth;
255 var yFactor = zoomAnim.height / flick.contentHeight;
256
257 zoomAnim.x = flick.contentX * xFactor + (((wheel.x - flick.contentX) * xFactor) - (wheel.x - flick.contentX))
258 zoomAnim.y = flick.contentY * yFactor + (((wheel.y - flick.contentY) * yFactor) - (wheel.y - flick.contentY))
259 zoomAnim.running = true;
260
261 } else if (wheel.pixelDelta.y != 0) {
262 flick.resizeContent(Math.min(Math.max(flick.width, flick.contentWidth + wheel.pixelDelta.y), flick.width * 4),
263 Math.min(Math.max(flick.height, flick.contentHeight + wheel.pixelDelta.y), flick.height * 4),
264 wheel);
265 }
266 } else {
267
268 if(zoomAnim.width !== flick.contentWidth || zoomAnim.height !== flick.contentHeight)
269 {
270 flick.contentX += wheel.pixelDelta.x;
271 flick.contentY += wheel.pixelDelta.y;
272 }else
273 {
274 wheel.accepted = false
275 }
276 }
277 }
278 }
279 }
280 }
281
282 /**
283 * @brief Forces the image to fit in the viewport.
284 */
285 function fit()
286 {
287 image.width = image.sourceSize.width
288 }
289
290 /**
291 * @brief Forces the image to fill-in the viewport, this is done horizontally, so the image might be out of view vertically.
292 */
293 function fill()
294 {
295 image.width = parent.width
296 }
297
298 /**
299 * @brief Forces the image to be rotated 90 degrees to the left.
300 */
301 function rotateLeft()
302 {
303 image.rotation = image.rotation - 90
304 }
305
306 /**
307 * @brief Forces the image to be rotated 90 degrees to the right.
308 */
309 function rotateRight()
310 {
311 image.rotation = image.rotation + 90
312 }
313}
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 8 2024 11:48:43 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.