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