~unity-team/+junk/dashboard-playground

« back to all changes in this revision

Viewing changes to Panel/Indicators.qml

  • Committer: Michał Sawicz
  • Date: 2013-06-05 22:03:08 UTC
  • Revision ID: michal.sawicz@canonical.com-20130605220308-yny8fv3futtr04fg
Inital unity8 commit.

Previous history can be found at https://code.launchpad.net/~unity-team/unity/phablet

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.ChewieUI 0.1 as ChewieUI
 
20
 
 
21
import "../Components"
 
22
import "../Components/Math.js" as MathLocal
 
23
 
 
24
Showable {
 
25
    id: indicators
 
26
 
 
27
    property int progress: panelHeight
 
28
    property int 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
 
31
 
 
32
    property int hintValue
 
33
    readonly property int lockThreshold: shell.height/2
 
34
    property bool fullyOpened: revealer ? progress == revealer.openedValue : false
 
35
    property bool partiallyOpened: revealer ? progress > revealer.closedValue && progress < revealer.openedValue : false
 
36
 
 
37
    property Revealer revealer: null
 
38
 
 
39
    height: menuContent.height + handle.height + menuContent.y
 
40
 
 
41
    onProgressChanged: {
 
42
        // need to use handle.get_height(). As the handle height depends on progress changes (but this is called first!)
 
43
        var contentProgress = progress - handle.get_height()
 
44
        if (!showAnimation.running && !hideAnimation.running && !revealer.hintingAnimation.running) {
 
45
            if (contentProgress <= hintValue && indicators.state == "reveal") {
 
46
                indicators.state = "hint"
 
47
                menuContent.hideAll()
 
48
            } else if (contentProgress > hintValue && contentProgress < lockThreshold) {
 
49
                menuContent.showMenu()
 
50
                indicators.state = "reveal"
 
51
            } else if (contentProgress >= lockThreshold && lockThreshold > 0) {
 
52
                // If we've shown the overview and are closing the view with progress changes (revealer handle),
 
53
                // we dont want to switch to a indicator menu until we hit reveal state.
 
54
                if (menuContent.overviewActive) {
 
55
                    menuContent.showOverview()
 
56
                }
 
57
                else {
 
58
                    menuContent.showMenu()
 
59
                }
 
60
                indicators.state = "locked"
 
61
            }
 
62
        }
 
63
 
 
64
        if (contentProgress == 0) {
 
65
            menuContent.releaseContent()
 
66
        }
 
67
    }
 
68
 
 
69
    function handlePress() {
 
70
        menuContent.hideAll()
 
71
        menuContent.activateContent()
 
72
        indicators.state = "hint"
 
73
    }
 
74
 
 
75
    function openOverview() {
 
76
        indicatorRow.currentItem = null
 
77
        menuContent.showOverview()
 
78
    }
 
79
 
 
80
    function calculateCurrentItem(xValue, useBuffer) {
 
81
        var rowCoordinates
 
82
        var itemCoordinates
 
83
        var currentItem
 
84
        var distanceFromRightEdge
 
85
        var bufferExceeded = false
 
86
 
 
87
        if (indicators.state == "commit" || indicators.state == "locked" || showAnimation.running || hideAnimation.running) return
 
88
 
 
89
        /*
 
90
          If user drags the indicator handle bar down a distance hintValue or less, this is 0.
 
91
          If bar is dragged down a distance greater than or equal to lockThreshold, this is 1.
 
92
          Otherwise it contains the bar's location as a fraction of the distance between hintValue (is 0) and lockThreshold (is 1).
 
93
        */
 
94
        var verticalProgress = MathLocal.clamp((indicators.progress - handle.height - hintValue) / (lockThreshold - hintValue), 0, 1)
 
95
 
 
96
        /*
 
97
          Percentage of an indicator icon's width the user's press can stray horizontally from the
 
98
          focused icon before we change focus to another icon. E.g. a value of 0.5 means you must
 
99
          go right a distance of half an icon's width before focus moves to the icon on the right
 
100
        */
 
101
        var maxBufferThreshold = 0.5
 
102
 
 
103
        /*
 
104
          To help users find the indicator of their choice while opening the indicators, we add logic to add a
 
105
          left/right buffer to each icon so it is harder for the focus to be moved accidentally to another icon,
 
106
          as the user moves their finger down, but yet allows them to switch indicator if they want.
 
107
          This buffer is wider the further the user's finger is from the top of the screen.
 
108
        */
 
109
        var effectiveBufferThreshold = maxBufferThreshold * verticalProgress;
 
110
 
 
111
        rowCoordinates = indicatorRow.mapToItem(indicatorRow.row, xValue, 0);
 
112
        // get the current delegate
 
113
        currentItem = indicatorRow.row.childAt(rowCoordinates.x, 0);
 
114
        if (currentItem && currentItem != indicatorRow.currentItem ) {
 
115
            itemCoordinates = indicatorRow.row.mapToItem(currentItem, rowCoordinates.x, 0);
 
116
            distanceFromRightEdge = (currentItem.width - itemCoordinates.x) / (currentItem.width)
 
117
            if (Math.abs(currentItem.ownIndex - indicatorRow.currentItemIndex) > 1) {
 
118
                bufferExceeded = true
 
119
            } else {
 
120
                if (indicatorRow.currentItemIndex < currentItem.ownIndex && distanceFromRightEdge < (1 - effectiveBufferThreshold)) {
 
121
                    bufferExceeded = true
 
122
                } else if (indicatorRow.currentItemIndex > currentItem.ownIndex && distanceFromRightEdge > effectiveBufferThreshold) {
 
123
                    bufferExceeded = true
 
124
                }
 
125
            }
 
126
            if ((!useBuffer || (useBuffer && bufferExceeded)) || indicatorRow.currentItem < 0 || indicatorRow.currentItem == null)  {
 
127
                indicatorRow.currentItem = currentItem;
 
128
            }
 
129
        }
 
130
    }
 
131
 
 
132
    // eater
 
133
    MouseArea {
 
134
        anchors {
 
135
            top: parent.top
 
136
            bottom: handle.bottom
 
137
            left: parent.left
 
138
            right: parent.right
 
139
        }
 
140
    }
 
141
 
 
142
    MenuContent {
 
143
        id: menuContent
 
144
        objectName: "menuContent"
 
145
 
 
146
        anchors {
 
147
            left: parent.left
 
148
            right: parent.right
 
149
            top: indicatorRow.bottom
 
150
        }
 
151
        height: progress - y
 
152
        indicatorsModel: indicatorsModel
 
153
        animate: false
 
154
        clip: indicators.partiallyOpened
 
155
 
 
156
        onMenuSelected: {
 
157
            indicatorRow.setItem(index)
 
158
        }
 
159
 
 
160
        //small shadow gradient at bottom of menu
 
161
        Rectangle {
 
162
            anchors {
 
163
                left: parent.left
 
164
                right: parent.right
 
165
                bottom: parent.bottom
 
166
            }
 
167
            height: units.gu(0.5)
 
168
            gradient: Gradient {
 
169
                GradientStop { position: 0.0; color: "transparent" }
 
170
                GradientStop { position: 1.0; color: "black" }
 
171
            }
 
172
            opacity: 0.4
 
173
        }
 
174
    }
 
175
 
 
176
    Rectangle {
 
177
        id: handle
 
178
 
 
179
        color:  menuContent.backgroundColor
 
180
 
 
181
        anchors {
 
182
            left: parent.left
 
183
            right: parent.right
 
184
            top: menuContent.bottom
 
185
        }
 
186
        height: get_height()
 
187
        clip: height < handleImage.height
 
188
 
 
189
        function get_height() {
 
190
            return Math.max(Math.min(handleImage.height, progress - handleImage.height), 0)
 
191
        }
 
192
 
 
193
        BorderImage {
 
194
            id: handleImage
 
195
            source: "graphics/handle.sci"
 
196
            height: panelHeight
 
197
            anchors {
 
198
                left: parent.left
 
199
                right: parent.right
 
200
                bottom: parent.bottom
 
201
            }
 
202
        }
 
203
        MouseArea { //prevent clicks passing through
 
204
            anchors.fill: parent
 
205
        }
 
206
    }
 
207
 
 
208
    PanelBackground {
 
209
        anchors.fill: indicatorRow
 
210
    }
 
211
 
 
212
    IndicatorRow {
 
213
        id: indicatorRow
 
214
        objectName: "indicatorRow"
 
215
        anchors {
 
216
            left: parent.left
 
217
            right: parent.right
 
218
        }
 
219
        height: indicators.panelHeight
 
220
        indicatorsModel: indicatorsModel
 
221
        state: indicators.state
 
222
        overviewActive: menuContent.overviewActive
 
223
 
 
224
        onCurrentItemIndexChanged: menuContent.currentIndex = currentItemIndex
 
225
 
 
226
    }
 
227
 
 
228
    ChewieUI.PluginModel {
 
229
        id: indicatorsModel
 
230
        Component.onCompleted: load()
 
231
    }
 
232
 
 
233
    Connections {
 
234
        target: hideAnimation
 
235
        onRunningChanged: {
 
236
            if (hideAnimation.running) {
 
237
                indicators.state = "initial"
 
238
                menuContent.hideAll()
 
239
            } else  {
 
240
                if (state == "initial") indicatorRow.setDefaultItem()
 
241
            }
 
242
        }
 
243
    }
 
244
    Connections {
 
245
        target: showAnimation
 
246
        onRunningChanged: {
 
247
            if (showAnimation.running) {
 
248
                if (indicators.state == "initial") {
 
249
                    openOverview()
 
250
                }
 
251
                else {
 
252
                    indicators.calculateCurrentItem(revealer.lateralPosition, false)
 
253
                    menuContent.showMenu()
 
254
                }
 
255
                indicators.state = "commit"
 
256
            }
 
257
        }
 
258
    }
 
259
 
 
260
    Connections {
 
261
        target: revealer
 
262
        onLateralPositionChanged: {
 
263
            var buffer = revealer.dragging ? true : false
 
264
            indicators.calculateCurrentItem(revealer.lateralPosition, buffer)
 
265
        }
 
266
    }
 
267
 
 
268
    // We start with pinned states. (default pinnedMode: true)
 
269
    states: pinnedModeStates
 
270
    // because of dynamic assignment of states, we need to assign state after completion.
 
271
    Component.onCompleted: state = "initial"
 
272
 
 
273
    // changing states will reset state to "".
 
274
    onPinnedModeChanged: {
 
275
        var last_state = state;
 
276
        states = (pinnedMode) ? pinnedModeStates : offScreenModeStates;
 
277
        state = last_state;
 
278
    }
 
279
 
 
280
    property list<State> offScreenModeStates: [
 
281
        State {
 
282
            name: "initial"
 
283
        },
 
284
        State {
 
285
            name: "hint"
 
286
            PropertyChanges { target: indicatorRow; y: panelHeight }
 
287
        },
 
288
        State {
 
289
            name: "reveal"
 
290
            extend: "hint"
 
291
            PropertyChanges { target: menuContent; animate: true }
 
292
            StateChangeScript { script: calculateCurrentItem(revealer.lateralPosition, false); }
 
293
        },
 
294
        State {
 
295
            name: "locked"
 
296
            extend: "hint"
 
297
        },
 
298
        State {
 
299
            name: "commit"
 
300
            extend: "hint"
 
301
        }
 
302
    ]
 
303
 
 
304
    property list<State> pinnedModeStates: [
 
305
        State {
 
306
            name: "initial"
 
307
        },
 
308
        State {
 
309
            name: "hint"
 
310
        },
 
311
        State {
 
312
            name: "reveal"
 
313
            PropertyChanges { target: menuContent; animate: true }
 
314
            StateChangeScript { script: calculateCurrentItem(revealer.lateralPosition, false); }
 
315
        },
 
316
        State {
 
317
            name: "locked"
 
318
        },
 
319
        State {
 
320
            name: "commit"
 
321
        }
 
322
    ]
 
323
 
 
324
    transitions: [
 
325
        Transition  {
 
326
            NumberAnimation {targets: [indicatorRow, menuContent]; property: "y"; duration: 300; easing.type: Easing.OutCubic}
 
327
        }
 
328
    ]
 
329
}