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