2 * Copyright (C) 2014 Canonical Ltd.
3 * Copyright (C) 2020 UBports Foundation
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.
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.
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/>.
19import Lomiri.Components 1.3
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
36 property alias hideRow: row.hideRow
37 property alias rowItemDelegate: row.delegate
39 implicitWidth: flickable.contentWidth
41 function selectItemAt(lateralPosition) {
43 row.resetCurrentItem();
45 var mapped = root.mapToItem(row, lateralPosition, 0);
46 row.selectItemAt(mapped.x);
49 function selectPreviousItem() {
51 row.resetCurrentItem();
53 row.selectPreviousItem();
57 function selectNextItem() {
59 row.resetCurrentItem();
65 function setCurrentItemIndex(index) {
67 row.resetCurrentItem();
69 row.setCurrentItemIndex(index);
73 function addScrollOffset(scrollAmmout) {
74 if (root.alignment == Qt.AlignLeft) {
75 scrollAmmout = -scrollAmmout;
78 if (scrollAmmout < 0) { // left scroll
79 if (flickable.contentX + flickable.width > row.width) return; // already off the left.
81 if (flickable.contentX + flickable.width - scrollAmmout > row.width) { // going to be off the left
82 scrollAmmout = (flickable.contentX + flickable.width) - row.width;
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;
90 d.scrollOffset = d.scrollOffset + scrollAmmout;
95 property var initialItem
96 // the non-expanded distance from alignment edge to center of initial item
97 property real originalDistanceFromEdge: -1
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;
104 if (root.alignment == Qt.AlignLeft) {
105 return initialItem.x - initialItem.width / 2;
107 return row.width - initialItem.x - initialItem.width / 2;
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
117 // when the scroll offset changes, we need to reclaculate the relative lateral position
118 onScrollOffsetChanged: root.lateralPositionChanged()
120 onInitialItemChanged: {
121 if (root.alignment == Qt.AlignLeft) {
122 originalDistanceFromEdge = initialItem ? (initialItem.x - initialItem.width/2) : -1;
124 originalDistanceFromEdge = initialItem ? (row.width - initialItem.x - initialItem.width/2) : -1;
128 Behavior on alignmentAdjustment {
129 NumberAnimation { duration: LomiriAnimation.BriskDuration; easing: LomiriAnimation.StandardEasing}
132 function alignIndicators() {
133 flickable.resetContentXComponents();
135 if (expanded && !flickable.moving) {
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));
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);
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;
153 d.alignmentAdjustment -= ((flickable.contentX + flickable.width) - row.width);
156 // gap between right and row?
157 } else if (flickable.contentX < 0) {
158 d.alignmentAdjustment -= flickable.contentX;
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));
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);
176 clip: expanded || row.width > rowContainer.width
180 objectName: "flickable"
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
187 contentWidth: row.width
188 contentX: d.combinedOffset
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;
197 rebound: Transition {
201 easing.type: Easing.OutCubic
207 objectName: "panelItemRow"
208 lightMode: root.lightMode
211 bottom: parent.bottom
214 // Compensate for the Flickable rotation (ie, counter-rotate)
215 rotation: root.alignment != Qt.AlignRight ? 0 : 180
218 if (root.lateralPosition == -1) return -1;
220 var mapped = root.mapToItem(row, root.lateralPosition, 0);
221 return Math.min(Math.max(mapped.x, 0), row.width);
224 onCurrentItemChanged: {
225 if (!currentItem) d.initialItem = undefined;
226 else if (!d.initialItem) d.initialItem = currentItem;
231 enabled: root.expanded
233 row.selectItemAt(mouse.x);
244 interval: LomiriAnimation.FastDuration // enough for row animation.
247 onTriggered: d.alignIndicators();
258 alignmentAdjustment: 0
260 restoreEntryValues: false
265 when: expanded && !interactive
269 combinedOffset: rowOffset + alignmentAdjustment - scrollOffset
274 if (!initialItem) return 0;
275 if (distanceFromEdge - initialItem.width <= 0) return 0;
277 var rowOffset = distanceFromEdge - originalDistanceFromEdge;
280 restoreEntryValues: false
285 when: expanded && interactive
289 // don't use row offset anymore.
290 d.scrollOffset -= d.rowOffset;
292 d.initialItem = undefined;
293 alignmentTimer.start();
298 combinedOffset: rowOffset + alignmentAdjustment - scrollOffset
299 restoreEntryValues: false
310 properties: "rowOffset, scrollOffset, alignmentAdjustment"
315 properties: "combinedOffset"
316 duration: LomiriAnimation.SnapDuration
317 easing: LomiriAnimation.StandardEasing