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