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