Unity 8
DashNavigation.qml
1 /*
2  * Copyright (C) 2014,2015 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 Dash 0.1
20 
21 Item {
22  id: root
23  objectName: "dashNavigation"
24 
25  // set by parent
26  property var scope: null
27  property real availableHeight
28 
29  signal leafClicked()
30 
31  // internal
32  readonly property var currentNavigation: scope && scope.hasNavigation ? scope.getNavigation(scope.currentNavigationId) : null
33  // Are we drilling down the tree or up?
34  property bool isEnteringChildren: false
35 
36  visible: height != 0
37  implicitHeight: scope && scope.hasNavigation ? navigationListView.y + navigationListView.height : 0
38 
39  function resetNavigation() {
40  if (navigationModel.count > 1) {
41  clear();
42  }
43  }
44 
45  Column {
46  id: headersColumn
47  anchors {
48  top: parent.top
49  left: parent.left
50  right: parent.right
51  }
52 
53  function pop(popsNeeded) {
54  if (popsNeeded == 0)
55  return;
56  isEnteringChildren = false;
57  navigationListView.currentIndex = navigationListView.currentIndex - popsNeeded;
58  navigationModel.setProperty(navigationListView.currentIndex, "nullifyNavigation", false);
59  navigationModel.remove(navigationModel.count - popsNeeded, popsNeeded);
60 
61  popsNeeded = Math.min(headersModel.count, popsNeeded);
62  // This is effectively deleting ourselves, so needs to be the last thing of the function
63  headersModel.remove(headersModel.count - popsNeeded, popsNeeded);
64  }
65 
66  Repeater {
67  model: ListModel {
68  id: headersModel
69  // Roles
70  // headerText: the text to show
71  // navigationId: the navigation Id that represents
72  // parentNavigationId: the parent navigation Id
73  }
74  delegate: DashNavigationHeader {
75  objectName: "dashNavigationHeader" + index
76  height: index == 0 && headersModel.count > 1 ? 0 : units.gu(5)
77  width: headersColumn.width
78 
79  backVisible: index != 0
80  text: headerText
81 
82  onBackClicked: {
83  scope.setNavigationState(parentNavigationId);
84 
85  var popsNeeded = headersModel.count - index;
86  headersColumn.pop(popsNeeded);
87  }
88 
89  onTextClicked: {
90  scope.setNavigationState(navigationId);
91 
92  var popsNeeded = headersModel.count - index - 1;
93  headersColumn.pop(popsNeeded);
94 
95  root.leafClicked();
96  }
97  }
98  }
99  }
100 
101  ListView {
102  id: navigationListView
103  objectName: "navigationListView"
104  orientation: ListView.Horizontal
105  interactive: false
106  model: ListModel {
107  id: navigationModel
108  // We have two roles
109  // navigationId: the navigation id of the navigation the list represents
110  // nullifyNavigation: overrides navigationId to be null
111  // This is used to "clear" the delegate when going "right" on the tree
112  }
113  anchors {
114  top: headersColumn.bottom
115  left: parent.left
116  right: parent.right
117  }
118  readonly property int maxHeight: root.availableHeight - navigationListView.y
119  property int prevHeight: maxHeight
120  height: currentItem ? currentItem.height : maxHeight
121 
122  onHeightChanged: {
123  if (currentItem) {
124  prevHeight = currentItem.desiredHeight;
125  }
126  }
127  highlightMoveDuration: UbuntuAnimation.FastDuration
128  delegate: DashNavigationList {
129  objectName: "navigation" + index
130  visible: height > 0
131  width: navigationListView.width
132  property real desiredHeight: {
133  if (navigation && navigation.loaded && x == navigationListView.contentX)
134  {
135  return Math.min(implicitHeight, navigationListView.maxHeight);
136  } else {
137  return navigationListView.prevHeight;
138  }
139  }
140  height: desiredHeight
141  navigation: (nullifyNavigation || !scope) ? null : scope.getNavigation(navigationId)
142  currentNavigation: root.currentNavigation
143  onEnterNavigation: { // var newNavigationId, string newNavigationLabel, bool hasChildren
144  scope.setNavigationState(newNavigationId);
145  // We only need to add a new item to the model
146  // if we have children, otherwise just load it
147  if (hasChildren) {
148  isEnteringChildren = true;
149  navigationModel.append({"navigationId": newNavigationId, "nullifyNavigation": false});
150  headersModel.append({"headerText": newNavigationLabel,
151  "navigationId": newNavigationId,
152  "parentNavigationId": navigationId
153  });
154  navigationListView.currentIndex++;
155  } else {
156  root.leafClicked();
157  }
158  }
159  }
160  onContentXChanged: {
161  if (navigationListView.highlightMoveDuration == 0)
162  return;
163 
164  if (contentX == width * navigationListView.currentIndex) {
165  if (isEnteringChildren) {
166  navigationModel.setProperty(navigationListView.currentIndex - 1, "nullifyNavigation", true);
167  }
168  }
169  }
170  }
171 
172  property bool isFirstLoad: false
173  onScopeChanged: clear();
174  function clear() {
175  navigationModel.clear();
176  headersModel.clear();
177  isFirstLoad = true;
178  }
179  function setNewNavigation() {
180  if (isFirstLoad && currentNavigation && currentNavigation.loaded) {
181  isFirstLoad = false;
182  if (currentNavigation.count > 0) {
183  navigationModel.append({"navigationId": scope.currentNavigationId, "nullifyNavigation": false});
184  } else {
185  navigationModel.append({"navigationId": currentNavigation.parentNavigationId, "nullifyNavigation": false});
186  }
187  headersModel.append({"headerText": currentNavigation.allLabel != "" ? currentNavigation.allLabel : currentNavigation.label,
188  "navigationId": currentNavigation.navigationId,
189  "parentNavigationId": currentNavigation.parentNavigationId
190  });
191  }
192  }
193  Connections {
194  target: currentNavigation
195  onLoadedChanged: setNewNavigation();
196  }
197  onCurrentNavigationChanged: setNewNavigation();
198 }