Fluid 1.3.0
Material Design for QtQuick and Qml
Loading...
Searching...
No Matches
SmoothFadeImage.qml
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2018 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
2// SPDX-License-Identifier: MPL-2.0
3
4import QtQuick
5
47Item {
48 id: root
49
50 // Images are decorative by default. Set Accessible.name (and optionally
51 // role/ignored) on this item when the image conveys content.
52 Accessible.role: Accessible.Graphic
53 Accessible.ignored: Accessible.name.length === 0
54
62 property url source
63
85 property int fillMode: Image.Stretch
86
91 property int fadeDuration: 250
92
96 readonly property bool running: animation.running
97
104 property bool animationEnabled: true
105
155 property alias sourceSize: __priv.sourceSize
156
165 readonly property int status: __priv.loadingImage ? __priv.loadingImage.status : Image.Null
166
177 property bool smooth: true
178
183 signal imageSwapped
184
185 QtObject {
186 id: __priv
187
188 property size sourceSize: Qt.size(undefined, undefined)
189
190 property Image currentImage: image1
191 property Image nextImage: image2
192 property Image loadingImage: currentImage
193
194 onSourceSizeChanged: {
195 // Change source size for both images
196 image1.sourceSize = sourceSize;
197 image2.sourceSize = sourceSize;
198 }
199
200 function swapImages() {
201 // Swap images stacking order and start fading animation
202 __priv.currentImage.z = 0;
203 __priv.nextImage.z = 1;
204 if (root.animationEnabled)
205 animation.start();
206
207 // Swap images pointers
208 var oldImage = __priv.currentImage;
209 __priv.currentImage = __priv.nextImage;
210 __priv.nextImage = oldImage;
211 }
212 }
213
214 onSourceChanged: {
215 // Set image pointers at creation time
216 if (__priv.currentImage === null) {
217 __priv.currentImage = image1;
218 __priv.nextImage = image2;
219 }
220
221 // Stop the animation if the source is changed while
222 // it's still running
223 animation.stop();
224
225 // Unload both images
226 if (root.source == "") {
227 __priv.currentImage.source = "";
228 __priv.nextImage.source = "";
229 __priv.loadingImage = null;
230 return;
231 }
232
233 if (__priv.currentImage.source == "") {
234 // Assign the source to the current image for the first time
235 __priv.currentImage.source = root.source;
236 __priv.loadingImage = __priv.currentImage;
237 } else {
238 // Image source is changed, make sure the animation is not running
239 animation.stop();
240
241 // Prepare the next image
242 __priv.nextImage.opacity = 0.0;
243 __priv.nextImage.source = root.source;
244 __priv.loadingImage = __priv.nextImage;
245
246 // If the next image is still cached the status will already be Ready
247 // otherwise it's not loaded, either way we need to swap
248 if (__priv.nextImage.status === Image.Ready || __priv.nextImage.source === "")
249 __priv.swapImages();
250 }
251 }
252
253 Connections {
254 target: __priv.nextImage
255
256 function onOpacityChanged() {
257 if (__priv.nextImage.opacity == 1.0)
258 root.imageSwapped();
259 }
260
261 function onStatusChanged() {
262 if (__priv.nextImage.status === Image.Ready)
263 __priv.swapImages();
264 }
265 }
266
267 Image {
268 id: image1
269 anchors.fill: parent
270 cache: false
271 asynchronous: true
272 fillMode: root.fillMode
273 smooth: root.smooth
274 clip: root.clip
275 z: 1
276 Accessible.ignored: true
277 }
278
279 Image {
280 id: image2
281 anchors.fill: parent
282 cache: false
283 asynchronous: true
284 fillMode: root.fillMode
285 smooth: root.smooth
286 clip: root.clip
287 z: 0
288 Accessible.ignored: true
289 }
290
291 NumberAnimation {
292 id: animation
293 target: __priv.nextImage
294 property: "opacity"
295 to: 1.0
296 duration: root.fadeDuration
297 onRunningChanged: {
298 // When the fade animation stops, we unload the second image in
299 // order to save some memory (only one image will be load at a time)
300 if (!running)
301 __priv.nextImage.source = "";
302 }
303 }
304}