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) {
70 } else if (application == stage.focusedApplication) {
75 showStartingApplicationAnimation.prepare(application);
76 if (!stage.fullyShown) {
77 // if launching this app reveals the stage, make new application screenshot visible
78 newApplicationScreenshot.visible = true;
81 showStartingApplicationAnimation.start();
85 stage.__startingApplicationDesktopFile = desktopFile;
90 target: applicationManager
91 onFocusRequested: activateApplication(appId)
94 /* Keep a reference to the focused application so that we can safely
95 unfocus it when the stage is not fully shown and refocus it when the stage
98 property var focusedApplicationWhenUsingScreenshots
99 onShouldUseScreenshotsChanged: {
100 if (showStartingApplicationAnimation.running || switchToApplicationAnimation.running) {
104 if (shouldUseScreenshots) {
105 stage.__focusApplicationUsingScreenshots(stage.focusedApplication);
107 if (stage.focusedApplicationWhenUsingScreenshots) {
108 stage.__focusActualApplication(stage.focusedApplicationWhenUsingScreenshots);
114 property Item newApplicationScreenshot: ApplicationScreenshot {
117 height: stage.height - y
119 property Item oldApplicationScreenshot: ApplicationScreenshot {
122 height: stage.height - y
125 function __getApplicationIndex(application) {
128 for (var i = 0; i < stage.applications.count; i++ ) {
129 if (stage.applications.get(i).desktopFile == application.desktopFile) {
137 property string __startingApplicationDesktopFile
140 id: delayedHideScreenshots
141 interval: 500 // on Galaxy Nexus, Telephony app is slowest application to draw a frame so needs this delay
148 id: hideScreenshotsAfterBriefDelay
149 // Since Mir & Qt have independent event loops, Mir often applies a focus request a frame later than when Qt
150 // asks for it. To work around, delay removing the screenshots a touch so Mir has definitely shown the app.
152 onTriggered: stage.__hideScreenshots();
155 function __hideScreenshots() {
156 newApplicationScreenshot.clearApplication();
157 oldApplicationScreenshot.clearApplication();
158 newApplicationScreenshot.visible = false;
159 oldApplicationScreenshot.visible = false;
162 function __focusApplication(application) {
163 if (!application) return
164 if (shouldUseScreenshots) {
165 stage.__focusApplicationUsingScreenshots(application);
167 stage.__focusActualApplication(application);
172 function __focusActualApplication(application) {
173 if (application == null) {
177 if (stage.focusedApplication == application) {
178 /* FIXME: this case is reached when showing the sidestage, we
179 need to refocus the side stage app in order to show it.
180 FIXME: this case is reached when doing a right edge swipe
181 while in the dash to bring back the stage.
182 FIXME: this case is reached when showStartingApplicationAnimation
183 finishes after the newly started application has been focused
184 automatically by the ApplicationManager.
186 if (stage.type == ApplicationInfo.SideStage) {
187 applicationManager.focusApplication(application);
189 if (!delayedHideScreenshots.running) {
190 hideScreenshotsAfterBriefDelay.start();
193 /* FIXME: calling ApplicationManager::focusApplication does not focus
194 the application immediately nor show the application's surface
195 immediately. Delay hiding the screenshots until it has done so.
197 delayedHideScreenshots.stop();
198 applicationManager.focusApplication(application);
200 stage.focusedApplicationWhenUsingScreenshots = null;
203 function __focusApplicationUsingScreenshots(application) {
204 delayedHideScreenshots.stop();
205 if (application == null) {
209 if (newApplicationScreenshot.application != null) {
210 stage.focusedApplicationWhenUsingScreenshots = application;
214 newApplicationScreenshot.setApplication(application);
215 newApplicationScreenshot.scheduleUpdate();
216 newApplicationScreenshot.opacity = 1.0;
217 newApplicationScreenshot.x = 0.0;
218 newApplicationScreenshot.y = __getApplicationY(application);
219 newApplicationScreenshot.scale = 1.0;
220 newApplicationScreenshot.visible = true;
221 stage.focusedApplicationWhenUsingScreenshots = application;
222 stage.switchingFromFullscreenToFullscreen = false;
225 function __getApplicationY(application) {
226 return Qt.binding( function(){
227 return (application && application.fullscreen
228 && application.stage !== ApplicationInfo.SideStage)
229 ? 0 : normalApplicationY;
233 /* FIXME: should hide the screenshots when the focused application's surface
234 is visible. There is no API in QtUbuntu to support that yet.
238 onFocusedApplicationChanged: {
239 if (stage.focusedApplication != null) {
240 /* Move application to the beginning of the list of running applications */
241 var index = stage.__getApplicationIndex(stage.focusedApplication);
242 applicationManager.moveRunningApplicationStackPosition(index, 0, stage.type);
244 var startingApplication = applicationManager.getApplicationFromDesktopFile(
245 stage.__startingApplicationDesktopFile, stage.type);
247 if (stage.focusedApplication == startingApplication) {
248 stage.__startingApplicationDesktopFile = "";
250 /* Screenshots might still be in use at this point and their
251 use can be stopped before delayedHideScreenshots is complete.
252 Prepare for that case by setting appropriately
253 stage.focusedApplicationWhenUsingScreenshots.
255 stage.focusedApplicationWhenUsingScreenshots = stage.focusedApplication;
257 /* FIXME: should hide the screenshots when the focused application's window
258 is visible. There is no API in qtubuntu to support that yet. Therefore
259 we delay their hiding to avoid a state where neither the screenshot nor
260 the application's window is visible.
262 delayedHideScreenshots.start();
264 stage.focusedApplicationWhenUsingScreenshots = null;
265 hideScreenshotsAfterBriefDelay.start();
272 target: stage.applications
273 onCountChanged: { __discardObsoleteScreenshots(); }
274 ignoreUnknownSignals: true
276 onFullyHiddenChanged: { __discardObsoleteScreenshots(); }
277 function __discardObsoleteScreenshots() {
278 if (stage.fullyHidden && stage.applications && stage.applications.count == 0) {
279 stage.focusedApplicationWhenUsingScreenshots = undefined;
280 stage.__hideScreenshots();
285 SequentialAnimation {
286 id: showStartingApplicationAnimation
288 function prepare(newApplication) {
289 var oldApplication = stage.focusedApplication;
290 newApplicationScreenshot.setApplication(newApplication);
291 newApplicationScreenshot.updateFromCache();
292 oldApplicationScreenshot.setApplication(oldApplication);
293 oldApplicationScreenshot.scheduleUpdate();
294 newApplicationScreenshot.withBackground = true; // FIXME: should be withBackground = newApplication.state == Application.Starting
295 newApplicationScreenshot.y = __getApplicationY(newApplication);
296 oldApplicationScreenshot.y = __getApplicationY(oldApplication);
297 stage.focusedApplicationWhenUsingScreenshots = newApplication;
298 stage.switchingFromFullscreenToFullscreen = false;
301 PropertyAction {target: newApplicationScreenshot; property: "visible"; value: false}
302 PropertyAction {target: oldApplicationScreenshot; property: "visible"; value: false}
303 PropertyAction {target: newApplicationScreenshot; property: "z"; value: 0}
304 PropertyAction {target: oldApplicationScreenshot; property: "z"; value: 1}
305 PropertyAction {target: newApplicationScreenshot; property: "scale"; value: 0.8}
306 PropertyAction {target: oldApplicationScreenshot; property: "scale"; value: 1.0}
307 PropertyAction {target: newApplicationScreenshot; property: "opacity"; value: 0.0}
308 PropertyAction {target: oldApplicationScreenshot; property: "opacity"; value: 1.0}
309 PropertyAction {target: newApplicationScreenshot; property: "x"; value: 0.0}
310 PropertyAction {target: oldApplicationScreenshot; property: "x"; value: 0.0}
313 PropertyAction {target: newApplicationScreenshot; property: "visible"; value: true}
315 target: newApplicationScreenshot
318 easing.type: Easing.OutQuad
322 target: newApplicationScreenshot
325 easing.type: Easing.OutQuad
329 PropertyAction {target: oldApplicationScreenshot; property: "visible"; value: true}
332 target: oldApplicationScreenshot
335 easing.type: Easing.OutQuad
342 // FIXME: only here to support case where application does
343 // not exist yet but is about to
344 if (stage.focusedApplicationWhenUsingScreenshots) {
345 stage.__focusApplication(stage.focusedApplicationWhenUsingScreenshots);
352 SequentialAnimation {
353 id: switchToApplicationAnimation
355 function prepare(newApplication) {
356 var oldApplication = stage.focusedApplication;
357 newApplicationScreenshot.setApplication(newApplication);
358 newApplicationScreenshot.updateFromCache();
359 oldApplicationScreenshot.setApplication(oldApplication);
360 oldApplicationScreenshot.scheduleUpdate();
361 newApplicationScreenshot.withBackground = false;
362 newApplicationScreenshot.y = __getApplicationY(newApplication);
363 oldApplicationScreenshot.y = __getApplicationY(oldApplication);
364 stage.focusedApplicationWhenUsingScreenshots = newApplication;
365 stage.switchingFromFullscreenToFullscreen = newApplication && newApplication.fullscreen;
368 PropertyAction {target: newApplicationScreenshot; property: "visible"; value: false}
369 PropertyAction {target: oldApplicationScreenshot; property: "visible"; value: false}
370 PropertyAction {target: newApplicationScreenshot; property: "z"; value: 1}
371 PropertyAction {target: oldApplicationScreenshot; property: "z"; value: 0}
372 PropertyAction {target: newApplicationScreenshot; property: "scale"; value: 1.0}
373 PropertyAction {target: oldApplicationScreenshot; property: "scale"; value: 1.0}
374 PropertyAction {target: newApplicationScreenshot; property: "opacity"; value: 1.0}
375 PropertyAction {target: oldApplicationScreenshot; property: "opacity"; value: 1.0}
376 PropertyAction {target: newApplicationScreenshot; property: "x"; value: stage.width}
377 PropertyAction {target: oldApplicationScreenshot; property: "x"; value: 0.0}
378 PropertyAction {target: newApplicationScreenshot; property: "visible"; value: true}
379 PropertyAction {target: oldApplicationScreenshot; property: "visible"; value: true}
383 target: oldApplicationScreenshot
386 easing.type: Easing.OutQuad
390 target: oldApplicationScreenshot
393 easing.type: Easing.OutQuad
398 target: newApplicationScreenshot
401 easing.type: Easing.OutQuad
408 // move previously focused application to the end of the list of running applications
409 var oldApplication = oldApplicationScreenshot.application;
410 if (oldApplication != null) {
411 var oldApplicationIndex = stage.__getApplicationIndex(oldApplicationScreenshot.application);
412 applicationManager.moveRunningApplicationStackPosition(
413 oldApplicationIndex, stage.applications.count - 1, stage.type);
416 stage.__focusApplication(stage.focusedApplicationWhenUsingScreenshots);
422 function __dragValueToProgress(dragValue, startValue, applicationsCount) {
423 if (applicationsCount == 0) {
425 } else if (applicationsCount == 1) {
426 // prevent the current app from moving more than 1/4 of the screen
427 var elasticValue = __elastic(dragValue, startValue/4);
428 return elasticValue / startValue;
430 return dragValue / startValue;
434 function __elastic(x, limit) {
435 return limit * ( 1 - Math.pow( ( limit - 1 ) / limit, x ) )
438 property real __startValue: stage.width
440 AnimationControllerWithSignals {
441 id: switchToApplicationAnimationController
443 onAnimationCompletedAtBeginning: {
445 stage.__focusApplication(oldApplicationScreenshot.application);
451 trackedPosition: rightEdgeDraggingArea.touchX
452 direction: rightEdgeDraggingArea.direction
453 maxDragDistance: stage.width
457 id: rightEdgeDraggingArea
460 if (status == DirectionalDragArea.WaitingForTouch) {
461 return switchToApplicationAnimationController.completed
462 && stage.applications.count > 0 && stage.rightEdgeEnabled
463 && !delayedHideScreenshots.running
469 anchors.top: parent.top
470 anchors.bottom: parent.bottom
471 anchors.right: parent.right
473 direction: Direction.Leftwards
476 if (status !== DirectionalDragArea.Recognized)
479 var targetProgress = __dragValueToProgress(-distance, __startValue,
480 stage.applications.count)
481 var delta = targetProgress - switchToApplicationAnimationController.progress
483 // When the gesture finally gets recognized, the finger will likely be
484 // reasonably far from the edge. If we made the progress immediately
485 // follow the finger position it would be visually unpleasant as there
486 // would be a jump to meet the finger's current position.
488 // The trick is not to go all the way (1.0) as it would cause the
489 // aforementioned jump.
490 switchToApplicationAnimationController.progress += 0.4 * delta
493 property int lastStatus: -1
496 if (status == DirectionalDragArea.Undecided) {
498 } else if (status == DirectionalDragArea.WaitingForTouch) {
499 if (lastStatus == DirectionalDragArea.Undecided) {
501 switchToApplicationAnimationController.completeToBeginningWithSignal();
509 function onUndecided() {
510 dragEvaluator.reset();
512 var nextApplication = null;
513 if (stage.applications.count > 1) {
514 nextApplication = stage.applications.get(1);
516 switchToApplicationAnimationController.progress = 0
517 switchToApplicationAnimation.prepare(nextApplication);
518 switchToApplicationAnimationController.animation = switchToApplicationAnimation;
522 function onGestureEnded() {
523 if (stage.applications.count > 1 && dragEvaluator.shouldAutoComplete()) {
524 switchToApplicationAnimationController.completeToEndWithSignal();
526 switchToApplicationAnimationController.completeToBeginningWithSignal();
532 // FIXME: very bad hack to detect when an application was closed or crashed
533 property var __previouslyFocusedApplication
536 onFocusedApplicationChanged: {
537 if (stage.focusedApplication == null
538 && __getApplicationIndex(stage.__previouslyFocusedApplication) == -1) {
539 stage.__onFocusedApplicationClosed();
541 stage.__previouslyFocusedApplication = stage.focusedApplication;
545 function __onFocusedApplicationClosed() {
550 if (stage.applications.count > 0) {
551 // Do not change the focused application from within onFocusedApplicationChanged.
552 // This causes binding loop warnings besides being a bad practice in general
553 // (setting a property from within its "changed" signal handling)
555 focusTopmostAppTimer.start();
562 id: focusTopmostAppTimer
563 interval: 1; repeat: false
565 if (stage.applications.count > 0) {
566 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