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