2 * Copyright 2014-2015 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 appWindow: appWindow
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 int shellOrientationAngle
50 property int shellOrientation
51 property QtObject orientations
52 property bool highlightShown: false
54 // overrideable from outside
55 property alias fullscreen: appWindow.fullscreen
57 function matchShellOrientation() {
58 if (!root.application)
60 appWindowWithShadow.orientationAngle = root.shellOrientationAngle;
63 function animateToShellOrientation() {
64 if (!root.application)
67 if (root.application.rotatesWindowContents) {
68 appWindowWithShadow.orientationAngle = root.shellOrientationAngle;
70 orientationChangeAnimation.start();
74 OrientationChangeAnimation {
75 id: orientationChangeAnimation
76 objectName: "orientationChangeAnimation"
78 background: background
79 window: appWindowWithShadow
80 screenshot: appWindowScreenshotWithShadow
85 property bool startingUp: true
88 Component.onCompleted: { finishStartUpTimer.start(); }
89 Timer { id: finishStartUpTimer; interval: 400; onTriggered: priv.startingUp = false }
99 objectName: "displacedAppWindowWithShadow"
101 readonly property real limit: root.height / 4
103 y: root.closeable ? dragArea.distance : elastic(dragArea.distance)
105 height: parent.height
107 function elastic(distance) {
108 var k = distance < 0 ? -limit : limit
109 return k * (1 - Math.pow((k - 1) / k, distance))
113 id: appWindowWithShadow
114 objectName: "appWindowWithShadow"
116 property int orientationAngle
118 property real transformRotationAngle: 0
119 property real transformOriginX
120 property real transformOriginY
122 property var window: appWindow
124 transform: Rotation {
125 origin.x: appWindowWithShadow.transformOriginX
126 origin.y: appWindowWithShadow.transformOriginY
127 axis { x: 0; y: 0; z: 1 }
128 angle: appWindowWithShadow.transformRotationAngle
132 if (priv.startingUp) {
134 } else if (root.application && root.application.rotatesWindowContents) {
135 return "counterRotate";
136 } else if (orientationChangeAnimation.running) {
137 return "animatingRotation";
139 return "keepSceneRotation";
143 // Ensures the given angle is in the form (0,90,180,270)
144 function normalizeAngle(angle) {
152 // Sets the initial orientationAngle of the window, when it first slides into view
153 // (with the splash screen likely being displayed). At that point we just try to
154 // match shell's current orientation. We need a bit of time in this state as the
155 // information we need to decide orientationAngle may take a few cycles to
160 target: appWindowWithShadow
161 restoreEntryValues: false
163 if (!root.application || root.application.rotatesWindowContents) {
167 var supportedOrientations = root.supportedOrientations;
169 if (supportedOrientations === Qt.PrimaryOrientation) {
170 supportedOrientations = root.orientations.primary;
173 // If it doesn't support shell's current orientation
174 // then simply pick some arbitraty one that it does support
175 var chosenOrientation = 0;
176 if (supportedOrientations & root.shellOrientation) {
177 chosenOrientation = root.shellOrientation;
178 } else if (supportedOrientations & Qt.PortraitOrientation) {
179 chosenOrientation = root.orientations.portrait;
180 } else if (supportedOrientations & Qt.LandscapeOrientation) {
181 chosenOrientation = root.orientations.landscape;
182 } else if (supportedOrientations & Qt.InvertedPortraitOrientation) {
183 chosenOrientation = root.orientations.invertedPortrait;
184 } else if (supportedOrientations & Qt.InvertedLandscapeOrientation) {
185 chosenOrientation = root.orientations.invertedLandscape;
187 chosenOrientation = root.orientations.primary;
190 return Screen.angleBetween(root.orientations.native_, chosenOrientation);
193 rotation: normalizeAngle(appWindowWithShadow.orientationAngle - root.shellOrientationAngle)
195 if (rotation == 0 || rotation == 180) {
202 if (rotation == 0 || rotation == 180)
209 // In this state we stick to our currently set orientationAngle, which may change only due
210 // to calls made to matchShellOrientation() or animateToShellOrientation()
212 id: keepSceneRotationState
213 name: "keepSceneRotation"
215 StateChangeScript { script: {
217 appWindowWithShadow.orientationAngle = appWindowWithShadow.orientationAngle;
220 target: appWindowWithShadow
221 restoreEntryValues: false
222 rotation: normalizeAngle(appWindowWithShadow.orientationAngle - root.shellOrientationAngle)
224 if (rotation == 0 || rotation == 180) {
231 if (rotation == 0 || rotation == 180)
238 // In this state we counteract any shell rotation so that the window, in scene coordinates,
239 // remains unrotated.
241 name: "counterRotate"
242 StateChangeScript { script: {
244 appWindowWithShadow.orientationAngle = appWindowWithShadow.orientationAngle;
247 target: appWindowWithShadow
248 width: root.shellOrientationAngle == 0 || root.shellOrientationAngle == 180 ? root.width : root.height
249 height: root.shellOrientationAngle == 0 || root.shellOrientationAngle == 180 ? root.height : root.width
250 rotation: normalizeAngle(-root.shellOrientationAngle)
254 surfaceOrientationAngle: orientationAngle
258 name: "animatingRotation"
262 x: (parent.width - width) / 2
263 y: (parent.height - height) / 2
268 margins: -units.gu(2)
270 source: "graphics/dropshadow2gu.sci"
271 opacity: root.dropShadow ? .3 : 0
272 Behavior on opacity { UbuntuNumberAnimation {} }
276 id: selectionHighlight
277 objectName: "selectionHighlight"
278 anchors.fill: appWindow
279 anchors.margins: -units.gu(1)
281 opacity: root.highlightShown ? 0.15 : 0
287 anchors { left: selectionHighlight.left; right: selectionHighlight.right; bottom: selectionHighlight.bottom; }
289 color: UbuntuColors.orange
290 visible: root.highlightShown
296 objectName: application ? "appWindow_" + application.appId : "appWindow_null"
300 topMargin: appWindow.fullscreen || (application && application.rotatesWindowContents)
301 ? 0 : maximizedAppTopMargin
304 interactive: root.interactive
310 // mimics appWindowWithShadow. Do the positioning of screenshots of non-fullscreen
312 id: appWindowScreenshotWithShadow
315 property real transformRotationAngle: 0
316 property real transformOriginX
317 property real transformOriginY
319 transform: Rotation {
320 origin.x: appWindowScreenshotWithShadow.transformOriginX
321 origin.y: appWindowScreenshotWithShadow.transformOriginY
322 axis { x: 0; y: 0; z: 1 }
323 angle: appWindowScreenshotWithShadow.transformRotationAngle
326 property var window: appWindowScreenshot
329 // Format: "image://application/$APP_ID/$CURRENT_TIME_MS"
330 // eg: "image://application/calculator-app/123456"
331 var timeMs = new Date().getTime();
332 appWindowScreenshot.source = "image://application/" + root.application.appId + "/" + timeMs;
335 appWindowScreenshot.source = "";
339 id: appWindowScreenshot
344 sourceSize.width: width
345 sourceSize.height: height
351 objectName: "dragArea"
354 property bool moving: false
355 property real distance: 0
356 readonly property int threshold: units.gu(2)
357 property int offset: 0
359 readonly property real minSpeedToClose: units.gu(40)
361 onDragValueChanged: {
365 moving = moving || Math.abs(dragValue) > threshold;
367 distance = dragValue + offset;
373 offset = (dragValue > 0 ? -threshold: threshold)
386 if (!root.closeable) {
387 animation.animate("center")
391 // velocity and distance values specified by design prototype
392 if ((dragVelocity < -minSpeedToClose && distance < -units.gu(8)) || distance < -root.height / 2) {
393 animation.animate("up")
394 } else if ((dragVelocity > minSpeedToClose && distance > units.gu(8)) || distance > root.height / 2) {
395 animation.animate("down")
397 animation.animate("center")
401 UbuntuNumberAnimation {
403 objectName: "closeAnimation"
406 property bool requestClose: false
408 function animate(direction) {
409 animation.from = dragArea.distance;
412 animation.to = -root.height * 1.5;
416 animation.to = root.height * 1.5;
427 dragArea.moving = false;
431 dragArea.distance = 0;