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