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