~unity-team/unity8/dash-only

« back to all changes in this revision

Viewing changes to qml/Stages/SpreadDelegate.qml

  • Committer: Kevin Gunn
  • Date: 2016-10-24 19:51:33 UTC
  • Revision ID: kevin.gunn@canonical.com-20161024195133-61lwdzzdwsnue1mn
shave some more

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
 * Copyright 2014-2016 Canonical Ltd.
3
 
 *
4
 
 * This program is free software; you can redistribute it and/or modify
5
 
 * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12
 
 *
13
 
 * You should have received a copy of the GNU Lesser General Public License
14
 
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15
 
 *
16
 
 * Authors: Michael Zanetti <michael.zanetti@canonical.com>
17
 
 *          Daniel d'Andrada <daniel.dandrada@canonical.com>
18
 
 */
19
 
 
20
 
import QtQuick 2.4
21
 
import QtQuick.Window 2.2
22
 
import Ubuntu.Components 1.3
23
 
import "../Components"
24
 
 
25
 
FocusScope {
26
 
    id: root
27
 
 
28
 
    // to be read from outside
29
 
    readonly property bool dragged: dragArea.moving
30
 
    signal clicked()
31
 
    signal closed()
32
 
    readonly property alias appWindowOrientationAngle: appWindowWithShadow.orientationAngle
33
 
    readonly property alias appWindowRotation: appWindowWithShadow.rotation
34
 
    readonly property alias orientationChangesEnabled: appWindow.orientationChangesEnabled
35
 
    property int supportedOrientations: application ? application.supportedOrientations :
36
 
                                                      Qt.PortraitOrientation
37
 
                                                      | Qt.LandscapeOrientation
38
 
                                                      | Qt.InvertedPortraitOrientation
39
 
                                                      | Qt.InvertedLandscapeOrientation
40
 
    readonly property alias focusedSurface: appWindow.focusedSurface
41
 
 
42
 
    // to be set from outside
43
 
    property bool interactive: true
44
 
    property bool dropShadow: true
45
 
    property real maximizedAppTopMargin
46
 
    property alias swipeToCloseEnabled: dragArea.enabled
47
 
    property bool closeable
48
 
    property alias application: appWindow.application
49
 
    property alias surface: appWindow.surface
50
 
    property int shellOrientationAngle
51
 
    property int shellOrientation
52
 
    property QtObject orientations
53
 
    property bool highlightShown: false
54
 
 
55
 
    // overrideable from outside
56
 
    property alias fullscreen: appWindow.fullscreen
57
 
 
58
 
    function matchShellOrientation() {
59
 
        if (!root.application)
60
 
            return;
61
 
        appWindowWithShadow.orientationAngle = root.shellOrientationAngle;
62
 
    }
63
 
 
64
 
    function animateToShellOrientation() {
65
 
        if (!root.application)
66
 
            return;
67
 
 
68
 
        if (root.application.rotatesWindowContents) {
69
 
            appWindowWithShadow.orientationAngle = root.shellOrientationAngle;
70
 
        } else {
71
 
            orientationChangeAnimation.start();
72
 
        }
73
 
    }
74
 
 
75
 
    OrientationChangeAnimation {
76
 
        id: orientationChangeAnimation
77
 
        objectName: "orientationChangeAnimation"
78
 
        spreadDelegate: root
79
 
        background: background
80
 
        window: appWindowWithShadow
81
 
        screenshot: appWindowScreenshotWithShadow
82
 
    }
83
 
 
84
 
    QtObject {
85
 
        id: priv
86
 
        objectName: "spreadDelegatePriv"
87
 
        property bool startingUp: true
88
 
    }
89
 
 
90
 
    Component.onCompleted: { finishStartUpTimer.start(); }
91
 
    Timer { id: finishStartUpTimer; interval: 400; onTriggered: priv.startingUp = false }
92
 
 
93
 
    // JS version of QPlatformScreen::angleBetween C++ implementation.
94
 
    // So don't ask me how it works because I don't know.
95
 
    // Calling Screen.angleBetween from within a Binding component doesn't work for some reason.
96
 
    function angleBetween(a, b) {
97
 
        if (a == b)
98
 
            return 0;
99
 
 
100
 
        var ia = Math.log(a) / Math.LN2;
101
 
        var ib = Math.log(b) / Math.LN2;
102
 
 
103
 
        var delta = ia - ib;
104
 
 
105
 
        if (delta < 0)
106
 
            delta = delta + 4;
107
 
 
108
 
        var angles = [ 0, 90, 180, 270 ];
109
 
        return angles[delta];
110
 
    }
111
 
 
112
 
    // Sets the initial orientationAngle of the window, when it first slides into view
113
 
    // (with the splash screen likely being displayed). At that point we just try to
114
 
    // match shell's current orientation. We need a bit of time in this state as the
115
 
    // information we need to decide orientationAngle may take a few cycles to
116
 
    // be set.
117
 
    Binding {
118
 
        target: appWindowWithShadow
119
 
        property: "orientationAngle"
120
 
        when: priv.startingUp
121
 
        value: {
122
 
            var supportedOrientations = root.supportedOrientations;
123
 
 
124
 
            if (supportedOrientations === Qt.PrimaryOrientation) {
125
 
                supportedOrientations = root.orientations.primary;
126
 
            }
127
 
 
128
 
            // If it doesn't support shell's current orientation
129
 
            // then simply pick some arbitraty one that it does support
130
 
            var chosenOrientation = 0;
131
 
            if (supportedOrientations & root.shellOrientation) {
132
 
                chosenOrientation = root.shellOrientation;
133
 
            } else if (supportedOrientations & Qt.PortraitOrientation) {
134
 
                chosenOrientation = root.orientations.portrait;
135
 
            } else if (supportedOrientations & Qt.LandscapeOrientation) {
136
 
                chosenOrientation = root.orientations.landscape;
137
 
            } else if (supportedOrientations & Qt.InvertedPortraitOrientation) {
138
 
                chosenOrientation = root.orientations.invertedPortrait;
139
 
            } else if (supportedOrientations & Qt.InvertedLandscapeOrientation) {
140
 
                chosenOrientation = root.orientations.invertedLandscape;
141
 
            } else {
142
 
                chosenOrientation = root.orientations.primary;
143
 
            }
144
 
 
145
 
            return angleBetween(root.orientations.native_, chosenOrientation);
146
 
        }
147
 
    }
148
 
 
149
 
    Rectangle {
150
 
        id: background
151
 
        color: "black"
152
 
        anchors.fill: parent
153
 
        visible: false
154
 
    }
155
 
 
156
 
    Item {
157
 
        objectName: "displacedAppWindowWithShadow"
158
 
 
159
 
        readonly property real limit: root.height / 4
160
 
 
161
 
        y: root.closeable ? dragArea.distance : elastic(dragArea.distance)
162
 
        width: parent.width
163
 
        height: parent.height
164
 
 
165
 
        function elastic(distance) {
166
 
            var k = distance < 0 ? -limit : limit
167
 
            return k * (1 - Math.pow((k - 1) / k, distance))
168
 
        }
169
 
 
170
 
        Item {
171
 
            id: appWindowWithShadow
172
 
            objectName: "appWindowWithShadow"
173
 
 
174
 
            property int orientationAngle
175
 
 
176
 
            property real transformRotationAngle: 0
177
 
            property real transformOriginX
178
 
            property real transformOriginY
179
 
 
180
 
            property var window: appWindow
181
 
 
182
 
            transform: Rotation {
183
 
                origin.x: appWindowWithShadow.transformOriginX
184
 
                origin.y: appWindowWithShadow.transformOriginY
185
 
                axis { x: 0; y: 0; z: 1 }
186
 
                angle: appWindowWithShadow.transformRotationAngle
187
 
            }
188
 
 
189
 
            state: {
190
 
                if (root.application && root.application.rotatesWindowContents) {
191
 
                    return "counterRotate";
192
 
                } else if (orientationChangeAnimation.running) {
193
 
                    return "animatingRotation";
194
 
                } else  {
195
 
                    return "keepSceneRotation";
196
 
                }
197
 
            }
198
 
 
199
 
            // Ensures the given angle is in the form (0,90,180,270)
200
 
            function normalizeAngle(angle) {
201
 
                while (angle < 0) {
202
 
                    angle += 360;
203
 
                }
204
 
                return angle % 360;
205
 
            }
206
 
 
207
 
            states: [
208
 
                // A base state containing bindings common to others
209
 
                // Ensures appWindowWithShadow fills the root area.
210
 
                State {
211
 
                    name: "fillRootArea"
212
 
                    PropertyChanges {
213
 
                        target: appWindowWithShadow
214
 
                        restoreEntryValues: false
215
 
                        width: appWindowWithShadow.rotation == 0 || appWindowWithShadow.rotation == 180 ? root.width : root.height
216
 
                        height: appWindowWithShadow.rotation == 0 || appWindowWithShadow.rotation == 180 ? root.height : root.width
217
 
                    }
218
 
                },
219
 
                // In this state we stick to our currently set orientationAngle, which may change only due
220
 
                // to calls made to matchShellOrientation() or animateToShellOrientation()
221
 
                State {
222
 
                    name: "keepSceneRotation"
223
 
                    extend: "fillRootArea"
224
 
                    PropertyChanges {
225
 
                        target: appWindowWithShadow
226
 
                        restoreEntryValues: false
227
 
                        rotation: normalizeAngle(appWindowWithShadow.orientationAngle - root.shellOrientationAngle)
228
 
                    }
229
 
                },
230
 
                // In this state we counteract any shell rotation so that the window, in scene coordinates,
231
 
                // remains unrotated.
232
 
                // But the splash screen should still obey the set orientationAngle set by shell
233
 
                State {
234
 
                    name: "counterRotate"
235
 
                    extend: "fillRootArea"
236
 
                    PropertyChanges {
237
 
                        target: appWindowWithShadow
238
 
                        rotation: normalizeAngle(-root.shellOrientationAngle)
239
 
                    }
240
 
                    PropertyChanges {
241
 
                        target: appWindow
242
 
                        surfaceOrientationAngle: appWindowWithShadow.orientationAngle
243
 
                        splashRotation: appWindowWithShadow.orientationAngle
244
 
                    }
245
 
                },
246
 
                // Dummy state.
247
 
                // The animation may indiscriminately break any bindings assigned to appWindowWithShadow rotation,
248
 
                // width or height.
249
 
                State {
250
 
                    name: "animatingRotation"
251
 
                }
252
 
            ]
253
 
 
254
 
            anchors.centerIn: parent
255
 
 
256
 
            BorderImage {
257
 
                anchors {
258
 
                    fill: appWindow
259
 
                    margins: -units.gu(2)
260
 
                }
261
 
                source: "graphics/dropshadow2gu.sci"
262
 
                opacity: root.dropShadow ? .3 : 0
263
 
                Behavior on opacity { UbuntuNumberAnimation {} }
264
 
            }
265
 
 
266
 
            Rectangle {
267
 
                id: selectionHighlight
268
 
                objectName: "selectionHighlight"
269
 
                anchors.fill: appWindow
270
 
                anchors.margins: -units.gu(1)
271
 
                color: "white"
272
 
                opacity: root.highlightShown ? 0.15 : 0
273
 
                antialiasing: true
274
 
                visible: opacity > 0
275
 
            }
276
 
 
277
 
            Rectangle {
278
 
                anchors { left: selectionHighlight.left; right: selectionHighlight.right; bottom: selectionHighlight.bottom; }
279
 
                height: units.dp(2)
280
 
                color: theme.palette.normal.focus
281
 
                visible: root.highlightShown
282
 
                antialiasing: true
283
 
            }
284
 
 
285
 
            ApplicationWindow {
286
 
                id: appWindow
287
 
                objectName: "appWindow"
288
 
                focus: true
289
 
                anchors {
290
 
                    fill: parent
291
 
                    topMargin: appWindow.fullscreen || (application && application.rotatesWindowContents)
292
 
                                   ? 0 : maximizedAppTopMargin
293
 
                }
294
 
 
295
 
                interactive: root.interactive
296
 
            }
297
 
        }
298
 
    }
299
 
 
300
 
    Item {
301
 
        // mimics appWindowWithShadow. Do the positioning of screenshots of non-fullscreen
302
 
        // app windows
303
 
        id: appWindowScreenshotWithShadow
304
 
        visible: false
305
 
 
306
 
        property real transformRotationAngle: 0
307
 
        property real transformOriginX
308
 
        property real transformOriginY
309
 
 
310
 
        transform: Rotation {
311
 
            origin.x: appWindowScreenshotWithShadow.transformOriginX
312
 
            origin.y: appWindowScreenshotWithShadow.transformOriginY
313
 
            axis { x: 0; y: 0; z: 1 }
314
 
            angle: appWindowScreenshotWithShadow.transformRotationAngle
315
 
        }
316
 
 
317
 
        readonly property Item window: appWindowScreenshot
318
 
        readonly property bool ready: appWindowScreenshot.status === Image.Ready
319
 
 
320
 
        function take() {
321
 
            appWindow.grabToImage(
322
 
                function(result) {
323
 
                    appWindowScreenshot.source = result.url;
324
 
                });
325
 
        }
326
 
        function discard() {
327
 
            appWindowScreenshot.source = "";
328
 
        }
329
 
 
330
 
        Image {
331
 
            id: appWindowScreenshot
332
 
            anchors.top: parent.top
333
 
        }
334
 
    }
335
 
 
336
 
    DraggingArea {
337
 
        id: dragArea
338
 
        objectName: "dragArea"
339
 
        anchors.fill: parent
340
 
 
341
 
        property bool moving: false
342
 
        property real distance: 0
343
 
        readonly property int threshold: units.gu(2)
344
 
        property int offset: 0
345
 
 
346
 
        readonly property real minSpeedToClose: units.gu(40)
347
 
 
348
 
        onDragValueChanged: {
349
 
            if (!dragging) {
350
 
                return;
351
 
            }
352
 
            moving = moving || Math.abs(dragValue) > threshold;
353
 
            if (moving) {
354
 
                distance = dragValue + offset;
355
 
            }
356
 
        }
357
 
 
358
 
        onMovingChanged: {
359
 
            if (moving) {
360
 
                offset = (dragValue > 0 ? -threshold: threshold)
361
 
            } else {
362
 
                offset = 0;
363
 
            }
364
 
        }
365
 
 
366
 
        onClicked: {
367
 
            if (!moving) {
368
 
                root.clicked();
369
 
            }
370
 
        }
371
 
 
372
 
        onDragEnd: {
373
 
            if (!root.closeable) {
374
 
                animation.animate("center")
375
 
                return;
376
 
            }
377
 
 
378
 
            // velocity and distance values specified by design prototype
379
 
            if ((dragVelocity < -minSpeedToClose && distance < -units.gu(8)) || distance < -root.height / 2) {
380
 
                animation.animate("up")
381
 
            } else if ((dragVelocity > minSpeedToClose  && distance > units.gu(8)) || distance > root.height / 2) {
382
 
                animation.animate("down")
383
 
            } else {
384
 
                animation.animate("center")
385
 
            }
386
 
        }
387
 
 
388
 
        UbuntuNumberAnimation {
389
 
            id: animation
390
 
            objectName: "closeAnimation"
391
 
            target: dragArea
392
 
            property: "distance"
393
 
            property bool requestClose: false
394
 
 
395
 
            function animate(direction) {
396
 
                animation.from = dragArea.distance;
397
 
                switch (direction) {
398
 
                case "up":
399
 
                    animation.to = -root.height * 1.5;
400
 
                    requestClose = true;
401
 
                    break;
402
 
                case "down":
403
 
                    animation.to = root.height * 1.5;
404
 
                    requestClose = true;
405
 
                    break;
406
 
                default:
407
 
                    animation.to = 0
408
 
                }
409
 
                animation.start();
410
 
            }
411
 
 
412
 
            onRunningChanged: {
413
 
                if (!running) {
414
 
                    dragArea.moving = false;
415
 
                    if (requestClose) {
416
 
                        root.closed();
417
 
                    } else {
418
 
                        dragArea.distance = 0;
419
 
                    }
420
 
                }
421
 
            }
422
 
        }
423
 
    }
424
 
}