diff --git a/js/ui/overview.js b/js/ui/overview.js index fc9f05504..50e42e609 100644 --- a/js/ui/overview.js +++ b/js/ui/overview.js @@ -623,7 +623,7 @@ const Overview = new Lang.Class({ this.animationInProgress = true; this.visibleTarget = false; - this.viewSelector.zoomFromOverview(); + this.viewSelector.animateFromOverview(); // Make other elements fade out. Tweener.addTween(this._stack, diff --git a/js/ui/viewSelector.js b/js/ui/viewSelector.js index bf2d73311..80c3035d4 100644 --- a/js/ui/viewSelector.js +++ b/js/ui/viewSelector.js @@ -260,6 +260,16 @@ const ViewSelector = new Lang.Class({ this._stageKeyPressId = 0; } })); + Main.overview.connect('shown', Lang.bind(this, + function() { + // If we were animating from the desktop view to the + // apps page the workspace page was visible, allowing + // the windows to animate, but now we no longer want to + // show it given that we are now on the apps page or + // search page. + if (this._activePage != this._workspacesPage) + this._workspacesPage.opacity = 0; + })); Main.wm.addKeybinding('toggle-application-view', new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }), @@ -295,29 +305,36 @@ const ViewSelector = new Lang.Class({ }, _toggleAppsPage: function() { - Main.overview.show(); this._showAppsButton.checked = !this._showAppsButton.checked; + Main.overview.show(); }, showApps: function() { - Main.overview.show(); this._showAppsButton.checked = true; + Main.overview.show(); }, show: function() { this.reset(); - - this._workspacesDisplay.show(); + this._workspacesDisplay.show(this._showAppsButton.checked); this._activePage = null; - this._showPage(this._workspacesPage); + if (this._showAppsButton.checked) + this._showPage(this._appsPage); + else + this._showPage(this._workspacesPage); if (!this._workspacesDisplay.activeWorkspaceHasMaximizedWindows()) Main.overview.fadeOutDesktop(); }, - zoomFromOverview: function() { + animateFromOverview: function() { + // Make sure workspace page is fully visible to allow + // workspace.js do the animation of the windows + this._workspacesPage.opacity = 255; + + this._workspacesDisplay.animateFromOverview(this._activePage != this._workspacesPage); + this._showAppsButton.checked = false; - this._workspacesDisplay.zoomFromOverview(); if (!this._workspacesDisplay.activeWorkspaceHasMaximizedWindows()) Main.overview.fadeInDesktop(); @@ -369,6 +386,9 @@ const ViewSelector = new Lang.Class({ }, _showPage: function(page) { + if (!Main.overview.visible) + return; + if (page == this._activePage) return; diff --git a/js/ui/workspace.js b/js/ui/workspace.js index 5fd055b01..7e157add2 100644 --- a/js/ui/workspace.js +++ b/js/ui/workspace.js @@ -34,6 +34,8 @@ const DRAGGING_WINDOW_OPACITY = 100; const LAYOUT_SCALE_WEIGHT = 1; const LAYOUT_SPACE_WEIGHT = 0.1; +const WINDOW_ANIMATION_MAX_NUMBER_BLENDING = 3; + function _interpolate(start, end, step) { return start + (end - start) * step; } @@ -1258,10 +1260,11 @@ const Workspace = new Lang.Class({ return; } - // We will reposition windows when enter again overview anyway. + // We will reposition windows anyway when enter again overview or when ending the windows + // animations whith fade animation. // In this way we avoid unwanted animations of windows repositioning while - // animating overview - if (this.leavingOverview) + // animating overview. + if (this.leavingOverview || this._animatingWindowsFade) return; let initialPositioning = flags & WindowPositionFlags.INITIAL; @@ -1562,14 +1565,141 @@ const Workspace = new Lang.Class({ return false; }, - // Animate the full-screen to Overview transition. - zoomToOverview : function() { + fadeToOverview: function() { + // We don't want to reposition windows while animating in this way. + this._animatingWindowsFade = true; + this._overviewShownId = Main.overview.connect('shown', Lang.bind(this, + this._doneShowingOverview)); + if (this._windows.length == 0) + return; + + if (this.metaWorkspace != null && this.metaWorkspace != global.screen.get_active_workspace()) + return; + + // Special case maximized windows, since it doesn't make sense + // to animate windows below in the stack + let topMaximizedWindow; + // It is ok to treat the case where there is no maximized + // window as if the bottom-most window was maximized given that + // it won't affect the result of the animation + for (topMaximizedWindow = this._windows.length - 1; topMaximizedWindow > 0; topMaximizedWindow--) { + let metaWindow = this._windows[topMaximizedWindow].metaWindow; + if (metaWindow.maximized_horizontally && metaWindow.maximized_vertically) + break; + } + + let nTimeSlots = Math.min(WINDOW_ANIMATION_MAX_NUMBER_BLENDING + 1, this._windows.length - topMaximizedWindow); + let windowBaseTime = Overview.ANIMATION_TIME / nTimeSlots; + + let topIndex = this._windows.length - 1; + for (let i = 0; i < this._windows.length; i++) { + if (i < topMaximizedWindow) { + // below top-most maximized window, don't animate + let overlay = this._windowOverlays[i]; + if (overlay) + overlay.hide(); + this._windows[i].actor.opacity = 0; + } else { + let fromTop = topIndex - i; + let time; + if (fromTop < nTimeSlots) // animate top-most windows gradually + time = windowBaseTime * (nTimeSlots - fromTop); + else + time = windowBaseTime; + + this._windows[i].actor.opacity = 255; + this._fadeWindow(i, time, 0); + } + } + }, + + fadeFromOverview: function() { + this.leavingOverview = true; + this._overviewHiddenId = Main.overview.connect('hidden', Lang.bind(this, + this._doneLeavingOverview)); + if (this._windows.length == 0) + return; + + for (let i = 0; i < this._windows.length; i++) { + let clone = this._windows[i]; + Tweener.removeTweens(clone.actor); + } + + if (this._repositionWindowsId > 0) { + Mainloop.source_remove(this._repositionWindowsId); + this._repositionWindowsId = 0; + } + + if (this.metaWorkspace != null && this.metaWorkspace != global.screen.get_active_workspace()) + return; + + // Special case maximized windows, since it doesn't make sense + // to animate windows below in the stack + let topMaximizedWindow; + // It is ok to treat the case where there is no maximized + // window as if the bottom-most window was maximized given that + // it won't affect the result of the animation + for (topMaximizedWindow = this._windows.length - 1; topMaximizedWindow > 0; topMaximizedWindow--) { + let metaWindow = this._windows[topMaximizedWindow].metaWindow; + if (metaWindow.maximized_horizontally && metaWindow.maximized_vertically) + break; + } + + let nTimeSlots = Math.min(WINDOW_ANIMATION_MAX_NUMBER_BLENDING + 1, this._windows.length - topMaximizedWindow); + let windowBaseTime = Overview.ANIMATION_TIME / nTimeSlots; + + let topIndex = this._windows.length - 1; + for (let i = 0; i < this._windows.length; i++) { + if (i < topMaximizedWindow) { + // below top-most maximized window, don't animate + let overlay = this._windowOverlays[i]; + if (overlay) + overlay.hide(); + this._windows[i].actor.opacity = 0; + } else { + let fromTop = topIndex - i; + let time; + if (fromTop < nTimeSlots) // animate top-most windows gradually + time = windowBaseTime * (fromTop + 1); + else + time = windowBaseTime * nTimeSlots; + + this._windows[i].actor.opacity = 0; + this._fadeWindow(i, time, 255); + } + } + }, + + _fadeWindow: function(index, time, opacity) { + let clone = this._windows[index]; + let overlay = this._windowOverlays[index]; + + if (overlay) + overlay.hide(); + + if (clone.metaWindow.showing_on_its_workspace()) { + let [origX, origY] = clone.getOriginalPosition(); + clone.actor.scale_x = 1; + clone.actor.scale_y = 1; + clone.actor.x = origX; + clone.actor.y = origY; + Tweener.addTween(clone.actor, + { time: time, + opacity: opacity, + transition: 'easeOutQuad' + }); + } else { + // The window is hidden + clone.actor.opacity = 0; + } + }, + + zoomToOverview: function() { // Position and scale the windows. this._recalculateWindowPositions(WindowPositionFlags.ANIMATE | WindowPositionFlags.INITIAL); }, - // Animates the return from Overview mode - zoomFromOverview : function() { + zoomFromOverview: function() { let currentWorkspace = global.screen.get_active_workspace(); this.leavingOverview = true; @@ -1590,35 +1720,37 @@ const Workspace = new Lang.Class({ return; // Position and scale the windows. - for (let i = 0; i < this._windows.length; i++) { - let clone = this._windows[i]; - let overlay = this._windowOverlays[i]; + for (let i = 0; i < this._windows.length; i++) + this._zoomWindowFromOverview(i); + }, - if (overlay) - overlay.hide(); + _zoomWindowFromOverview: function(index) { + let clone = this._windows[index]; + let overlay = this._windowOverlays[index]; - if (clone.metaWindow.showing_on_its_workspace()) { - let [origX, origY] = clone.getOriginalPosition(); + if (overlay) + overlay.hide(); - Tweener.addTween(clone.actor, - { x: origX, - y: origY, - scale_x: 1.0, - scale_y: 1.0, - time: Overview.ANIMATION_TIME, - opacity: 255, - transition: 'easeOutQuad' - }); - } else { - // The window is hidden, make it shrink and fade it out - Tweener.addTween(clone.actor, - { scale_x: 0, - scale_y: 0, - opacity: 0, - time: Overview.ANIMATION_TIME, - transition: 'easeOutQuad' - }); - } + if (clone.metaWindow.showing_on_its_workspace()) { + let [origX, origY] = clone.getOriginalPosition(); + Tweener.addTween(clone.actor, + { x: origX, + y: origY, + scale_x: 1.0, + scale_y: 1.0, + time: Overview.ANIMATION_TIME, + opacity: 255, + transition: 'easeOutQuad' + }); + } else { + // The window is hidden, make it shrink and fade it out + Tweener.addTween(clone.actor, + { scale_x: 0, + scale_y: 0, + opacity: 0, + time: Overview.ANIMATION_TIME, + transition: 'easeOutQuad' + }); } }, @@ -1657,6 +1789,11 @@ const Workspace = new Lang.Class({ this.leavingOverview = false; }, + _doneShowingOverview: function() { + this._animatingWindowsFade = false; + this._recalculateWindowPositions(WindowPositionFlags.INITIAL); + }, + // Tests if @actor belongs to this workspaces and monitor _isMyWindow : function (actor) { let win = actor.meta_window; diff --git a/js/ui/workspacesView.js b/js/ui/workspacesView.js index 61df6ab96..004ac81ae 100644 --- a/js/ui/workspacesView.js +++ b/js/ui/workspacesView.js @@ -21,6 +21,11 @@ const WORKSPACE_SWITCH_TIME = 0.25; // Note that mutter has a compile-time limit of 36 const MAX_WORKSPACES = 16; +const AnimationType = { + ZOOM: 0, + FADE: 1 +}; + const OVERRIDE_SCHEMA = 'org.gnome.shell.overrides'; const WorkspacesViewBase = new Lang.Class({ @@ -142,17 +147,25 @@ const WorkspacesView = new Lang.Class({ return this._workspaces[active]; }, - zoomToOverview: function() { - for (let w = 0; w < this._workspaces.length; w++) - this._workspaces[w].zoomToOverview(); + animateToOverview: function(animationType) { + for (let w = 0; w < this._workspaces.length; w++) { + if (animationType == AnimationType.ZOOM) + this._workspaces[w].zoomToOverview(); + else + this._workspaces[w].fadeToOverview(); + } this._updateWorkspaceActors(false); }, - zoomFromOverview: function() { + animateFromOverview: function(animationType) { this.actor.remove_clip(); - for (let w = 0; w < this._workspaces.length; w++) - this._workspaces[w].zoomFromOverview(); + for (let w = 0; w < this._workspaces.length; w++) { + if (animationType == AnimationType.ZOOM) + this._workspaces[w].zoomFromOverview(); + else + this._workspaces[w].fadeFromOverview(); + } }, syncStacking: function(stackIndices) { @@ -365,12 +378,18 @@ const ExtraWorkspaceView = new Lang.Class({ this._workspace.setActualGeometry(this._actualGeometry); }, - zoomToOverview: function() { - this._workspace.zoomToOverview(); + animateToOverview: function(animationType) { + if (animationType == AnimationType.ZOOM) + this._workspace.zoomToOverview(); + else + this._workspace.fadeToOverview(); }, - zoomFromOverview: function() { - this._workspace.zoomFromOverview(); + animateFromOverview: function(animationType) { + if (animationType == AnimationType.ZOOM) + this._workspace.zoomFromOverview(); + else + this._workspace.fadeFromOverview(); }, syncStacking: function(stackIndices) { @@ -462,10 +481,16 @@ const WorkspacesDisplay = new Lang.Class({ return this._getPrimaryView().actor.navigate_focus(from, direction, false); }, - show: function() { + show: function(fadeOnPrimary) { this._updateWorkspacesViews(); - for (let i = 0; i < this._workspacesViews.length; i++) - this._workspacesViews[i].zoomToOverview(); + for (let i = 0; i < this._workspacesViews.length; i++) { + let animationType; + if (fadeOnPrimary && i == this._primaryIndex) + animationType = AnimationType.FADE; + else + animationType = AnimationType.ZOOM; + this._workspacesViews[i].animateToOverview(animationType); + } this._restackedNotifyId = Main.overview.connect('windows-restacked', @@ -474,9 +499,15 @@ const WorkspacesDisplay = new Lang.Class({ this._scrollEventId = Main.overview.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); }, - zoomFromOverview: function() { - for (let i = 0; i < this._workspacesViews.length; i++) - this._workspacesViews[i].zoomFromOverview(); + animateFromOverview: function(fadeOnPrimary) { + for (let i = 0; i < this._workspacesViews.length; i++) { + let animationType; + if (fadeOnPrimary && i == this._primaryIndex) + animationType = AnimationType.FADE; + else + animationType = AnimationType.ZOOM; + this._workspacesViews[i].animateFromOverview(animationType); + } }, hide: function() {