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