~mterry/+junk/u8.2

« back to all changes in this revision

Viewing changes to qml/Panel/Indicators.qml

  • Committer: Michael Terry
  • Date: 2014-11-17 14:56:04 UTC
  • mfrom: (1317.1.118 unity8)
  • Revision ID: michael.terry@canonical.com-20141117145604-96dn9p5nwkifq2f4
MergeĀ fromĀ trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
 */
16
 
 
17
 
import QtQuick 2.0
18
 
import Ubuntu.Components 0.1
19
 
import Ubuntu.Gestures 0.1
20
 
import Unity.Indicators 0.1 as Indicators
21
 
 
22
 
import "../Components"
23
 
import "../Components/ListItems"
24
 
import "Indicators"
25
 
 
26
 
Showable {
27
 
    id: indicators
28
 
 
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
34
 
 
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
46
 
 
47
 
    signal showTapped(point position)
48
 
 
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 {
52
 
        property: "height"
53
 
        to: openedHeight
54
 
    }
55
 
 
56
 
    hideAnimation: StandardAnimation {
57
 
        property: "height"
58
 
        duration: 350
59
 
        to: panelHeight
60
 
        easing.type: Easing.OutCubic
61
 
    }
62
 
 
63
 
    onOpenedHeightChanged: {
64
 
        if (showAnimation.running) {
65
 
            showAnimation.restart();
66
 
        } else if (indicators.shown) {
67
 
            height = openedHeight;
68
 
        }
69
 
    }
70
 
 
71
 
    height: panelHeight
72
 
    onHeightChanged: updateRevealProgressState(indicators.height - panelHeight - showHintBottomMargin, true)
73
 
 
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";
84
 
            }
85
 
        }
86
 
    }
87
 
 
88
 
    function calculateCurrentItem(xValue, useBuffer) {
89
 
        var rowCoordinates;
90
 
        var itemCoordinates;
91
 
        var currentItem;
92
 
        var distanceFromRightEdge;
93
 
        var bufferExceeded = false;
94
 
 
95
 
        if (indicators.state == "commit" || indicators.state == "locked" || showAnimation.running || hideAnimation.running) return;
96
 
 
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
 
            MathUtils.clamp((indicators.height - handle.height - hintValue) /
104
 
                            (lockThreshold - hintValue), 0, 1);
105
 
 
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 && !initalizeItem) {
111
 
            return;
112
 
        }
113
 
 
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;
120
 
 
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;
128
 
 
129
 
        rowCoordinates = indicatorRow.mapToItem(indicatorRow.row, xValue, 0);
130
 
        // get the current delegate
131
 
        currentItem = indicatorRow.row.itemAt(rowCoordinates.x, 0);
132
 
        if (currentItem) {
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;
138
 
                } else {
139
 
                    if (indicatorRow.currentItemIndex < currentItem.ownIndex && distanceFromRightEdge < (1 - effectiveBufferThreshold)) {
140
 
                        bufferExceeded = true;
141
 
                    } else if (indicatorRow.currentItemIndex > currentItem.ownIndex && distanceFromRightEdge > effectiveBufferThreshold) {
142
 
                        bufferExceeded = true;
143
 
                    }
144
 
                }
145
 
                if ((!useBuffer || (useBuffer && bufferExceeded)) || indicatorRow.currentItemIndex < 0 || indicatorRow.currentItem == null)  {
146
 
                    indicatorRow.setCurrentItem(currentItem);
147
 
                }
148
 
 
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);
152
 
            }
153
 
            indicatorRow.currentItemOffset = 1 - (distanceFromRightEdge * 2);
154
 
        } else if (initalizeItem) {
155
 
            indicatorRow.setDefaultItem();
156
 
            indicatorRow.currentItemOffset = 0;
157
 
        }
158
 
        initalizeItem = indicatorRow.currentItem == null;
159
 
    }
160
 
 
161
 
    // eater
162
 
    MouseArea {
163
 
        anchors {
164
 
            top: parent.top
165
 
            bottom: handle.bottom
166
 
            left: parent.left
167
 
            right: parent.right
168
 
        }
169
 
    }
170
 
 
171
 
    VisibleIndicators {
172
 
        id: visibleIndicators
173
 
        objectName: "visibleIndicators"
174
 
    }
175
 
 
176
 
    MenuContent {
177
 
        id: menuContent
178
 
        objectName: "menuContent"
179
 
 
180
 
        anchors {
181
 
            left: parent.left
182
 
            right: parent.right
183
 
            top: indicatorRow.bottom
184
 
            bottom: handle.top
185
 
        }
186
 
        indicatorsModel: visibleIndicators.model
187
 
        visible: indicators.partiallyOpened || indicators.fullyOpened
188
 
        clip: indicators.partiallyOpened
189
 
        enabled: contentEnabled
190
 
 
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
 
    }
206
 
 
207
 
    Rectangle {
208
 
        id: handle
209
 
 
210
 
        color:  menuContent.color
211
 
 
212
 
        anchors {
213
 
            left: parent.left
214
 
            right: parent.right
215
 
            bottom: parent.bottom
216
 
        }
217
 
        height: Math.max(Math.min(handleImage.height, indicators.height - handleImage.height), 0)
218
 
        clip: height < handleImage.height
219
 
        visible: menuContent.visible
220
 
 
221
 
        BorderImage {
222
 
            id: handleImage
223
 
            source: "graphics/handle.sci"
224
 
            height: panelHeight
225
 
            anchors {
226
 
                left: parent.left
227
 
                right: parent.right
228
 
                bottom: parent.bottom
229
 
            }
230
 
        }
231
 
        MouseArea { //prevent clicks passing through
232
 
            anchors.fill: parent
233
 
        }
234
 
    }
235
 
 
236
 
    IndicatorRow {
237
 
        id: indicatorRow
238
 
        objectName: "indicatorRow"
239
 
        anchors {
240
 
            left: parent.left
241
 
            right: parent.right
242
 
        }
243
 
        height: indicators.panelHeight
244
 
        indicatorsModel: visibleIndicators.model
245
 
        state: indicators.state
246
 
        unitProgress: indicators.unitProgress
247
 
 
248
 
        EdgeDragArea {
249
 
            id: rowDragArea
250
 
            anchors.fill: indicatorRow
251
 
            direction: Direction.Downwards
252
 
            maxSilenceTime: 2000
253
 
            distanceThreshold: 0
254
 
 
255
 
            enabled: fullyOpened
256
 
            onDraggingChanged: {
257
 
                if (dragging) {
258
 
                    initalizeItem = true;
259
 
                    updateRevealProgressState(Math.max(touchSceneY - panelHeight, hintValue), false);
260
 
                    indicators.calculateCurrentItem(touchX, false);
261
 
                } else {
262
 
                    indicators.state = "commit";
263
 
                    indicatorRow.currentItemOffset = 0;
264
 
                }
265
 
            }
266
 
 
267
 
            onTouchXChanged: {
268
 
                indicators.calculateCurrentItem(touchX, true);
269
 
            }
270
 
            onTouchSceneYChanged: {
271
 
                updateRevealProgressState(Math.max(touchSceneY - panelHeight, hintValue), false);
272
 
                yVelocityCalculator.trackedPosition = touchSceneY;
273
 
            }
274
 
        }
275
 
    }
276
 
 
277
 
    Connections {
278
 
        target: showAnimation
279
 
        onRunningChanged: {
280
 
            if (showAnimation.running) {
281
 
                indicators.state = "commit";
282
 
                indicatorRow.currentItemOffset = 0;
283
 
            }
284
 
        }
285
 
    }
286
 
 
287
 
    Connections {
288
 
        target: hideAnimation
289
 
        onRunningChanged: {
290
 
            if (hideAnimation.running) {
291
 
                indicators.state = "initial";
292
 
                initalizeItem = true;
293
 
                indicatorRow.currentItemOffset = 0;
294
 
            }
295
 
        }
296
 
    }
297
 
 
298
 
    QtObject {
299
 
        id: d
300
 
        property bool enableIndexChangeSignal: true
301
 
        property var activeDragHandle: showDragHandle.dragging ? showDragHandle : hideDragHandle.dragging ? hideDragHandle : null
302
 
    }
303
 
 
304
 
    Connections {
305
 
        target: menuContent
306
 
        onCurrentMenuIndexChanged: {
307
 
            var oldActive = d.enableIndexChangeSignal;
308
 
            if (!oldActive) return;
309
 
            d.enableIndexChangeSignal = false;
310
 
 
311
 
            indicatorRow.setCurrentItemIndex(menuContent.currentMenuIndex);
312
 
 
313
 
            d.enableIndexChangeSignal = oldActive;
314
 
        }
315
 
    }
316
 
 
317
 
    Connections {
318
 
        target: indicatorRow
319
 
        onCurrentItemIndexChanged: {
320
 
            var oldActive = d.enableIndexChangeSignal;
321
 
            if (!oldActive) return;
322
 
            d.enableIndexChangeSignal = false;
323
 
 
324
 
            menuContent.setCurrentMenuIndex(indicatorRow.currentItemIndex, fullyOpened || partiallyOpened);
325
 
 
326
 
            d.enableIndexChangeSignal = oldActive;
327
 
        }
328
 
    }
329
 
    // connections to the active drag handle
330
 
    Connections {
331
 
        target: d.activeDragHandle
332
 
        onTouchXChanged: {
333
 
            indicators.calculateCurrentItem(d.activeDragHandle.touchX, true);
334
 
        }
335
 
        onTouchSceneYChanged: {
336
 
            yVelocityCalculator.trackedPosition = d.activeDragHandle.touchSceneY;
337
 
        }
338
 
    }
339
 
 
340
 
    DragHandle {
341
 
        id: showDragHandle
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
347
 
        height: panelHeight
348
 
        direction: Direction.Downwards
349
 
        enabled: !indicators.shown && indicators.available
350
 
        hintDisplacement: enableHint ? indicators.hintValue : 0
351
 
        autoCompleteDragThreshold: maxTotalDragDistance / 2
352
 
        stretch: true
353
 
        maxTotalDragDistance: openedHeight - panelHeight
354
 
        distanceThreshold: panelHeight
355
 
 
356
 
        onTapped: showTapped(Qt.point(touchSceneX, touchSceneY));
357
 
    }
358
 
 
359
 
    DragHandle {
360
 
        id: hideDragHandle
361
 
        anchors.fill: handle
362
 
        direction: Direction.Upwards
363
 
        enabled: indicators.shown && indicators.available
364
 
        hintDisplacement: indicators.hintValue
365
 
        autoCompleteDragThreshold: maxTotalDragDistance / 6
366
 
        stretch: true
367
 
        maxTotalDragDistance: openedHeight - panelHeight
368
 
        distanceThreshold: 0
369
 
    }
370
 
 
371
 
    AxisVelocityCalculator {
372
 
        id: yVelocityCalculator
373
 
    }
374
 
 
375
 
    states: [
376
 
        State {
377
 
            name: "initial"
378
 
        },
379
 
        State {
380
 
            name: "hint"
381
 
            StateChangeScript {
382
 
                script: {
383
 
                    if (d.activeDragHandle) {
384
 
                        calculateCurrentItem(d.activeDragHandle.touchX, false);
385
 
                    }
386
 
                }
387
 
            }
388
 
        },
389
 
        State {
390
 
            name: "reveal"
391
 
            extend: "hint"
392
 
        },
393
 
        State {
394
 
            name: "locked"
395
 
            extend: "hint"
396
 
        },
397
 
        State {
398
 
            name: "commit"
399
 
            extend: "hint"
400
 
        }
401
 
    ]
402
 
    state: "initial"
403
 
 
404
 
    transitions: [
405
 
        Transition  {
406
 
            NumberAnimation {targets: [indicatorRow, menuContent]; property: "y"; duration: 300; easing.type: Easing.OutCubic}
407
 
        }
408
 
    ]
409
 
 
410
 
    Component.onCompleted: initialise();
411
 
    function initialise() {
412
 
        visibleIndicators.load(profile);
413
 
        indicatorRow.setDefaultItem();
414
 
    }
415
 
}