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