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.scope = scope;
126  x = -width;
127  }
128  Behavior on x {
129  UbuntuNumberAnimation {
130  onRunningChanged: {
131  if (!running && dashContent.x == 0) {
132  scopes.closeScope(scopeItem.scope);
133  scopeItem.scope = null;
134  }
135  }
136  }
137  }
138 
139  // This is to avoid the situation where a bottom-edge swipe would bring up the dash overview
140  // (as expected) but would also cause the dash content flickable to move a bit, because
141  // that flickable was getting the touch events while overviewDragHandle was still undecided
142  // about whether that touch was indeed performing a directional drag gesture.
143  forceNonInteractive: overviewDragHandle.status != DirectionalDragArea.WaitingForTouch
144 
145  enabled: bottomEdgeController.progress == 0
146  }
147 
148  Rectangle {
149  color: "black"
150  opacity: bottomEdgeController.progress
151  anchors.fill: dashContent
152  }
153 
154  ScopesList {
155  id: scopesList
156  objectName: "scopesList"
157  width: dash.width
158  height: dash.height
159  scope: scopes.overviewScope
160  y: dash.height * (1 - bottomEdgeController.progress)
161  visible: bottomEdgeController.progress != 0
162  onBackClicked: {
163  bottomEdgeController.enableAnimation = true;
164  bottomEdgeController.progress = 0;
165  }
166  onStoreClicked: {
167  bottomEdgeController.enableAnimation = true;
168  bottomEdgeController.progress = 0;
169  scopesList.scope.performQuery("scope://com.canonical.scopes.clickstore");
170  }
171  onRequestFavorite: {
172  scopes.setFavorite(scopeId, favorite);
173  }
174  onRequestFavoriteMoveTo: {
175  scopes.moveFavoriteTo(scopeId, index);
176  }
177 
178  Binding {
179  target: scopesList.scope
180  property: "isActive"
181  value: bottomEdgeController.progress === 1
182  }
183 
184  Connections {
185  target: scopesList.scope
186  onOpenScope: {
187  bottomEdgeController.enableAnimation = true;
188  bottomEdgeController.progress = 0;
189  scopeItem.scope = scope;
190  dashContent.x = -dashContent.width;
191  }
192  onGotoScope: {
193  bottomEdgeController.enableAnimation = true;
194  bottomEdgeController.progress = 0;
195  dashContent.gotoScope(scopeId);
196  }
197  }
198  }
199 
200  DashBackground {
201  anchors.fill: scopeItem
202  visible: scopeItem.visible
203  }
204 
205  GenericScopeView {
206  id: scopeItem
207  objectName: "dashTempScopeItem"
208 
209  x: dashContent.x + width
210  y: dashContent.y
211  width: parent.width
212  height: parent.height
213  visible: scope != null
214  hasBackAction: true
215  isCurrent: visible
216  onBackClicked: {
217  closeOverlayScope();
218  closePreview();
219  }
220 
221  Connections {
222  target: scopeItem.scope
223  onGotoScope: {
224  dashContent.gotoScope(scopeId);
225  }
226  onOpenScope: {
227  scopeItem.closePreview();
228  var oldScope = scopeItem.scope;
229  scopeItem.scope = scope;
230  scopes.closeScope(oldScope);
231  }
232  }
233  }
234 
235  Rectangle {
236  id: indicator
237  objectName: "processingIndicator"
238  anchors {
239  left: parent.left
240  right: parent.right
241  bottom: parent.bottom
242  bottomMargin: Qt.inputMethod.keyboardRectangle.height
243  }
244  height: units.dp(3)
245  color: scopeStyle.backgroundLuminance > 0.7 ? "#50000000" : "#50ffffff"
246  opacity: 0
247  visible: opacity > 0
248 
249  readonly property bool processing: dashContent.processing || scopeItem.processing || scopesList.processing
250 
251  Behavior on opacity {
252  UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration }
253  }
254 
255  onProcessingChanged: {
256  if (processing) delay.start();
257  else if (!persist.running) indicator.opacity = 0;
258  }
259 
260  Timer {
261  id: delay
262  interval: 200
263  onTriggered: if (indicator.processing) {
264  persist.restart();
265  indicator.opacity = 1;
266  }
267  }
268 
269  Timer {
270  id: persist
271  interval: 2 * UbuntuAnimation.SleepyDuration - UbuntuAnimation.FastDuration
272  onTriggered: if (!indicator.processing) indicator.opacity = 0
273  }
274 
275  Rectangle {
276  id: orange
277  anchors { top: parent.top; bottom: parent.bottom }
278  width: parent.width / 4
279  color: UbuntuColors.orange
280 
281  SequentialAnimation {
282  running: indicator.visible
283  loops: Animation.Infinite
284  XAnimator {
285  from: -orange.width / 2
286  to: indicator.width - orange.width / 2
287  duration: UbuntuAnimation.SleepyDuration
288  easing.type: Easing.InOutSine
289  target: orange
290  }
291  XAnimator {
292  from: indicator.width - orange.width / 2
293  to: -orange.width / 2
294  duration: UbuntuAnimation.SleepyDuration
295  easing.type: Easing.InOutSine
296  target: orange
297  }
298  }
299  }
300  }
301 
302  Image {
303  objectName: "overviewHint"
304  source: "graphics/overview_hint.png"
305  anchors.horizontalCenter: parent.horizontalCenter
306  opacity: !scopeItem.scope && (scopes.count == 0 || dashContent.pageHeaderTotallyVisible) &&
307  (overviewDragHandle.enabled || bottomEdgeController.progress != 0) ? 1 : 0
308  Behavior on opacity {
309  enabled: bottomEdgeController.progress == 0
310  UbuntuNumberAnimation {}
311  }
312  y: parent.height - height * (1 - bottomEdgeController.progress * 4)
313  MouseArea {
314  // Eat direct presses on the overview hint so that they do not end up in the card below
315  anchors.fill: parent
316  enabled: parent.opacity != 0
317  }
318  }
319 
320  EdgeDragArea {
321  id: overviewDragHandle
322  objectName: "overviewDragHandle"
323  z: 1
324  direction: Direction.Upwards
325  enabled: !dashContent.subPageShown &&
326  (scopes.count == 0 || (dashContent.currentScope && dashContent.currentScope.searchQuery == "")) &&
327  !scopeItem.scope &&
328  (bottomEdgeController.progress == 0 || dragging)
329 
330  readonly property real fullMovement: dash.height
331 
332  anchors { left: parent.left; right: parent.right; bottom: parent.bottom }
333  height: units.gu(2)
334 
335  onSceneDistanceChanged: {
336  if (status == DirectionalDragArea.Recognized) {
337  bottomEdgeController.enableAnimation = false;
338  bottomEdgeController.progress = Math.max(0, Math.min(1, sceneDistance / fullMovement));
339  }
340  }
341 
342  property int previousStatus: -1
343  property int currentStatus: DirectionalDragArea.WaitingForTouch
344 
345  onStatusChanged: {
346  previousStatus = currentStatus;
347  currentStatus = status;
348 
349  if (status == DirectionalDragArea.WaitingForTouch &&
350  previousStatus == DirectionalDragArea.Recognized) {
351  bottomEdgeController.enableAnimation = true;
352  bottomEdgeController.progress = (bottomEdgeController.progress > 0.2) ? 1 : 0;
353  }
354  }
355  }
356 }