on b0f3641 help sign in
unity8/Panel/Indicators.qml
373 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)
37 property bool contentEnabled: true
39 // TODO: Perhaps we need a animation standard for showing/hiding? Each showable seems to
40 // use its own values. Need to ask design about this.
41 showAnimation: StandardAnimation {
42 property: "height"
43 duration: 350
44 to: openedHeight
45 easing.type: Easing.OutCubic
47 // Re-size if we've changed the openHeight while shown.
48 onToChanged: {
49 if (indicators.shown) {
50 indicators.show();
51 }
52 }
53 }
55 hideAnimation: StandardAnimation {
56 property: "height"
57 duration: 350
58 to: panelHeight
59 easing.type: Easing.OutCubic
60 }
62 height: panelHeight
64 onHeightChanged: {
65 // need to use handle.get_height(). As the handle height depends on indicators.height changes (but this is called first!)
66 var revealProgress = indicators.height - panelHeight
67 if (!showAnimation.running && !hideAnimation.running) {
68 if (revealProgress == 0) {
69 indicators.state = "initial";
70 } else if (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 (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 && indicatorRow.currentItem!=null) {
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 }
148 // eater
149 MouseArea {
150 anchors {
151 top: parent.top
152 bottom: handle.bottom
153 left: parent.left
154 right: parent.right
155 }
156 }
158 VerticalThinDivider {
159 anchors {
160 top: indicators.top
161 topMargin: panelHeight
162 bottom: handle.bottom
163 right: indicators.left
164 }
165 width: units.dp(2)
166 source: "graphics/VerticalDivider.png"
167 }
169 MenuContent {
170 id: menuContent
171 objectName: "menuContent"
173 anchors {
174 left: parent.left
175 right: parent.right
176 top: indicatorRow.bottom
177 bottom: handle.top
178 }
179 indicatorsModel: indicatorsModel
180 clip: !indicators.fullyOpened
181 activeHeader: indicators.state == "hint" || indicators.state == "reveal"
182 enabled: contentEnabled
184 Connections {
185 target: indicatorRow
186 onCurrentItemIndexChanged: {
187 menuContent.setCurrentMenuIndex(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 activeDragHandle: showDragHandle.dragging ? showDragHandle : hideDragHandle.dragging ? hideDragHandle : null
286 // connections to the active drag handle
287 Connections {
288 target: activeDragHandle
289 onTouchXChanged: {
290 indicators.calculateCurrentItem(activeDragHandle.touchX, true);
291 }
292 onTouchSceneYChanged: {
293 yVelocityCalculator.trackedPosition = activeDragHandle.touchSceneY;
294 }
295 }
297 DragHandle {
298 id: showDragHandle
299 anchors.bottom: parent.bottom
300 // go beyond parent so that it stays reachable, at the top of the screen.
301 anchors.bottomMargin: pinnedMode ? 0 : -panelHeight
302 anchors.left: parent.left
303 anchors.right: parent.right
304 height: panelHeight
305 direction: Direction.Downwards
306 enabled: !indicators.shown && indicators.available
307 hintDisplacement: pinnedMode ? indicators.hintValue : 0
308 autoCompleteDragThreshold: maxTotalDragDistance / 2
309 stretch: true
310 maxTotalDragDistance: openedHeight - panelHeight
311 distanceThreshold: pinnedMode ? 0 : units.gu(3)
313 onStatusChanged: {
314 if (status === DirectionalDragArea.Recognized) {
315 menuContent.activateContent();
316 }
317 }
318 }
319 DragHandle {
320 id: hideDragHandle
321 anchors.fill: handle
322 direction: Direction.Upwards
323 enabled: indicators.shown && indicators.available
324 hintDisplacement: indicators.hintValue
325 autoCompleteDragThreshold: maxTotalDragDistance / 6
326 stretch: true
327 maxTotalDragDistance: openedHeight - panelHeight
328 distanceThreshold: 0
329 }
331 AxisVelocityCalculator {
332 id: yVelocityCalculator
333 }
335 states: [
336 State {
337 name: "initial"
338 },
339 State {
340 name: "hint"
341 PropertyChanges {
342 target: indicatorRow;
343 y: pinnedMode ? 0 : panelHeight
344 }
345 StateChangeScript {
346 script: {
347 if (activeDragHandle) {
348 calculateCurrentItem(activeDragHandle.touchX, false);
349 }
350 }
351 }
352 },
353 State {
354 name: "reveal"
355 extend: "hint"
356 },
357 State {
358 name: "locked"
359 extend: "hint"
360 },
361 State {
362 name: "commit"
363 extend: "hint"
364 }
365 ]
366 state: "initial"
368 transitions: [
369 Transition {
370 NumberAnimation {targets: [indicatorRow, menuContent]; property: "y"; duration: 300; easing.type: Easing.OutCubic}
371 }
372 ]