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