Unity 8
DashNavigationButton.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 
20 AbstractButton {
21  id: root
22  objectName: "dashNavigation"
23 
24  // Set by parent
25  property var scope: null
26  property var scopeStyle: null
27  property color foregroundColor: theme.palette.normal.baseText
28  property bool isAltNavigation: false
29  property bool showDivider: false
30 
31  // Used by parent
32  readonly property var currentNavigation: scope && scope[hasNavigation] ? getNavigation(scope[currentNavigationId]) : null
33  readonly property alias listView: navigationListView
34  readonly property bool inverseMousePressed: inverseMouseArea.pressed
35  property bool showList: false
36 
37  // Internal
38  // Are we drilling down the tree or up?
39  property bool isGoingBack: false
40  readonly property string hasNavigation: isAltNavigation ? "hasAltNavigation" : "hasNavigation"
41  readonly property string currentNavigationId: isAltNavigation ? "currentAltNavigationId" : "currentNavigationId"
42  function getNavigation(navId) {
43  if (isAltNavigation) {
44  return scope.getAltNavigation(navId);
45  } else {
46  return scope.getNavigation(navId);
47  }
48  }
49 
50  visible: root.currentNavigation != null
51 
52  onClicked: {
53  navigationListView.updateMaxHeight();
54  root.showList = !root.showList;
55  }
56 
57  Label {
58  anchors.fill: parent
59  anchors.margins: units.gu(2)
60  anchors.rightMargin: units.gu(5)
61  verticalAlignment: Text.AlignVCenter
62  text: root.currentNavigation ? root.currentNavigation.label : ""
63  color: root.foregroundColor
64  elide: Text.ElideRight
65  maximumLineCount: 1
66  }
67 
68  Icon {
69  anchors.verticalCenter: parent.verticalCenter
70  anchors.right: parent.right
71  anchors.rightMargin: units.gu(2)
72  name: showList ? "up" : "down"
73  height: units.gu(2)
74  width: height
75  color: root.foregroundColor
76  }
77 
78  // navigationListView is outside root
79  ListView {
80  id: navigationListView
81  objectName: "navigationListView"
82  visible: height > 0
83  orientation: ListView.Horizontal
84  interactive: false
85  clip: root.width != windowWidth
86  model: ListModel {
87  id: navigationModel
88  // We have two roles
89  // navigationId: the navigation id of the navigation the list represents
90  // nullifyNavigation: overrides navigationId to be null
91  // This is used to "clear" the delegate when going "right" on the tree
92  }
93  anchors.top: root.bottom
94  property int maxHeight: -1
95  Component.onCompleted: updateMaxHeight();
96  function updateMaxHeight()
97  {
98  maxHeight = (windowHeight - mapToItem(null, 0, 0).y) - units.gu(8);
99  }
100  property int prevHeight: maxHeight
101  height: currentItem ? currentItem.height : maxHeight
102  onHeightChanged: {
103  if (root.showList) {
104  prevHeight = currentItem.desiredHeight;
105  }
106  }
107  highlightMoveDuration: UbuntuAnimation.FastDuration
108  delegate: DashNavigationList {
109  objectName: "navigation" + index
110  visible: height > 0
111  width: navigationListView.width
112  scopeStyle: root.scopeStyle
113  foregroundColor: root.foregroundColor
114  property real desiredHeight: {
115  if (root.showList) {
116  if (navigation && navigation.loaded && x == navigationListView.contentX)
117  {
118  navigationListView.updateMaxHeight();
119  return Math.min(implicitHeight, navigationListView.maxHeight);
120  } else {
121  return navigationListView.prevHeight;
122  }
123  } else {
124  return 0;
125  }
126  }
127  height: desiredHeight
128  navigation: (nullifyNavigation || !scope) ? null : getNavigation(navigationId)
129  currentNavigation: root.currentNavigation
130  onEnterNavigation: {
131  scope.setNavigationState(newNavigationId, isAltNavigation);
132  // We only need to add a new item to the model
133  // if we have children, otherwise just load it
134  if (hasChildren) {
135  isGoingBack = false;
136  navigationModel.append({"navigationId": newNavigationId, "nullifyNavigation": false});
137  navigationListView.currentIndex++;
138  } else {
139  showList = false;
140  }
141  }
142  onGoBackToParentClicked: {
143  if (navigationListView.currentIndex == 0) {
144  // This can happen if we jumped to the non root of a deep tree and the user
145  // is now going back, create space in the list for the list to move "left"
146  var aux = navigationListView.highlightMoveDuration;
147  navigationListView.highlightMoveDuration = 0;
148  navigationModel.insert(0, {"navigationId": navigation.parentNavigationId, "nullifyNavigation": false});
149  navigationListView.currentIndex = navigationListView.currentIndex + 1;
150  navigationListView.contentX = width * navigationListView.currentIndex;
151  navigationListView.highlightMoveDuration = aux;
152  }
153 
154  scope.setNavigationState(navigation.parentNavigationId, isAltNavigation);
155  isGoingBack = true;
156  navigationModel.setProperty(navigationListView.currentIndex - 1, "nullifyNavigation", false);
157  navigationListView.currentIndex--;
158  }
159  onAllNavigationClicked: {
160  showList = false;
161  if (root.currentNavigation.parentNavigationId == navigation.navigationId) {
162  // For leaves we have to go to the parent too
163  scope.setNavigationState(root.currentNavigation.parentNavigationId, isAltNavigation);
164  }
165  }
166  }
167  onContentXChanged: {
168  if (navigationListView.highlightMoveDuration == 0)
169  return;
170 
171  if (contentX == width * navigationListView.currentIndex) {
172  if (isGoingBack) {
173  navigationModel.remove(navigationListView.currentIndex + 1);
174  } else {
175  navigationModel.setProperty(navigationListView.currentIndex - 1, "nullifyNavigation", true);
176  }
177  }
178  }
179  }
180 
181  Image {
182  anchors {
183  top: navigationListView.bottom
184  left: navigationListView.left
185  right: navigationListView.right
186  }
187  fillMode: Image.Stretch
188  source: "graphics/navigation_shadow.png"
189  visible: root.showList
190  }
191 
192  property bool isFirstLoad: false
193  onScopeChanged: {
194  navigationModel.clear();
195  isFirstLoad = true;
196  }
197  function setNewNavigation() {
198  if (isFirstLoad && currentNavigation && currentNavigation.loaded) {
199  isFirstLoad = false;
200  if (currentNavigation.count > 0) {
201  navigationModel.append({"navigationId": scope[currentNavigationId], "nullifyNavigation": false});
202  } else {
203  navigationModel.append({"navigationId": currentNavigation.parentNavigationId, "nullifyNavigation": false});
204  }
205  }
206  }
207  Connections {
208  target: currentNavigation
209  onLoadedChanged: setNewNavigation();
210  }
211  onCurrentNavigationChanged: setNewNavigation();
212 
213  InverseMouseArea {
214  id: inverseMouseArea
215  anchors.fill: navigationListView
216  enabled: root.showList
217  onPressed: root.showList = false
218  }
219 
220  Rectangle {
221  visible: root.showDivider
222  anchors {
223  top: parent.top
224  topMargin: units.dp(1)
225  bottom: parent.bottom
226  left: parent.right
227  leftMargin: -units.dp(0.5)
228  }
229  width: units.dp(1)
230  color: root.foregroundColor
231  opacity: 0.2
232  }
233 }