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 readonly property int rowContentX: row.contentX
35 property alias hideRow: row.hideRow
36 property alias rowItemDelegate: row.delegate
38 implicitWidth: flickable.contentWidth
40 function selectItemAt(lateralPosition) {
42 row.resetCurrentItem();
44 var mapped = root.mapToItem(row, lateralPosition, 0);
45 row.selectItemAt(mapped.x);
48 function selectPreviousItem() {
50 row.resetCurrentItem();
52 row.selectPreviousItem();
56 function selectNextItem() {
58 row.resetCurrentItem();
64 function setCurrentItemIndex(index) {
66 row.resetCurrentItem();
68 row.setCurrentItemIndex(index);
72 function addScrollOffset(scrollAmmout) {
73 if (root.alignment == Qt.AlignLeft) {
74 scrollAmmout = -scrollAmmout;
77 if (scrollAmmout < 0) { // left scroll
78 if (flickable.contentX + flickable.width > row.width) return; // already off the left.
80 if (flickable.contentX + flickable.width - scrollAmmout > row.width) { // going to be off the left
81 scrollAmmout = (flickable.contentX + flickable.width) - row.width;
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;
89 d.scrollOffset = d.scrollOffset + scrollAmmout;
94 property var initialItem
95 // the non-expanded distance from alignment edge to center of initial item
96 property real originalDistanceFromEdge: -1
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;
103 if (root.alignment == Qt.AlignLeft) {
104 return initialItem.x - initialItem.width / 2;
106 return row.width - initialItem.x - initialItem.width / 2;
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
116 // when the scroll offset changes, we need to reclaculate the relative lateral position
117 onScrollOffsetChanged: root.lateralPositionChanged()
119 onInitialItemChanged: {
120 if (root.alignment == Qt.AlignLeft) {
121 originalDistanceFromEdge = initialItem ? (initialItem.x - initialItem.width/2) : -1;
123 originalDistanceFromEdge = initialItem ? (row.width - initialItem.x - initialItem.width/2) : -1;
127 Behavior on alignmentAdjustment {
128 NumberAnimation { duration: LomiriAnimation.BriskDuration; easing: LomiriAnimation.StandardEasing}
131 function alignIndicators() {
132 flickable.resetContentXComponents();
134 if (expanded && !flickable.moving) {
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));
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);
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;
152 d.alignmentAdjustment -= ((flickable.contentX + flickable.width) - row.width);
155 // gap between right and row?
156 } else if (flickable.contentX < 0) {
157 d.alignmentAdjustment -= flickable.contentX;
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));
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);
175 clip: expanded || row.width > rowContainer.width
179 objectName: "flickable"
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
186 contentWidth: row.width
187 contentX: d.combinedOffset
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;
196 rebound: Transition {
200 easing.type: Easing.OutCubic
206 objectName: "panelItemRow"
209 bottom: parent.bottom
212 // Compensate for the Flickable rotation (ie, counter-rotate)
213 rotation: root.alignment != Qt.AlignRight ? 0 : 180
216 if (root.lateralPosition == -1) return -1;
218 var mapped = root.mapToItem(row, root.lateralPosition, 0);
219 return Math.min(Math.max(mapped.x, 0), row.width);
222 onCurrentItemChanged: {
223 if (!currentItem) d.initialItem = undefined;
224 else if (!d.initialItem) d.initialItem = currentItem;
229 enabled: root.expanded
231 row.selectItemAt(mouse.x);
242 interval: LomiriAnimation.FastDuration // enough for row animation.
245 onTriggered: d.alignIndicators();
256 alignmentAdjustment: 0
258 restoreEntryValues: false
263 when: expanded && !interactive
267 combinedOffset: rowOffset + alignmentAdjustment - scrollOffset
272 if (!initialItem) return 0;
273 if (distanceFromEdge - initialItem.width <= 0) return 0;
275 var rowOffset = distanceFromEdge - originalDistanceFromEdge;
278 restoreEntryValues: false
283 when: expanded && interactive
287 // don't use row offset anymore.
288 d.scrollOffset -= d.rowOffset;
290 d.initialItem = undefined;
291 alignmentTimer.start();
296 combinedOffset: rowOffset + alignmentAdjustment - scrollOffset
297 restoreEntryValues: false
308 properties: "rowOffset, scrollOffset, alignmentAdjustment"
313 properties: "combinedOffset"
314 duration: LomiriAnimation.SnapDuration
315 easing: LomiriAnimation.StandardEasing