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/>.
19 import Ubuntu.Components 1.3
20 import "../Components"
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: UbuntuAnimation.BriskDuration; easing: UbuntuAnimation.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);
176 anchors.bottom: parent.bottom
179 opacity: expanded ? 1.0 : 0.0
180 Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.SnapDuration } }
186 clip: expanded || row.width > rowContainer.width
190 objectName: "flickable"
192 // we rotate it because we want the Flickable to align its content item
193 // on the right instead of on the left
194 rotation: root.alignment != Qt.AlignRight ? 0 : 180
197 contentWidth: row.width
198 contentX: d.combinedOffset
201 // contentX can change by user interaction as well as user offset changes
202 // This function re-aligns the offsets so that the offsets match the contentX
203 function resetContentXComponents() {
204 d.scrollOffset += d.combinedOffset - flickable.contentX;
207 rebound: Transition {
211 easing.type: Easing.OutCubic
217 objectName: "panelItemRow"
220 bottom: parent.bottom
223 // Compensate for the Flickable rotation (ie, counter-rotate)
224 rotation: root.alignment != Qt.AlignRight ? 0 : 180
227 if (root.lateralPosition == -1) return -1;
229 var mapped = root.mapToItem(row, root.lateralPosition, 0);
230 return Math.min(Math.max(mapped.x, 0), row.width);
233 onCurrentItemChanged: {
234 if (!currentItem) d.initialItem = undefined;
235 else if (!d.initialItem) d.initialItem = currentItem;
240 enabled: root.expanded
242 row.selectItemAt(mouse.x);
253 interval: UbuntuAnimation.FastDuration // enough for row animation.
256 onTriggered: d.alignIndicators();
267 alignmentAdjustment: 0
269 restoreEntryValues: false
274 when: expanded && !interactive
278 combinedOffset: rowOffset + alignmentAdjustment - scrollOffset
283 if (!initialItem) return 0;
284 if (distanceFromEdge - initialItem.width <= 0) return 0;
286 var rowOffset = distanceFromEdge - originalDistanceFromEdge;
289 restoreEntryValues: false
294 when: expanded && interactive
298 // don't use row offset anymore.
299 d.scrollOffset -= d.rowOffset;
301 d.initialItem = undefined;
302 alignmentTimer.start();
307 combinedOffset: rowOffset + alignmentAdjustment - scrollOffset
308 restoreEntryValues: false
319 properties: "rowOffset, scrollOffset, alignmentAdjustment"
324 properties: "combinedOffset"
325 duration: UbuntuAnimation.SnapDuration
326 easing: UbuntuAnimation.StandardEasing