2 * Copyright (C) 2015 Canonical, Ltd.
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.
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.
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/>.
18 import Ubuntu.Components 1.3
21 // Why the state machine is done that way:
22 // We cannot use regular PropertyChanges{} inside the State elements as steps in the
23 // transition animations must take place in a well defined order.
24 // Which means that we also cannot jump to a new state in the middle of a transition
25 // as that would make hell brake loose.
29 // to be set from the outside
30 property Item orientedShell
32 property Item shellCover
33 property Item windowScreenshot
35 property int rotationDuration: 450
36 property int rotationEasing: Easing.InOutCubic
37 // Those values are good for debugging/development
38 //property int rotationDuration: 3000
39 //property int rotationEasing: Easing.Linear
45 State { name: "180" },
49 property QtObject d: QtObject {
52 property bool startingUp: true
53 property var finishStartUpTimer: Timer {
55 onTriggered: d.startingUp = false
57 Component.onCompleted: {
58 finishStartUpTimer.start();
61 property bool transitioning: false
62 onTransitioningChanged: {
66 readonly property int requestedOrientationAngle: root.orientedShell.acceptedOrientationAngle
68 // Avoiding a direct call to tryUpdateState() as the state change might trigger an immediate
69 // change to Shell.orientationAngle which, in its turn, causes a reevaluation of
70 // requestedOrientationAngle (ie., OrientedShell.acceptedOrientationAngle). A reentrant evaluation
71 // of a binding is detected by QML as a binding loop and QML will deny the reevalutation, which
72 // will leave us in a bogus state.
74 // To avoid this mess we update the state in the next event loop iteration, ensuring a clean
76 onRequestedOrientationAngleChanged: {
77 stateUpdateTimer.start();
79 property Timer stateUpdateTimer: Timer {
82 onTriggered: { d.tryUpdateState(); }
85 function tryUpdateState() {
86 if (d.transitioning || (!d.startingUp && !root.orientedShell.orientationChangesEnabled)) {
90 var requestedState = d.requestedOrientationAngle.toString();
91 if (requestedState !== root.state) {
92 d.resolveAnimationType();
93 root.state = requestedState;
97 property Connections shellConnections: Connections {
98 target: root.orientedShell
99 onOrientationChangesEnabledChanged: {
104 property var shellBeingResized: Binding {
106 property: "beingResized"
107 value: d.transitioning
110 readonly property int fullAnimation: 0
111 readonly property int indicatorsBarAnimation: 1
112 readonly property int noAnimation: 2
114 property int animationType
116 // animationType update *must* take place *before* the state update.
117 // If animationType and state were updated through bindings, as with normal qml code,
118 // there would be no guarantee in the order of the binding updates, which could then
119 // cause the wrong transitions to be chosen for the state changes.
120 function resolveAnimationType() {
122 // During start up, inital property values are still settling while we're still
123 // to render the very first frame
124 d.animationType = d.noAnimation;
125 } else if (Powerd.status === Powerd.Off) {
126 // There's no point in animating if the user can't see it (display is off).
127 d.animationType = d.noAnimation;
128 } else if (root.shell.showingGreeter) {
129 // A rotating greeter looks weird.
130 d.animationType = d.noAnimation;
132 if (!root.shell.mainApp) {
133 // shouldn't happen but, anyway
134 d.animationType = d.fullAnimation;
138 if (root.shell.mainApp.rotatesWindowContents) {
139 // The application will animate its own GUI, so we don't have to do anything ourselves.
140 d.animationType = d.noAnimation;
141 } else if (root.shell.mainAppWindowOrientationAngle == d.requestedOrientationAngle) {
142 // The app window is already on its final orientation angle.
143 // So we just animate the indicators bar
144 // TODO: what if the app is fullscreen?
145 d.animationType = d.indicatorsBarAnimation;
147 d.animationType = d.fullAnimation;
152 // When an application switch takes place, d.requestedOrientationAngle and
153 // root.shell.mainAppWindowOrientationAngle get updated separately, at different moments.
154 // So, when one of those properties change, we shouldn't make a decision straight away
155 // as the other might be stale and about to be changed. So let's give it a bit of time for
156 // them to get properly updated.
157 // This approach is indeed a bit hacky.
158 property bool appWindowOrientationAngleNeedsUpdateUnstable:
159 root.shell.orientationAngle === d.requestedOrientationAngle
160 && root.shell.mainApp
161 && root.shell.mainAppWindowOrientationAngle !== root.shell.orientationAngle
163 onAppWindowOrientationAngleNeedsUpdateUnstableChanged: {
164 stableTimer.restart();
166 property Timer stableTimer: Timer {
169 if (d.appWindowOrientationAngleNeedsUpdateUnstable) {
170 shell.updateFocusedAppOrientationAnimated();
179 enabled: d.animationType == d.fullAnimation
180 NinetyRotationAnimation { fromAngle: 90; toAngle: 0
181 info: d; shell: root.shell }
185 enabled: d.animationType == d.fullAnimation
186 NinetyRotationAnimation { fromAngle: 0; toAngle: 90
187 info: d; shell: root.shell }
191 enabled: d.animationType == d.fullAnimation
192 NinetyRotationAnimation { fromAngle: 0; toAngle: 270
193 info: d; shell: root.shell }
197 enabled: d.animationType == d.fullAnimation
198 NinetyRotationAnimation { fromAngle: 270; toAngle: 0
199 info: d; shell: root.shell }
202 from: "90"; to: "180"
203 enabled: d.animationType == d.fullAnimation
204 NinetyRotationAnimation { fromAngle: 90; toAngle: 180
205 info: d; shell: root.shell }
208 from: "180"; to: "90"
209 enabled: d.animationType == d.fullAnimation
210 NinetyRotationAnimation { fromAngle: 180; toAngle: 90
211 info: d; shell: root.shell }
214 from: "180"; to: "270"
215 enabled: d.animationType == d.fullAnimation
216 NinetyRotationAnimation { fromAngle: 180; toAngle: 270
217 info: d; shell: root.shell }
220 from: "270"; to: "180"
221 enabled: d.animationType == d.fullAnimation
222 NinetyRotationAnimation { fromAngle: 270; toAngle: 180
223 info: d; shell: root.shell }
227 enabled: d.animationType == d.fullAnimation
228 HalfLoopRotationAnimation { fromAngle: 0; toAngle: 180
229 info: d; shell: root.shell }
233 enabled: d.animationType == d.fullAnimation
234 HalfLoopRotationAnimation { fromAngle: 180; toAngle: 0
235 info: d; shell: root.shell }
238 from: "90"; to: "270"
239 enabled: d.animationType == d.fullAnimation
240 HalfLoopRotationAnimation { fromAngle: 90; toAngle: 270
241 info: d; shell: root.shell }
244 from: "270"; to: "90"
245 enabled: d.animationType == d.fullAnimation
246 HalfLoopRotationAnimation { fromAngle: 270; toAngle: 90
247 info: d; shell: root.shell }
250 objectName: "immediateTransition"
251 enabled: d.animationType == d.noAnimation
252 ImmediateRotationAction { info: d; shell: root.shell }
255 enabled: d.animationType == d.indicatorsBarAnimation
256 SequentialAnimation {
257 ScriptAction { script: {
258 d.transitioning = true;
261 duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing
262 target: root.shell; property: "indicatorAreaShowProgress"
265 ImmediateRotationAction { info: d; shell: root.shell }
267 duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing
268 target: root.shell; property: "indicatorAreaShowProgress"
271 ScriptAction { script: {
272 d.transitioning = false;