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