on df73bd5 help sign in
unity8/qml/Panel/Indicators.qml
444 lines 15 kB history raw
1/*
2 * Copyright (C) 2013 Canonical, Ltd.
3 *
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.
7 *
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.
12 *
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/>.
15 */
17import QtQuick 2.0
18import Ubuntu.Components 0.1
19import Ubuntu.Gestures 0.1
20import Unity.Indicators 0.1 as Indicators
22import "../Components"
23import "../Components/ListItems"
25Showable {
26 id: indicators
28 property real openedHeight: units.gu(71)
29 property int panelHeight: units.gu(3)
30 property bool pinnedMode: true //should be set true if indicators menu can cover whole screen
31 property alias overFlowWidth: indicatorRow.overFlowWidth
32 property alias showAll: indicatorRow.showAll
34 property int hintValue
35 readonly property int lockThreshold: openedHeight / 2
36 property bool fullyOpened: height == openedHeight
37 property bool partiallyOpened: height > panelHeight && !fullyOpened
38 property real visualBottom: Math.max(y+height, y+indicatorRow.y+indicatorRow.height)
39 property bool contentEnabled: true
40 property bool initalizeItem: true
41 readonly property alias content: menuContent
42 property real unitProgress: 0.0
44 // TODO: Perhaps we need a animation standard for showing/hiding? Each showable seems to
45 // use its own values. Need to ask design about this.
46 showAnimation: StandardAnimation {
47 property: "height"
48 duration: 350
49 to: openedHeight
50 easing.type: Easing.OutCubic
52 // Re-size if we've changed the openHeight while shown.
53 onToChanged: {
54 if (indicators.shown) {
55 indicators.show();
56 }
57 }
58 }
60 hideAnimation: StandardAnimation {
61 property: "height"
62 duration: 350
63 to: panelHeight
64 easing.type: Easing.OutCubic
65 }
67 height: panelHeight
68 onHeightChanged: updateRevealProgressState(indicators.height - panelHeight, true)
70 function updateRevealProgressState(revealProgress, enableRelease) {
71 if (!showAnimation.running && !hideAnimation.running) {
72 if (revealProgress === 0) {
73 indicators.state = "initial";
74 } else if (revealProgress > 0 && revealProgress <= hintValue) {
75 indicators.state = "hint";
76 } else if (revealProgress > hintValue && revealProgress < lockThreshold) {
77 indicators.state = "reveal";
78 } else if (revealProgress >= lockThreshold && lockThreshold > 0) {
79 indicators.state = "locked";
80 }
81 }
83 if (enableRelease && revealProgress === 0) {
84 menuContent.releaseContent();
85 }
86 }
88 function calculateCurrentItem(xValue, useBuffer) {
89 var rowCoordinates;
90 var itemCoordinates;
91 var currentItem;
92 var distanceFromRightEdge;
93 var bufferExceeded = false;
95 if (indicators.state == "commit" || indicators.state == "locked" || showAnimation.running || hideAnimation.running) return;
97 /*
98 If user drags the indicator handle bar down a distance hintValue or less, this is 0.
99 If bar is dragged down a distance greater than or equal to lockThreshold, this is 1.
100 Otherwise it contains the bar's location as a fraction of the distance between hintValue (is 0) and lockThreshold (is 1).
101 */
102 var verticalProgress =
103 MathUtils.clamp((indicators.height - handle.height - hintValue) /
104 (lockThreshold - hintValue), 0, 1);
106 /*
107 Vertical velocity check. Don't change the indicator if we're moving too quickly.
108 */
109 var verticalSpeed = Math.abs(yVelocityCalculator.calculate());
110 if (verticalSpeed >= 0.05 && !initalizeItem) {
111 return;
112 }
114 /*
115 Percentage of an indicator icon's width the user's press can stray horizontally from the
116 focused icon before we change focus to another icon. E.g. a value of 0.5 means you must
117 go right a distance of half an icon's width before focus moves to the icon on the right
118 */
119 var maxBufferThreshold = 0.5;
121 /*
122 To help users find the indicator of their choice while opening the indicators, we add logic to add a
123 left/right buffer to each icon so it is harder for the focus to be moved accidentally to another icon,
124 as the user moves their finger down, but yet allows them to switch indicator if they want.
125 This buffer is wider the further the user's finger is from the top of the screen.
126 */
127 var effectiveBufferThreshold = maxBufferThreshold * verticalProgress;
129 rowCoordinates = indicatorRow.mapToItem(indicatorRow.row, xValue, 0);
130 // get the current delegate
131 currentItem = indicatorRow.row.childAt(rowCoordinates.x, 0);
132 if (currentItem) {
133 itemCoordinates = indicatorRow.row.mapToItem(currentItem, rowCoordinates.x, 0);
134 distanceFromRightEdge = (currentItem.width - itemCoordinates.x) / (currentItem.width);
135 if (currentItem != indicatorRow.currentItem) {
136 if (Math.abs(currentItem.ownIndex - indicatorRow.currentItemIndex) > 1) {
137 bufferExceeded = true;
138 } else {
139 if (indicatorRow.currentItemIndex < currentItem.ownIndex && distanceFromRightEdge < (1 - effectiveBufferThreshold)) {
140 bufferExceeded = true;
141 } else if (indicatorRow.currentItemIndex > currentItem.ownIndex && distanceFromRightEdge > effectiveBufferThreshold) {
142 bufferExceeded = true;
143 }
144 }
145 if ((!useBuffer || (useBuffer && bufferExceeded)) || indicatorRow.currentItemIndex < 0 || indicatorRow.currentItem == null) {
146 indicatorRow.currentItem = currentItem;
147 }
149 // need to re-init the distanceFromRightEdge for offset calculation
150 itemCoordinates = indicatorRow.row.mapToItem(indicatorRow.currentItem, rowCoordinates.x, 0);
151 distanceFromRightEdge = (indicatorRow.currentItem.width - itemCoordinates.x) / (indicatorRow.currentItem.width);
152 }
153 indicatorRow.currentItemOffset = 1 - (distanceFromRightEdge * 2);
154 } else {
155 indicatorRow.setDefaultItem();
156 indicatorRow.currentItemOffset = 0;
157 }
158 initalizeItem = indicatorRow.currentItem == null;
159 }
161 // eater
162 MouseArea {
163 anchors {
164 top: parent.top
165 bottom: handle.bottom
166 left: parent.left
167 right: parent.right
168 }
169 }
171 VerticalThinDivider {
172 anchors {
173 top: indicators.top
174 topMargin: panelHeight
175 bottom: handle.bottom
176 right: indicators.left
177 }
178 width: units.dp(2)
179 source: "graphics/VerticalDivider.png"
180 }
182 MenuContent {
183 id: menuContent
184 objectName: "menuContent"
186 anchors {
187 left: parent.left
188 right: parent.right
189 top: indicatorRow.bottom
190 bottom: handle.top
191 }
192 indicatorsModel: indicatorsModel
193 clip: !indicators.fullyOpened
194 activeHeader: indicators.state == "hint" || indicators.state == "reveal"
195 enabled: contentEnabled
197 //small shadow gradient at bottom of menu
198 Rectangle {
199 anchors {
200 left: parent.left
201 right: parent.right
202 bottom: parent.bottom
203 }
204 height: units.gu(0.5)
205 gradient: Gradient {
206 GradientStop { position: 0.0; color: "transparent" }
207 GradientStop { position: 1.0; color: "black" }
208 }
209 opacity: 0.4
210 }
211 }
213 Rectangle {
214 id: handle
216 color: menuContent.backgroundColor
218 anchors {
219 left: parent.left
220 right: parent.right
221 bottom: parent.bottom
222 }
223 height: get_height()
224 clip: height < handleImage.height
226 function get_height() {
227 return Math.max(Math.min(handleImage.height, indicators.height - handleImage.height), 0);
228 }
230 BorderImage {
231 id: handleImage
232 source: "graphics/handle.sci"
233 height: panelHeight
234 anchors {
235 left: parent.left
236 right: parent.right
237 bottom: parent.bottom
238 }
239 }
240 MouseArea { //prevent clicks passing through
241 anchors.fill: parent
242 }
243 }
245 PanelBackground {
246 anchors.fill: indicatorRow
247 }
249 Indicators.IndicatorsModel {
250 id: indicatorsModel
251 }
253 IndicatorRow {
254 id: indicatorRow
255 objectName: "indicatorRow"
256 anchors {
257 left: parent.left
258 right: parent.right
259 }
260 height: indicators.panelHeight
261 indicatorsModel: indicatorsModel
262 state: indicators.state
263 unitProgress: indicators.unitProgress
265 onVisibleIndicatorsChanged: {
266 // need to do it here so we can control sequence
267 if (visibleIndicators !== undefined) {
268 menuContent.visibleIndicators = visibleIndicators;
269 menuContent.setCurrentMenuIndex(currentItemIndex);
270 }
271 }
273 EdgeDragArea {
274 id: rowDragArea
275 anchors.fill: indicatorRow
276 direction: Direction.Downwards
277 maxSilenceTime: 2000
278 distanceThreshold: 0
280 enabled: fullyOpened
281 onDraggingChanged: {
282 if (dragging) {
283 initalizeItem = true;
284 updateRevealProgressState(Math.max(touchSceneY - panelHeight, hintValue), false);
285 indicators.calculateCurrentItem(touchX, false);
286 } else {
287 indicators.state = "commit";
288 }
289 }
291 onTouchXChanged: {
292 indicators.calculateCurrentItem(touchX, true);
293 }
294 onTouchSceneYChanged: {
295 updateRevealProgressState(Math.max(touchSceneY - panelHeight, hintValue), false);
296 yVelocityCalculator.trackedPosition = touchSceneY;
297 }
298 }
299 }
301 Connections {
302 target: showAnimation
303 onRunningChanged: {
304 if (showAnimation.running) {
305 indicators.state = "commit";
306 }
307 }
308 }
310 Connections {
311 target: hideAnimation
312 onRunningChanged: {
313 if (hideAnimation.running) {
314 indicators.state = "initial";
315 initalizeItem = true;
316 menuContent.animateNextMenuChange = false;
317 }
318 }
319 }
321 QtObject {
322 id: d
323 property bool enableIndexChangeSignal: true
324 property var activeDragHandle: showDragHandle.dragging ? showDragHandle : hideDragHandle.dragging ? hideDragHandle : null
325 }
327 Connections {
328 target: menuContent
329 onCurrentMenuIndexChanged: {
330 var oldActive = d.enableIndexChangeSignal;
331 if (!oldActive) return;
332 d.enableIndexChangeSignal = false;
334 indicatorRow.setCurrentItem(menuContent.currentMenuIndex);
336 d.enableIndexChangeSignal = oldActive;
337 }
338 }
340 Connections {
341 target: indicatorRow
342 onCurrentItemIndexChanged: {
343 var oldActive = d.enableIndexChangeSignal;
344 if (!oldActive) return;
345 d.enableIndexChangeSignal = false;
347 menuContent.setCurrentMenuIndex(indicatorRow.currentItemIndex);
349 d.enableIndexChangeSignal = oldActive;
350 }
351 }
352 // connections to the active drag handle
353 Connections {
354 target: d.activeDragHandle
355 onTouchXChanged: {
356 indicators.calculateCurrentItem(d.activeDragHandle.touchX, true);
357 }
358 onTouchSceneYChanged: {
359 yVelocityCalculator.trackedPosition = d.activeDragHandle.touchSceneY;
360 }
361 }
363 DragHandle {
364 id: showDragHandle
365 anchors.bottom: parent.bottom
366 // go beyond parent so that it stays reachable, at the top of the screen.
367 anchors.bottomMargin: pinnedMode ? 0 : -panelHeight
368 anchors.left: parent.left
369 anchors.right: parent.right
370 height: panelHeight
371 direction: Direction.Downwards
372 enabled: !indicators.shown && indicators.available
373 hintDisplacement: pinnedMode ? indicators.hintValue : 0
374 autoCompleteDragThreshold: maxTotalDragDistance / 2
375 stretch: true
376 maxTotalDragDistance: openedHeight - panelHeight
377 distanceThreshold: pinnedMode ? 0 : units.gu(3)
379 onStatusChanged: {
380 if (status === DirectionalDragArea.Recognized) {
381 menuContent.activateContent();
382 }
383 }
384 }
385 DragHandle {
386 id: hideDragHandle
387 anchors.fill: handle
388 direction: Direction.Upwards
389 enabled: indicators.shown && indicators.available
390 hintDisplacement: indicators.hintValue
391 autoCompleteDragThreshold: maxTotalDragDistance / 6
392 stretch: true
393 maxTotalDragDistance: openedHeight - panelHeight
394 distanceThreshold: 0
395 }
397 AxisVelocityCalculator {
398 id: yVelocityCalculator
399 }
401 states: [
402 State {
403 name: "initial"
404 },
405 State {
406 name: "hint"
407 PropertyChanges {
408 target: indicatorRow;
409 y: pinnedMode ? 0 : panelHeight
410 }
411 StateChangeScript {
412 script: {
413 if (d.activeDragHandle) {
414 calculateCurrentItem(d.activeDragHandle.touchX, false);
415 }
416 }
417 }
418 },
419 State {
420 name: "reveal"
421 extend: "hint"
422 },
423 State {
424 name: "locked"
425 extend: "hint"
426 },
427 State {
428 name: "commit"
429 extend: "hint"
430 }
431 ]
432 state: "initial"
434 transitions: [
435 Transition {
436 NumberAnimation {targets: [indicatorRow, menuContent]; property: "y"; duration: 300; easing.type: Easing.OutCubic}
437 }
438 ]
440 Component.onCompleted: initialise();
441 function initialise() {
442 indicatorsModel.load();
443 }