Lomiri
Loading...
Searching...
No Matches
PanelBar.qml
1/*
2 * Copyright (C) 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 property alias expanded: row.expanded
25 property alias interactive: flickable.interactive
26 property alias model: row.model
27 property alias unitProgress: row.unitProgress
28 property alias enableLateralChanges: row.enableLateralChanges
29 property alias overFlowWidth: row.overFlowWidth
30 readonly property alias currentItemIndex: row.currentItemIndex
31 property real lateralPosition: -1
32 property int alignment: Qt.AlignRight
33 readonly property int rowContentX: row.contentX
34
35 property alias hideRow: row.hideRow
36 property alias rowItemDelegate: row.delegate
37
38 implicitWidth: flickable.contentWidth
39
40 function selectItemAt(lateralPosition) {
41 if (!expanded) {
42 row.resetCurrentItem();
43 }
44 var mapped = root.mapToItem(row, lateralPosition, 0);
45 row.selectItemAt(mapped.x);
46 }
47
48 function selectPreviousItem() {
49 if (!expanded) {
50 row.resetCurrentItem();
51 }
52 row.selectPreviousItem();
53 d.alignIndicators();
54 }
55
56 function selectNextItem() {
57 if (!expanded) {
58 row.resetCurrentItem();
59 }
60 row.selectNextItem();
61 d.alignIndicators();
62 }
63
64 function setCurrentItemIndex(index) {
65 if (!expanded) {
66 row.resetCurrentItem();
67 }
68 row.setCurrentItemIndex(index);
69 d.alignIndicators();
70 }
71
72 function addScrollOffset(scrollAmmout) {
73 if (root.alignment == Qt.AlignLeft) {
74 scrollAmmout = -scrollAmmout;
75 }
76
77 if (scrollAmmout < 0) { // left scroll
78 if (flickable.contentX + flickable.width > row.width) return; // already off the left.
79
80 if (flickable.contentX + flickable.width - scrollAmmout > row.width) { // going to be off the left
81 scrollAmmout = (flickable.contentX + flickable.width) - row.width;
82 }
83 } else { // right scroll
84 if (flickable.contentX < 0) return; // already off the right.
85 if (flickable.contentX - scrollAmmout < 0) { // going to be off the right
86 scrollAmmout = flickable.contentX;
87 }
88 }
89 d.scrollOffset = d.scrollOffset + scrollAmmout;
90 }
91
92 QtObject {
93 id: d
94 property var initialItem
95 // the non-expanded distance from alignment edge to center of initial item
96 property real originalDistanceFromEdge: -1
97
98 // calculate the distance from row alignment edge edge to center of initial item
99 property real distanceFromEdge: {
100 if (originalDistanceFromEdge == -1) return 0;
101 if (!initialItem) return 0;
102
103 if (root.alignment == Qt.AlignLeft) {
104 return initialItem.x - initialItem.width / 2;
105 } else {
106 return row.width - initialItem.x - initialItem.width / 2;
107 }
108 }
109
110 // offset to the intially selected expanded item
111 property real rowOffset: 0
112 property real scrollOffset: 0
113 property real alignmentAdjustment: 0
114 property real combinedOffset: 0
115
116 // when the scroll offset changes, we need to reclaculate the relative lateral position
117 onScrollOffsetChanged: root.lateralPositionChanged()
118
119 onInitialItemChanged: {
120 if (root.alignment == Qt.AlignLeft) {
121 originalDistanceFromEdge = initialItem ? (initialItem.x - initialItem.width/2) : -1;
122 } else {
123 originalDistanceFromEdge = initialItem ? (row.width - initialItem.x - initialItem.width/2) : -1;
124 }
125 }
126
127 Behavior on alignmentAdjustment {
128 NumberAnimation { duration: LomiriAnimation.BriskDuration; easing: LomiriAnimation.StandardEasing}
129 }
130
131 function alignIndicators() {
132 flickable.resetContentXComponents();
133
134 if (expanded && !flickable.moving) {
135
136 if (root.alignment == Qt.AlignLeft) {
137 // current item overlap on left
138 if (row.currentItem && flickable.contentX > (row.currentItem.x - row.contentX)) {
139 d.alignmentAdjustment -= (flickable.contentX - (row.currentItem.x - row.contentX));
140
141 // current item overlap on right
142 } else if (row.currentItem && flickable.contentX + flickable.width < (row.currentItem.x - row.contentX) + row.currentItem.width) {
143 d.alignmentAdjustment += ((row.currentItem.x - row.contentX) + row.currentItem.width) - (flickable.contentX + flickable.width);
144 }
145 } else {
146 // gap between left and row?
147 if (flickable.contentX + flickable.width > row.width) {
148 // row width is less than flickable
149 if (row.width < flickable.width) {
150 d.alignmentAdjustment -= flickable.contentX;
151 } else {
152 d.alignmentAdjustment -= ((flickable.contentX + flickable.width) - row.width);
153 }
154
155 // gap between right and row?
156 } else if (flickable.contentX < 0) {
157 d.alignmentAdjustment -= flickable.contentX;
158
159 // current item overlap on left
160 } else if (row.currentItem && (flickable.contentX + flickable.width) < (row.width - (row.currentItem.x - row.contentX))) {
161 d.alignmentAdjustment += ((row.width - (row.currentItem.x - row.contentX)) - (flickable.contentX + flickable.width));
162
163 // current item overlap on right
164 } else if (row.currentItem && flickable.contentX > (row.width - (row.currentItem.x - row.contentX) - row.currentItem.width)) {
165 d.alignmentAdjustment -= flickable.contentX - (row.width - (row.currentItem.x - row.contentX) - row.currentItem.width);
166 }
167 }
168 }
169 }
170 }
171
172 Item {
173 id: rowContainer
174 anchors.fill: parent
175 clip: expanded || row.width > rowContainer.width
176
177 Flickable {
178 id: flickable
179 objectName: "flickable"
180
181 // we rotate it because we want the Flickable to align its content item
182 // on the right instead of on the left
183 rotation: root.alignment != Qt.AlignRight ? 0 : 180
184
185 anchors.fill: parent
186 contentWidth: row.width
187 contentX: d.combinedOffset
188 interactive: false
189
190 // contentX can change by user interaction as well as user offset changes
191 // This function re-aligns the offsets so that the offsets match the contentX
192 function resetContentXComponents() {
193 d.scrollOffset += d.combinedOffset - flickable.contentX;
194 }
195
196 rebound: Transition {
197 NumberAnimation {
198 properties: "x"
199 duration: 600
200 easing.type: Easing.OutCubic
201 }
202 }
203
204 PanelItemRow {
205 id: row
206 objectName: "panelItemRow"
207 anchors {
208 top: parent.top
209 bottom: parent.bottom
210 }
211
212 // Compensate for the Flickable rotation (ie, counter-rotate)
213 rotation: root.alignment != Qt.AlignRight ? 0 : 180
214
215 lateralPosition: {
216 if (root.lateralPosition == -1) return -1;
217
218 var mapped = root.mapToItem(row, root.lateralPosition, 0);
219 return Math.min(Math.max(mapped.x, 0), row.width);
220 }
221
222 onCurrentItemChanged: {
223 if (!currentItem) d.initialItem = undefined;
224 else if (!d.initialItem) d.initialItem = currentItem;
225 }
226
227 MouseArea {
228 anchors.fill: parent
229 enabled: root.expanded
230 onClicked: {
231 row.selectItemAt(mouse.x);
232 d.alignIndicators();
233 }
234 }
235 }
236
237 }
238 }
239
240 Timer {
241 id: alignmentTimer
242 interval: LomiriAnimation.FastDuration // enough for row animation.
243 repeat: false
244
245 onTriggered: d.alignIndicators();
246 }
247
248 states: [
249 State {
250 name: "minimized"
251 when: !expanded
252 PropertyChanges {
253 target: d
254 rowOffset: 0
255 scrollOffset: 0
256 alignmentAdjustment: 0
257 combinedOffset: 0
258 restoreEntryValues: false
259 }
260 },
261 State {
262 name: "expanded"
263 when: expanded && !interactive
264
265 PropertyChanges {
266 target: d
267 combinedOffset: rowOffset + alignmentAdjustment - scrollOffset
268 }
269 PropertyChanges {
270 target: d
271 rowOffset: {
272 if (!initialItem) return 0;
273 if (distanceFromEdge - initialItem.width <= 0) return 0;
274
275 var rowOffset = distanceFromEdge - originalDistanceFromEdge;
276 return rowOffset;
277 }
278 restoreEntryValues: false
279 }
280 },
281 State {
282 name: "interactive"
283 when: expanded && interactive
284
285 StateChangeScript {
286 script: {
287 // don't use row offset anymore.
288 d.scrollOffset -= d.rowOffset;
289 d.rowOffset = 0;
290 d.initialItem = undefined;
291 alignmentTimer.start();
292 }
293 }
294 PropertyChanges {
295 target: d
296 combinedOffset: rowOffset + alignmentAdjustment - scrollOffset
297 restoreEntryValues: false
298 }
299 }
300 ]
301
302 transitions: [
303 Transition {
304 from: "expanded"
305 to: "minimized"
306 PropertyAction {
307 target: d
308 properties: "rowOffset, scrollOffset, alignmentAdjustment"
309 value: 0
310 }
311 PropertyAnimation {
312 target: d
313 properties: "combinedOffset"
314 duration: LomiriAnimation.SnapDuration
315 easing: LomiriAnimation.StandardEasing
316 }
317 }
318 ]
319}