Lomiri
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 Lomiri.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 useLomiriShape: 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: useLomiriShape
58  visible: opacity != 0
59  sourceComponent: LomiriShape {
60  aspect: LomiriShape.Flat
61  backgroundColor: "#22FFFFFF"
62  }
63 
64  ActivityIndicator {
65  id: activity
66  anchors.centerIn: parent
67  opacity: 0
68  visible: opacity != 0
69 
70  running: visible
71  }
72 
73  Image {
74  id: errorImage
75  objectName: "errorImage"
76  anchors.centerIn: parent
77  opacity: 0
78  visible: opacity != 0
79 
80  source: "graphics/close.png"
81  sourceSize { width: units.gu(3); height: units.gu(3) }
82  }
83  }
84 
85  Loader {
86  id: shape
87  objectName: "shape"
88  height: root.initialHeight
89  width: root.initialWidth
90  anchors.centerIn: root.scaleTo == "fit" ? parent : undefined
91  active: useLomiriShape
92  opacity: 0
93  visible: opacity != 0
94  sourceComponent: LomiriShapeOverlay {
95  property bool pressed: false
96  aspect: LomiriShape.Flat
97  overlayColor: Qt.rgba(0, 0, 0, pressed ? 0.1 : 0)
98  overlayRect: Qt.rect(0.0, 0.0, 1.0, 1.0)
99  }
100  onLoaded: {
101  item.source = image;
102  item.pressed = Qt.binding(function() { return root.pressed; });
103  }
104 
105  Image {
106  id: image
107  objectName: "image"
108 
109  property url nextSource
110  property string format: image.implicitWidth > image.implicitHeight ? "landscape" : "portrait"
111 
112  anchors.fill: parent
113  visible: !useLomiriShape
114  fillMode: Image.PreserveAspectFit
115  asynchronous: true
116  cache: false
117  sourceSize.width: root.scaleTo == "width" ? root.width
118  : root.scaleTo == "fit" && root.width <= root.height ? root.width
119  : 0
120  sourceSize.height: root.scaleTo == "height" ? root.height
121  : root.scaleTo == "fit" && root.height <= root.width ? root.height
122  : 0
123  }
124  }
125 
126  states: [
127  State {
128  name: "default"
129  when: image.source == ""
130  PropertyChanges { target: root; implicitWidth: root.initialWidth; implicitHeight: root.initialHeight }
131  PropertyChanges { target: errorImage; opacity: 0 }
132  },
133  State {
134  name: "loading"
135  extend: "default"
136  when: image.status === Image.Loading
137  PropertyChanges { target: activity; opacity: 1 }
138  },
139  State {
140  name: "ready"
141  when: image.status === Image.Ready && image.source != ""
142  PropertyChanges { target: root; implicitWidth: shape.width; implicitHeight: shape.height }
143  PropertyChanges { target: placeholder; opacity: 0 }
144  PropertyChanges { target: shape; opacity: 1
145  width: root.scaleTo == "width" || (root.scaleTo == "fit" && image.format == "landscape") ? root.width
146  : root.scaleTo == "" ? image.implicitWidth : image.implicitWidth * height / image.implicitHeight
147  height: root.scaleTo == "height" || (root.scaleTo == "fit" && image.format == "portrait") ? root.height
148  : root.scaleTo == "" ? image.implicitHeight : image.implicitHeight * width / image.implicitWidth
149  }
150  },
151  State {
152  name: "error"
153  extend: "default"
154  when: image.status === Image.Error
155  PropertyChanges { target: errorImage; opacity: 1.0 }
156  }
157  ]
158 
159  transitions: [
160  Transition {
161  to: "ready"
162  objectName: "readyTransition"
163  SequentialAnimation {
164  PropertyAction { target: shape; property: "visible" }
165  ParallelAnimation {
166  NumberAnimation { target: shape; property: "opacity"; easing.type: Easing.Linear }
167  LomiriNumberAnimation { target: root; properties: "implicitWidth,implicitHeight" }
168  LomiriNumberAnimation { target: shape; properties: "width,height" }
169  NumberAnimation {
170  targets: [placeholder, activity, errorImage]; property: "opacity";
171  easing.type: Easing.Linear; duration: LomiriAnimation.SnapDuration
172  }
173  }
174  ScriptAction { script: { lastScaledDimension = scaleTo == "height" || scaleTo == "fit" ? root.width : root.height } }
175  }
176  },
177 
178  Transition {
179  to: "*"
180  objectName: "genericTransition"
181  SequentialAnimation {
182  ParallelAnimation {
183  NumberAnimation { target: shape; property: "opacity"; easing.type: Easing.Linear }
184  NumberAnimation {
185  targets: [placeholder, activity, errorImage]; property: "opacity";
186  easing.type: Easing.Linear; duration: LomiriAnimation.SnapDuration
187  }
188  LomiriNumberAnimation { target: root; properties: "implicitWidth,implicitHeight" }
189  LomiriNumberAnimation { target: shape; properties: "width,height" }
190  }
191  PropertyAction { target: shape; property: "visible" }
192  }
193 
194  onRunningChanged: {
195  if (!running && state === "default" && image.nextSource !== "") {
196  image.source = image.nextSource;
197  image.nextSource = "";
198  }
199  }
200  }
201  ]
202 }