2
* Copyright 2014-2016 Canonical Ltd.
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.
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.
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/>.
16
* Authors: Michael Zanetti <michael.zanetti@canonical.com>
17
* Daniel d'Andrada <daniel.dandrada@canonical.com>
21
import QtQuick.Window 2.2
22
import Ubuntu.Components 1.3
23
import "../Components"
28
// to be read from outside
29
readonly property bool dragged: dragArea.moving
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
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
55
// overrideable from outside
56
property alias fullscreen: appWindow.fullscreen
58
function matchShellOrientation() {
59
if (!root.application)
61
appWindowWithShadow.orientationAngle = root.shellOrientationAngle;
64
function animateToShellOrientation() {
65
if (!root.application)
68
if (root.application.rotatesWindowContents) {
69
appWindowWithShadow.orientationAngle = root.shellOrientationAngle;
71
orientationChangeAnimation.start();
75
OrientationChangeAnimation {
76
id: orientationChangeAnimation
77
objectName: "orientationChangeAnimation"
79
background: background
80
window: appWindowWithShadow
81
screenshot: appWindowScreenshotWithShadow
86
objectName: "spreadDelegatePriv"
87
property bool startingUp: true
90
Component.onCompleted: { finishStartUpTimer.start(); }
91
Timer { id: finishStartUpTimer; interval: 400; onTriggered: priv.startingUp = false }
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) {
100
var ia = Math.log(a) / Math.LN2;
101
var ib = Math.log(b) / Math.LN2;
108
var angles = [ 0, 90, 180, 270 ];
109
return angles[delta];
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
118
target: appWindowWithShadow
119
property: "orientationAngle"
120
when: priv.startingUp
122
var supportedOrientations = root.supportedOrientations;
124
if (supportedOrientations === Qt.PrimaryOrientation) {
125
supportedOrientations = root.orientations.primary;
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;
142
chosenOrientation = root.orientations.primary;
145
return angleBetween(root.orientations.native_, chosenOrientation);
157
objectName: "displacedAppWindowWithShadow"
159
readonly property real limit: root.height / 4
161
y: root.closeable ? dragArea.distance : elastic(dragArea.distance)
163
height: parent.height
165
function elastic(distance) {
166
var k = distance < 0 ? -limit : limit
167
return k * (1 - Math.pow((k - 1) / k, distance))
171
id: appWindowWithShadow
172
objectName: "appWindowWithShadow"
174
property int orientationAngle
176
property real transformRotationAngle: 0
177
property real transformOriginX
178
property real transformOriginY
180
property var window: appWindow
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
190
if (root.application && root.application.rotatesWindowContents) {
191
return "counterRotate";
192
} else if (orientationChangeAnimation.running) {
193
return "animatingRotation";
195
return "keepSceneRotation";
199
// Ensures the given angle is in the form (0,90,180,270)
200
function normalizeAngle(angle) {
208
// A base state containing bindings common to others
209
// Ensures appWindowWithShadow fills the root area.
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
219
// In this state we stick to our currently set orientationAngle, which may change only due
220
// to calls made to matchShellOrientation() or animateToShellOrientation()
222
name: "keepSceneRotation"
223
extend: "fillRootArea"
225
target: appWindowWithShadow
226
restoreEntryValues: false
227
rotation: normalizeAngle(appWindowWithShadow.orientationAngle - root.shellOrientationAngle)
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
234
name: "counterRotate"
235
extend: "fillRootArea"
237
target: appWindowWithShadow
238
rotation: normalizeAngle(-root.shellOrientationAngle)
242
surfaceOrientationAngle: appWindowWithShadow.orientationAngle
243
splashRotation: appWindowWithShadow.orientationAngle
247
// The animation may indiscriminately break any bindings assigned to appWindowWithShadow rotation,
250
name: "animatingRotation"
254
anchors.centerIn: parent
259
margins: -units.gu(2)
261
source: "graphics/dropshadow2gu.sci"
262
opacity: root.dropShadow ? .3 : 0
263
Behavior on opacity { UbuntuNumberAnimation {} }
267
id: selectionHighlight
268
objectName: "selectionHighlight"
269
anchors.fill: appWindow
270
anchors.margins: -units.gu(1)
272
opacity: root.highlightShown ? 0.15 : 0
278
anchors { left: selectionHighlight.left; right: selectionHighlight.right; bottom: selectionHighlight.bottom; }
280
color: theme.palette.normal.focus
281
visible: root.highlightShown
287
objectName: "appWindow"
291
topMargin: appWindow.fullscreen || (application && application.rotatesWindowContents)
292
? 0 : maximizedAppTopMargin
295
interactive: root.interactive
301
// mimics appWindowWithShadow. Do the positioning of screenshots of non-fullscreen
303
id: appWindowScreenshotWithShadow
306
property real transformRotationAngle: 0
307
property real transformOriginX
308
property real transformOriginY
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
317
readonly property Item window: appWindowScreenshot
318
readonly property bool ready: appWindowScreenshot.status === Image.Ready
321
appWindow.grabToImage(
323
appWindowScreenshot.source = result.url;
327
appWindowScreenshot.source = "";
331
id: appWindowScreenshot
332
anchors.top: parent.top
338
objectName: "dragArea"
341
property bool moving: false
342
property real distance: 0
343
readonly property int threshold: units.gu(2)
344
property int offset: 0
346
readonly property real minSpeedToClose: units.gu(40)
348
onDragValueChanged: {
352
moving = moving || Math.abs(dragValue) > threshold;
354
distance = dragValue + offset;
360
offset = (dragValue > 0 ? -threshold: threshold)
373
if (!root.closeable) {
374
animation.animate("center")
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")
384
animation.animate("center")
388
UbuntuNumberAnimation {
390
objectName: "closeAnimation"
393
property bool requestClose: false
395
function animate(direction) {
396
animation.from = dragArea.distance;
399
animation.to = -root.height * 1.5;
403
animation.to = root.height * 1.5;
414
dragArea.moving = false;
418
dragArea.distance = 0;