2 * Copyright (C) 2013-2014 Canonical, Ltd.
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 import Ubuntu.Components 1.3
25 property QtObject indicatorsModel: null
26 property real overFlowWidth: width
27 property bool expanded: false
28 property var currentItem
29 readonly property int currentItemIndex: currentItem ? currentItem.ownIndex : -1
31 property real unitProgress: 0.0
32 property real selectionChangeBuffer: units.gu(2)
33 property bool enableLateralChanges: false
34 property color hightlightColor: "#ffffff"
36 property real lateralPosition: -1
37 onLateralPositionChanged: {
38 updateItemFromLateralPosition();
41 onEnableLateralChangesChanged: {
42 updateItemFromLateralPosition();
45 function updateItemFromLateralPosition() {
46 if (currentItem && !enableLateralChanges) return;
47 if (lateralPosition === -1) return;
50 selectItemAt(lateralPosition);
54 var maximumBufferOffset = selectionChangeBuffer * unitProgress;
55 var proposedItem = indicatorAt(lateralPosition, 0);
57 var bufferExceeded = false;
59 if (proposedItem !== currentItem) {
60 // Proposed item is not directly adjacent to current?
61 if (Math.abs(proposedItem.ownIndex - currentItem.ownIndex) > 1) {
62 bufferExceeded = true;
64 var currentItemLateralPosition = root.mapToItem(proposedItem, lateralPosition, 0).x;
66 // Is the distance into proposed item greater than max buffer?
67 // Proposed item is before current item
68 if (proposedItem.x < currentItem.x) {
69 bufferExceeded = (proposedItem.width - currentItemLateralPosition) > maximumBufferOffset;
71 bufferExceeded = currentItemLateralPosition > maximumBufferOffset;
75 selectItemAt(lateralPosition);
79 selectItemAt(lateralPosition);
83 function indicatorAt(x, y) {
84 var item = row.childAt(x, y);
85 return item && item.hasOwnProperty("ownIndex") ? item : null;
88 function resetCurrentItem() {
89 d.firstItemSwitch = true;
90 d.previousItem = undefined;
91 currentItem = undefined;
94 function setCurrentItemIndex(index) {
95 for (var i = 0; i < row.children.length; i++) {
96 var item = row.children[i];
97 if (item.hasOwnProperty("ownIndex") && item.ownIndex === index) {
98 if (currentItem !== item) currentItem = item;
104 function selectItemAt(lateralPosition) {
105 var item = indicatorAt(lateralPosition, 0);
106 if (item && item.opacity > 0) {
109 // Select default item.
110 var searchIndex = lateralPosition > width ? repeater.count - 1 : 0;
112 for (var i = 0; i < row.children.length; i++) {
113 if (row.children[i].hasOwnProperty("ownIndex") && row.children[i].ownIndex === searchIndex) {
114 item = row.children[i];
118 if (currentItem !== item) currentItem = item;
124 property bool firstItemSwitch: true
125 property var previousItem
126 property bool forceAlignmentAnimationDisabled: false
129 onCurrentItemChanged: {
130 if (d.previousItem) {
131 d.firstItemSwitch = false;
133 d.previousItem = currentItem;
140 bottom: parent.bottom
143 // TODO: make this better
144 // when the width changes, the highlight will lag behind due to animation, so we need to disable the animation
145 // and adjust the highlight X immediately.
148 SequentialAnimation {
151 d.forceAlignmentAnimationDisabled = true;
152 highlight.currentItemX = Qt.binding(function() { return currentItem ? currentItem.x : 0 });
153 d.forceAlignmentAnimationDisabled = false;
161 model: indicatorsModel
165 // current item removed.
166 if (currentItem === item) {
168 while (i < row.children.length) {
169 var childItem = row.children[i];
170 if (childItem !== item) {
171 setCurrentItemIndex(i);
180 delegate: IndicatorItem {
182 objectName: identifier+"-panelItem"
184 property int ownIndex: index
185 property bool overflow: row.width - x > overFlowWidth
186 property bool hidden: !expanded && (overflow || !indicatorVisible)
189 expanded: root.expanded
190 selected: currentItem === this
192 identifier: model.identifier
193 busName: indicatorProperties.busName
194 actionsObjectPath: indicatorProperties.actionsObjectPath
195 menuObjectPath: indicatorProperties.menuObjectPath
197 opacity: hidden ? 0.0 : 1.0
198 Behavior on opacity {
199 NumberAnimation { duration: UbuntuAnimation.SnapDuration; easing: UbuntuAnimation.StandardEasing }
202 width: (expanded || indicatorVisible) ? implicitWidth : 0
205 NumberAnimation { duration: UbuntuAnimation.SnapDuration; easing: UbuntuAnimation.StandardEasing }
208 Component.onDestruction: {
209 // current item removed.
210 if (currentItem === this) {
212 while (i < row.children.length) {
213 var childItem = row.children[i];
214 if (childItem !== this) {
215 setCurrentItemIndex(i);
228 objectName: "highlight"
230 anchors.bottom: row.bottom
232 color: root.hightlightColor
233 visible: currentItem !== undefined
236 width: currentItem ? currentItem.width : 0
238 enabled: !d.firstItemSwitch && expanded
239 UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
242 // micromovements of the highlight line when user moves the finger across the items while pulling
243 // the handle downwards.
244 property real highlightCenterOffset: {
245 if (!currentItem || lateralPosition == -1 || !enableLateralChanges) return 0;
247 var itemMapped = root.mapToItem(currentItem, lateralPosition, 0);
249 var distanceFromCenter = itemMapped.x - currentItem.width / 2;
250 if (distanceFromCenter > 0) {
251 distanceFromCenter = Math.max(0, distanceFromCenter - currentItem.width / 8);
253 distanceFromCenter = Math.min(0, distanceFromCenter + currentItem.width / 8);
256 if (currentItem && currentItem.ownIndex === 0 && distanceFromCenter < 0) {
258 } else if (currentItem && currentItem.ownIndex === repeater.count-1 & distanceFromCenter > 0) {
261 return (distanceFromCenter / (currentItem.width / 4)) * units.gu(1);
263 Behavior on highlightCenterOffset {
264 NumberAnimation { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
267 property real currentItemX: currentItem ? currentItem.x : 0
268 Behavior on currentItemX {
269 id: currentItemXBehavior
270 enabled: !d.firstItemSwitch && expanded && !d.forceAlignmentAnimationDisabled
271 NumberAnimation { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
273 x: currentItemX + highlightCenterOffset
284 PropertyChanges { target: highlight; opacity: 0.9 }
291 properties: "opacity";
292 duration: UbuntuAnimation.SnapDuration
293 easing: UbuntuAnimation.StandardEasing