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