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 QtQuick.Window 2.2
19 import Ubuntu.Components 1.3
26 property QtObject indicatorsModel: null
27 property real overFlowWidth: width
28 property bool expanded: false
29 property var currentItem
30 readonly property int currentItemIndex: currentItem ? currentItem.ownIndex : -1
32 property real unitProgress: 0.0
33 property real selectionChangeBuffer: units.gu(2)
34 property bool enableLateralChanges: false
35 property color hightlightColor: "#ffffff"
37 property real lateralPosition: -1
38 onLateralPositionChanged: {
39 updateItemFromLateralPosition();
42 onEnableLateralChangesChanged: {
43 updateItemFromLateralPosition();
46 function updateItemFromLateralPosition() {
47 if (currentItem && !enableLateralChanges) return;
48 if (lateralPosition === -1) return;
51 selectItemAt(lateralPosition);
55 var maximumBufferOffset = selectionChangeBuffer * unitProgress;
56 var proposedItem = indicatorAt(lateralPosition, 0);
58 var bufferExceeded = false;
60 if (proposedItem !== currentItem) {
61 // Proposed item is not directly adjacent to current?
62 if (Math.abs(proposedItem.ownIndex - currentItem.ownIndex) > 1) {
63 bufferExceeded = true;
65 var currentItemLateralPosition = root.mapToItem(proposedItem, lateralPosition, 0).x;
67 // Is the distance into proposed item greater than max buffer?
68 // Proposed item is before current item
69 if (proposedItem.x < currentItem.x) {
70 bufferExceeded = (proposedItem.width - currentItemLateralPosition) > maximumBufferOffset;
72 bufferExceeded = currentItemLateralPosition > maximumBufferOffset;
76 selectItemAt(lateralPosition);
80 selectItemAt(lateralPosition);
84 function indicatorAt(x, y) {
85 var item = row.childAt(x, y);
86 return item && item.hasOwnProperty("ownIndex") ? item : null;
89 function resetCurrentItem() {
90 d.firstItemSwitch = true;
91 d.previousItem = undefined;
92 currentItem = undefined;
95 function setCurrentItemIndex(index) {
96 for (var i = 0; i < row.children.length; i++) {
97 var item = row.children[i];
98 if (item.hasOwnProperty("ownIndex") && item.ownIndex === index) {
99 if (currentItem !== item) currentItem = item;
105 function selectItemAt(lateralPosition) {
106 var item = indicatorAt(lateralPosition, 0);
107 if (item && item.opacity > 0) {
110 // Select default item.
111 var searchIndex = lateralPosition > width ? repeater.count - 1 : 0;
113 for (var i = 0; i < row.children.length; i++) {
114 if (row.children[i].hasOwnProperty("ownIndex") && row.children[i].ownIndex === searchIndex) {
115 item = row.children[i];
119 if (currentItem !== item) currentItem = item;
125 property bool firstItemSwitch: true
126 property var previousItem
127 property bool forceAlignmentAnimationDisabled: false
130 onCurrentItemChanged: {
131 if (d.previousItem) {
132 d.firstItemSwitch = false;
134 d.previousItem = currentItem;
141 bottom: parent.bottom
144 // TODO: make this better
145 // when the width changes, the highlight will lag behind due to animation, so we need to disable the animation
146 // and adjust the highlight X immediately.
149 SequentialAnimation {
152 d.forceAlignmentAnimationDisabled = true;
153 highlight.currentItemX = Qt.binding(function() { return currentItem ? currentItem.x : 0 });
154 d.forceAlignmentAnimationDisabled = false;
162 model: indicatorsModel
166 // current item removed.
167 if (currentItem === item) {
169 while (i < row.children.length) {
170 var childItem = row.children[i];
171 if (childItem !== item) {
172 setCurrentItemIndex(i);
181 delegate: IndicatorItem {
183 objectName: identifier+"-panelItem"
185 property int ownIndex: index
186 property bool overflow: row.width - x > overFlowWidth
187 property bool hidden: !expanded && (overflow || !indicatorVisible || hideSessionIndicator)
188 // HACK for indicator-session
189 readonly property bool hideSessionIndicator: identifier == "indicator-session" && Math.min(Screen.width, Screen.height) <= units.gu(60)
192 expanded: root.expanded
193 selected: currentItem === this
195 identifier: model.identifier
196 busName: indicatorProperties.busName
197 actionsObjectPath: indicatorProperties.actionsObjectPath
198 menuObjectPath: indicatorProperties.menuObjectPath
200 opacity: hidden ? 0.0 : 1.0
201 Behavior on opacity {
202 NumberAnimation { duration: UbuntuAnimation.SnapDuration; easing: UbuntuAnimation.StandardEasing }
205 width: ((expanded || indicatorVisible) && !hideSessionIndicator) ? implicitWidth : 0
208 NumberAnimation { duration: UbuntuAnimation.SnapDuration; easing: UbuntuAnimation.StandardEasing }
211 Component.onDestruction: {
212 // current item removed.
213 if (currentItem === this) {
215 while (i < row.children.length) {
216 var childItem = row.children[i];
217 if (childItem !== this) {
218 setCurrentItemIndex(i);
231 objectName: "highlight"
233 anchors.bottom: row.bottom
235 color: root.hightlightColor
236 visible: currentItem !== undefined
239 width: currentItem ? currentItem.width : 0
241 enabled: !d.firstItemSwitch && expanded
242 UbuntuNumberAnimation { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
245 // micromovements of the highlight line when user moves the finger across the items while pulling
246 // the handle downwards.
247 property real highlightCenterOffset: {
248 if (!currentItem || lateralPosition == -1 || !enableLateralChanges) return 0;
250 var itemMapped = root.mapToItem(currentItem, lateralPosition, 0);
252 var distanceFromCenter = itemMapped.x - currentItem.width / 2;
253 if (distanceFromCenter > 0) {
254 distanceFromCenter = Math.max(0, distanceFromCenter - currentItem.width / 8);
256 distanceFromCenter = Math.min(0, distanceFromCenter + currentItem.width / 8);
259 if (currentItem && currentItem.ownIndex === 0 && distanceFromCenter < 0) {
261 } else if (currentItem && currentItem.ownIndex === repeater.count-1 & distanceFromCenter > 0) {
264 return (distanceFromCenter / (currentItem.width / 4)) * units.gu(1);
266 Behavior on highlightCenterOffset {
267 NumberAnimation { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
270 property real currentItemX: currentItem ? currentItem.x : 0
271 Behavior on currentItemX {
272 id: currentItemXBehavior
273 enabled: !d.firstItemSwitch && expanded && !d.forceAlignmentAnimationDisabled
274 NumberAnimation { duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing }
276 x: currentItemX + highlightCenterOffset
287 PropertyChanges { target: highlight; opacity: 0.9 }
294 properties: "opacity";
295 duration: UbuntuAnimation.SnapDuration
296 easing: UbuntuAnimation.StandardEasing