Unity 8
 All Classes Functions
ScopesOverview.qml
1 /*
2  * Copyright (C) 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.0
18 import Dash 0.1
19 import Ubuntu.Components 0.1
20 import "../Components"
21 
22 Item {
23  id: root
24 
25  // Properties set by parent
26  property real progress: 0
27  property var scope: null
28  property int currentIndex: 0
29  property real scopeScale: 1
30 
31  // Properties set and used by parent
32  property alias currentTab: tabBar.currentTab
33 
34  // Properties used by parent
35  readonly property bool processing: searchResultsViewer.processing || tempScopeItem.processing || previewListView.processing
36  property bool growingDashFromPos: false
37  readonly property bool searching: scope && scope.searchQuery == ""
38  readonly property bool showingNonFavoriteScope: tempScopeItem.scope != null
39  readonly property var dashItemEater: {
40  if (!forceXYScalerEater && tabBar.currentTab == 0 && middleItems.count > 0) {
41  var loaderItem = middleItems.itemAt(0).item;
42  return loaderItem && loaderItem.currentItem ? loaderItem.currentItem : null;
43  }
44  return scopesOverviewXYScaler;
45  }
46  readonly property size allCardSize: {
47  if (middleItems.count > 1) {
48  var loaderItem = middleItems.itemAt(1).item;
49  if (loaderItem) {
50  var cardTool = loaderItem.cardTool;
51  return Qt.size(cardTool.cardWidth, cardTool.cardHeight);
52  }
53  }
54  return Qt.size(0, 0);
55  }
56 
57  // Internal properties
58  property bool forceXYScalerEater: false
59 
60  signal done()
61  signal favoriteSelected(var scopeId)
62  signal allFavoriteSelected(var scopeId)
63  signal searchSelected(var scopeId, var result, var pos, var size)
64 
65  Connections {
66  target: scope
67  onOpenScope: {
68  var itemPos = scopesOverviewXYScaler.restorePosition;
69  var itemSize = scopesOverviewXYScaler.restoreSize;
70  scopesOverviewXYScaler.scale = itemSize.width / scopesOverviewXYScaler.width;
71  if (itemPos) {
72  scopesOverviewXYScaler.x = itemPos.x -(scopesOverviewXYScaler.width - scopesOverviewXYScaler.width * scopesOverviewXYScaler.scale) / 2;
73  scopesOverviewXYScaler.y = itemPos.y -(scopesOverviewXYScaler.height - scopesOverviewXYScaler.height * scopesOverviewXYScaler.scale) / 2;
74  } else {
75  scopesOverviewXYScaler.x = 0;
76  scopesOverviewXYScaler.y = 0;
77  }
78  scopesOverviewXYScaler.opacity = 0;
79  tempScopeItem.scope = scope;
80  middleItems.overrideOpacity = 0;
81  scopesOverviewXYScaler.scale = 1;
82  scopesOverviewXYScaler.x = 0;
83  scopesOverviewXYScaler.y = 0;
84  scopesOverviewXYScaler.opacity = 1;
85  }
86  onGotoScope: {
87  if (tabBar.currentTab == 0) {
88  root.favoriteSelected(scopeId);
89  } else {
90  root.allFavoriteSelected(scopeId);
91  }
92  }
93  }
94 
95  Binding {
96  target: scope
97  property: "isActive"
98  value: progress === 1
99  }
100 
101  function animateDashFromAll(scopeId) {
102  var currentScopePos = allScopeCardPosition(scopeId);
103  if (currentScopePos) {
104  showDashFromPos(currentScopePos, allCardSize);
105  } else {
106  console.log("Warning: Could not find Dash OverView All card position for scope", dashContent.currentScopeId);
107  }
108  }
109 
110  function showDashFromPos(itemPos, itemSize) {
111  scopesOverviewXYScaler.scale = itemSize.width / scopesOverviewXYScaler.width;
112  scopesOverviewXYScaler.x = itemPos.x -(scopesOverviewXYScaler.width - scopesOverviewXYScaler.width * scopesOverviewXYScaler.scale) / 2;
113  scopesOverviewXYScaler.y = itemPos.y -(scopesOverviewXYScaler.height - scopesOverviewXYScaler.height * scopesOverviewXYScaler.scale) / 2;
114  scopesOverviewXYScaler.opacity = 0;
115  root.growingDashFromPos = true;
116  scopesOverviewXYScaler.scale = 1;
117  scopesOverviewXYScaler.x = 0;
118  scopesOverviewXYScaler.y = 0;
119  scopesOverviewXYScaler.opacity = 1;
120  }
121 
122  function allScopeCardPosition(scopeId) {
123  if (middleItems.count > 1) {
124  var loaderItem = middleItems.itemAt(1).item;
125  if (loaderItem) {
126  var pos = loaderItem.scopeCardPosition(scopeId);
127  return loaderItem.mapToItem(null, pos.x, pos.y);
128  }
129  }
130  }
131 
132  function ensureAllScopeVisible(scopeId) {
133  if (middleItems.count > 1) {
134  var loaderItem = middleItems.itemAt(1).item;
135  if (loaderItem) {
136  var pos = loaderItem.scopeCardPosition(scopeId);
137  loaderItem.contentY = Math.min(pos.y, loaderItem.contentHeight - loaderItem.height);
138  }
139  }
140  }
141 
142  onProgressChanged: {
143  if (progress == 0) {
144  pageHeader.resetSearch();
145  pageHeader.unfocus(); // Shouldn't the previous call do this too?
146  }
147  }
148 
149  ScopeStyle {
150  id: overviewScopeStyle
151  style: { "foreground-color" : "white",
152  "background-color" : "transparent",
153  "page-header": {
154  "background": "color:///transparent"
155  }
156  }
157  }
158 
159  DashBackground {
160  anchors.fill: parent
161  source: "graphics/dark_background.jpg"
162  }
163 
164  Connections {
165  target: pageHeader
166  onSearchQueryChanged: {
167  // Need this in order, otherwise something gets unhappy in rendering
168  // of the overlay in carousels because the parent of the dash dies for
169  // a moment, this way we make sure it's reparented first
170  // by forceXYScalerEater making dashItemEater return scopesOverviewXYScaler
171  // before we kill the previous parent by scope.searchQuery
172  root.forceXYScalerEater = true;
173  root.scope.searchQuery = pageHeader.searchQuery;
174  root.forceXYScalerEater = false;
175  }
176  }
177 
178  Binding {
179  target: pageHeader
180  property: "searchQuery"
181  value: scope ? scope.searchQuery : ""
182  }
183 
184  Item {
185  id: scopesOverviewContent
186  x: previewListView.open ? -width : 0
187  Behavior on x { UbuntuNumberAnimation { } }
188  width: parent.width
189  height: parent.height
190 
191  PageHeader {
192  id: pageHeader
193  objectName: "scopesOverviewPageHeader"
194 
195  readonly property real yDisplacement: pageHeader.height + tabBar.height + tabBar.anchors.margins
196 
197  y: {
198  if (root.progress < 0.5) {
199  return -yDisplacement;
200  } else {
201  return -yDisplacement + (root.progress - 0.5) * yDisplacement * 2;
202  }
203  }
204  width: parent.width
205  clip: true
206  title: i18n.tr("Manage Dash")
207  scopeStyle: overviewScopeStyle
208  showSignatureLine: false
209  searchEntryEnabled: true
210  }
211 
212  ScopesOverviewTab {
213  id: tabBar
214  anchors {
215  left: parent.left
216  right: parent.right
217  top: pageHeader.bottom
218  margins: units.gu(2)
219  }
220  height: units.gu(4)
221 
222  enabled: opacity == 1
223  opacity: !scope || scope.searchQuery == "" ? 1 : 0
224  Behavior on opacity { UbuntuNumberAnimation { } }
225  }
226 
227  Repeater {
228  id: middleItems
229  objectName: "scopesOverviewRepeater"
230  property real overrideOpacity: -1
231  model: scope && scope.searchQuery == "" ? scope.categories : null
232  delegate: Loader {
233  id: loader
234  objectName: "scopesOverviewRepeaterChild" + index
235 
236  height: {
237  if (index == 0) {
238  return root.height;
239  } else {
240  return root.height - pageHeader.height - tabBar.height - tabBar.anchors.margins - units.gu(2);
241  }
242  }
243  width: {
244  if (index == 0) {
245  return root.width / scopeScale;
246  } else {
247  return root.width;
248  }
249  }
250  x: {
251  if (index == 0) {
252  return (root.width - width) / 2;
253  } else {
254  return 0;
255  }
256  }
257  anchors {
258  bottom: scopesOverviewContent.bottom
259  }
260 
261  scale: index == 0 ? scopeScale : 1
262 
263  opacity: {
264  if (middleItems.overrideOpacity >= 0)
265  return middleItems.overrideOpacity;
266 
267  if (tabBar.currentTab != index)
268  return 0;
269 
270  return index == 0 ? 1 : root.progress;
271  }
272  Behavior on opacity {
273  enabled: root.progress == 1
274  UbuntuNumberAnimation { }
275  }
276  enabled: opacity == 1
277 
278  clip: index == 1
279 
280  CardTool {
281  id: cardTool
282  objectName: "cardTool"
283  count: results.count
284  template: model.renderer
285  components: model.components
286  viewWidth: parent.width
287  }
288 
289  source: {
290  if (index == 0 && categoryId == "favorites") return "ScopesOverviewFavorites.qml";
291  else if (index == 1 && categoryId == "all") return "ScopesOverviewAll.qml";
292  else return "";
293  }
294 
295  onLoaded: {
296  item.model = Qt.binding(function() { return results; });
297  item.cardTool = cardTool;
298  if (index == 0) {
299  item.scopeWidth = Qt.binding(function() { return root.width; });
300  item.scopeHeight = Qt.binding(function() { return root.height; });
301  item.appliedScale = Qt.binding(function() { return loader.scale });
302  item.currentIndex = Qt.binding(function() { return root.currentIndex });
303  } else if (index == 1) {
304  item.extraHeight = bottomBar.height;
305  }
306  }
307 
308  Connections {
309  target: loader.item
310  onClicked: {
311  pageHeader.unfocus();
312  if (tabBar.currentTab == 0) {
313  root.favoriteSelected(itemModel.scopeId);
314  } else {
315  var favoriteScopesItem = middleItems.itemAt(0).item;
316  var scopeIndex = favoriteScopesItem.model.scopeIndex(itemModel.scopeId);
317  if (scopeIndex >= 0) {
318  root.allFavoriteSelected(itemModel.scopeId);
319  } else {
320  // Will result in an openScope from root.scope
321  scopesOverviewXYScaler.restorePosition = item.mapToItem(null, 0, 0);
322  scopesOverviewXYScaler.restoreSize = allCardSize;
323  root.scope.activate(result);
324  }
325  }
326  }
327  onPressAndHold: {
328  // Preview can call openScope so make sure restorePosition and restoreSize are set
329  scopesOverviewXYScaler.restorePosition = undefined;
330  scopesOverviewXYScaler.restoreSize = allCardSize;
331 
332  previewListView.model = target.model;
333  previewListView.currentIndex = -1;
334  previewListView.currentIndex = index;
335  previewListView.open = true;
336  }
337  }
338  }
339  }
340 
341  GenericScopeView {
342  id: searchResultsViewer
343  objectName: "searchResultsViewer"
344  anchors {
345  top: pageHeader.bottom
346  right: parent.right
347  left: parent.left
348  bottom: parent.bottom
349  }
350  scope: root.scope && root.scope.searchQuery != "" ? root.scope : null
351  scopeStyle: overviewScopeStyle
352  enabled: opacity == 1
353  showPageHeader: false
354  clip: true
355  opacity: searchResultsViewer.scope ? 1 : 0
356  isCurrent: true
357  Behavior on opacity { UbuntuNumberAnimation { } }
358 
359  function itemClicked(index, result, item, itemModel, resultsModel, limitedCategoryItemCount) {
360  pageHeader.unfocus();
361  pageHeader.closePopup();
362  if (itemModel.scopeId) {
363  // This can end up in openScope so save restorePosition and restoreSize
364  scopesOverviewXYScaler.restorePosition = item.mapToItem(null, 0, 0);
365  scopesOverviewXYScaler.restoreSize = Qt.size(item.width, item.height);
366  root.searchSelected(itemModel.scopeId, result, item.mapToItem(null, 0, 0), Qt.size(item.width, item.height));
367  } else {
368  // Not a scope, just activate it
369  searchResultsViewer.scope.activate(result);
370  }
371  }
372 
373  function itemPressedAndHeld(index, itemModel, resultsModel, limitedCategoryItemCount) {
374  if (itemModel.uri.indexOf("scope://") === 0) {
375  // Preview can call openScope so make sure restorePosition and restoreSize are set
376  scopesOverviewXYScaler.restorePosition = undefined;
377  scopesOverviewXYScaler.restoreSize = allCardSize;
378 
379  previewListView.model = resultsModel;
380  previewListView.currentIndex = -1;
381  previewListView.currentIndex = index;
382  previewListView.open = true;
383  }
384  }
385  }
386 
387  Rectangle {
388  id: bottomBar
389  objectName: "bottomBar"
390  color: "black"
391  height: units.gu(8)
392  width: parent.width
393  enabled: opacity == 1
394  opacity: scope && scope.searchQuery == "" ? 1 : 0
395  Behavior on opacity { UbuntuNumberAnimation { } }
396  y: {
397  if (root.progress < 0.5) {
398  return parent.height;
399  } else {
400  return parent.height - (root.progress - 0.5) * height * 2;
401  }
402  }
403 
404  MouseArea {
405  // Just eat any other press since this parent is black opaque
406  anchors.fill: parent
407  }
408 
409  AbstractButton {
410  objectName: "scopesOverviewDoneButton"
411  width: Math.max(label.width + units.gu(2), units.gu(10))
412  height: units.gu(4)
413  anchors {
414  left: parent.left
415  leftMargin: units.gu(2)
416  verticalCenter: parent.verticalCenter
417  }
418  Rectangle {
419  anchors.fill: parent
420  border.color: "white"
421  border.width: units.dp(1)
422  radius: units.dp(10)
423  color: parent.pressed ? Theme.palette.normal.baseText : "transparent"
424  }
425  Label {
426  id: label
427  anchors.centerIn: parent
428  text: i18n.tr("Done")
429  color: parent.pressed ? "black" : "white"
430  }
431  onClicked: root.done();
432  }
433 
434  AbstractButton {
435  objectName: "scopesOverviewStoreButton"
436  width: Math.max(storeLabel.width, units.gu(10))
437  height: units.gu(4)
438  anchors {
439  right: parent.right
440  verticalCenter: parent.verticalCenter
441  }
442  Icon {
443  id: storeImage
444  name: "ubuntu-store-symbolic"
445  color: "white"
446  anchors.horizontalCenter: parent.horizontalCenter
447  width: units.gu(2)
448  height: units.gu(2)
449  }
450  Label {
451  id: storeLabel
452  anchors.horizontalCenter: parent.horizontalCenter
453  anchors.top: storeImage.bottom
454  text: i18n.tr("Store")
455  color: "white"
456  }
457  onClicked: {
458  // Just zoom from the middle
459  scopesOverviewXYScaler.restorePosition = undefined;
460  scopesOverviewXYScaler.restoreSize = allCardSize;
461  scope.performQuery("scope://com.canonical.scopes.clickstore");
462  }
463  }
464  }
465  }
466 
467  PreviewListView {
468  id: previewListView
469  objectName: "scopesOverviewPreviewListView"
470  scope: root.scope
471  scopeStyle: overviewScopeStyle
472  showSignatureLine: false
473  visible: x != width
474  width: parent.width
475  height: parent.height
476  anchors.left: scopesOverviewContent.right
477 
478  onBackClicked: open = false
479  }
480 
481  Item {
482  id: scopesOverviewXYScaler
483  width: parent.width
484  height: parent.height
485 
486  clip: scale != 1.0
487  enabled: scale == 1
488 
489  property bool animationsEnabled: root.showingNonFavoriteScope || root.growingDashFromPos
490 
491  property var restorePosition
492  property var restoreSize
493 
494  Behavior on x {
495  enabled: scopesOverviewXYScaler.animationsEnabled
496  UbuntuNumberAnimation { }
497  }
498  Behavior on y {
499  enabled: scopesOverviewXYScaler.animationsEnabled
500  UbuntuNumberAnimation { }
501  }
502  Behavior on opacity {
503  enabled: scopesOverviewXYScaler.animationsEnabled
504  UbuntuNumberAnimation { }
505  }
506  Behavior on scale {
507  enabled: scopesOverviewXYScaler.animationsEnabled
508  UbuntuNumberAnimation {
509  onRunningChanged: {
510  if (!running) {
511  if (root.showingNonFavoriteScope && scopesOverviewXYScaler.scale != 1) {
512  root.scope.closeScope(tempScopeItem.scope);
513  tempScopeItem.scope = null;
514  } else if (root.growingDashFromPos) {
515  root.growingDashFromPos = false;
516  }
517  }
518  }
519  }
520  }
521 
522  DashBackground {
523  anchors.fill: tempScopeItem
524  visible: tempScopeItem.visible
525  parent: tempScopeItem.parent
526  }
527 
528  GenericScopeView {
529  id: tempScopeItem
530  objectName: "scopesOverviewTempScopeItem"
531 
532  width: parent.width
533  height: parent.height
534  scale: dash.contentScale
535  clip: scale != 1.0
536  visible: scope != null
537  hasBackAction: true
538  isCurrent: visible
539  onBackClicked: {
540  var v = scopesOverviewXYScaler.restoreSize.width / tempScopeItem.width;
541  scopesOverviewXYScaler.scale = v;
542  if (scopesOverviewXYScaler.restorePosition) {
543  scopesOverviewXYScaler.x = scopesOverviewXYScaler.restorePosition.x -(tempScopeItem.width - tempScopeItem.width * v) / 2;
544  scopesOverviewXYScaler.y = scopesOverviewXYScaler.restorePosition.y -(tempScopeItem.height - tempScopeItem.height * v) / 2;
545  } else {
546  scopesOverviewXYScaler.x = 0;
547  scopesOverviewXYScaler.y = 0;
548  }
549  scopesOverviewXYScaler.opacity = 0;
550  middleItems.overrideOpacity = -1;
551  }
552  // TODO Add tests for these connections
553  Connections {
554  target: tempScopeItem.scope
555  onOpenScope: {
556  // TODO Animate the newly opened scope into the foreground (stacked on top of the current scope)
557  tempScopeItem.scope = scope;
558  }
559  onGotoScope: {
560  tempScopeItem.backClicked();
561  root.currentTab = 0;
562  root.scope.gotoScope(scopeId);
563  }
564  }
565  }
566  }
567 }