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