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 
41  DashCommunicatorService {
42  objectName: "dashCommunicatorService"
43  onSetCurrentScopeRequested: {
44  if (!isSwipe || !windowActive || bottomEdgeController.progress != 0 || scopeItem.scope || dashContent.subPageShown) {
45  if (bottomEdgeController.progress != 0 && window.active) animate = false;
46  dashContent.setCurrentScopeAtIndex(index, animate, true)
47  backToDashContent()
48  }
49  }
50  }
51 
52  function backToDashContent()
53  {
54  // Close dash overview and nested temp scopes in it
55  if (bottomEdgeController.progress != 0) {
56  bottomEdgeController.enableAnimation = window.active;
57  bottomEdgeController.progress = 0;
58  }
59  // Close normal temp scopes (e.g. App Store)
60  if (scopeItem.scope) {
61  scopeItem.backClicked();
62  }
63  // Close previews
64  if (dashContent.subPageShown) {
65  dashContent.closePreview();
66  }
67  }
68 
69  function setCurrentScope(scopeId, animate, reset) {
70  var scopeIndex = -1;
71  for (var i = 0; i < scopes.count; ++i) {
72  if (scopes.getScope(i).id == scopeId) {
73  scopeIndex = i;
74  break;
75  }
76  }
77 
78  if (scopeIndex == -1) {
79  console.warn("No match for scope with id: %1".arg(scopeId))
80  return
81  }
82 
83  closeOverlayScope();
84 
85  dashContent.closePreview();
86 
87  if (scopeIndex == dashContent.currentIndex && !reset) {
88  // the scope is already the current one
89  return
90  }
91 
92  dashContent.workaroundRestoreIndex = -1;
93  dashContent.setCurrentScopeAtIndex(scopeIndex, animate, reset)
94  }
95 
96  function closeOverlayScope() {
97  if (dashContent.x != 0) {
98  dashContent.x = 0;
99  }
100  }
101 
102  Scopes {
103  id: scopes
104  }
105 
106  QtObject {
107  id: bottomEdgeController
108  objectName: "bottomEdgeController"
109 
110  property alias enableAnimation: progressAnimation.enabled
111  property real progress: 0
112  Behavior on progress {
113  id: progressAnimation
114  UbuntuNumberAnimation { }
115  }
116 
117  onProgressChanged: {
118  // FIXME This is to workaround a Qt bug with the model moving the current item
119  // when the list is ListView.SnapOneItem and ListView.StrictlyEnforceRange
120  // together with the code in DashContent.qml
121  if (dashContent.workaroundRestoreIndex != -1) {
122  dashContent.currentIndex = dashContent.workaroundRestoreIndex;
123  dashContent.workaroundRestoreIndex = -1;
124  }
125  }
126  }
127 
128  DashContent {
129  id: dashContent
130 
131  objectName: "dashContent"
132  width: dash.width
133  height: dash.height
134  scopes: scopes
135  visible: x != -width
136  onGotoScope: {
137  dash.setCurrentScope(scopeId, true, false);
138  }
139  onOpenScope: {
140  scopeItem.scope = scope;
141  x = -width;
142  }
143  Behavior on x {
144  UbuntuNumberAnimation {
145  onRunningChanged: {
146  if (!running && dashContent.x == 0) {
147  scopes.closeScope(scopeItem.scope);
148  scopeItem.scope = null;
149  }
150  }
151  }
152  }
153 
154  // This is to avoid the situation where a bottom-edge swipe would bring up the dash overview
155  // (as expected) but would also cause the dash content flickable to move a bit, because
156  // that flickable was getting the touch events while overviewDragHandle was still undecided
157  // about whether that touch was indeed performing a directional drag gesture.
158  forceNonInteractive: overviewDragHandle.dragging
159 
160  enabled: bottomEdgeController.progress == 0
161  }
162 
163  Rectangle {
164  color: "black"
165  opacity: bottomEdgeController.progress
166  anchors.fill: dashContent
167  }
168 
169  ScopesList {
170  id: scopesList
171  objectName: "scopesList"
172  width: dash.width
173  height: dash.height
174  scope: scopes.overviewScope
175  y: dash.height * (1 - bottomEdgeController.progress)
176  visible: bottomEdgeController.progress != 0
177  onBackClicked: {
178  bottomEdgeController.enableAnimation = true;
179  bottomEdgeController.progress = 0;
180  }
181  onStoreClicked: {
182  bottomEdgeController.enableAnimation = true;
183  bottomEdgeController.progress = 0;
184  scopesList.scope.performQuery("scope://com.canonical.scopes.clickstore");
185  }
186  onRequestFavorite: {
187  scopes.setFavorite(scopeId, favorite);
188  }
189  onRequestFavoriteMoveTo: {
190  scopes.moveFavoriteTo(scopeId, index);
191  }
192  onRequestRestore: {
193  bottomEdgeController.enableAnimation = true;
194  bottomEdgeController.progress = 0;
195  dash.setCurrentScope(scopeId, false, false);
196  }
197 
198  Binding {
199  target: scopesList.scope
200  property: "isActive"
201  value: bottomEdgeController.progress === 1 && (Qt.application.state == Qt.ApplicationActive)
202  }
203 
204  Connections {
205  target: scopesList.scope
206  onOpenScope: {
207  bottomEdgeController.enableAnimation = true;
208  bottomEdgeController.progress = 0;
209  scopeItem.scope = scope;
210  dashContent.x = -dashContent.width;
211  }
212  onGotoScope: {
213  bottomEdgeController.enableAnimation = true;
214  bottomEdgeController.progress = 0;
215  dashContent.gotoScope(scopeId);
216  }
217  }
218  }
219 
220  DashBackground {
221  anchors.fill: scopeItem
222  visible: scopeItem.visible
223  }
224 
225  GenericScopeView {
226  id: scopeItem
227  objectName: "dashTempScopeItem"
228 
229  x: dashContent.x + width
230  y: dashContent.y
231  width: parent.width
232  height: parent.height
233  visible: scope != null
234  hasBackAction: true
235  isCurrent: visible
236  onBackClicked: {
237  closeOverlayScope();
238  closePreview();
239  }
240 
241  Connections {
242  target: scopeItem.scope
243  onGotoScope: {
244  dashContent.gotoScope(scopeId);
245  }
246  onOpenScope: {
247  scopeItem.closePreview();
248  var oldScope = scopeItem.scope;
249  scopeItem.scope = scope;
250  scopes.closeScope(oldScope);
251  }
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: orange
297  anchors { top: parent.top; bottom: parent.bottom }
298  width: parent.width / 4
299  color: UbuntuColors.orange
300 
301  SequentialAnimation {
302  running: indicator.visible
303  loops: Animation.Infinite
304  XAnimator {
305  from: -orange.width / 2
306  to: indicator.width - orange.width / 2
307  duration: UbuntuAnimation.SleepyDuration
308  easing.type: Easing.InOutSine
309  target: orange
310  }
311  XAnimator {
312  from: indicator.width - orange.width / 2
313  to: -orange.width / 2
314  duration: UbuntuAnimation.SleepyDuration
315  easing.type: Easing.InOutSine
316  target: orange
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  DirectionalDragArea {
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  onSceneDistanceChanged: {
368  if (dragging) {
369  bottomEdgeController.enableAnimation = false;
370  bottomEdgeController.progress = Math.max(0, Math.min(1, sceneDistance / 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 }