~paulliu/unity8/zoomImage

« back to all changes in this revision

Viewing changes to qml/Stages/PhoneStage.qml

  • Committer: Ying-Chun Liu
  • Date: 2014-04-22 15:33:24 UTC
  • mfrom: (734.1.119 unity8)
  • Revision ID: paul.liu@canonical.com-20140422153324-9o7r3gh3dzczx4sr
Merge upstream
[ Albert Astals ]
* Fix last item X position Fixes clicking on the last item sometimes
  not working (LP: #1301871)
* Use upstart in ./run Makes it so that you can use the lock button on
  the device without getting that nasty hwc crash
* Remove AnimationControllerWithSignals.
* Use the correct delegate base item for the Carousel test
* Some simplification in DashContent Kill the ScopeDelegateMapper in
  favour of a simple if (that will eventually go away). Removal of all
  the fake scopes in the tests that added nothing of value to the
  tests. Removal of movementEnded signal that was unused. Removal of
  movementStarted and positionedAtBeginning signals that were being
  used as function calls. Rework DashContent tests so they what the
  function does what it is supposed to do instead of just making sure
  QML signals work .
* Improve Card creation time by adding loaders that make sure only
  what's needed is loaded In my computer it goes from RESULT :
  qmltestrunner::benchmark_time:"cardTitleArtSubtitleMascotSummaryMode
  l": 3.217 msecs per iteration (total: 3,218, iterations: 1000)
  RESULT :
  qmltestrunner::benchmark_time:"cardTitleArtSubtitleMascotModel":
  1.647 msecs per iteration (total: 1,648, iterations: 1000) RESULT :
  qmltestrunner::benchmark_time:"cardTitleArtSubtitleModel": 1.514
  msecs per iteration (total: 1,515, iterations: 1000) RESULT :
  qmltestrunner::benchmark_time:"cardTitleArtModel": 1.471 msecs per
  iteration (total: 1,471, iterations: 1000) RESULT :
  qmltestrunner::benchmark_time:"cardArtModel": 1.447 msecs per
  iteration (total: 1,448, iterations: 1000) RESULT :
  qmltestrunner::benchmark_time:"cardTitleModel": 1.276 msecs per
  iteration (total: 1,276, iterations: 1000) to RESULT :
  qmltestrunner::benchmark_time:"cardTitleArtSubtitleMascotSummaryMode
  l": 2.916 msecs per iteration (total: 2,917, iterations: 1000)
  RESULT :
  qmltestrunner::benchmark_time:"cardTitleArtSubtitleMascotModel":
  1.504 msecs per iteration (total: 1,504, iterations: 1000) RESULT :
  qmltestrunner::benchmark_time:"cardTitleArtSubtitleModel": 1.060
  msecs per iteration (total: 1,061, iterations: 1000) RESULT :
  qmltestrunner::benchmark_time:"cardTitleArtModel": 1.052 msecs per
  iteration (total: 1,053, iterations: 1000) RESULT :
  qmltestrunner::benchmark_time:"cardArtModel": 0.727 msecs per
  iteration (total: 728, iterations: 1000) RESULT :
  qmltestrunner::benchmark_time:"cardTitleModel": 0.817 msecs per
  iteration (total: 818, iterations: 1000) (LP: #1297197)
[ Allan LeSage ]
* DashApps emulator get_applications should return a list ordered by
  visible y, x.
[ Andrea Cimitan ]
* Workaround for lp1301309 until fixes for palette in ui toolkit (LP:
  #1301309)
[ Leo Arias ]
* Reverted the change that returns application cards instead of
  titles.
[ Nick Dedekind ]
* Indicator services started by unity8 upstart configuration rather
  than manual emmision from indicator manager.
[ Mirco Müller ]
* Fix notification ap-test assertions.
[ Michael Terry ]
* Use new tablet and phone backgrounds from Design.
[ Michael Zanetti ]
* workaround the QTestLogger assertion issue with make tryXyz and our
  custom uqmlscene
[ Michael Terry ]
* When an application requests focus, handle it in Shell.qml by hiding
  the greeter and stopping any edge demo. (LP: #1227753)
[ Leo Arias ]
* Use subprocess.check_call when caling url-dispatcher, so an error
  will be raised if it fails.
* Test application life cycle with fake apps, instead of messaging and
  address book.
[ Didier Roche ]
* Resync trunk with previous revert upload
[ Michał Sawicz ]
* Set the Qt.ImhNoPredictiveText flag on wifi password field, fixes
  lp:1291575 (LP: #1291575)
[ Albert Astals ]
* Take into account the originY when specifying the delegate ranges
  Fixes bug #1300302 (LP: #1300302)
[ CI bot ]
* Resync trunk
[ Allan LeSage ]
* Swiping open an indicator must show its correct title--protect
  against lp:1253804 . (LP: #1253804)
[ Alexander Sack ]
* Fix TypeError: issue seen in system_integration autopilot test on
  image 279. (LP: #1303685)
[ Bill Filler ]
* Set the Qt.ImhNoPredictiveText flag on wifi password field, fixes
  lp:1291575 (LP: #1291575)
[ Leo Arias ]
* Added a search autopilot helper.
[ Michael Terry ]
* Provide a all-in-one script for getting a device to an unlocked
  state.
* Revert to previous version as it's linked to latest sdk change which
  is making gallery-app AP tests failing on the CI dashboard
[ Albert Astals ]
* Adapt to new TabBar
[ Michael Terry ]
* Re-enable test_networkmanager_integration autopilot test on phone
  platforms
[ CI bot ]
* Resync trunk
[ Leo Arias ]
* Reverted the open_preview autopilot helper to return a Preview
  object.
[ Albert Astals ]
* If not running in Mir load the "fake" application manager (LP:
  #1301547)
* Remove unused properties from DashRenderer
[ Michael Zanetti ]
* Fix tests after right edge merge. Drop old stages tests. Fix right
  edge tests if someone doesn't have the GRID_UNIT_PX exported. make
  GenericScopeView test more robust that broke because the ordering
  changed
* add "make xvfbtestSomething" target to run qml tests in xvfb
* make the "make test" commit hook work again
[ Michał Sawicz ]
* Bump version to ensure incompatibility with previous Unity.Application
  implementations.
* We'll only have the unity-mir and mock Ubuntu.Application plugins
  now, no need for mangling the import paths.
[ Michal Hruby ]
* Remove the albumart image provider. (LP: #1262711)
* Don't reset search string after 2 seconds. (LP: #1297246)
[ James Henstridge ]
* Remove the albumart image provider. (LP: #1262711)
[ Albert Astals ]
* Carousel: Add test to make sure we only create the needed delegates
  and not more
* LVWPH: Remove processEvents() call from updatePolish() It causes
  some reentrancy issues and in some times you end up in polishItems()
  with items that have been deleted because you called processEvents()
  This means i need a small tweak in itemGeometryChanged to not
  reposition items if we are inside a setContentHeight call and two
  small tweaks to tests since now things happen in a different order
  and numbers are different (though equivalent) (LP: #1297240)
* Card.qml binding loops are gone. hooray \o/ Also made the aspect
  properties readonly
[ Mirco Müller ]
* A potential fix for "Cannot read property 'state' of null"-failure
  on Jenkins with the VisualSnapDecisionsQueue QML-test of
  notifications.
[ Michael Terry ]
* Pass user's preference for auto-brightness on to powerd. (LP:
  #1273174)
[ Michael Zanetti ]
* Registers a dummy QObject as QTestRootObject in uqmlscene in order
  to fix make trySomething with Qt 5.2.
* For now, have libunity-private depending on libunity-core-6.0-9 as the
  gsettings schema is here. The dependency wasn't direct and dropped from
  Touch image #271. Consequently, unity8 didn't start (gsettings
  segfaulting).
  Proper strategy will be to include the schema in another package to only
  pull it.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (C) 2014 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.Application 0.1
 
21
import Utils 0.1
 
22
import "../Components"
 
23
 
 
24
Item {
 
25
    id: root
 
26
 
 
27
    // Controls to be set from outside
 
28
    property bool shown: false
 
29
    property bool moving: false
 
30
    property int dragAreaWidth
 
31
 
 
32
    // State information propagated to the outside
 
33
    readonly property bool painting: mainScreenshotImage.visible || fadeInScreenshotImage.visible || appSplash.visible || spreadView.visible
 
34
    property bool fullscreen: priv.focusedApplication ? priv.focusedApplication.fullscreen : false
 
35
    property bool locked: spreadView.visible
 
36
 
 
37
    // Not used for PhoneStage, only useful for SideStage and similar
 
38
    property bool overlayMode: false
 
39
    property int overlayWidth: 0
 
40
 
 
41
    function select(appId) {
 
42
        spreadView.snapTo(priv.indexOf(appId))
 
43
    }
 
44
 
 
45
    onMovingChanged: {
 
46
        if (moving) {
 
47
            if (ApplicationManager.focusedApplicationId) {
 
48
                priv.requestNewScreenshot();
 
49
            } else {
 
50
                mainScreenshotImage.anchors.leftMargin = 0;
 
51
                mainScreenshotImage.source = ApplicationManager.get(0).screenshot;
 
52
                mainScreenshotImage.visible = true;
 
53
            }
 
54
        } else {
 
55
            mainScreenshotImage.visible = false;
 
56
        }
 
57
    }
 
58
 
 
59
    Connections {
 
60
        target: ApplicationManager
 
61
 
 
62
        onFocusRequested: {
 
63
            if (spreadView.visible) {
 
64
                spreadView.snapTo(priv.indexOf(appId));
 
65
            } else {
 
66
                priv.switchToApp(appId);
 
67
            }
 
68
        }
 
69
 
 
70
        onFocusedApplicationIdChanged: {
 
71
            if (ApplicationManager.focusedApplicationId.length > 0) {
 
72
                if (priv.secondApplicationStarting || priv.applicationStarting) {
 
73
                    appSplashTimer.restart();
 
74
                } else {
 
75
                    var application = priv.focusedApplication;
 
76
                    root.fullscreen = application.fullscreen;
 
77
                    mainScreenshotImage.source = application.screenshot;
 
78
                }
 
79
            } else {
 
80
                spreadView.selectedIndex = -1;
 
81
                spreadView.phase = 0;
 
82
                spreadView.contentX = -spreadView.shift;
 
83
            }
 
84
        }
 
85
 
 
86
        onApplicationAdded: {
 
87
            if (!priv.focusedApplication) {
 
88
                mainScreenshotImage.source = "";
 
89
                mainScreenshotImage.visible = false;
 
90
                priv.applicationStarting = true;
 
91
            } else {
 
92
                mainScreenshotImage.source = "";
 
93
                priv.newFocusedAppId = appId;
 
94
                priv.secondApplicationStarting = true;
 
95
                priv.requestNewScreenshot();
 
96
            }
 
97
 
 
98
            if (spreadView.visible) {
 
99
                spreadView.snapTo(0);
 
100
            }
 
101
        }
 
102
 
 
103
        onApplicationRemoved: {
 
104
            if (ApplicationManager.count == 0) {
 
105
                mainScreenshotImage.source = ""
 
106
                mainScreenshotImage.visible = false;
 
107
            } else {
 
108
                mainScreenshotImage.source = ApplicationManager.get(0).screenshot;
 
109
            }
 
110
        }
 
111
    }
 
112
 
 
113
    QtObject {
 
114
        id: priv
 
115
 
 
116
        property string focusedAppId: ApplicationManager.focusedApplicationId
 
117
        property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
 
118
        property url focusedScreenshot: focusedApplication ? focusedApplication.screenshot : ""
 
119
 
 
120
        property bool waitingForScreenshot: false
 
121
 
 
122
        property bool applicationStarting: false
 
123
        property bool secondApplicationStarting: false
 
124
 
 
125
        property string newFocusedAppId
 
126
 
 
127
        onFocusedScreenshotChanged: {
 
128
            if (root.moving && priv.waitingForScreenshot) {
 
129
                mainScreenshotImage.anchors.leftMargin = 0;
 
130
                mainScreenshotImage.source = priv.focusedScreenshot
 
131
                mainScreenshotImage.visible = true;
 
132
            } else if (priv.secondApplicationStarting && priv.waitingForScreenshot) {
 
133
                applicationSwitchingAnimation.start();
 
134
            }
 
135
            waitingForScreenshot = false;
 
136
        }
 
137
 
 
138
        function requestNewScreenshot() {
 
139
            waitingForScreenshot = true;
 
140
            ApplicationManager.updateScreenshot(focusedAppId);
 
141
        }
 
142
 
 
143
        function switchToApp(appId) {
 
144
            if (priv.focusedAppId) {
 
145
                priv.newFocusedAppId = appId;
 
146
                root.fullscreen = ApplicationManager.findApplication(appId).fullscreen;
 
147
                applicationSwitchingAnimation.start();
 
148
            } else {
 
149
                ApplicationManager.focusApplication(appId);
 
150
            }
 
151
        }
 
152
 
 
153
        function indexOf(appId) {
 
154
            for (var i = 0; i < ApplicationManager.count; i++) {
 
155
                if (ApplicationManager.get(i).appId == appId) {
 
156
                    return i;
 
157
                }
 
158
            }
 
159
            return -1;
 
160
        }
 
161
 
 
162
    }
 
163
 
 
164
    // FIXME: the signal connections seems to get lost.
 
165
    Connections {
 
166
        target: priv.focusedApplication
 
167
        onScreenshotChanged: priv.focusedScreenshot = priv.focusedApplication.screenshot
 
168
    }
 
169
    Binding {
 
170
        target: root
 
171
        property: "fullscreen"
 
172
        value: priv.focusedApplication ? priv.focusedApplication.fullscreen : false
 
173
    }
 
174
 
 
175
    Timer {
 
176
        id: appSplashTimer
 
177
        // FIXME: We really need to show something meaningful in the app surface instead of guessing
 
178
        // when it might be ready
 
179
        interval: 500
 
180
        repeat: false
 
181
        onTriggered: {
 
182
            priv.applicationStarting = false;
 
183
            priv.secondApplicationStarting = false;
 
184
        }
 
185
    }
 
186
 
 
187
    SequentialAnimation {
 
188
        id: applicationSwitchingAnimation
 
189
        // setup
 
190
        PropertyAction { target: mainScreenshotImage; property: "anchors.leftMargin"; value: 0 }
 
191
        PropertyAction { target: mainScreenshotImage; property: "source"; value: priv.focusedScreenshot }
 
192
        PropertyAction { targets: [mainScreenshotImage, fadeInScreenshotImage]; property: "visible"; value: true }
 
193
        PropertyAction { target: fadeInScreenshotImage; property: "source"; value: {
 
194
                var newFocusedApp = ApplicationManager.findApplication(priv.newFocusedAppId);
 
195
                return newFocusedApp ? newFocusedApp.screenshot : "" }
 
196
        }
 
197
        PropertyAction { target: fadeInScreenshotImage; property: "opacity"; value: 0 }
 
198
        PropertyAction { target: fadeInScreenshotImage; property: "scale"; value: .8 }
 
199
 
 
200
 
 
201
        // The actual animation
 
202
        ParallelAnimation {
 
203
            UbuntuNumberAnimation { target: mainScreenshotImage; property: "anchors.leftMargin"; to: root.width; duration: UbuntuAnimation.SlowDuration }
 
204
            UbuntuNumberAnimation { target: fadeInScreenshotImage; properties: "opacity,scale"; to: 1; duration: UbuntuAnimation.SlowDuration }
 
205
        }
 
206
 
 
207
        // restore stuff
 
208
        ScriptAction { script: ApplicationManager.focusApplication(priv.newFocusedAppId); }
 
209
        PropertyAction { target: fadeInScreenshotImage; property: "visible"; value: false }
 
210
        PropertyAction { target: mainScreenshotImage; property: "visible"; value: false }
 
211
    }
 
212
 
 
213
    // FIXME: Drop this and make the imageprovider show a splashscreen instead
 
214
    Rectangle {
 
215
        id: appSplash2
 
216
        anchors.fill: parent
 
217
        color: "white"
 
218
        visible: priv.secondApplicationStarting
 
219
    }
 
220
    Image {
 
221
        id: fadeInScreenshotImage
 
222
        anchors { left: parent.left; bottom: parent.bottom }
 
223
        width: parent.width
 
224
        scale: .7
 
225
        visible: false
 
226
    }
 
227
 
 
228
    Rectangle {
 
229
        id: appSplash
 
230
        anchors.fill: parent
 
231
        color: "white"
 
232
        visible: priv.applicationStarting
 
233
    }
 
234
    Image {
 
235
        id: mainScreenshotImage
 
236
        anchors { left: parent.left; bottom: parent.bottom }
 
237
        width: parent.width
 
238
        visible: false
 
239
    }
 
240
 
 
241
    EdgeDragArea {
 
242
        id: spreadDragArea
 
243
        direction: Direction.Leftwards
 
244
        enabled: ApplicationManager.count > 1 && spreadView.phase != 2
 
245
 
 
246
        anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
 
247
        width: root.dragAreaWidth
 
248
 
 
249
        // Sitting at the right edge of the screen, this EdgeDragArea directly controls the spreadView when
 
250
        // attachedToView is true. When the finger movement passes positionMarker3 we detach it from the
 
251
        // spreadView and make the spreadView snap to positionMarker4.
 
252
        property bool attachedToView: true
 
253
 
 
254
        property var gesturePoints: new Array()
 
255
 
 
256
        onTouchXChanged: {
 
257
            if (!dragging && !priv.waitingForScreenshot) {
 
258
                // Initial touch. Let's update the screenshot and reset the spreadView to the starting position.
 
259
                priv.requestNewScreenshot();
 
260
                spreadView.phase = 0;
 
261
                spreadView.contentX = -spreadView.shift;
 
262
            }
 
263
            if (dragging && attachedToView) {
 
264
                // Gesture recognized. Let's move the spreadView with the finger
 
265
                spreadView.contentX = -touchX - spreadView.shift;
 
266
            }
 
267
            if (attachedToView && spreadView.shiftedContentX >= spreadView.width * spreadView.positionMarker3) {
 
268
                // We passed positionMarker3. Detach from spreadView and snap it.
 
269
                attachedToView = false;
 
270
                spreadView.snap();
 
271
            }
 
272
            gesturePoints.push(touchX);
 
273
        }
 
274
 
 
275
        onStatusChanged: {
 
276
            if (status == DirectionalDragArea.Recognized) {
 
277
                attachedToView = true;
 
278
            }
 
279
        }
 
280
 
 
281
        onDraggingChanged: {
 
282
            if (dragging) {
 
283
                // Gesture recognized. Start recording this gesture
 
284
                gesturePoints = [];
 
285
                return;
 
286
            }
 
287
 
 
288
            // Ok. The user released. Find out if it was a one-way movement.
 
289
            var oneWayFlick = true;
 
290
            var smallestX = spreadDragArea.width;
 
291
            for (var i = 0; i < gesturePoints.length; i++) {
 
292
                if (gesturePoints[i] >= smallestX) {
 
293
                    oneWayFlick = false;
 
294
                    break;
 
295
                }
 
296
                smallestX = gesturePoints[i];
 
297
            }
 
298
            gesturePoints = [];
 
299
 
 
300
            if (oneWayFlick && spreadView.shiftedContentX > units.gu(2) &&
 
301
                    spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
 
302
                // If it was a short one-way movement, do the Alt+Tab switch
 
303
                // no matter if we didn't cross positionMarker1 yet.
 
304
                spreadView.snapTo(1);
 
305
            } else if (!dragging && attachedToView) {
 
306
                // otherwise snap to the closest snap position we can find
 
307
                // (might be back to start, to app 1 or to spread)
 
308
                spreadView.snap();
 
309
            }
 
310
        }
 
311
    }
 
312
 
 
313
    Rectangle {
 
314
        id: coverFlipBackground
 
315
        anchors.fill: parent
 
316
        color: "black"
 
317
        visible: spreadView.visible
 
318
    }
 
319
 
 
320
    InputFilterArea {
 
321
        anchors.fill: root
 
322
        blockInput: spreadView.visible
 
323
    }
 
324
 
 
325
    Flickable {
 
326
        id: spreadView
 
327
        objectName: "spreadView"
 
328
        anchors.fill: parent
 
329
        visible: spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1 || snapAnimation.running
 
330
        contentWidth: spreadRow.width - shift
 
331
        contentX: -shift
 
332
 
 
333
        // The flickable needs to fill the screen in order to get touch events all over.
 
334
        // However, we don't want to the user to be able to scroll back all the way. For
 
335
        // that, the beginning of the gesture starts with a negative value for contentX
 
336
        // so the flickable wants to pull it into the view already. "shift" tunes the
 
337
        // distance where to "lock" the content.
 
338
        property real shift: width / 2
 
339
        property real shiftedContentX: contentX + shift
 
340
 
 
341
        property int tileDistance: width / 4
 
342
 
 
343
        // Those markers mark the various positions in the spread (ratio to screen width from right to left):
 
344
        // 0 - 1: following finger, snap back to the beginning on release
 
345
        property real positionMarker1: 0.3
 
346
        // 1 - 2: curved snapping movement, snap to app 1 on release
 
347
        property real positionMarker2: 0.45
 
348
        // 2 - 3: movement follows finger, snaps back to app 1 on release
 
349
        property real positionMarker3: 0.6
 
350
        // passing 3, we detach movement from the finger and snap to 4
 
351
        property real positionMarker4: 0.9
 
352
 
 
353
        // This is where the first app snaps to when bringing it in from the right edge.
 
354
        property real snapPosition: 0.75
 
355
 
 
356
        // Phase of the animation:
 
357
        // 0: Starting from right edge, a new app (index 1) comes in from the right
 
358
        // 1: The app has reached the first snap position.
 
359
        // 2: The list is dragged further and snaps into the spread view when entering phase 2
 
360
        property int phase: 0
 
361
 
 
362
        property int selectedIndex: -1
 
363
 
 
364
        onShiftedContentXChanged: {
 
365
            switch (phase) {
 
366
            case 0:
 
367
                if (shiftedContentX > width * positionMarker2) {
 
368
                    phase = 1;
 
369
                }
 
370
                break;
 
371
            case 1:
 
372
                if (shiftedContentX < width * positionMarker2) {
 
373
                    phase = 0;
 
374
                } else if (shiftedContentX >= width * positionMarker4) {
 
375
                    phase = 2;
 
376
                }
 
377
                break;
 
378
            }
 
379
        }
 
380
 
 
381
        function snap() {
 
382
            if (shiftedContentX < positionMarker1 * width) {
 
383
                snapAnimation.targetContentX = -shift;
 
384
                snapAnimation.start();
 
385
            } else if (shiftedContentX < positionMarker2 * width) {
 
386
                snapTo(1)
 
387
            } else if (shiftedContentX < positionMarker3 * width) {
 
388
                snapTo(1)
 
389
            } else if (phase < 2){
 
390
                // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
 
391
                snapAnimation.targetContentX = width * positionMarker4 + 1 - shift;
 
392
                snapAnimation.start();
 
393
            }
 
394
        }
 
395
        function snapTo(index) {
 
396
            spreadView.selectedIndex = index;
 
397
            root.fullscreen = ApplicationManager.get(index).fullscreen;
 
398
            snapAnimation.targetContentX = -shift;
 
399
            snapAnimation.start();
 
400
        }
 
401
 
 
402
        SequentialAnimation {
 
403
            id: snapAnimation
 
404
            property int targetContentX: -spreadView.shift
 
405
 
 
406
            UbuntuNumberAnimation {
 
407
                target: spreadView
 
408
                property: "contentX"
 
409
                to: snapAnimation.targetContentX
 
410
                duration: UbuntuAnimation.FastDuration
 
411
            }
 
412
 
 
413
            ScriptAction {
 
414
                script: {
 
415
                    if (spreadView.selectedIndex >= 0) {
 
416
                        ApplicationManager.focusApplication(ApplicationManager.get(spreadView.selectedIndex).appId);
 
417
                        spreadView.selectedIndex = -1
 
418
                        spreadView.phase = 0;
 
419
                        spreadView.contentX = -spreadView.shift;
 
420
                    }
 
421
                }
 
422
            }
 
423
        }
 
424
 
 
425
        Item {
 
426
            id: spreadRow
 
427
            // This width controls how much the spread can be flicked left/right. It's composed of:
 
428
            //  tileDistance * app count (with a minimum of 3 apps, in order to also allow moving 1 and 2 apps a bit)
 
429
            //  + some constant value (still scales with the screen width) which looks good and somewhat fills the screen
 
430
            width: Math.max(3, ApplicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
 
431
 
 
432
            x: spreadView.contentX
 
433
 
 
434
            Repeater {
 
435
                id: spreadRepeater
 
436
                model: ApplicationManager
 
437
                delegate: TransformedSpreadDelegate {
 
438
                    id: appDelegate
 
439
                    objectName: "appDelegate" + index
 
440
                    startAngle: 45
 
441
                    endAngle: 5
 
442
                    startScale: 1.1
 
443
                    endScale: 0.7
 
444
                    startDistance: spreadView.tileDistance
 
445
                    endDistance: units.gu(.5)
 
446
                    width: spreadView.width
 
447
                    height: spreadView.height
 
448
                    selected: spreadView.selectedIndex == index
 
449
                    otherSelected: spreadView.selectedIndex >= 0 && !selected
 
450
 
 
451
                    z: index
 
452
                    x: index == 0 ? 0 : spreadView.width + (index - 1) * spreadView.tileDistance
 
453
 
 
454
                    // Each tile has a different progress value running from 0 to 1.
 
455
                    // A progress value of 0 means the tile is at the right edge. 1 means the tile has reched the left edge.
 
456
                    progress: {
 
457
                        var tileProgress = (spreadView.shiftedContentX - index * spreadView.tileDistance) / spreadView.width;
 
458
                        // Tile 1 needs to move directly from the beginning...
 
459
                        if (index == 1 && spreadView.phase < 2) {
 
460
                            tileProgress += spreadView.tileDistance / spreadView.width;
 
461
                        }
 
462
                        return tileProgress;
 
463
                    }
 
464
 
 
465
                    // This mostly is the same as progress, just adds the snapping to phase 1 for tiles 0 and 1
 
466
                    animatedProgress: {
 
467
                        if (spreadView.phase == 0 && index < 2) {
 
468
                            if (progress < spreadView.positionMarker1) {
 
469
                                return progress;
 
470
                            } else if (progress < spreadView.positionMarker1 + snappingCurve.period){
 
471
                                return spreadView.positionMarker1 + snappingCurve.value * 3;
 
472
                            } else {
 
473
                                return spreadView.positionMarker2;
 
474
                            }
 
475
                        }
 
476
                        return progress;
 
477
                    }
 
478
 
 
479
                    EasingCurve {
 
480
                        id: snappingCurve
 
481
                        type: EasingCurve.OutQuad
 
482
                        period: 0.05
 
483
                        progress: appDelegate.progress - spreadView.positionMarker1
 
484
                    }
 
485
 
 
486
                    onClicked: {
 
487
                        if (spreadView.phase == 2) {
 
488
                            if (ApplicationManager.focusedApplicationId == ApplicationManager.get(index).appId) {
 
489
                                spreadView.snapTo(index);
 
490
                            } else {
 
491
                                ApplicationManager.requestFocusApplication(ApplicationManager.get(index).appId);
 
492
                            }
 
493
                        }
 
494
                    }
 
495
                }
 
496
            }
 
497
        }
 
498
    }
 
499
}