Unity 8
LazyImage.qml
1 /*
2  * Copyright (C) 2013-2016 Canonical, Ltd.
3  *
4  * Authors:
5  * MichaƂ Sawicz <michal.sawicz@canonical.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; version 3.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 import QtQuick 2.4
21 import Ubuntu.Components 1.3
22 
23 Item {
24  id: root
25 
26  property url source
27  // TODO convert into enums when available in QML
28  property string scaleTo
29 
30  property real initialWidth: scaleTo == "width" || scaleTo == "fit" ? width : units.gu(10)
31  property real initialHeight: scaleTo == "height" || scaleTo == "fit" ? height : units.gu(10)
32  property real lastScaledDimension: scaleTo == "height" || scaleTo == "fit" ? width : height
33 
34  property alias sourceSize: image.sourceSize
35  property alias asynchronous: image.asynchronous
36  property alias cache: image.cache
37  property alias sourceImage: image
38 
39  property bool useUbuntuShape: true
40  property bool pressed: false
41 
42  state: "default"
43 
44  onSourceChanged: {
45  if (state === "ready") {
46  state = "default";
47  image.nextSource = source;
48  } else {
49  image.source = source;
50  }
51  }
52 
53  Loader {
54  id: placeholder
55  objectName: "placeholder"
56  anchors.fill: shape
57  active: useUbuntuShape
58  visible: opacity != 0
59  sourceComponent: UbuntuShape {
60  backgroundColor: "#22FFFFFF"
61  }
62 
63  ActivityIndicator {
64  id: activity
65  anchors.centerIn: parent
66  opacity: 0
67  visible: opacity != 0
68 
69  running: visible
70  }
71 
72  Image {
73  id: errorImage
74  objectName: "errorImage"
75  anchors.centerIn: parent
76  opacity: 0
77  visible: opacity != 0
78 
79  source: "graphics/close.png"
80  sourceSize { width: units.gu(3); height: units.gu(3) }
81  }
82  }
83 
84  Loader {
85  id: shape
86  objectName: "shape"
87  height: root.initialHeight
88  width: root.initialWidth
89  anchors.centerIn: root.scaleTo == "fit" ? parent : undefined
90  active: useUbuntuShape
91  opacity: 0
92  visible: opacity != 0
93  sourceComponent: UbuntuShapeOverlay {
94  property bool pressed: false
95  overlayColor: Qt.rgba(0, 0, 0, pressed ? 0.1 : 0)
96  overlayRect: Qt.rect(0.0, 0.0, 1.0, 1.0)
97  }
98  onLoaded: {
99  item.source = image;
100  item.pressed = Qt.binding(function() { return root.pressed; });
101  }
102 
103  Image {
104  id: image
105  objectName: "image"
106 
107  property url nextSource
108  property string format: image.implicitWidth > image.implicitHeight ? "landscape" : "portrait"
109 
110  anchors.fill: parent
111  visible: !useUbuntuShape
112  fillMode: Image.PreserveAspectFit
113  asynchronous: true
114  cache: false
115  sourceSize.width: root.scaleTo == "width" ? root.width
116  : root.scaleTo == "fit" && root.width <= root.height ? root.width
117  : 0
118  sourceSize.height: root.scaleTo == "height" ? root.height
119  : root.scaleTo == "fit" && root.height <= root.width ? root.height
120  : 0
121  }
122  }
123 
124  states: [
125  State {
126  name: "default"
127  when: image.source == ""
128  PropertyChanges { target: root; implicitWidth: root.initialWidth; implicitHeight: root.initialHeight }
129  PropertyChanges { target: errorImage; opacity: 0 }
130  },
131  State {
132  name: "loading"
133  extend: "default"
134  when: image.status === Image.Loading
135  PropertyChanges { target: activity; opacity: 1 }
136  },
137  State {
138  name: "ready"
139  when: image.status === Image.Ready && image.source != ""
140  PropertyChanges { target: root; implicitWidth: shape.width; implicitHeight: shape.height }
141  PropertyChanges { target: placeholder; opacity: 0 }
142  PropertyChanges { target: shape; opacity: 1
143  width: root.scaleTo == "width" || (root.scaleTo == "fit" && image.format == "landscape") ? root.width
144  : root.scaleTo == "" ? image.implicitWidth : image.implicitWidth * height / image.implicitHeight
145  height: root.scaleTo == "height" || (root.scaleTo == "fit" && image.format == "portrait") ? root.height
146  : root.scaleTo == "" ? image.implicitHeight : image.implicitHeight * width / image.implicitWidth
147  }
148  },
149  State {
150  name: "error"
151  extend: "default"
152  when: image.status === Image.Error
153  PropertyChanges { target: errorImage; opacity: 1.0 }
154  }
155  ]
156 
157  transitions: [
158  Transition {
159  to: "ready"
160  objectName: "readyTransition"
161  SequentialAnimation {
162  PropertyAction { target: shape; property: "visible" }
163  ParallelAnimation {
164  NumberAnimation { target: shape; property: "opacity"; easing.type: Easing.Linear }
165  UbuntuNumberAnimation { target: root; properties: "implicitWidth,implicitHeight" }
166  UbuntuNumberAnimation { target: shape; properties: "width,height" }
167  NumberAnimation {
168  targets: [placeholder, activity, errorImage]; property: "opacity";
169  easing.type: Easing.Linear; duration: UbuntuAnimation.SnapDuration
170  }
171  }
172  ScriptAction { script: { lastScaledDimension = scaleTo == "height" || scaleTo == "fit" ? root.width : root.height } }
173  }
174  },
175 
176  Transition {
177  to: "*"
178  objectName: "genericTransition"
179  SequentialAnimation {
180  ParallelAnimation {
181  NumberAnimation { target: shape; property: "opacity"; easing.type: Easing.Linear }
182  NumberAnimation {
183  targets: [placeholder, activity, errorImage]; property: "opacity";
184  easing.type: Easing.Linear; duration: UbuntuAnimation.SnapDuration
185  }
186  UbuntuNumberAnimation { target: root; properties: "implicitWidth,implicitHeight" }
187  UbuntuNumberAnimation { target: shape; properties: "width,height" }
188  }
189  PropertyAction { target: shape; property: "visible" }
190  }
191 
192  onRunningChanged: {
193  if (!running && state === "default" && image.nextSource !== "") {
194  image.source = image.nextSource;
195  image.nextSource = "";
196  }
197  }
198  }
199  ]
200 }