Unity 8
Dash.qml
1 /*
2  * Copyright (C) 2013, 2014 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 QtQuick 2.2
18 import Ubuntu.Components 0.1
19 import Ubuntu.Gestures 0.1
20 import Unity 0.2
21 import Utils 0.1
22 import Unity.DashCommunicator 0.1
23 import "../Components"
24 
25 Showable {
26  id: dash
27  objectName: "dash"
28 
29  visible: shown
30 
31  Connections {
32  target: UriHandler
33  onOpened: {
34  backToDashContent()
35  dashContent.currentScope.performQuery(uris[0])
36  }
37  }
38 
39  DashCommunicatorService {
40  objectName: "dashCommunicatorService"
41  onSetCurrentScopeRequested: {
42  if (!isSwipe || !window.active || bottomEdgeController.progress != 0 || scopeItem.scope || dashContent.subPageShown) {
43  if (bottomEdgeController.progress != 0 && window.active) animate = false;
44  dashContent.setCurrentScopeAtIndex(index, animate, true)
45  backToDashContent()
46  }
47  }
48  }
49 
50  function backToDashContent()
51  {
52  // Close dash overview and nested temp scopes in it
53  if (bottomEdgeController.progress != 0) {
54  bottomEdgeController.enableAnimation = window.active;
55  bottomEdgeController.progress = 0;
56  }
57  // Close normal temp scopes (e.g. App Store)
58  if (scopeItem.scope) {
59  scopeItem.backClicked();
60  }
61  // Close previews
62  if (dashContent.subPageShown) {
63  dashContent.closePreview();
64  }
65  }
66 
67  function setCurrentScope(scopeId, animate, reset) {
68  var scopeIndex = -1;
69  for (var i = 0; i < scopes.count; ++i) {
70  if (scopes.getScope(i).id == scopeId) {
71  scopeIndex = i;
72  break;
73  }
74  }
75 
76  if (scopeIndex == -1) {
77  console.warn("No match for scope with id: %1".arg(scopeId))
78  return
79  }
80 
81  closeOverlayScope();
82 
83  dashContent.closePreview();
84 
85  if (scopeIndex == dashContent.currentIndex && !reset) {
86  // the scope is already the current one
87  return
88  }
89 
90  dashContent.workaroundRestoreIndex = -1;
91  dashContent.setCurrentScopeAtIndex(scopeIndex, animate, reset)
92  }
93 
94  function closeOverlayScope() {
95  if (dashContent.x != 0) {
96  dashContent.x = 0;
97  }
98  }
99 
100  Scopes {
101  id: scopes
102  }
103 
104  QtObject {
105  id: bottomEdgeController
106  objectName: "bottomEdgeController"
107 
108  property alias enableAnimation: progressAnimation.enabled
109  property real progress: 0
110  Behavior on progress {
111  id: progressAnimation
112  UbuntuNumberAnimation { }
113  }
114 
115  onProgressChanged: {
116  // FIXME This is to workaround a Qt bug with the model moving the current item
117  // when the list is ListView.SnapOneItem and ListView.StrictlyEnforceRange
118  // together with the code in DashContent.qml
119  if (dashContent.workaroundRestoreIndex != -1) {
120  dashContent.currentIndex = dashContent.workaroundRestoreIndex;
121  dashContent.workaroundRestoreIndex = -1;
122  }
123  }
124  }
125 
126  DashContent {
127  id: dashContent
128 
129  objectName: "dashContent"
130  width: dash.width
131  height: dash.height
132  scopes: scopes
133  visible: x != -width
134  onGotoScope: {
135  dash.setCurrentScope(scopeId, true, false);
136  }
137  onOpenScope: {
138  scopeItem.scope = scope;
139  x = -width;
140  }
141  Behavior on x {
142  UbuntuNumberAnimation {
143  onRunningChanged: {
144  if (!running && dashContent.x == 0) {
145  scopes.closeScope(scopeItem.scope);
146  scopeItem.scope = null;
147  }
148  }
149  }
150  }
151 
152  // This is to avoid the situation where a bottom-edge swipe would bring up the dash overview
153  // (as expected) but would also cause the dash content flickable to move a bit, because
154  // that flickable was getting the touch events while overviewDragHandle was still undecided
155  // about whether that touch was indeed performing a directional drag gesture.
156  forceNonInteractive: overviewDragHandle.dragging
157 
158  enabled: bottomEdgeController.progress == 0
159  }
160 
161  Rectangle {
162  color: "black"
163  opacity: bottomEdgeController.progress
164  anchors.fill: dashContent
165  }
166 
167  ScopesList {
168  id: scopesList
169  objectName: "scopesList"
170  width: dash.width
171  height: dash.height
172  scope: scopes.overviewScope
173  y: dash.height * (1 - bottomEdgeController.progress)
174  visible: bottomEdgeController.progress != 0
175  onBackClicked: {
176  bottomEdgeController.enableAnimation = true;
177  bottomEdgeController.progress = 0;
178  }
179  onStoreClicked: {
180  bottomEdgeController.enableAnimation = true;
181  bottomEdgeController.progress = 0;
182  scopesList.scope.performQuery("scope://com.canonical.scopes.clickstore");
183  }
184  onRequestFavorite: {
185  scopes.setFavorite(scopeId, favorite);
186  }
187  onRequestFavoriteMoveTo: {
188  scopes.moveFavoriteTo(scopeId, index);
189  }
190  onRequestRestore: {
191  bottomEdgeController.enableAnimation = true;
192  bottomEdgeController.progress = 0;
193  dash.setCurrentScope(scopeId, false, false);
194  }
195 
196  Binding {
197  target: scopesList.scope
198  property: "isActive"
199  value: bottomEdgeController.progress === 1 && (Qt.application.state == Qt.ApplicationActive)
200  }
201 
202  Connections {
203  target: scopesList.scope
204  onOpenScope: {
205  bottomEdgeController.enableAnimation = true;
206  bottomEdgeController.progress = 0;
207  scopeItem.scope = scope;
208  dashContent.x = -dashContent.width;
209  }
210  onGotoScope: {
211  bottomEdgeController.enableAnimation = true;
212  bottomEdgeController.progress = 0;
213  dashContent.gotoScope(scopeId);
214  }
215  }
216  }
217 
218  DashBackground {
219  anchors.fill: scopeItem
220  visible: scopeItem.visible
221  }
222 
223  GenericScopeView {
224  id: scopeItem
225  objectName: "dashTempScopeItem"
226 
227  x: dashContent.x + width
228  y: dashContent.y
229  width: parent.width
230  height: parent.height
231  visible: scope != null
232  hasBackAction: true
233  isCurrent: visible
234  onBackClicked: {
235  closeOverlayScope();
236  closePreview();
237  }
238 
239  Connections {
240  target: scopeItem.scope
241  onGotoScope: {
242  dashContent.gotoScope(scopeId);
243  }
244  onOpenScope: {
245  scopeItem.closePreview();
246  var oldScope = scopeItem.scope;
247  scopeItem.scope = scope;
248  scopes.closeScope(oldScope);
249  }
250  }
251  }
252 
253  Rectangle {
254  id: indicator
255  objectName: "processingIndicator"
256  anchors {
257  left: parent.left
258  right: parent.right
259  bottom: parent.bottom
260  bottomMargin: Qt.inputMethod.keyboardRectangle.height
261  }
262  height: units.dp(3)
263  color: scopeStyle.backgroundLuminance > 0.7 ? "#50000000" : "#50ffffff"
264  opacity: 0
265  visible: opacity > 0
266 
267  readonly property bool processing: dashContent.processing || scopeItem.processing || scopesList.processing
268 
269  Behavior on opacity {
270  UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
271  }
272 
273  onProcessingChanged: {
274  if (processing) delay.start();
275  else if (!persist.running) indicator.opacity = 0;
276  }
277 
278  Timer {
279  id: delay
280  interval: 200
281  onTriggered: if (indicator.processing) {
282  persist.restart();
283  indicator.opacity = 1;
284  }
285  }
286 
287  Timer {
288  id: persist
289  interval: 2 * UbuntuAnimation.SleepyDuration - UbuntuAnimation.FastDuration
290  onTriggered: if (!indicator.processing) indicator.opacity = 0
291  }
292 
293  Rectangle {
294  id: orange
295  anchors { top: parent.top; bottom: parent.bottom }
296  width: parent.width / 4
297  color: UbuntuColors.orange
298 
299  SequentialAnimation {
300  running: indicator.visible
301  loops: Animation.Infinite
302  XAnimator {
303  from: -orange.width / 2
304  to: indicator.width - orange.width / 2
305  duration: UbuntuAnimation.SleepyDuration
306  easing.type: Easing.InOutSine
307  target: orange
308  }
309  XAnimator {
310  from: indicator.width - orange.width / 2
311  to: -orange.width / 2
312  duration: UbuntuAnimation.SleepyDuration
313  easing.type: Easing.InOutSine
314  target: orange
315  }
316  }
317  }
318  }
319 
320  Image {
321  objectName: "overviewHint"
322  source: "graphics/overview_hint.png"
323  anchors.horizontalCenter: parent.horizontalCenter
324  opacity: !scopeItem.scope && (scopes.count == 0 || dashContent.pageHeaderTotallyVisible) &&
325  (overviewDragHandle.enabled || bottomEdgeController.progress != 0) ? 1 : 0
326  Behavior on opacity {
327  enabled: bottomEdgeController.progress == 0
328  UbuntuNumberAnimation {}
329  }
330  y: parent.height - height * (1 - bottomEdgeController.progress * 4)
331  MouseArea {
332  // Eat direct presses on the overview hint so that they do not end up in the card below
333  anchors.fill: parent
334  enabled: parent.opacity != 0
335 
336  // TODO: This is a temporary workaround to allow people opening the
337  // dash overview when there's no touch input around. Will be replaced with
338  // a SDK component once that's available
339  onClicked: bottomEdgeController.progress = 1;
340 
341  // We need to eat touch events here in order to not allow opening the bottom edge with a touch press
342  MultiPointTouchArea {
343  anchors.fill: parent
344  mouseEnabled: false
345  enabled: parent.enabled
346  }
347  }
348  }
349 
350  DirectionalDragArea {
351  id: overviewDragHandle
352  objectName: "overviewDragHandle"
353  z: 1
354  direction: Direction.Upwards
355  enabled: !dashContent.subPageShown &&
356  (scopes.count == 0 || (dashContent.currentScope && dashContent.currentScope.searchQuery == "")) &&
357  !scopeItem.scope &&
358  (bottomEdgeController.progress == 0 || dragging)
359 
360  readonly property real fullMovement: dash.height
361 
362  anchors { left: parent.left; right: parent.right; bottom: parent.bottom }
363  height: units.gu(2)
364 
365  onSceneDistanceChanged: {
366  if (dragging) {
367  bottomEdgeController.enableAnimation = false;
368  bottomEdgeController.progress = Math.max(0, Math.min(1, sceneDistance / fullMovement));
369  }
370  }
371 
372  onDraggingChanged: {
373  if (!dragging) {
374  bottomEdgeController.enableAnimation = true;
375  bottomEdgeController.progress = (bottomEdgeController.progress > 0.2) ? 1 : 0;
376  }
377  }
378  }
379 }