on 5ab40fb help sign in
unity8/Panel/Indicators.qml
381 lines 12 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
37 // TODO: Perhaps we need a animation standard for showing/hiding? Each showable seems to
38 // use its own values. Need to ask design about this.
39 showAnimation: StandardAnimation {
40 property: "height"
41 duration: 350
42 to: openedHeight
43 easing.type: Easing.OutCubic
45 // Re-size if we've changed the openHeight while shown.
46 onToChanged: {
47 if (indicators.shown) {
48 indicators.show();
49 }
50 }
51 }
53 hideAnimation: StandardAnimation {
54 property: "height"
55 duration: 350
56 to: panelHeight
57 easing.type: Easing.OutCubic
58 }
60 height: panelHeight
62 onHeightChanged: {
63 // need to use handle.get_height(). As the handle height depends on indicators.height changes (but this is called first!)
64 var revealProgress = indicators.height - panelHeight
65 if (!showAnimation.running && !hideAnimation.running) {
66 if (revealProgress == 0) {
67 indicators.state = "initial";
68 } else if (revealProgress <= hintValue) {
69 indicators.state = "hint";
70 } else if (revealProgress > hintValue && revealProgress < lockThreshold) {
71 indicators.state = "reveal";
72 } else if (revealProgress >= lockThreshold && lockThreshold > 0) {
73 indicators.state = "locked";
74 }
75 }
77 if (revealProgress == 0) {
78 menuContent.releaseContent();
79 }
80 }
82 function calculateCurrentItem(xValue, useBuffer) {
83 var rowCoordinates;
84 var itemCoordinates;
85 var currentItem;
86 var distanceFromRightEdge;
87 var bufferExceeded = false;
89 if (indicators.state == "commit" || indicators.state == "locked" || showAnimation.running || hideAnimation.running) return;
91 /*
92 If user drags the indicator handle bar down a distance hintValue or less, this is 0.
93 If bar is dragged down a distance greater than or equal to lockThreshold, this is 1.
94 Otherwise it contains the bar's location as a fraction of the distance between hintValue (is 0) and lockThreshold (is 1).
95 */
96 var verticalProgress =
97 MathLocal.clamp((indicators.height - handle.height - hintValue) /
98 (lockThreshold - hintValue), 0, 1);
100 /*
101 Vertical velocity check. Don't change the indicator if we're moving too quickly.
102 */
103 var verticalVelocity = Math.abs(yVelocityCalculator.calculate());
104 if (verticalVelocity >= 0.05 && indicatorRow.currentItem!=null) {
105 return;
106 }
108 /*
109 Percentage of an indicator icon's width the user's press can stray horizontally from the
110 focused icon before we change focus to another icon. E.g. a value of 0.5 means you must
111 go right a distance of half an icon's width before focus moves to the icon on the right
112 */
113 var maxBufferThreshold = 0.5;
115 /*
116 To help users find the indicator of their choice while opening the indicators, we add logic to add a
117 left/right buffer to each icon so it is harder for the focus to be moved accidentally to another icon,
118 as the user moves their finger down, but yet allows them to switch indicator if they want.
119 This buffer is wider the further the user's finger is from the top of the screen.
120 */
121 var effectiveBufferThreshold = maxBufferThreshold * verticalProgress;
123 rowCoordinates = indicatorRow.mapToItem(indicatorRow.row, xValue, 0);
124 // get the current delegate
125 currentItem = indicatorRow.row.childAt(rowCoordinates.x, 0);
126 if (currentItem && currentItem != indicatorRow.currentItem ) {
127 itemCoordinates = indicatorRow.row.mapToItem(currentItem, rowCoordinates.x, 0);
128 distanceFromRightEdge = (currentItem.width - itemCoordinates.x) / (currentItem.width);
129 if (Math.abs(currentItem.ownIndex - indicatorRow.currentItemIndex) > 1) {
130 bufferExceeded = true;
131 } else {
132 if (indicatorRow.currentItemIndex < currentItem.ownIndex && distanceFromRightEdge < (1 - effectiveBufferThreshold)) {
133 bufferExceeded = true;
134 } else if (indicatorRow.currentItemIndex > currentItem.ownIndex && distanceFromRightEdge > effectiveBufferThreshold) {
135 bufferExceeded = true;
136 }
137 }
138 if ((!useBuffer || (useBuffer && bufferExceeded)) || indicatorRow.currentItem < 0 || indicatorRow.currentItem == null) {
139 indicatorRow.currentItem = currentItem;
140 }
141 } else if (!currentItem) {
142 indicatorRow.setDefaultItem();
143 }
144 }
146 // eater
147 MouseArea {
148 anchors {
149 top: parent.top
150 bottom: handle.bottom
151 left: parent.left
152 right: parent.right
153 }
154 }
156 VerticalThinDivider {
157 anchors {
158 top: indicators.top
159 topMargin: panelHeight
160 bottom: handle.bottom
161 right: indicators.left
162 }
163 width: units.dp(2)
164 source: "graphics/VerticalDivider.png"
165 }
167 MenuContent {
168 id: menuContent
169 objectName: "menuContent"
171 anchors {
172 left: parent.left
173 right: parent.right
174 top: indicatorRow.bottom
175 bottom: handle.top
176 }
177 indicatorsModel: indicatorsModel
178 clip: !indicators.fullyOpened
180 Connections {
181 target: indicatorRow
182 onCurrentItemIndexChanged: menuContent.updateCurrentMenu();
183 }
185 function updateCurrentMenu() {
186 if (currentMenuIndex != indicatorRow.currentItemIndex) {
187 currentMenuIndex = indicatorRow.currentItemIndex;
188 }
189 }
191 //small shadow gradient at bottom of menu
192 Rectangle {
193 anchors {
194 left: parent.left
195 right: parent.right
196 bottom: parent.bottom
197 }
198 height: units.gu(0.5)
199 gradient: Gradient {
200 GradientStop { position: 0.0; color: "transparent" }
201 GradientStop { position: 1.0; color: "black" }
202 }
203 opacity: 0.4
204 }
205 }
207 Rectangle {
208 id: handle
210 color: menuContent.backgroundColor
212 anchors {
213 left: parent.left
214 right: parent.right
215 bottom: parent.bottom
216 }
217 height: get_height()
218 clip: height < handleImage.height
220 function get_height() {
221 return Math.max(Math.min(handleImage.height, indicators.height - handleImage.height), 0);
222 }
224 BorderImage {
225 id: handleImage
226 source: "graphics/handle.sci"
227 height: panelHeight
228 anchors {
229 left: parent.left
230 right: parent.right
231 bottom: parent.bottom
232 }
233 }
234 MouseArea { //prevent clicks passing through
235 anchors.fill: parent
236 }
237 }
239 PanelBackground {
240 anchors.fill: indicatorRow
241 }
243 IndicatorsDataModel {
244 id: indicatorsModel
245 }
247 IndicatorRow {
248 id: indicatorRow
249 objectName: "indicatorRow"
250 anchors {
251 left: parent.left
252 right: parent.right
253 }
254 height: indicators.panelHeight
255 indicatorsModel: indicatorsModel
256 state: indicators.state
258 Connections {
259 target: menuContent
260 onCurrentMenuIndexChanged: {
261 indicatorRow.setCurrentItem(menuContent.currentMenuIndex);
262 }
263 }
264 }
266 Connections {
267 target: showAnimation
268 onRunningChanged: {
269 if (showAnimation.running) {
270 indicators.state = "commit";
271 }
272 }
273 }
275 Connections {
276 target: hideAnimation
277 onRunningChanged: {
278 if (hideAnimation.running) {
279 indicators.state = "initial";
280 indicatorRow.currentItem = null;
281 }
282 }
283 }
285 property var dragHandle: null
286 // connections to the active drag handle
287 Connections {
288 target: dragHandle
289 onTouchXChanged: {
290 var buffer = dragHandle.dragging ? true : false;
291 indicators.calculateCurrentItem(dragHandle.touchX, buffer);
292 }
293 onTouchSceneYChanged: {
294 yVelocityCalculator.trackedPosition = dragHandle.touchSceneY;
295 }
296 }
298 DragHandle {
299 id: showDragHandle
300 anchors.bottom: parent.bottom
301 // go beyond parent so that it stays reachable, at the top of the screen.
302 anchors.bottomMargin: pinnedMode ? 0 : -panelHeight
303 anchors.left: parent.left
304 anchors.right: parent.right
305 height: panelHeight
306 direction: Direction.Downwards
307 enabled: !indicators.shown
308 hintDisplacement: pinnedMode ? indicators.hintValue : 0
309 autoCompleteDragThreshold: maxTotalDragDistance / 2
310 stretch: true
311 maxTotalDragDistance: openedHeight - panelHeight
312 distanceThreshold: pinnedMode ? 0 : units.gu(3)
314 onStatusChanged: {
315 if (status === DirectionalDragArea.Recognized) {
316 menuContent.activateContent();
317 }
318 }
319 onDraggingChanged: {
320 dragHandle = dragging ? showDragHandle : null;
321 }
322 }
323 DragHandle {
324 id: hideDragHandle
325 anchors.fill: handle
326 direction: Direction.Upwards
327 enabled: indicators.shown
328 hintDisplacement: indicators.hintValue
329 autoCompleteDragThreshold: maxTotalDragDistance / 6
330 stretch: true
331 maxTotalDragDistance: openedHeight - panelHeight
332 distanceThreshold: 0
334 onDraggingChanged: {
335 dragHandle = dragging ? hideDragHandle : null;
336 }
337 }
339 AxisVelocityCalculator {
340 id: yVelocityCalculator
341 }
343 states: [
344 State {
345 name: "initial"
346 },
347 State {
348 name: "hint"
349 PropertyChanges {
350 target: indicatorRow;
351 y: pinnedMode ? 0 : panelHeight
352 }
353 StateChangeScript {
354 script: {
355 if (dragHandle) {
356 calculateCurrentItem(dragHandle.touchX, false);
357 }
358 }
359 }
360 },
361 State {
362 name: "reveal"
363 extend: "hint"
364 },
365 State {
366 name: "locked"
367 extend: "hint"
368 },
369 State {
370 name: "commit"
371 extend: "hint"
372 }
373 ]
374 state: "initial"
376 transitions: [
377 Transition {
378 NumberAnimation {targets: [indicatorRow, menuContent]; property: "y"; duration: 300; easing.type: Easing.OutCubic}
379 }
380 ]