Unity 8
IndicatorItemRow.qml
1 /*
2  * Copyright (C) 2013-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 QtQuick.Window 2.2
19 import Ubuntu.Components 1.3
20 
21 Item {
22  id: root
23  width: row.width
24  height: units.gu(3)
25 
26  property QtObject indicatorsModel: null
27  property real overFlowWidth: width
28  property bool expanded: false
29  property var currentItem
30  readonly property int currentItemIndex: currentItem ? currentItem.ownIndex : -1
31 
32  property real unitProgress: 0.0
33  property real selectionChangeBuffer: units.gu(2)
34  property bool enableLateralChanges: false
35  property color hightlightColor: "#ffffff"
36 
37  property real lateralPosition: -1
38  onLateralPositionChanged: {
39  updateItemFromLateralPosition();
40  }
41 
42  onEnableLateralChangesChanged: {
43  updateItemFromLateralPosition();
44  }
45 
46  function updateItemFromLateralPosition() {
47  if (currentItem && !enableLateralChanges) return;
48  if (lateralPosition === -1) return;
49 
50  if (!currentItem) {
51  selectItemAt(lateralPosition);
52  return;
53  }
54 
55  var maximumBufferOffset = selectionChangeBuffer * unitProgress;
56  var proposedItem = indicatorAt(lateralPosition, 0);
57  if (proposedItem) {
58  var bufferExceeded = false;
59 
60  if (proposedItem !== currentItem) {
61  // Proposed item is not directly adjacent to current?
62  if (Math.abs(proposedItem.ownIndex - currentItem.ownIndex) > 1) {
63  bufferExceeded = true;
64  } else { // no
65  var currentItemLateralPosition = root.mapToItem(proposedItem, lateralPosition, 0).x;
66 
67  // Is the distance into proposed item greater than max buffer?
68  // Proposed item is before current item
69  if (proposedItem.x < currentItem.x) {
70  bufferExceeded = (proposedItem.width - currentItemLateralPosition) > maximumBufferOffset;
71  } else { // After
72  bufferExceeded = currentItemLateralPosition > maximumBufferOffset;
73  }
74  }
75  if (bufferExceeded) {
76  selectItemAt(lateralPosition);
77  }
78  }
79  } else {
80  selectItemAt(lateralPosition);
81  }
82  }
83 
84  function indicatorAt(x, y) {
85  var item = row.childAt(x, y);
86  return item && item.hasOwnProperty("ownIndex") ? item : null;
87  }
88 
89  function resetCurrentItem() {
90  d.firstItemSwitch = true;
91  d.previousItem = undefined;
92  currentItem = undefined;
93  }
94 
95  function setCurrentItemIndex(index) {
96  for (var i = 0; i < row.children.length; i++) {
97  var item = row.children[i];
98  if (item.hasOwnProperty("ownIndex") && item.ownIndex === index) {
99  if (currentItem !== item) currentItem = item;
100  break;
101  }
102  }
103  }
104 
105  function selectItemAt(lateralPosition) {
106  var item = indicatorAt(lateralPosition, 0);
107  if (item && item.opacity > 0) {
108  currentItem = item;
109  } else {
110  // Select default item.
111  var searchIndex = lateralPosition > width ? repeater.count - 1 : 0;
112 
113  for (var i = 0; i < row.children.length; i++) {
114  if (row.children[i].hasOwnProperty("ownIndex") && row.children[i].ownIndex === searchIndex) {
115  item = row.children[i];
116  break;
117  }
118  }
119  if (currentItem !== item) currentItem = item;
120  }
121  }
122 
123  QtObject {
124  id: d
125  property bool firstItemSwitch: true
126  property var previousItem
127  property bool forceAlignmentAnimationDisabled: false
128  }
129 
130  onCurrentItemChanged: {
131  if (d.previousItem) {
132  d.firstItemSwitch = false;
133  }
134  d.previousItem = currentItem;
135  }
136 
137  Row {
138  id: row
139  anchors {
140  top: parent.top
141  bottom: parent.bottom
142  }
143 
144  // TODO: make this better
145  // when the width changes, the highlight will lag behind due to animation, so we need to disable the animation
146  // and adjust the highlight X immediately.
147  width: implicitWidth
148  Behavior on width {
149  SequentialAnimation {
150  ScriptAction {
151  script: {
152  d.forceAlignmentAnimationDisabled = true;
153  highlight.currentItemX = Qt.binding(function() { return currentItem ? currentItem.x : 0 });
154  d.forceAlignmentAnimationDisabled = false;
155  }
156  }
157  }
158  }
159 
160  Repeater {
161  id: repeater
162  model: indicatorsModel
163  visible: false
164 
165  onItemRemoved: {
166  // current item removed.
167  if (currentItem === item) {
168  var i = 0;
169  while (i < row.children.length) {
170  var childItem = row.children[i];
171  if (childItem !== item) {
172  setCurrentItemIndex(i);
173  break;
174  }
175  i++;
176  }
177  }
178  }
179 
180 
181  delegate: IndicatorItem {
182  id: indicatorItem
183  objectName: identifier+"-panelItem"
184 
185  property int ownIndex: index
186  property bool overflow: row.width - x > overFlowWidth
187  property bool hidden: !expanded && (overflow || !indicatorVisible || hideSessionIndicator)
188  // HACK for indicator-session
189  readonly property bool hideSessionIndicator: identifier == "indicator-session" && Math.min(Screen.width, Screen.height) <= units.gu(60)
190 
191  height: row.height
192  expanded: root.expanded
193  selected: currentItem === this
194 
195  identifier: model.identifier
196  busName: indicatorProperties.busName
197  actionsObjectPath: indicatorProperties.actionsObjectPath
198  menuObjectPath: indicatorProperties.menuObjectPath
199 
200  opacity: hidden ? 0.0 : 1.0
201  Behavior on opacity {
202  NumberAnimation { duration: UbuntuAnimation.SnapDuration; easing: UbuntuAnimation.StandardEasing }
203  }
204 
205  width: ((expanded || indicatorVisible) && !hideSessionIndicator) ? implicitWidth : 0
206 
207  Behavior on width {
208  NumberAnimation { duration: UbuntuAnimation.SnapDuration; easing: UbuntuAnimation.StandardEasing }
209  }
210 
211  Component.onDestruction: {
212  // current item removed.
213  if (currentItem === this) {
214  var i = 0;
215  while (i < row.children.length) {
216  var childItem = row.children[i];
217  if (childItem !== this) {
218  setCurrentItemIndex(i);
219  break;
220  }
221  i++;
222  }
223  }
224  }
225  }
226  }
227  }
228 
229  Rectangle {
230  id: highlight
231  objectName: "highlight"
232 
233  anchors.bottom: row.bottom
234  height: units.dp(2)
235  color: root.hightlightColor
236  visible: currentItem !== undefined
237  opacity: 0.0
238 
239  width: currentItem ? currentItem.width : 0
240  Behavior on width {
241  enabled: !d.firstItemSwitch && expanded
242  UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
243  }
244 
245  // micromovements of the highlight line when user moves the finger across the items while pulling
246  // the handle downwards.
247  property real highlightCenterOffset: {
248  if (!currentItem || lateralPosition == -1 || !enableLateralChanges) return 0;
249 
250  var itemMapped = root.mapToItem(currentItem, lateralPosition, 0);
251 
252  var distanceFromCenter = itemMapped.x - currentItem.width / 2;
253  if (distanceFromCenter > 0) {
254  distanceFromCenter = Math.max(0, distanceFromCenter - currentItem.width / 8);
255  } else {
256  distanceFromCenter = Math.min(0, distanceFromCenter + currentItem.width / 8);
257  }
258 
259  if (currentItem && currentItem.ownIndex === 0 && distanceFromCenter < 0) {
260  return 0;
261  } else if (currentItem && currentItem.ownIndex === repeater.count-1 & distanceFromCenter > 0) {
262  return 0;
263  }
264  return (distanceFromCenter / (currentItem.width / 4)) * units.gu(1);
265  }
266  Behavior on highlightCenterOffset {
267  NumberAnimation { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
268  }
269 
270  property real currentItemX: currentItem ? currentItem.x : 0
271  Behavior on currentItemX {
272  id: currentItemXBehavior
273  enabled: !d.firstItemSwitch && expanded && !d.forceAlignmentAnimationDisabled
274  NumberAnimation { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
275  }
276  x: currentItemX + highlightCenterOffset
277  }
278 
279  states: [
280  State {
281  name: "minimised"
282  when: !expanded
283  },
284  State {
285  name: "expanded"
286  when: expanded
287  PropertyChanges { target: highlight; opacity: 0.9 }
288  }
289  ]
290 
291  transitions: [
292  Transition {
293  PropertyAnimation {
294  properties: "opacity";
295  duration: UbuntuAnimation.SnapDuration
296  easing: UbuntuAnimation.StandardEasing
297  }
298  }
299  ]
300 }