MauiKit Controls

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

KDE's Doxygen guidelines are available online.