2
* Copyright (C) 2013 Canonical, Ltd.
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.
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.
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/>.
18
import Ubuntu.Components 0.1
19
import Ubuntu.Gestures 0.1
20
import Unity.Indicators 0.1 as Indicators
22
import "../Components"
23
import "../Components/ListItems"
29
property real openedHeight: units.gu(71)
30
property int panelHeight: units.gu(3)
31
property alias overFlowWidth: indicatorRow.overFlowWidth
32
property alias showAll: indicatorRow.showAll
33
property alias profile: visibleIndicators.profile
35
readonly property real hintValue: panelHeight + menuContent.headerHeight
36
readonly property int lockThreshold: openedHeight / 2
37
property bool fullyOpened: height == openedHeight
38
property bool partiallyOpened: height > panelHeight && !fullyOpened
39
property bool fullyClosed: height <= panelHeight
40
property bool contentEnabled: true
41
property bool initalizeItem: true
42
readonly property alias content: menuContent
43
property real unitProgress: (height - panelHeight) / (openedHeight - panelHeight)
44
property bool enableHint: true
45
property real showHintBottomMargin: 0
47
signal showTapped(point position)
49
// TODO: Perhaps we need a animation standard for showing/hiding? Each showable seems to
50
// use its own values. Need to ask design about this.
51
showAnimation: StandardAnimation {
56
hideAnimation: StandardAnimation {
60
easing.type: Easing.OutCubic
63
onOpenedHeightChanged: {
64
if (showAnimation.running) {
65
showAnimation.restart();
66
} else if (indicators.shown) {
67
height = openedHeight;
72
onHeightChanged: updateRevealProgressState(indicators.height - panelHeight - showHintBottomMargin, true)
74
function updateRevealProgressState(revealProgress, enableRelease) {
75
if (!showAnimation.running && !hideAnimation.running) {
76
if (revealProgress === 0) {
77
indicators.state = "initial";
78
} else if (enableHint && revealProgress > 0 && revealProgress <= hintValue) {
79
indicators.state = "hint";
80
} else if ((!enableHint || revealProgress > hintValue) && revealProgress < lockThreshold) {
81
indicators.state = "reveal";
82
} else if (revealProgress >= lockThreshold && lockThreshold > 0) {
83
indicators.state = "locked";
88
function calculateCurrentItem(xValue, useBuffer) {
92
var distanceFromRightEdge;
93
var bufferExceeded = false;
95
if (indicators.state == "commit" || indicators.state == "locked" || showAnimation.running || hideAnimation.running) return;
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).
102
var verticalProgress =
103
MathUtils.clamp((indicators.height - handle.height - hintValue) /
104
(lockThreshold - hintValue), 0, 1);
107
Vertical velocity check. Don't change the indicator if we're moving too quickly.
109
var verticalSpeed = Math.abs(yVelocityCalculator.calculate());
110
if (verticalSpeed >= 0.05 && !initalizeItem) {
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
119
var maxBufferThreshold = 0.5;
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.
127
var effectiveBufferThreshold = maxBufferThreshold * verticalProgress;
129
rowCoordinates = indicatorRow.mapToItem(indicatorRow.row, xValue, 0);
130
// get the current delegate
131
currentItem = indicatorRow.row.itemAt(rowCoordinates.x, 0);
133
itemCoordinates = indicatorRow.row.mapToItem(currentItem, rowCoordinates.x, 0);
134
distanceFromRightEdge = (currentItem.width - itemCoordinates.x) / (currentItem.width);
135
if (currentItem != indicatorRow.currentItem) {
136
if (Math.abs(currentItem.ownIndex - indicatorRow.currentItemIndex) > 1) {
137
bufferExceeded = true;
139
if (indicatorRow.currentItemIndex < currentItem.ownIndex && distanceFromRightEdge < (1 - effectiveBufferThreshold)) {
140
bufferExceeded = true;
141
} else if (indicatorRow.currentItemIndex > currentItem.ownIndex && distanceFromRightEdge > effectiveBufferThreshold) {
142
bufferExceeded = true;
145
if ((!useBuffer || (useBuffer && bufferExceeded)) || indicatorRow.currentItemIndex < 0 || indicatorRow.currentItem == null) {
146
indicatorRow.setCurrentItem(currentItem);
149
// need to re-init the distanceFromRightEdge for offset calculation
150
itemCoordinates = indicatorRow.row.mapToItem(indicatorRow.currentItem, rowCoordinates.x, 0);
151
distanceFromRightEdge = (indicatorRow.currentItem.width - itemCoordinates.x) / (indicatorRow.currentItem.width);
153
indicatorRow.currentItemOffset = 1 - (distanceFromRightEdge * 2);
154
} else if (initalizeItem) {
155
indicatorRow.setDefaultItem();
156
indicatorRow.currentItemOffset = 0;
158
initalizeItem = indicatorRow.currentItem == null;
165
bottom: handle.bottom
172
id: visibleIndicators
173
objectName: "visibleIndicators"
178
objectName: "menuContent"
183
top: indicatorRow.bottom
186
indicatorsModel: visibleIndicators.model
187
visible: indicators.partiallyOpened || indicators.fullyOpened
188
clip: indicators.partiallyOpened
189
enabled: contentEnabled
191
//small shadow gradient at bottom of menu
196
bottom: parent.bottom
198
height: units.gu(0.5)
200
GradientStop { position: 0.0; color: "transparent" }
201
GradientStop { position: 1.0; color: "black" }
210
color: menuContent.color
215
bottom: parent.bottom
217
height: Math.max(Math.min(handleImage.height, indicators.height - handleImage.height), 0)
218
clip: height < handleImage.height
219
visible: menuContent.visible
223
source: "graphics/handle.sci"
228
bottom: parent.bottom
231
MouseArea { //prevent clicks passing through
238
objectName: "indicatorRow"
243
height: indicators.panelHeight
244
indicatorsModel: visibleIndicators.model
245
state: indicators.state
246
unitProgress: indicators.unitProgress
250
anchors.fill: indicatorRow
251
direction: Direction.Downwards
258
initalizeItem = true;
259
updateRevealProgressState(Math.max(touchSceneY - panelHeight, hintValue), false);
260
indicators.calculateCurrentItem(touchX, false);
262
indicators.state = "commit";
263
indicatorRow.currentItemOffset = 0;
268
indicators.calculateCurrentItem(touchX, true);
270
onTouchSceneYChanged: {
271
updateRevealProgressState(Math.max(touchSceneY - panelHeight, hintValue), false);
272
yVelocityCalculator.trackedPosition = touchSceneY;
278
target: showAnimation
280
if (showAnimation.running) {
281
indicators.state = "commit";
282
indicatorRow.currentItemOffset = 0;
288
target: hideAnimation
290
if (hideAnimation.running) {
291
indicators.state = "initial";
292
initalizeItem = true;
293
indicatorRow.currentItemOffset = 0;
300
property bool enableIndexChangeSignal: true
301
property var activeDragHandle: showDragHandle.dragging ? showDragHandle : hideDragHandle.dragging ? hideDragHandle : null
306
onCurrentMenuIndexChanged: {
307
var oldActive = d.enableIndexChangeSignal;
308
if (!oldActive) return;
309
d.enableIndexChangeSignal = false;
311
indicatorRow.setCurrentItemIndex(menuContent.currentMenuIndex);
313
d.enableIndexChangeSignal = oldActive;
319
onCurrentItemIndexChanged: {
320
var oldActive = d.enableIndexChangeSignal;
321
if (!oldActive) return;
322
d.enableIndexChangeSignal = false;
324
menuContent.setCurrentMenuIndex(indicatorRow.currentItemIndex, fullyOpened || partiallyOpened);
326
d.enableIndexChangeSignal = oldActive;
329
// connections to the active drag handle
331
target: d.activeDragHandle
333
indicators.calculateCurrentItem(d.activeDragHandle.touchX, true);
335
onTouchSceneYChanged: {
336
yVelocityCalculator.trackedPosition = d.activeDragHandle.touchSceneY;
342
anchors.bottom: parent.bottom
343
// go beyond parent so that it stays reachable, at the top of the screen.
344
anchors.bottomMargin: showHintBottomMargin
345
anchors.left: parent.left
346
anchors.right: parent.right
348
direction: Direction.Downwards
349
enabled: !indicators.shown && indicators.available
350
hintDisplacement: enableHint ? indicators.hintValue : 0
351
autoCompleteDragThreshold: maxTotalDragDistance / 2
353
maxTotalDragDistance: openedHeight - panelHeight
354
distanceThreshold: panelHeight
356
onTapped: showTapped(Qt.point(touchSceneX, touchSceneY));
362
direction: Direction.Upwards
363
enabled: indicators.shown && indicators.available
364
hintDisplacement: indicators.hintValue
365
autoCompleteDragThreshold: maxTotalDragDistance / 6
367
maxTotalDragDistance: openedHeight - panelHeight
371
AxisVelocityCalculator {
372
id: yVelocityCalculator
383
if (d.activeDragHandle) {
384
calculateCurrentItem(d.activeDragHandle.touchX, false);
406
NumberAnimation {targets: [indicatorRow, menuContent]; property: "y"; duration: 300; easing.type: Easing.OutCubic}
410
Component.onCompleted: initialise();
411
function initialise() {
412
visibleIndicators.load(profile);
413
indicatorRow.setDefaultItem();