on bf461a2 help sign in
unity8/Panel/Indicators.qml
388 lines 13 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
183 visibleIndicators: indicatorRow.visibleIndicators
185 Connections {
186 property bool enableIndexChangeSignal: true
188 target: enableIndexChangeSignal ? indicatorRow : null
189 onCurrentItemIndexChanged: {
190 var oldActive = enableIndexChangeSignal;
191 enableIndexChangeSignal = false;
193 menuContent.setCurrentMenuIndex(indicatorRow.currentItemIndex);
195 enableIndexChangeSignal = oldActive;
196 }
197 }
199 //small shadow gradient at bottom of menu
200 Rectangle {
201 anchors {
202 left: parent.left
203 right: parent.right
204 bottom: parent.bottom
205 }
206 height: units.gu(0.5)
207 gradient: Gradient {
208 GradientStop { position: 0.0; color: "transparent" }
209 GradientStop { position: 1.0; color: "black" }
210 }
211 opacity: 0.4
212 }
213 }
215 Rectangle {
216 id: handle
218 color: menuContent.backgroundColor
220 anchors {
221 left: parent.left
222 right: parent.right
223 bottom: parent.bottom
224 }
225 height: get_height()
226 clip: height < handleImage.height
228 function get_height() {
229 return Math.max(Math.min(handleImage.height, indicators.height - handleImage.height), 0);
230 }
232 BorderImage {
233 id: handleImage
234 source: "graphics/handle.sci"
235 height: panelHeight
236 anchors {
237 left: parent.left
238 right: parent.right
239 bottom: parent.bottom
240 }
241 }
242 MouseArea { //prevent clicks passing through
243 anchors.fill: parent
244 }
245 }
247 PanelBackground {
248 anchors.fill: indicatorRow
249 }
251 IndicatorsDataModel {
252 id: indicatorsModel
253 }
255 IndicatorRow {
256 id: indicatorRow
257 objectName: "indicatorRow"
258 anchors {
259 left: parent.left
260 right: parent.right
261 }
262 height: indicators.panelHeight
263 indicatorsModel: indicatorsModel
264 state: indicators.state
266 Connections {
267 property bool enableIndexChangeSignal: true
269 target: enableIndexChangeSignal ? menuContent : null
270 onCurrentMenuIndexChanged: {
271 var oldActive = enableIndexChangeSignal;
272 enableIndexChangeSignal = false;
274 indicatorRow.setCurrentItem(menuContent.currentMenuIndex);
276 enableIndexChangeSignal = oldActive;
277 }
278 }
279 }
281 Connections {
282 target: showAnimation
283 onRunningChanged: {
284 if (showAnimation.running) {
285 indicators.state = "commit";
286 }
287 }
288 }
290 Connections {
291 target: hideAnimation
292 onRunningChanged: {
293 if (hideAnimation.running) {
294 indicators.state = "initial";
295 indicatorRow.currentItem = null;
296 }
297 }
298 }
300 property var activeDragHandle: showDragHandle.dragging ? showDragHandle : hideDragHandle.dragging ? hideDragHandle : null
301 // connections to the active drag handle
302 Connections {
303 target: activeDragHandle
304 onTouchXChanged: {
305 indicators.calculateCurrentItem(activeDragHandle.touchX, true);
306 }
307 onTouchSceneYChanged: {
308 yVelocityCalculator.trackedPosition = activeDragHandle.touchSceneY;
309 }
310 }
312 DragHandle {
313 id: showDragHandle
314 anchors.bottom: parent.bottom
315 // go beyond parent so that it stays reachable, at the top of the screen.
316 anchors.bottomMargin: pinnedMode ? 0 : -panelHeight
317 anchors.left: parent.left
318 anchors.right: parent.right
319 height: panelHeight
320 direction: Direction.Downwards
321 enabled: !indicators.shown && indicators.available
322 hintDisplacement: pinnedMode ? indicators.hintValue : 0
323 autoCompleteDragThreshold: maxTotalDragDistance / 2
324 stretch: true
325 maxTotalDragDistance: openedHeight - panelHeight
326 distanceThreshold: pinnedMode ? 0 : units.gu(3)
328 onStatusChanged: {
329 if (status === DirectionalDragArea.Recognized) {
330 menuContent.activateContent();
331 }
332 }
333 }
334 DragHandle {
335 id: hideDragHandle
336 anchors.fill: handle
337 direction: Direction.Upwards
338 enabled: indicators.shown && indicators.available
339 hintDisplacement: indicators.hintValue
340 autoCompleteDragThreshold: maxTotalDragDistance / 6
341 stretch: true
342 maxTotalDragDistance: openedHeight - panelHeight
343 distanceThreshold: 0
344 }
346 AxisVelocityCalculator {
347 id: yVelocityCalculator
348 }
350 states: [
351 State {
352 name: "initial"
353 },
354 State {
355 name: "hint"
356 PropertyChanges {
357 target: indicatorRow;
358 y: pinnedMode ? 0 : panelHeight
359 }
360 StateChangeScript {
361 script: {
362 if (activeDragHandle) {
363 calculateCurrentItem(activeDragHandle.touchX, false);
364 }
365 }
366 }
367 },
368 State {
369 name: "reveal"
370 extend: "hint"
371 },
372 State {
373 name: "locked"
374 extend: "hint"
375 },
376 State {
377 name: "commit"
378 extend: "hint"
379 }
380 ]
381 state: "initial"
383 transitions: [
384 Transition {
385 NumberAnimation {targets: [indicatorRow, menuContent]; property: "y"; duration: 300; easing.type: Easing.OutCubic}
386 }
387 ]