Unity 8
Infographics.qml
1 /*
2  * Copyright (C) 2013-2016 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 "Gradient.js" as Gradient
18 import QtQuick 2.4
19 import Ubuntu.Components 1.3
20 
21 Item {
22  id: infographic
23 
24  property var model
25 
26  property int animDuration: 10
27 
28  QtObject {
29  id: d
30  objectName: "infographicPrivate"
31  property bool useDotAnimation: true
32  property int circleModifier: useDotAnimation ? 1 : 2
33  property bool animating: dotHideAnimTimer.running
34  || dotShowAnimTimer.running
35  || circleChangeAnimTimer.running
36  }
37 
38  QtObject {
39  id: whiteTheme
40  property color main: "white"
41  property color start: "white"
42  property color end: "white"
43  }
44 
45  Connections {
46  target: model
47  ignoreUnknownSignals: model === undefined
48 
49  onDataAboutToAppear: startHideAnimation() // hide "no data" label
50  onDataAppeared: startShowAnimation()
51 
52  onDataAboutToChange: startHideAnimation()
53  onDataChanged: startShowAnimation()
54 
55  onDataAboutToDisappear: startHideAnimation()
56  onDataDisappeared: startShowAnimation() // show "no data" label
57  }
58 
59  function startShowAnimation() {
60  dotHideAnimTimer.stop()
61  notification.hideAnim.stop()
62 
63  if (d.useDotAnimation) {
64  dotShowAnimTimer.startFromBeginning()
65  }
66  notification.showAnim.start()
67  }
68 
69  function startHideAnimation() {
70  dotShowAnimTimer.stop()
71  circleChangeAnimTimer.stop()
72  notification.showAnim.stop()
73 
74  if (d.useDotAnimation) {
75  dotHideAnimTimer.startFromBeginning()
76  } else {
77  circleChangeAnimTimer.startFromBeginning()
78  }
79  notification.hideAnim.start()
80  }
81 
82  visible: model.username !== ""
83 
84  Component.onCompleted: startShowAnimation()
85 
86  Item {
87  id: dataCircle
88  objectName: "dataCircle"
89 
90  property real divisor: 1.5
91 
92  width: Math.min(parent.height, parent.width) / divisor
93  height: width
94 
95  anchors.centerIn: parent
96 
97  Timer {
98  id: circleChangeAnimTimer
99 
100  property int pastCircleCounter
101  property int presentCircleCounter
102 
103  interval: notification.duration
104  running: false
105  repeat: true
106  onTriggered: {
107  if (pastCircleCounter < pastCircles.count) {
108  var nextCircle = pastCircles.itemAt(pastCircleCounter++)
109  if (nextCircle !== null) nextCircle.pastCircleChangeAnim.start()
110  }
111  if (pastCircleCounter > pastCircles.count / 2) {
112  var nextCircle = presentCircles.itemAt(presentCircleCounter++)
113  if (nextCircle !== null) nextCircle.presentCircleChangeAnim.start()
114  }
115  if (presentCircleCounter > infographic.model.currentDay && pastCircleCounter >= pastCircles.count) {
116  stop()
117  }
118  }
119 
120  function startFromBeginning() {
121  circleChangeAnimTimer.pastCircleCounter = 0
122  circleChangeAnimTimer.presentCircleCounter = 0
123  start()
124  }
125  }
126 
127  Repeater {
128  id: pastCircles
129  objectName: "pastCircles"
130  model: infographic.model.secondMonth
131 
132  delegate: ObjectPositioner {
133  property alias pastCircleChangeAnim: pastCircleChangeAnim
134 
135  index: model.index
136  count: pastCircles.count
137  radius: dataCircle.width / 2
138  halfSize: pastCircle.width / 2
139  posOffset: 0.0
140 
141  Circle {
142  id: pastCircle
143  objectName: "pastCircle" + index
144 
145  property real divisor: 1.8
146  property real circleOpacity: 0.1
147 
148  width: dataCircle.width / divisor
149  height: dataCircle.height / divisor
150  opacity: 0.0
151  circleScale: 0.0
152  visible: modelData !== undefined
153  color: "transparent"
154  centerCircle: dataCircle
155 
156  SequentialAnimation {
157  id: pastCircleChangeAnim
158 
159  loops: 1
160  ParallelAnimation {
161  PropertyAnimation {
162  target: pastCircle
163  property: "opacity"
164  to: pastCircle.circleOpacity
165  easing.type: Easing.OutCurve
166  duration: circleChangeAnimTimer.interval * d.circleModifier
167  }
168  PropertyAnimation {
169  target: pastCircle
170  property: "circleScale"
171  to: modelData
172  easing.type: Easing.OutCurve
173  duration: circleChangeAnimTimer.interval * d.circleModifier
174  }
175  ColorAnimation {
176  target: pastCircle
177  property: "color"
178  to: Gradient.threeColorByIndex(index, count, whiteTheme)
179  easing.type: Easing.OutCurve
180  duration: circleChangeAnimTimer.interval * d.circleModifier
181  }
182  }
183  }
184  }
185  }
186  }
187 
188  Repeater {
189  id: presentCircles
190  objectName: "presentCircles"
191  model: infographic.model.firstMonth
192 
193  delegate: ObjectPositioner {
194  property alias presentCircleChangeAnim: presentCircleChangeAnim
195 
196  index: model.index
197  count: presentCircles.count
198  radius: dataCircle.width / 2
199  halfSize: presentCircle.width / 2
200  posOffset: 0.0
201 
202  Circle {
203  id: presentCircle
204  objectName: "presentCircle" + index
205 
206  property real divisor: 1.8
207  property real circleOpacity: 0.3
208 
209  width: dataCircle.width / divisor
210  height: dataCircle.height / divisor
211  opacity: 0.0
212  circleScale: 0.0
213  visible: modelData !== undefined
214  color: "transparent"
215  centerCircle: dataCircle
216 
217  SequentialAnimation {
218  id: presentCircleChangeAnim
219 
220  loops: 1
221 
222  ParallelAnimation {
223  PropertyAnimation {
224  target: presentCircle
225  property: "opacity"
226  to: presentCircle.circleOpacity
227  easing.type: Easing.OutCurve
228  duration: circleChangeAnimTimer.interval * d.circleModifier
229  }
230  PropertyAnimation {
231  target: presentCircle
232  property: "circleScale"
233  to: modelData
234  easing.type: Easing.OutCurve
235  duration: circleChangeAnimTimer.interval * d.circleModifier
236  }
237  ColorAnimation {
238  target: presentCircle
239  property: "color"
240  to: Gradient.threeColorByIndex(index, infographic.model.currentDay, whiteTheme)
241  easing.type: Easing.OutCurve
242  duration: circleChangeAnimTimer.interval * d.circleModifier
243  }
244  }
245  }
246  }
247  }
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: dataCircle.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: 1
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: 1
366  property real duration: dotShowAnimTimer.interval * 5
367 
368  height: 0.7 * dataCircle.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  enabled: notification.text != ""
405 
406  onDoubleClicked: {
407  if (!d.animating) {
408  d.useDotAnimation = false
409  infographic.model.nextDataSource()
410  }
411  }
412  }
413 }