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
36 // to be set from outside
37 property bool interactive: true
38 property bool dropShadow: true
39 property real maximizedAppTopMargin
40 property alias swipeToCloseEnabled: dragArea.enabled
41 property bool closeable
42 property alias application: appWindow.application
43 property int shellOrientationAngle
44 property int shellOrientation
45 property QtObject orientations
46 property bool highlightShown: false
48 function matchShellOrientation() {
49 if (!root.application)
51 appWindowWithShadow.orientationAngle = root.shellOrientationAngle;
54 function animateToShellOrientation() {
55 if (!root.application)
58 if (root.application.rotatesWindowContents) {
59 appWindowWithShadow.orientationAngle = root.shellOrientationAngle;
61 orientationChangeAnimation.start();
65 OrientationChangeAnimation {
66 id: orientationChangeAnimation
67 objectName: "orientationChangeAnimation"
69 background: background
70 window: appWindowWithShadow
71 screenshot: appWindowScreenshotWithShadow
76 property bool startingUp: true
79 Component.onCompleted: { finishStartUpTimer.start(); }
80 Timer { id: finishStartUpTimer; interval: 400; onTriggered: priv.startingUp = false }
90 objectName: "displacedAppWindowWithShadow"
92 readonly property real limit: root.height / 4
94 y: root.closeable ? dragArea.distance : elastic(dragArea.distance)
98 function elastic(distance) {
99 var k = distance < 0 ? -limit : limit
100 return k * (1 - Math.pow((k - 1) / k, distance))
104 id: appWindowWithShadow
105 objectName: "appWindowWithShadow"
107 property int orientationAngle
109 property real transformRotationAngle: 0
110 property real transformOriginX
111 property real transformOriginY
113 property var window: appWindow
115 transform: Rotation {
116 origin.x: appWindowWithShadow.transformOriginX
117 origin.y: appWindowWithShadow.transformOriginY
118 axis { x: 0; y: 0; z: 1 }
119 angle: appWindowWithShadow.transformRotationAngle
123 if (priv.startingUp) {
125 } else if (root.application && root.application.rotatesWindowContents) {
126 return "counterRotate";
127 } else if (orientationChangeAnimation.running) {
128 return "animatingRotation";
130 return "keepSceneRotation";
134 // Ensures the given angle is in the form (0,90,180,270)
135 function normalizeAngle(angle) {
143 // Sets the initial orientationAngle of the window, when it first slides into view
144 // (with the splash screen likely being displayed). At that point we just try to
145 // match shell's current orientation. We need a bit of time in this state as the
146 // information we need to decide orientationAngle may take a few cycles to
151 target: appWindowWithShadow
152 restoreEntryValues: false
154 if (!root.application || root.application.rotatesWindowContents) {
157 var supportedOrientations = root.application.supportedOrientations;
159 if (supportedOrientations === Qt.PrimaryOrientation) {
160 supportedOrientations = root.orientations.primary;
163 // If it doesn't support shell's current orientation
164 // then simply pick some arbitraty one that it does support
165 var chosenOrientation = 0;
166 if (supportedOrientations & root.shellOrientation) {
167 chosenOrientation = root.shellOrientation;
168 } else if (supportedOrientations & Qt.PortraitOrientation) {
169 chosenOrientation = root.orientations.portrait;
170 } else if (supportedOrientations & Qt.LandscapeOrientation) {
171 chosenOrientation = root.orientations.landscape;
172 } else if (supportedOrientations & Qt.InvertedPortraitOrientation) {
173 chosenOrientation = root.orientations.invertedPortrait;
174 } else if (supportedOrientations & Qt.InvertedLandscapeOrientation) {
175 chosenOrientation = root.orientations.invertedLandscape;
177 chosenOrientation = root.orientations.primary;
180 return Screen.angleBetween(root.orientations.native_, chosenOrientation);
183 rotation: normalizeAngle(appWindowWithShadow.orientationAngle - root.shellOrientationAngle)
185 if (rotation == 0 || rotation == 180) {
192 if (rotation == 0 || rotation == 180)
199 // In this state we stick to our currently set orientationAngle, which may change only due
200 // to calls made to matchShellOrientation() or animateToShellOrientation()
202 id: keepSceneRotationState
203 name: "keepSceneRotation"
205 StateChangeScript { script: {
207 appWindowWithShadow.orientationAngle = appWindowWithShadow.orientationAngle;
210 target: appWindowWithShadow
211 restoreEntryValues: false
212 rotation: normalizeAngle(appWindowWithShadow.orientationAngle - root.shellOrientationAngle)
214 if (rotation == 0 || rotation == 180) {
221 if (rotation == 0 || rotation == 180)
228 // In this state we counteract any shell rotation so that the window, in scene coordinates,
229 // remains unrotated.
231 name: "counterRotate"
232 StateChangeScript { script: {
234 appWindowWithShadow.orientationAngle = appWindowWithShadow.orientationAngle;
237 target: appWindowWithShadow
238 width: root.shellOrientationAngle == 0 || root.shellOrientationAngle == 180 ? root.width : root.height
239 height: root.shellOrientationAngle == 0 || root.shellOrientationAngle == 180 ? root.height : root.width
240 rotation: normalizeAngle(-root.shellOrientationAngle)
244 surfaceOrientationAngle: orientationAngle
248 name: "animatingRotation"
252 x: (parent.width - width) / 2
253 y: (parent.height - height) / 2
258 margins: -units.gu(2)
260 source: "graphics/dropshadow2gu.sci"
261 opacity: root.dropShadow ? .3 : 0
262 Behavior on opacity { UbuntuNumberAnimation {} }
266 id: selectionHighlight
267 objectName: "selectionHighlight"
268 anchors.fill: appWindow
269 anchors.margins: -units.gu(1)
271 opacity: root.highlightShown ? 0.15 : 0
277 anchors { left: selectionHighlight.left; right: selectionHighlight.right; bottom: selectionHighlight.bottom; }
279 color: UbuntuColors.orange
280 visible: root.highlightShown
286 objectName: application ? "appWindow_" + application.appId : "appWindow_null"
290 topMargin: appWindow.fullscreen || (application && application.rotatesWindowContents)
291 ? 0 : maximizedAppTopMargin
294 interactive: root.interactive
300 // mimics appWindowWithShadow. Do the positioning of screenshots of non-fullscreen
302 id: appWindowScreenshotWithShadow
305 property real transformRotationAngle: 0
306 property real transformOriginX
307 property real transformOriginY
309 transform: Rotation {
310 origin.x: appWindowScreenshotWithShadow.transformOriginX
311 origin.y: appWindowScreenshotWithShadow.transformOriginY
312 axis { x: 0; y: 0; z: 1 }
313 angle: appWindowScreenshotWithShadow.transformRotationAngle
316 property var window: appWindowScreenshot
319 // Format: "image://application/$APP_ID/$CURRENT_TIME_MS"
320 // eg: "image://application/calculator-app/123456"
321 var timeMs = new Date().getTime();
322 appWindowScreenshot.source = "image://application/" + root.application.appId + "/" + timeMs;
325 appWindowScreenshot.source = "";
329 id: appWindowScreenshot
334 sourceSize.width: width
335 sourceSize.height: height
341 objectName: "dragArea"
344 property bool moving: false
345 property real distance: 0
346 readonly property int threshold: units.gu(2)
347 property int offset: 0
349 readonly property real minSpeedToClose: units.gu(40)
351 onDragValueChanged: {
355 moving = moving || Math.abs(dragValue) > threshold;
357 distance = dragValue + offset;
363 offset = (dragValue > 0 ? -threshold: threshold)
376 if (!root.closeable) {
377 animation.animate("center")
381 // velocity and distance values specified by design prototype
382 if ((dragVelocity < -minSpeedToClose && distance < -units.gu(8)) || distance < -root.height / 2) {
383 animation.animate("up")
384 } else if ((dragVelocity > minSpeedToClose && distance > units.gu(8)) || distance > root.height / 2) {
385 animation.animate("down")
387 animation.animate("center")
391 UbuntuNumberAnimation {
393 objectName: "closeAnimation"
396 property bool requestClose: false
398 function animate(direction) {
399 animation.from = dragArea.distance;
402 animation.to = -root.height * 1.5;
406 animation.to = root.height * 1.5;
417 dragArea.moving = false;
421 dragArea.distance = 0;