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