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 Unity.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 property bool fullyHidden: true
43 readonly property var focusedApplication:
44 type == ApplicationInfo.MainStage ? applicationManager.mainStageFocusedApplication
45 : applicationManager.sideStageFocusedApplication
46 property bool shouldUseScreenshots: true
48 /* Will be true whenever application screenshots are being shown.
49 i.e. during application window transitions */
50 readonly property bool usingScreenshots: {
51 if (newApplicationScreenshot.application != null && oldApplicationScreenshot.application != null) {
52 return newApplicationScreenshot.ready && oldApplicationScreenshot.ready;
54 return newApplicationScreenshot.ready || oldApplicationScreenshot.ready;
58 property bool rightEdgeEnabled: true
59 property bool switchingFromFullscreenToFullscreen: false
61 property alias rightEdgeDraggingAreaWidth: rightEdgeDraggingArea.width
63 // Y coordinate for non-fullscreen applications
64 property int normalApplicationY: 0
66 function activateApplication(desktopFile, addDelay) {
67 var application = applicationManager.getApplicationFromDesktopFile(desktopFile, stage.type);
68 if (application != null && application == stage.focusedApplication) {
73 showStartingApplicationAnimation.prepare(application);
74 if (!stage.fullyShown) {
75 // if launching this app reveals the stage, make new application screenshot visible
76 newApplicationScreenshot.visible = true;
79 showStartingApplicationAnimation.start();
83 stage.__startingApplicationDesktopFile = desktopFile;
88 target: applicationManager
89 onFocusRequested: activateApplication(appId)
92 /* Keep a reference to the focused application so that we can safely
93 unfocus it when the stage is not fully shown and refocus it when the stage
96 property var focusedApplicationWhenUsingScreenshots
97 onShouldUseScreenshotsChanged: {
98 if (showStartingApplicationAnimation.running || switchToApplicationAnimation.running) {
102 if (shouldUseScreenshots) {
103 stage.__focusApplicationUsingScreenshots(stage.focusedApplication);
105 if (stage.focusedApplicationWhenUsingScreenshots) {
106 stage.__focusActualApplication(stage.focusedApplicationWhenUsingScreenshots);
112 property Item newApplicationScreenshot: ApplicationScreenshot {
115 height: stage.height - y
117 property Item oldApplicationScreenshot: ApplicationScreenshot {
120 height: stage.height - y
123 function __getApplicationIndex(application) {
126 for (var i = 0; i < stage.applications.count; i++ ) {
127 if (stage.applications.get(i).desktopFile == application.desktopFile) {
135 property string __startingApplicationDesktopFile
138 id: delayedHideScreenshots
139 interval: 500 // on Galaxy Nexus, Telephony app is slowest application to draw a frame so needs this delay
145 function __hideScreenshots() {
146 newApplicationScreenshot.clearApplication();
147 oldApplicationScreenshot.clearApplication();
148 newApplicationScreenshot.visible = false;
149 oldApplicationScreenshot.visible = false;
152 function __focusApplication(application) {
153 if (!application) return
154 if (shouldUseScreenshots) {
155 stage.__focusApplicationUsingScreenshots(application);
157 stage.__focusActualApplication(application);
162 function __focusActualApplication(application) {
163 if (application == null) {
167 if (stage.focusedApplication == application) {
168 /* FIXME: this case is reached when showing the sidestage, we
169 need to refocus the side stage app in order to show it.
170 FIXME: this case is reached when doing a right edge swipe
171 while in the dash to bring back the stage.
172 FIXME: this case is reached when showStartingApplicationAnimation
173 finishes after the newly started application has been focused
174 automatically by the ApplicationManager.
176 if (stage.type == ApplicationInfo.SideStage) {
177 applicationManager.focusApplication(application);
179 if (!delayedHideScreenshots.running) {
180 stage.__hideScreenshots();
183 /* FIXME: calling ApplicationManager::focusApplication does not focus
184 the application immediately nor show the application's surface
185 immediately. Delay hiding the screenshots until it has done so.
187 delayedHideScreenshots.stop();
188 applicationManager.focusApplication(application);
190 stage.focusedApplicationWhenUsingScreenshots = null;
193 function __focusApplicationUsingScreenshots(application) {
194 delayedHideScreenshots.stop();
195 if (application == null) {
199 if (newApplicationScreenshot.application != null) {
200 stage.focusedApplicationWhenUsingScreenshots = application;
204 newApplicationScreenshot.setApplication(application);
205 newApplicationScreenshot.scheduleUpdate();
206 newApplicationScreenshot.opacity = 1.0;
207 newApplicationScreenshot.x = 0.0;
208 newApplicationScreenshot.y = __getApplicationY(application);
209 newApplicationScreenshot.scale = 1.0;
210 newApplicationScreenshot.visible = true;
211 stage.focusedApplicationWhenUsingScreenshots = application;
212 stage.switchingFromFullscreenToFullscreen = false;
215 function __getApplicationY(application) {
216 return Qt.binding( function(){
217 return (application && application.fullscreen
218 && application.stage !== ApplicationInfo.SideStage)
219 ? 0 : normalApplicationY;
223 /* FIXME: should hide the screenshots when the focused application's surface
224 is visible. There is no API in QtUbuntu to support that yet.
228 onFocusedApplicationChanged: {
229 if (stage.focusedApplication != null) {
230 /* Move application to the beginning of the list of running applications */
231 var index = stage.__getApplicationIndex(stage.focusedApplication);
232 applicationManager.moveRunningApplicationStackPosition(index, 0, stage.type);
234 var startingApplication = applicationManager.getApplicationFromDesktopFile(
235 stage.__startingApplicationDesktopFile, stage.type);
237 if (stage.focusedApplication == startingApplication) {
238 stage.__startingApplicationDesktopFile = "";
240 /* Screenshots might still be in use at this point and their
241 use can be stopped before delayedHideScreenshots is complete.
242 Prepare for that case by setting appropriately
243 stage.focusedApplicationWhenUsingScreenshots.
245 stage.focusedApplicationWhenUsingScreenshots = stage.focusedApplication;
247 /* FIXME: should hide the screenshots when the focused application's window
248 is visible. There is no API in qtubuntu to support that yet. Therefore
249 we delay their hiding to avoid a state where neither the screenshot nor
250 the application's window is visible.
252 delayedHideScreenshots.start();
254 stage.focusedApplicationWhenUsingScreenshots = null;
255 stage.__hideScreenshots();
262 target: stage.applications
263 onCountChanged: { __discardObsoleteScreenshots(); }
264 ignoreUnknownSignals: true
266 onFullyHiddenChanged: { __discardObsoleteScreenshots(); }
267 function __discardObsoleteScreenshots() {
268 if (stage.fullyHidden && stage.applications && stage.applications.count == 0) {
269 stage.focusedApplicationWhenUsingScreenshots = undefined;
270 stage.__hideScreenshots();
275 SequentialAnimation {
276 id: showStartingApplicationAnimation
278 function prepare(newApplication) {
279 var oldApplication = stage.focusedApplication;
280 newApplicationScreenshot.setApplication(newApplication);
281 newApplicationScreenshot.updateFromCache();
282 oldApplicationScreenshot.setApplication(oldApplication);
283 oldApplicationScreenshot.scheduleUpdate();
284 newApplicationScreenshot.withBackground = true; // FIXME: should be withBackground = newApplication.state == Application.Starting
285 newApplicationScreenshot.y = __getApplicationY(newApplication);
286 oldApplicationScreenshot.y = __getApplicationY(oldApplication);
287 stage.focusedApplicationWhenUsingScreenshots = newApplication;
288 stage.switchingFromFullscreenToFullscreen = false;
291 PropertyAction {target: newApplicationScreenshot; property: "visible"; value: false}
292 PropertyAction {target: oldApplicationScreenshot; property: "visible"; value: false}
293 PropertyAction {target: newApplicationScreenshot; property: "z"; value: 0}
294 PropertyAction {target: oldApplicationScreenshot; property: "z"; value: 1}
295 PropertyAction {target: newApplicationScreenshot; property: "scale"; value: 0.8}
296 PropertyAction {target: oldApplicationScreenshot; property: "scale"; value: 1.0}
297 PropertyAction {target: newApplicationScreenshot; property: "opacity"; value: 0.0}
298 PropertyAction {target: oldApplicationScreenshot; property: "opacity"; value: 1.0}
299 PropertyAction {target: newApplicationScreenshot; property: "x"; value: 0.0}
300 PropertyAction {target: oldApplicationScreenshot; property: "x"; value: 0.0}
303 PropertyAction {target: newApplicationScreenshot; property: "visible"; value: true}
305 target: newApplicationScreenshot
308 easing.type: Easing.OutQuad
312 target: newApplicationScreenshot
315 easing.type: Easing.OutQuad
319 PropertyAction {target: oldApplicationScreenshot; property: "visible"; value: true}
322 target: oldApplicationScreenshot
325 easing.type: Easing.OutQuad
332 if (!stage.focusedApplicationWhenUsingScreenshots) {
333 // FIXME: only here to support case where application does
334 // not exist yet but is about to
338 stage.__focusApplication(stage.focusedApplicationWhenUsingScreenshots);
344 SequentialAnimation {
345 id: switchToApplicationAnimation
347 function prepare(newApplication) {
348 var oldApplication = stage.focusedApplication;
349 newApplicationScreenshot.setApplication(newApplication);
350 newApplicationScreenshot.updateFromCache();
351 oldApplicationScreenshot.setApplication(oldApplication);
352 oldApplicationScreenshot.scheduleUpdate();
353 newApplicationScreenshot.withBackground = false;
354 newApplicationScreenshot.y = __getApplicationY(newApplication);
355 oldApplicationScreenshot.y = __getApplicationY(oldApplication);
356 stage.focusedApplicationWhenUsingScreenshots = newApplication;
357 stage.switchingFromFullscreenToFullscreen = newApplication && newApplication.fullscreen;
360 PropertyAction {target: newApplicationScreenshot; property: "visible"; value: false}
361 PropertyAction {target: oldApplicationScreenshot; property: "visible"; value: false}
362 PropertyAction {target: newApplicationScreenshot; property: "z"; value: 1}
363 PropertyAction {target: oldApplicationScreenshot; property: "z"; value: 0}
364 PropertyAction {target: newApplicationScreenshot; property: "scale"; value: 1.0}
365 PropertyAction {target: oldApplicationScreenshot; property: "scale"; value: 1.0}
366 PropertyAction {target: newApplicationScreenshot; property: "opacity"; value: 1.0}
367 PropertyAction {target: oldApplicationScreenshot; property: "opacity"; value: 1.0}
368 PropertyAction {target: newApplicationScreenshot; property: "x"; value: stage.width}
369 PropertyAction {target: oldApplicationScreenshot; property: "x"; value: 0.0}
370 PropertyAction {target: newApplicationScreenshot; property: "visible"; value: true}
371 PropertyAction {target: oldApplicationScreenshot; property: "visible"; value: true}
375 target: oldApplicationScreenshot
378 easing.type: Easing.OutQuad
382 target: oldApplicationScreenshot
385 easing.type: Easing.OutQuad
390 target: newApplicationScreenshot
393 easing.type: Easing.OutQuad
400 // move previously focused application to the end of the list of running applications
401 var oldApplication = oldApplicationScreenshot.application;
402 if (oldApplication != null) {
403 var oldApplicationIndex = stage.__getApplicationIndex(oldApplicationScreenshot.application);
404 applicationManager.moveRunningApplicationStackPosition(
405 oldApplicationIndex, stage.applications.count - 1, stage.type);
408 stage.__focusApplication(stage.focusedApplicationWhenUsingScreenshots);
414 function __dragValueToProgress(dragValue, startValue, applicationsCount) {
415 if (applicationsCount == 0) {
417 } else if (applicationsCount == 1) {
418 // prevent the current app from moving more than 1/4 of the screen
419 var elasticValue = __elastic(dragValue, startValue/4);
420 return elasticValue / startValue;
422 return dragValue / startValue;
426 function __elastic(x, limit) {
427 return limit * ( 1 - Math.pow( ( limit - 1 ) / limit, x ) )
430 property real __startValue: stage.width
432 AnimationControllerWithSignals {
433 id: switchToApplicationAnimationController
435 onAnimationCompletedAtBeginning: {
437 stage.__focusApplication(oldApplicationScreenshot.application);
443 trackedPosition: rightEdgeDraggingArea.touchX
444 direction: rightEdgeDraggingArea.direction
445 maxDragDistance: stage.width
449 id: rightEdgeDraggingArea
452 if (status == DirectionalDragArea.WaitingForTouch) {
453 return switchToApplicationAnimationController.completed
454 && stage.applications.count > 0 && stage.rightEdgeEnabled
455 && !delayedHideScreenshots.running
461 anchors.top: parent.top
462 anchors.bottom: parent.bottom
463 anchors.right: parent.right
465 direction: Direction.Leftwards
468 if (status !== DirectionalDragArea.Recognized)
471 var targetProgress = __dragValueToProgress(-distance, __startValue,
472 stage.applications.count)
473 var delta = targetProgress - switchToApplicationAnimationController.progress
475 // When the gesture finally gets recognized, the finger will likely be
476 // reasonably far from the edge. If we made the progress immediately
477 // follow the finger position it would be visually unpleasant as there
478 // would be a jump to meet the finger's current position.
480 // The trick is not to go all the way (1.0) as it would cause the
481 // aforementioned jump.
482 switchToApplicationAnimationController.progress += 0.4 * delta
485 property int lastStatus: -1
488 if (status == DirectionalDragArea.Undecided) {
490 } else if (status == DirectionalDragArea.WaitingForTouch) {
491 if (lastStatus == DirectionalDragArea.Undecided) {
493 switchToApplicationAnimationController.completeToBeginningWithSignal();
501 function onUndecided() {
502 dragEvaluator.reset();
504 var nextApplication = null;
505 if (stage.applications.count > 1) {
506 nextApplication = stage.applications.get(1);
508 switchToApplicationAnimationController.progress = 0
509 switchToApplicationAnimation.prepare(nextApplication);
510 switchToApplicationAnimationController.animation = switchToApplicationAnimation;
514 function onGestureEnded() {
515 if (stage.applications.count > 1 && dragEvaluator.shouldAutoComplete()) {
516 switchToApplicationAnimationController.completeToEndWithSignal();
518 switchToApplicationAnimationController.completeToBeginningWithSignal();
524 // FIXME: very bad hack to detect when an application was closed or crashed
525 property var __previouslyFocusedApplication
528 onFocusedApplicationChanged: {
529 if (stage.focusedApplication == null
530 && __getApplicationIndex(stage.__previouslyFocusedApplication) == -1) {
531 stage.__onFocusedApplicationClosed();
533 stage.__previouslyFocusedApplication = stage.focusedApplication;
537 function __onFocusedApplicationClosed() {
542 if (stage.applications.count > 0) {
543 // Do not change the focused application from within onFocusedApplicationChanged.
544 // This causes binding loop warnings besides being a bad practice in general
545 // (setting a property from within its "changed" signal handling)
547 focusTopmostAppTimer.start();
554 id: focusTopmostAppTimer
555 interval: 1; repeat: false
557 if (stage.applications.count > 0) {
558 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