Unity 8
PanelBar.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.4
18 import Ubuntu.Components 1.3
19 import "../Components"
20 
21 Item {
22  id: root
23  property alias expanded: row.expanded
24  property alias interactive: flickable.interactive
25  property alias model: row.model
26  property alias unitProgress: row.unitProgress
27  property alias enableLateralChanges: row.enableLateralChanges
28  property alias overFlowWidth: row.overFlowWidth
29  readonly property alias currentItemIndex: row.currentItemIndex
30  property real lateralPosition: -1
31  property int alignment: Qt.AlignRight
32 
33  property alias hideRow: row.hideRow
34  property alias rowItemDelegate: row.delegate
35 
36  implicitWidth: flickable.contentWidth
37 
38  function selectItemAt(lateralPosition) {
39  if (!expanded) {
40  row.resetCurrentItem();
41  }
42  var mapped = root.mapToItem(row, lateralPosition, 0);
43  row.selectItemAt(mapped.x);
44  }
45 
46  function selectPreviousItem() {
47  if (!expanded) {
48  row.resetCurrentItem();
49  }
50  row.selectPreviousItem();
51  d.alignIndicators();
52  }
53 
54  function selectNextItem() {
55  if (!expanded) {
56  row.resetCurrentItem();
57  }
58  row.selectNextItem();
59  d.alignIndicators();
60  }
61 
62  function setCurrentItemIndex(index) {
63  if (!expanded) {
64  row.resetCurrentItem();
65  }
66  row.setCurrentItemIndex(index);
67  d.alignIndicators();
68  }
69 
70  function addScrollOffset(scrollAmmout) {
71  if (root.alignment == Qt.AlignLeft) {
72  scrollAmmout = -scrollAmmout;
73  }
74 
75  if (scrollAmmout < 0) { // left scroll
76  if (flickable.contentX + flickable.width > row.width) return; // already off the left.
77 
78  if (flickable.contentX + flickable.width - scrollAmmout > row.width) { // going to be off the left
79  scrollAmmout = (flickable.contentX + flickable.width) - row.width;
80  }
81  } else { // right scroll
82  if (flickable.contentX < 0) return; // already off the right.
83  if (flickable.contentX - scrollAmmout < 0) { // going to be off the right
84  scrollAmmout = flickable.contentX;
85  }
86  }
87  d.scrollOffset = d.scrollOffset + scrollAmmout;
88  }
89 
90  QtObject {
91  id: d
92  property var initialItem
93  // the non-expanded distance from alignment edge to center of initial item
94  property real originalDistanceFromEdge: -1
95 
96  // calculate the distance from row alignment edge edge to center of initial item
97  property real distanceFromEdge: {
98  if (originalDistanceFromEdge == -1) return 0;
99  if (!initialItem) return 0;
100 
101  if (root.alignment == Qt.AlignLeft) {
102  return initialItem.x - initialItem.width / 2;
103  } else {
104  return row.width - initialItem.x - initialItem.width / 2;
105  }
106  }
107 
108  // offset to the intially selected expanded item
109  property real rowOffset: 0
110  property real scrollOffset: 0
111  property real alignmentAdjustment: 0
112  property real combinedOffset: 0
113 
114  // when the scroll offset changes, we need to reclaculate the relative lateral position
115  onScrollOffsetChanged: root.lateralPositionChanged()
116 
117  onInitialItemChanged: {
118  if (root.alignment == Qt.AlignLeft) {
119  originalDistanceFromEdge = initialItem ? (initialItem.x - initialItem.width/2) : -1;
120  } else {
121  originalDistanceFromEdge = initialItem ? (row.width - initialItem.x - initialItem.width/2) : -1;
122  }
123  }
124 
125  Behavior on alignmentAdjustment {
126  NumberAnimation { duration: UbuntuAnimation.BriskDuration; easing: UbuntuAnimation.StandardEasing}
127  }
128 
129  function alignIndicators() {
130  flickable.resetContentXComponents();
131 
132  if (expanded && !flickable.moving) {
133 
134  if (root.alignment == Qt.AlignLeft) {
135  // current item overlap on left
136  if (row.currentItem && flickable.contentX > row.currentItem.x) {
137  d.alignmentAdjustment -= (flickable.contentX - row.currentItem.x);
138 
139  // current item overlap on right
140  } else if (row.currentItem && flickable.contentX + flickable.width < row.currentItem.x + row.currentItem.width) {
141  d.alignmentAdjustment += (row.currentItem.x + row.currentItem.width) - (flickable.contentX + flickable.width);
142  }
143  } else {
144  // gap between left and row?
145  if (flickable.contentX + flickable.width > row.width) {
146  // row width is less than flickable
147  if (row.width < flickable.width) {
148  d.alignmentAdjustment -= flickable.contentX;
149  } else {
150  d.alignmentAdjustment -= ((flickable.contentX + flickable.width) - row.width);
151  }
152 
153  // gap between right and row?
154  } else if (flickable.contentX < 0) {
155  d.alignmentAdjustment -= flickable.contentX;
156 
157  // current item overlap on left
158  } else if (row.currentItem && (flickable.contentX + flickable.width) < (row.width - row.currentItem.x)) {
159  d.alignmentAdjustment += ((row.width - row.currentItem.x) - (flickable.contentX + flickable.width));
160 
161  // current item overlap on right
162  } else if (row.currentItem && flickable.contentX > (row.width - row.currentItem.x - row.currentItem.width)) {
163  d.alignmentAdjustment -= flickable.contentX - (row.width - row.currentItem.x - row.currentItem.width);
164  }
165  }
166  }
167  }
168  }
169 
170  Rectangle {
171  id: grayLine
172  height: units.dp(2)
173  width: parent.width
174  anchors.bottom: parent.bottom
175 
176  color: "#888888"
177  opacity: expanded ? 1.0 : 0.0
178  Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.SnapDuration } }
179  }
180 
181  Item {
182  id: rowContainer
183  anchors.fill: parent
184  clip: expanded || row.width > rowContainer.width
185 
186  Flickable {
187  id: flickable
188  objectName: "flickable"
189 
190  // we rotate it because we want the Flickable to align its content item
191  // on the right instead of on the left
192  rotation: root.alignment != Qt.AlignRight ? 0 : 180
193 
194  anchors.fill: parent
195  contentWidth: row.width
196  contentX: d.combinedOffset
197  interactive: false
198 
199  // contentX can change by user interaction as well as user offset changes
200  // This function re-aligns the offsets so that the offsets match the contentX
201  function resetContentXComponents() {
202  d.scrollOffset += d.combinedOffset - flickable.contentX;
203  }
204 
205  rebound: Transition {
206  NumberAnimation {
207  properties: "x"
208  duration: 600
209  easing.type: Easing.OutCubic
210  }
211  }
212 
213  PanelItemRow {
214  id: row
215  objectName: "panelItemRow"
216  anchors {
217  top: parent.top
218  bottom: parent.bottom
219  }
220 
221  // Compensate for the Flickable rotation (ie, counter-rotate)
222  rotation: root.alignment != Qt.AlignRight ? 0 : 180
223 
224  lateralPosition: {
225  if (root.lateralPosition == -1) return -1;
226 
227  var mapped = root.mapToItem(row, root.lateralPosition, 0);
228  return Math.min(Math.max(mapped.x, 0), row.width);
229  }
230 
231  onCurrentItemChanged: {
232  if (!currentItem) d.initialItem = undefined;
233  else if (!d.initialItem) d.initialItem = currentItem;
234  }
235 
236  MouseArea {
237  anchors.fill: parent
238  enabled: root.expanded
239  onClicked: {
240  row.selectItemAt(mouse.x);
241  d.alignIndicators();
242  }
243  }
244  }
245 
246  }
247  }
248 
249  Timer {
250  id: alignmentTimer
251  interval: UbuntuAnimation.FastDuration // enough for row animation.
252  repeat: false
253 
254  onTriggered: d.alignIndicators();
255  }
256 
257  states: [
258  State {
259  name: "minimized"
260  when: !expanded
261  PropertyChanges {
262  target: d
263  rowOffset: 0
264  scrollOffset: 0
265  alignmentAdjustment: 0
266  combinedOffset: 0
267  restoreEntryValues: false
268  }
269  },
270  State {
271  name: "expanded"
272  when: expanded && !interactive
273 
274  PropertyChanges {
275  target: d
276  combinedOffset: rowOffset + alignmentAdjustment - scrollOffset
277  }
278  PropertyChanges {
279  target: d
280  rowOffset: {
281  if (!initialItem) return 0;
282  if (distanceFromEdge - initialItem.width <= 0) return 0;
283 
284  var rowOffset = distanceFromEdge - originalDistanceFromEdge;
285  return rowOffset;
286  }
287  restoreEntryValues: false
288  }
289  },
290  State {
291  name: "interactive"
292  when: expanded && interactive
293 
294  StateChangeScript {
295  script: {
296  // don't use row offset anymore.
297  d.scrollOffset -= d.rowOffset;
298  d.rowOffset = 0;
299  d.initialItem = undefined;
300  alignmentTimer.start();
301  }
302  }
303  PropertyChanges {
304  target: d
305  combinedOffset: rowOffset + alignmentAdjustment - scrollOffset
306  restoreEntryValues: false
307  }
308  }
309  ]
310 
311  transitions: [
312  Transition {
313  from: "expanded"
314  to: "minimized"
315  PropertyAction {
316  target: d
317  properties: "rowOffset, scrollOffset, alignmentAdjustment"
318  value: 0
319  }
320  PropertyAnimation {
321  target: d
322  properties: "combinedOffset"
323  duration: UbuntuAnimation.SnapDuration
324  easing: UbuntuAnimation.StandardEasing
325  }
326  }
327  ]
328 }