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