Unity 8
Infographics.qml
1 /*
2  * Copyright (C) 2013 Canonical, Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 3.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 import "../Components"
18 import "Gradient.js" as Gradient
19 import QtQuick 2.0
20 import Ubuntu.Components 0.1
21 
22 Item {
23  id: infographic
24 
25  property var model
26 
27  property int animDuration: 10
28 
29  QtObject {
30  id: d
31  objectName: "infographicPrivate"
32  property bool useDotAnimation: true
33  property int circleModifier: useDotAnimation ? 1 : 2
34  property bool animating: dotHideAnimTimer.running
35  || dotShowAnimTimer.running
36  || circleChangeAnimTimer.running
37  }
38 
39  Connections {
40  target: model
41 
42  onDataAboutToAppear: startHideAnimation() // hide "no data" label
43  onDataAppeared: startShowAnimation()
44 
45  onDataAboutToChange: startHideAnimation()
46  onDataChanged: startShowAnimation()
47 
48  onDataAboutToDisappear: startHideAnimation()
49  onDataDisappeared: startShowAnimation() // show "no data" label
50  }
51 
52  function startShowAnimation() {
53  dotHideAnimTimer.stop()
54  notification.hideAnim.stop()
55 
56  if (d.useDotAnimation) {
57  dotShowAnimTimer.startFromBeginning()
58  }
59  notification.showAnim.start()
60  }
61 
62  function startHideAnimation() {
63  dotShowAnimTimer.stop()
64  circleChangeAnimTimer.stop()
65  notification.showAnim.stop()
66 
67  if (d.useDotAnimation) {
68  dotHideAnimTimer.startFromBeginning()
69  } else {
70  circleChangeAnimTimer.startFromBeginning()
71  }
72  notification.hideAnim.start()
73  }
74 
75  visible: model.label !== ""
76 
77  Component.onCompleted: startShowAnimation()
78 
79  Item {
80  id: dataCircle
81  objectName: "dataCircle"
82 
83  property real divisor: 1.5
84 
85  width: Math.min(parent.height, parent.width) / divisor
86  height: width
87 
88  anchors.centerIn: parent
89 
90  Timer {
91  id: circleChangeAnimTimer
92 
93  property int pastCircleCounter
94  property int presentCircleCounter
95 
96  interval: notification.duration
97  running: false
98  repeat: true
99  onTriggered: {
100  if (pastCircleCounter < pastCircles.count) {
101  var nextCircle = pastCircles.itemAt(pastCircleCounter++)
102  if (nextCircle !== null) nextCircle.pastCircleChangeAnim.start()
103  }
104  if (pastCircleCounter > pastCircles.count / 2) {
105  var nextCircle = presentCircles.itemAt(presentCircleCounter++)
106  if (nextCircle !== null) nextCircle.presentCircleChangeAnim.start()
107  }
108  if (presentCircleCounter > infographic.model.currentDay && pastCircleCounter >= pastCircles.count) {
109  stop()
110  }
111  }
112 
113  function startFromBeginning() {
114  circleChangeAnimTimer.pastCircleCounter = 0
115  circleChangeAnimTimer.presentCircleCounter = 0
116  start()
117  }
118  }
119 
120  Repeater {
121  id: pastCircles
122  objectName: "pastCircles"
123  model: infographic.model.secondMonth
124 
125  delegate: ObjectPositioner {
126  property alias pastCircleChangeAnim: pastCircleChangeAnim
127 
128  index: model.index
129  count: pastCircles.count
130  radius: dataCircle.width / 2
131  halfSize: pastCircle.width / 2
132  posOffset: 0.0
133 
134  Circle {
135  id: pastCircle
136  objectName: "pastCircle" + index
137 
138  property real divisor: 1.8
139  property real circleOpacity: 0.1
140 
141  width: dataCircle.width / divisor
142  height: dataCircle.height / divisor
143  opacity: 0.0
144  scale: 0.0
145  visible: modelData !== undefined
146  color: "transparent"
147 
148  SequentialAnimation {
149  id: pastCircleChangeAnim
150 
151  loops: 1
152  ParallelAnimation {
153  PropertyAnimation {
154  target: pastCircle
155  property: "opacity"
156  to: pastCircle.circleOpacity
157  easing.type: Easing.OutCurve
158  duration: circleChangeAnimTimer.interval * d.circleModifier
159  }
160  PropertyAnimation {
161  target: pastCircle
162  property: "scale"
163  to: modelData
164  easing.type: Easing.OutCurve
165  duration: circleChangeAnimTimer.interval * d.circleModifier
166  }
167  ColorAnimation {
168  target: pastCircle
169  property: "color"
170  to: Gradient.threeColorByIndex(index, count, infographic.model.secondColor)
171  easing.type: Easing.OutCurve
172  duration: circleChangeAnimTimer.interval * d.circleModifier
173  }
174  }
175  }
176  }
177  }
178  }
179 
180  Repeater {
181  id: presentCircles
182  objectName: "presentCircles"
183  model: infographic.model.firstMonth
184 
185  delegate: ObjectPositioner {
186  property alias presentCircleChangeAnim: presentCircleChangeAnim
187 
188  index: model.index
189  count: presentCircles.count
190  radius: dataCircle.width / 2
191  halfSize: presentCircle.width / 2
192  posOffset: 0.0
193 
194  Circle {
195  id: presentCircle
196  objectName: "presentCircle" + index
197 
198  property real divisor: 1.8
199  property real circleOpacity: 0.3
200 
201  width: dataCircle.width / divisor
202  height: dataCircle.height / divisor
203  opacity: 0.0
204  scale: 0.0
205  visible: modelData !== undefined
206  color: "transparent"
207 
208  SequentialAnimation {
209  id: presentCircleChangeAnim
210 
211  loops: 1
212 
213  ParallelAnimation {
214  PropertyAnimation {
215  target: presentCircle
216  property: "opacity"
217  to: presentCircle.circleOpacity
218  easing.type: Easing.OutCurve
219  duration: circleChangeAnimTimer.interval * d.circleModifier
220  }
221  PropertyAnimation {
222  target: presentCircle
223  property: "scale"
224  to: modelData
225  easing.type: Easing.OutCurve
226  duration: circleChangeAnimTimer.interval * d.circleModifier
227  }
228  ColorAnimation {
229  target: presentCircle
230  property: "color"
231  to: Gradient.threeColorByIndex(index, infographic.model.currentDay, infographic.model.firstColor)
232  easing.type: Easing.OutCurve
233  duration: circleChangeAnimTimer.interval * d.circleModifier
234  }
235  }
236  }
237  }
238  }
239  }
240 
241  Image {
242  id: backgroundCircle
243  objectName: "backgroundCircle"
244 
245  anchors.fill: parent
246 
247  source: "graphics/infographic_circle_back.png"
248  }
249 
250  Timer {
251  id: dotShowAnimTimer
252 
253  property int dotCounter: 0
254 
255  interval: animDuration * 0.5; running: false; repeat: true
256  onTriggered: {
257  if (dotCounter < dots.count) {
258  var nextDot = dots.itemAt(dotCounter);
259  if (nextDot) {
260  nextDot.unlockAnimation.start();
261  if (++dotCounter == Math.round(dots.count / 2)) {
262  circleChangeAnimTimer.startFromBeginning();
263  }
264  }
265  } else {
266  stop()
267  }
268  }
269 
270  function startFromBeginning() {
271  if (!dotShowAnimTimer.running)
272  dotCounter = 0
273 
274  start()
275  }
276  }
277 
278  Timer {
279  id: dotHideAnimTimer
280 
281  property int dotCounter
282 
283  interval: animDuration * 0.5
284  running: false
285  repeat: true
286  onTriggered: {
287  if (dotCounter >= 0) {
288  var nextDot = dots.itemAt(dotCounter--)
289  nextDot.changeAnimation.start()
290  } else {
291  stop()
292  }
293  if (dotCounter == 0) {
294  infographic.model.readyForDataChange()
295  }
296  }
297 
298  function startFromBeginning() {
299  if (!dotHideAnimTimer.running)
300  dotCounter = dots.count - 1
301 
302  start()
303  }
304  }
305 
306  Repeater {
307  id: dots
308  objectName: "dots"
309 
310  model: infographic.model.firstMonth
311 
312  delegate: ObjectPositioner {
313  property alias unlockAnimation: dotUnlockAnim
314  property alias changeAnimation: dotChangeAnim
315 
316  property int currentDay: infographic.model.currentDay
317 
318  index: model.index
319  count: dots.count
320  radius: backgroundCircle.width / 2
321  halfSize: dot.width / 2
322  posOffset: radius / dot.width / 3
323  state: dot.state
324 
325  Dot {
326  id: dot
327  objectName: "dot" + index
328 
329  property real baseOpacity: 0.4
330 
331  width: units.dp(5) * parent.radius / 200
332  height: units.dp(5) * parent.radius / 200
333  opacity: 0.0
334  smooth: true
335  state: index < currentDay ? "filled" : index == currentDay ? "pointer" : "unfilled"
336 
337  PropertyAnimation {
338  id: dotUnlockAnim
339 
340  target: dot
341  property: "opacity"
342  to: dot.baseOpacity
343  duration: dotShowAnimTimer.interval
344  }
345 
346  PropertyAnimation {
347  id: dotChangeAnim
348 
349  target: dot
350  property: "opacity"
351  to: 0.0
352  duration: dotHideAnimTimer.interval
353  }
354  }
355  }
356  }
357 
358  Label {
359  id: notification
360  objectName: "label"
361 
362  property alias hideAnim: decreaseOpacity
363  property alias showAnim: increaseOpacity
364 
365  property real baseOpacity: 0.6
366  property real duration: dotShowAnimTimer.interval * 5
367 
368  height: 0.7 * backgroundCircle.width
369  width: notification.height
370  anchors.centerIn: parent
371 
372  text: infographic.model.label
373 
374  wrapMode: Text.WordWrap
375  horizontalAlignment: Text.AlignHCenter
376  verticalAlignment: Text.AlignVCenter
377  color: "white"
378 
379  PropertyAnimation {
380  id: increaseOpacity
381 
382  target: notification
383  property: "opacity"
384  from: 0.0
385  to: notification.baseOpacity
386  duration: notification.duration * dots.count
387  }
388 
389  PropertyAnimation {
390  id: decreaseOpacity
391 
392  target: notification
393  property: "opacity"
394  from: notification.baseOpacity
395  to: 0.0
396  duration: notification.duration * dots.count
397  onStopped: if (!d.useDotAnimation) infographic.model.readyForDataChange()
398  }
399  }
400  }
401 
402  MouseArea {
403  anchors.fill: dataCircle
404 
405  onDoubleClicked: {
406  if (!d.animating) {
407  d.useDotAnimation = false
408  infographic.model.nextDataSource()
409  }
410  }
411  }
412 }