2 * Copyright (C) 2013 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/>.
18import Ubuntu.Application 0.1
19import Ubuntu.Components 0.1
20import Ubuntu.Gestures 0.1
23 Responsible for application switching.
25 Drag from the right edge to switch to the next application.
26 While being dragged, this component will show an animation for the user to
27 visualize that he's changing the stacking of applications (next/new app comes from
28 right edge and current/old app fades out).
36 property int type: ApplicationInfo.MainStage
37 property var applicationManager
38 readonly property var applications:
39 type == ApplicationInfo.MainStage ? applicationManager.mainStageApplications
40 : applicationManager.sideStageApplications
41 property bool fullyShown: false
42 readonly property var focusedApplication:
43 type == ApplicationInfo.MainStage ? applicationManager.mainStageFocusedApplication
44 : applicationManager.sideStageFocusedApplication
45 property bool shouldUseScreenshots: true
47 /* Will be true whenever application screenshots are being shown.
48 i.e. during application window transitions */
49 readonly property bool usingScreenshots: {
50 if (newApplicationScreenshot.application != null && oldApplicationScreenshot.application != null) {
51 return newApplicationScreenshot.ready && oldApplicationScreenshot.ready;
53 return newApplicationScreenshot.ready || oldApplicationScreenshot.ready;
57 property bool rightEdgeEnabled: true
58 property bool switchingFromFullscreenToFullscreen: false
60 property alias rightEdgeDraggingAreaWidth: rightEdgeDraggingArea.width
62 // Y coordinate for non-fullscreen applications
63 property int normalApplicationY: 0
65 function activateApplication(desktopFile, addDelay) {
66 var application = applicationManager.getApplicationFromDesktopFile(desktopFile, stage.type);
67 if (application != null && application == stage.focusedApplication) {
72 showStartingApplicationAnimation.prepare(application);
73 if (!stage.fullyShown) {
74 // if launching this app reveals the stage, make new application screenshot visible
75 newApplicationScreenshot.visible = true;
78 showStartingApplicationAnimation.start();
82 stage.__startingApplicationDesktopFile = desktopFile;
86 /* Keep a reference to the focused application so that we can safely
87 unfocus it when the stage is not fully shown and refocus it when the stage
90 property var focusedApplicationWhenUsingScreenshots
91 onShouldUseScreenshotsChanged: {
92 if (showStartingApplicationAnimation.running || switchToApplicationAnimation.running) {
96 if (shouldUseScreenshots) {
97 stage.__focusApplicationUsingScreenshots(stage.focusedApplication);
99 stage.__focusActualApplication(stage.focusedApplicationWhenUsingScreenshots);
104 property Item newApplicationScreenshot: ApplicationScreenshot {
107 height: stage.height - y
109 property Item oldApplicationScreenshot: ApplicationScreenshot {
112 height: stage.height - y
115 function __getApplicationIndex(application) {
118 for (var i = 0; i < stage.applications.count; i++ ) {
119 if (stage.applications.get(i).desktopFile == application.desktopFile) {
127 property string __startingApplicationDesktopFile
130 id: delayedHideScreenshots
131 interval: 500 // on Galaxy Nexus, Telephony app is slowest application to draw a frame so needs this delay
137 function __hideScreenshots() {
138 newApplicationScreenshot.clearApplication();
139 oldApplicationScreenshot.clearApplication();
140 newApplicationScreenshot.visible = false;
141 oldApplicationScreenshot.visible = false;
144 function __focusApplication(application) {
145 if (!application) return
146 if (shouldUseScreenshots) {
147 stage.__focusApplicationUsingScreenshots(application);
149 stage.__focusActualApplication(application);
154 function __focusActualApplication(application) {
155 if (application == null) {
159 if (stage.focusedApplication == application) {
160 /* FIXME: this case is reached when showing the sidestage, we
161 need to refocus the side stage app in order to show it.
162 FIXME: this case is reached when doing a right edge swipe
163 while in the dash to bring back the stage.
164 FIXME: this case is reached when showStartingApplicationAnimation
165 finishes after the newly started application has been focused
166 automatically by the ApplicationManager.
168 if (stage.type == ApplicationInfo.SideStage) {
169 applicationManager.focusApplication(application);
171 if (!delayedHideScreenshots.running) {
172 stage.__hideScreenshots();
175 /* FIXME: calling ApplicationManager::focusApplication does not focus
176 the application immediately nor show the application's surface
177 immediately. Delay hiding the screenshots until it has done so.
179 delayedHideScreenshots.stop();
180 applicationManager.focusApplication(application);
182 stage.focusedApplicationWhenUsingScreenshots = null;
185 function __focusApplicationUsingScreenshots(application) {
186 delayedHideScreenshots.stop();
187 if (application == null) {
191 if (newApplicationScreenshot.application != null) {
192 stage.focusedApplicationWhenUsingScreenshots = application;
196 newApplicationScreenshot.setApplication(application);
197 newApplicationScreenshot.scheduleUpdate();
198 newApplicationScreenshot.opacity = 1.0;
199 newApplicationScreenshot.x = 0.0;
200 newApplicationScreenshot.y = __getApplicationY(application);
201 newApplicationScreenshot.scale = 1.0;
202 newApplicationScreenshot.visible = true;
203 stage.focusedApplicationWhenUsingScreenshots = application;
204 stage.switchingFromFullscreenToFullscreen = false;
207 function __getApplicationY(application) {
208 return Qt.binding( function(){
209 return (application && application.fullscreen
210 && application.stage !== ApplicationInfo.SideStage)
211 ? 0 : normalApplicationY;
215 /* FIXME: should hide the screenshots when the focused application's surface
216 is visible. There is no API in QtUbuntu to support that yet.
220 onFocusedApplicationChanged: {
221 if (stage.focusedApplication != null) {
222 /* Move application to the beginning of the list of running applications */
223 var index = stage.__getApplicationIndex(stage.focusedApplication);
224 applicationManager.moveRunningApplicationStackPosition(index, 0, stage.type);
226 var startingApplication = applicationManager.getApplicationFromDesktopFile(
227 stage.__startingApplicationDesktopFile, stage.type);
229 if (stage.focusedApplication == startingApplication) {
230 stage.__startingApplicationDesktopFile = "";
232 /* Screenshots might still be in use at this point and their
233 use can be stopped before delayedHideScreenshots is complete.
234 Prepare for that case by setting appropriately
235 stage.focusedApplicationWhenUsingScreenshots.
237 stage.focusedApplicationWhenUsingScreenshots = stage.focusedApplication;
239 /* FIXME: should hide the screenshots when the focused application's window
240 is visible. There is no API in qtubuntu to support that yet. Therefore
241 we delay their hiding to avoid a state where neither the screenshot nor
242 the application's window is visible.
244 delayedHideScreenshots.start();
246 stage.__hideScreenshots();
253 SequentialAnimation {
254 id: showStartingApplicationAnimation
256 function prepare(newApplication) {
257 var oldApplication = stage.focusedApplication;
258 newApplicationScreenshot.setApplication(newApplication);
259 newApplicationScreenshot.updateFromCache();
260 oldApplicationScreenshot.setApplication(oldApplication);
261 oldApplicationScreenshot.scheduleUpdate();
262 newApplicationScreenshot.withBackground = true; // FIXME: should be withBackground = newApplication.state == Application.Starting
263 newApplicationScreenshot.y = __getApplicationY(newApplication);
264 oldApplicationScreenshot.y = __getApplicationY(oldApplication);
265 stage.focusedApplicationWhenUsingScreenshots = newApplication;
266 stage.switchingFromFullscreenToFullscreen = false;
269 PropertyAction {target: newApplicationScreenshot; property: "visible"; value: false}
270 PropertyAction {target: oldApplicationScreenshot; property: "visible"; value: false}
271 PropertyAction {target: newApplicationScreenshot; property: "z"; value: 0}
272 PropertyAction {target: oldApplicationScreenshot; property: "z"; value: 1}
273 PropertyAction {target: newApplicationScreenshot; property: "scale"; value: 0.8}
274 PropertyAction {target: oldApplicationScreenshot; property: "scale"; value: 1.0}
275 PropertyAction {target: newApplicationScreenshot; property: "opacity"; value: 0.0}
276 PropertyAction {target: oldApplicationScreenshot; property: "opacity"; value: 1.0}
277 PropertyAction {target: newApplicationScreenshot; property: "x"; value: 0.0}
278 PropertyAction {target: oldApplicationScreenshot; property: "x"; value: 0.0}
281 PropertyAction {target: newApplicationScreenshot; property: "visible"; value: true}
283 target: newApplicationScreenshot
286 easing.type: Easing.OutQuad
290 target: newApplicationScreenshot
293 easing.type: Easing.OutQuad
297 PropertyAction {target: oldApplicationScreenshot; property: "visible"; value: true}
300 target: oldApplicationScreenshot
303 easing.type: Easing.OutQuad
310 if (!stage.focusedApplicationWhenUsingScreenshots) {
311 // FIXME: only here to support case where application does
312 // not exist yet but is about to
316 stage.__focusApplication(stage.focusedApplicationWhenUsingScreenshots);
322 SequentialAnimation {
323 id: switchToApplicationAnimation
325 function prepare(newApplication) {
326 var oldApplication = stage.focusedApplication;
327 newApplicationScreenshot.setApplication(newApplication);
328 newApplicationScreenshot.updateFromCache();
329 oldApplicationScreenshot.setApplication(oldApplication);
330 oldApplicationScreenshot.scheduleUpdate();
331 newApplicationScreenshot.withBackground = false;
332 newApplicationScreenshot.y = __getApplicationY(newApplication);
333 oldApplicationScreenshot.y = __getApplicationY(oldApplication);
334 stage.focusedApplicationWhenUsingScreenshots = newApplication;
335 stage.switchingFromFullscreenToFullscreen = newApplication && newApplication.fullscreen;
338 PropertyAction {target: newApplicationScreenshot; property: "visible"; value: false}
339 PropertyAction {target: oldApplicationScreenshot; property: "visible"; value: false}
340 PropertyAction {target: newApplicationScreenshot; property: "z"; value: 1}
341 PropertyAction {target: oldApplicationScreenshot; property: "z"; value: 0}
342 PropertyAction {target: newApplicationScreenshot; property: "scale"; value: 1.0}
343 PropertyAction {target: oldApplicationScreenshot; property: "scale"; value: 1.0}
344 PropertyAction {target: newApplicationScreenshot; property: "opacity"; value: 1.0}
345 PropertyAction {target: oldApplicationScreenshot; property: "opacity"; value: 1.0}
346 PropertyAction {target: newApplicationScreenshot; property: "x"; value: stage.width}
347 PropertyAction {target: oldApplicationScreenshot; property: "x"; value: 0.0}
348 PropertyAction {target: newApplicationScreenshot; property: "visible"; value: true}
349 PropertyAction {target: oldApplicationScreenshot; property: "visible"; value: true}
353 target: oldApplicationScreenshot
356 easing.type: Easing.OutQuad
360 target: oldApplicationScreenshot
363 easing.type: Easing.OutQuad
368 target: newApplicationScreenshot
371 easing.type: Easing.OutQuad
378 // move previously focused application to the end of the list of running applications
379 var oldApplication = oldApplicationScreenshot.application;
380 if (oldApplication != null) {
381 var oldApplicationIndex = stage.__getApplicationIndex(oldApplicationScreenshot.application);
382 applicationManager.moveRunningApplicationStackPosition(
383 oldApplicationIndex, stage.applications.count - 1, stage.type);
386 stage.__focusApplication(stage.focusedApplicationWhenUsingScreenshots);
392 function __dragValueToProgress(dragValue, startValue, applicationsCount) {
393 if (applicationsCount == 0) {
395 } else if (applicationsCount == 1) {
396 // prevent the current app from moving more than 1/4 of the screen
397 var elasticValue = __elastic(dragValue, startValue/4);
398 return elasticValue / startValue;
400 return dragValue / startValue;
404 function __elastic(x, limit) {
405 return limit * ( 1 - Math.pow( ( limit - 1 ) / limit, x ) )
408 property real __startValue: stage.width
410 AnimationControllerWithSignals {
411 id: switchToApplicationAnimationController
413 onAnimationCompletedAtBeginning: {
415 stage.__focusApplication(oldApplicationScreenshot.application);
421 trackedPosition: rightEdgeDraggingArea.touchX
422 direction: rightEdgeDraggingArea.direction
423 maxDragDistance: stage.width
427 id: rightEdgeDraggingArea
430 if (status == DirectionalDragArea.WaitingForTouch) {
431 return switchToApplicationAnimationController.completed
432 && stage.applications.count > 0 && stage.rightEdgeEnabled
433 && !delayedHideScreenshots.running
439 anchors.top: parent.top
440 anchors.bottom: parent.bottom
441 anchors.right: parent.right
443 direction: Direction.Leftwards
446 if (status !== DirectionalDragArea.Recognized)
449 var targetProgress = __dragValueToProgress(-distance, __startValue,
450 stage.applications.count)
451 var delta = targetProgress - switchToApplicationAnimationController.progress
453 // When the gesture finally gets recognized, the finger will likely be
454 // reasonably far from the edge. If we made the progress immediately
455 // follow the finger position it would be visually unpleasant as there
456 // would be a jump to meet the finger's current position.
458 // The trick is not to go all the way (1.0) as it would cause the
459 // aforementioned jump.
460 switchToApplicationAnimationController.progress += 0.4 * delta
463 property bool gotRejected: false
466 if (status == DirectionalDragArea.Undecided) {
469 } else if (status == DirectionalDragArea.Rejected) {
470 switchToApplicationAnimationController.completeToBeginningWithSignal();
472 } else if (status == DirectionalDragArea.WaitingForTouch) {
478 function onUndecided() {
479 dragEvaluator.reset();
481 var nextApplication = null;
482 if (stage.applications.count > 1) {
483 nextApplication = stage.applications.get(1);
485 switchToApplicationAnimationController.progress = 0
486 switchToApplicationAnimation.prepare(nextApplication);
487 switchToApplicationAnimationController.animation = switchToApplicationAnimation;
491 function onGestureEnded() {
492 if (stage.applications.count > 1 && dragEvaluator.shouldAutoComplete()) {
493 switchToApplicationAnimationController.completeToEndWithSignal();
495 switchToApplicationAnimationController.completeToBeginningWithSignal();
501 // FIXME: very bad hack to detect when an application was closed or crashed
502 property var __previouslyFocusedApplication
505 onFocusedApplicationChanged: {
506 if (stage.focusedApplication == null
507 && __getApplicationIndex(stage.__previouslyFocusedApplication) == -1) {
508 stage.__onFocusedApplicationClosed();
510 stage.__previouslyFocusedApplication = stage.focusedApplication;
514 function __onFocusedApplicationClosed() {
519 if (stage.applications.count > 0) {
520 // Do not change the focused application from within onFocusedApplicationChanged.
521 // This causes binding loop warnings besides being a bad practice in general
522 // (setting a property from within its "changed" signal handling)
524 focusTopmostAppTimer.start();
531 id: focusTopmostAppTimer
532 interval: 1; repeat: false
534 if (stage.applications.count > 0) {
535 stage.__focusApplication(stage.applications.get(0));
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list