diff --git a/js/ui/overview.js b/js/ui/overview.js index 6f838db6b..bcddb412f 100644 --- a/js/ui/overview.js +++ b/js/ui/overview.js @@ -138,10 +138,6 @@ var Overview = class { return this.dash.iconSize; } - get viewSelector() { - return this._overview.viewSelector; - } - get animationInProgress() { return this._animationInProgress; } @@ -240,11 +236,11 @@ var Overview = class { } addSearchProvider(provider) { - this.viewSelector.addSearchProvider(provider); + this._overview.viewSelector.addSearchProvider(provider); } removeSearchProvider(provider) { - this.viewSelector.removeSearchProvider(provider); + this._overview.viewSelector.removeSearchProvider(provider); } // @@ -578,7 +574,6 @@ var Overview = class { // Re-enable unredirection Meta.enable_unredirect_for_display(global.display); - this.viewSelector.hide(); this._desktopFade.hide(); this._coverPane.hide(); diff --git a/js/ui/overviewControls.js b/js/ui/overviewControls.js index 64edcd763..e640564a5 100644 --- a/js/ui/overviewControls.js +++ b/js/ui/overviewControls.js @@ -3,11 +3,17 @@ const { Clutter, Gio, GObject, Meta, Shell, St } = imports.gi; +const AppDisplay = imports.ui.appDisplay; const Dash = imports.ui.dash; const Main = imports.ui.main; const Overview = imports.ui.overview; +const Util = imports.misc.util; const ViewSelector = imports.ui.viewSelector; const WindowManager = imports.ui.windowManager; +const WorkspaceThumbnail = imports.ui.workspaceThumbnail; +const WorkspacesView = imports.ui.workspacesView; + +const SMALL_WORKSPACE_RATIO = 0.15; var SIDE_CONTROLS_ANIMATION_TIME = Overview.ANIMATION_TIME; @@ -19,9 +25,13 @@ var ControlsState = { var ControlsManagerLayout = GObject.registerClass( class ControlsManagerLayout extends Clutter.BoxLayout { - _init(searchEntry, viewSelector, dash, stateAdjustment) { + _init(searchEntry, appDisplay, workspacesDisplay, workspacesThumbnails, + viewSelector, dash, stateAdjustment) { super._init({ orientation: Clutter.Orientation.VERTICAL }); + this._appDisplay = appDisplay; + this._workspacesDisplay = workspacesDisplay; + this._workspacesThumbnails = workspacesThumbnails; this._stateAdjustment = stateAdjustment; this._searchEntry = searchEntry; this._viewSelector = viewSelector; @@ -30,6 +40,35 @@ class ControlsManagerLayout extends Clutter.BoxLayout { stateAdjustment.connect('notify::value', () => this.layout_changed()); } + _getWorkspacesBoxForState(state, box, searchHeight, dashHeight, thumbnailsHeight) { + const workspaceBox = box.copy(); + const [width, height] = workspaceBox.get_size(); + const { spacing } = this; + + switch (state) { + case ControlsState.HIDDEN: + break; + case ControlsState.WINDOW_PICKER: + workspaceBox.set_origin(0, + searchHeight + spacing + + (thumbnailsHeight > 0 ? thumbnailsHeight + spacing : 0)); + workspaceBox.set_size(width, + height - + dashHeight - spacing - + searchHeight - spacing - + (thumbnailsHeight > 0 ? thumbnailsHeight + spacing : 0)); + break; + case ControlsState.APP_GRID: + workspaceBox.set_origin(0, searchHeight + spacing); + workspaceBox.set_size( + width, + Math.round(Math.max(height * SMALL_WORKSPACE_RATIO))); + break; + } + + return workspaceBox; + } + vfunc_set_container(container) { this._container = container; this.hookup_style(container); @@ -59,19 +98,55 @@ class ControlsManagerLayout extends Clutter.BoxLayout { availableHeight -= dashHeight + spacing; - // ViewSelector - const initialBox = new Clutter.ActorBox(); - initialBox.set_origin(0, 0); - initialBox.set_size(width, height); + // Workspace Thumbnails + let thumbnailsHeight = 0; + if (this._workspacesThumbnails.visible) { + [thumbnailsHeight] = + this._workspacesThumbnails.get_preferred_height(width); + thumbnailsHeight = Math.min( + thumbnailsHeight, + height * WorkspaceThumbnail.MAX_THUMBNAIL_SCALE); + childBox.set_origin(0, searchHeight + spacing); + childBox.set_size(width, thumbnailsHeight); + this._workspacesThumbnails.allocate(childBox); + } + // Workspaces + const params = [box, searchHeight, dashHeight, thumbnailsHeight]; + const transitionParams = this._stateAdjustment.getStateTransitionParams(); + + let workspacesBox; + if (!transitionParams.transitioning) { + workspacesBox = + this._getWorkspacesBoxForState(transitionParams.currentState, ...params); + } else { + const initialBox = + this._getWorkspacesBoxForState(transitionParams.initialState, ...params); + const finalBox = + this._getWorkspacesBoxForState(transitionParams.finalState, ...params); + workspacesBox = initialBox.interpolate(finalBox, transitionParams.progress); + } + + this._workspacesDisplay.allocate(workspacesBox); + + // AppDisplay + const workspaceAppGridBox = + this._getWorkspacesBoxForState(ControlsState.APP_GRID, ...params); + + childBox.set_origin(0, searchHeight + spacing + workspaceAppGridBox.get_height()); + childBox.set_size(width, + height - + searchHeight - spacing - + workspaceAppGridBox.get_height() - spacing - + dashHeight); + + this._appDisplay.allocate(childBox); + + // ViewSelector childBox.set_origin(0, searchHeight + spacing); childBox.set_size(width, availableHeight); - const page = this._viewSelector.getActivePage(); - const progress = page === ViewSelector.ViewPage.SEARCH - ? 1 : Math.min(this._stateAdjustment.value, 1); - const viewSelectorBox = initialBox.interpolate(childBox, progress); - this._viewSelector.allocate(viewSelectorBox); + this._viewSelector.allocate(childBox); } }); @@ -164,26 +239,65 @@ class ControlsManager extends St.Widget { }); this._stateAdjustment = new OverviewAdjustment(this); + this._stateAdjustment.connect('notify::value', this._update.bind(this)); this._nWorkspacesNotifyId = workspaceManager.connect('notify::n-workspaces', this._updateAdjustment.bind(this)); this.viewSelector = new ViewSelector.ViewSelector(this._searchEntry, + this.dash.showAppsButton); + this.viewSelector.connect('page-empty', this._onPageEmpty.bind(this)); + + this._thumbnailsBox = + new WorkspaceThumbnail.ThumbnailsBox(this._workspaceAdjustment); + this._workspacesDisplay = new WorkspacesView.WorkspacesDisplay( this._workspaceAdjustment, - this.dash.showAppsButton, this._stateAdjustment); + this._appDisplay = new AppDisplay.AppDisplay(); this.add_child(searchEntryBin); + this.add_child(this._appDisplay); this.add_child(this.dash); this.add_child(this.viewSelector); + this.add_child(this._thumbnailsBox); + this.add_child(this._workspacesDisplay); this.layout_manager = new ControlsManagerLayout(searchEntryBin, - this.viewSelector, this.dash, this._stateAdjustment); + this._appDisplay, + this._workspacesDisplay, + this._thumbnailsBox, + this.viewSelector, + this.dash, + this._stateAdjustment); this.dash.showAppsButton.connect('notify::checked', this._onShowAppsButtonToggled.bind(this)); + Main.ctrlAltTabManager.addGroup( + this.appDisplay, + _('Applications'), + 'view-app-grid-symbolic', { + proxy: this, + focusCallback: () => { + this.dash.showAppsButton.checked = true; + this.appDisplay.navigate_focus( + null, St.DirectionType.TAB_FORWARD, false); + }, + }); + + Main.ctrlAltTabManager.addGroup( + this._workspacesDisplay, + _('Windows'), + 'focus-windows-symbolic', { + proxy: this, + focusCallback: () => { + this.dash.showAppsButton.checked = false; + this._workspacesDisplay.navigate_focus( + null, St.DirectionType.TAB_FORWARD, false); + }, + }); + Main.wm.addKeybinding( 'toggle-application-view', new Gio.Settings({ schema_id: WindowManager.SHELL_KEYBINDINGS_SCHEMA }), @@ -192,6 +306,136 @@ class ControlsManager extends St.Widget { this._toggleAppsPage.bind(this)); this.connect('destroy', this._onDestroy.bind(this)); + + this._update(); + } + + _getFitModeForState(state) { + switch (state) { + case ControlsState.HIDDEN: + case ControlsState.WINDOW_PICKER: + return WorkspacesView.FitMode.SINGLE; + case ControlsState.APP_GRID: + return WorkspacesView.FitMode.ALL; + default: + return WorkspacesView.FitMode.SINGLE; + } + } + + _getThumbnailsBoxParams() { + const { initialState, finalState, progress } = + this._stateAdjustment.getStateTransitionParams(); + + const paramsForState = s => { + let opacity, scale, translationY; + switch (s) { + case ControlsState.HIDDEN: + case ControlsState.WINDOW_PICKER: + opacity = 255; + scale = 1; + translationY = 0; + break; + case ControlsState.APP_GRID: + opacity = 0; + scale = 0.5; + translationY = this._thumbnailsBox.height / 2; + break; + default: + opacity = 255; + scale = 1; + translationY = 0; + break; + } + + return { opacity, scale, translationY }; + }; + + const initialParams = paramsForState(initialState); + const finalParams = paramsForState(finalState); + + return [ + Util.lerp(initialParams.opacity, finalParams.opacity, progress), + Util.lerp(initialParams.scale, finalParams.scale, progress), + Util.lerp(initialParams.translationY, finalParams.translationY, progress), + ]; + } + + _updateThumbnailsBox(animate = false) { + const page = this.viewSelector.getActivePage(); + const searching = page === ViewSelector.ViewPage.SEARCH; + const [opacity, scale, translationY] = this._getThumbnailsBoxParams(); + + const thumbnailsBoxVisible = !searching && opacity !== 0; + if (thumbnailsBoxVisible) { + this._thumbnailsBox.opacity = 0; + this._thumbnailsBox.visible = thumbnailsBoxVisible; + } + + const params = { + opacity: searching ? 0 : opacity, + duration: animate ? SIDE_CONTROLS_ANIMATION_TIME : 0, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onComplete: () => (this._thumbnailsBox.visible = thumbnailsBoxVisible), + }; + + if (!searching) { + params.scale_x = scale; + params.scale_y = scale; + params.translation_y = translationY; + } + + this._thumbnailsBox.ease(params); + } + + _update() { + const params = this._stateAdjustment.getStateTransitionParams(); + + const fitMode = Util.lerp( + this._getFitModeForState(params.initialState), + this._getFitModeForState(params.finalState), + params.progress); + + const { fitModeAdjustment } = this._workspacesDisplay; + fitModeAdjustment.value = fitMode; + + this._updateThumbnailsBox(); + } + + _onPageEmpty() { + const page = this.viewSelector.getActivePage(); + const isActivities = page === ViewSelector.ViewPage.ACTIVITIES; + + if (isActivities) { + this._appDisplay.show(); + this._workspacesDisplay.reactive = true; + this._workspacesDisplay.setPrimaryWorkspaceVisible(true); + } else { + this.viewSelector.show(); + } + + this._updateThumbnailsBox(true); + + this._appDisplay.ease({ + opacity: isActivities ? 255 : 0, + duration: SIDE_CONTROLS_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onComplete: () => (this._appDisplay.visible = isActivities), + }); + this._workspacesDisplay.ease({ + opacity: isActivities ? 255 : 0, + duration: SIDE_CONTROLS_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onComplete: () => { + this._workspacesDisplay.reactive = isActivities; + this._workspacesDisplay.setPrimaryWorkspaceVisible(isActivities); + }, + }); + this.viewSelector.ease({ + opacity: isActivities ? 0 : 255, + duration: SIDE_CONTROLS_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onComplete: () => (this.viewSelector.visible = !isActivities), + }); } _onShowAppsButtonToggled() { @@ -235,10 +479,18 @@ class ControlsManager extends St.Widget { this._workspaceAdjustment.value = activeIndex; } + vfunc_unmap() { + this._workspacesDisplay.hide(); + super.vfunc_unmap(); + } + animateToOverview(state, callback) { this._ignoreShowAppsButtonToggle = true; this.viewSelector.prepareToEnterOverview(); + this._workspacesDisplay.prepareToEnterOverview(); + if (!this._workspacesDisplay.activeWorkspaceHasMaximizedWindows()) + Main.overview.fadeOutDesktop(); this._stateAdjustment.value = ControlsState.HIDDEN; this._stateAdjustment.ease(state, { @@ -259,7 +511,9 @@ class ControlsManager extends St.Widget { animateFromOverview(callback) { this._ignoreShowAppsButtonToggle = true; - this.viewSelector.prepareToLeaveOverview(); + this._workspacesDisplay.prepareToLeaveOverview(); + if (!this._workspacesDisplay.activeWorkspaceHasMaximizedWindows()) + Main.overview.fadeInDesktop(); this._stateAdjustment.ease(ControlsState.HIDDEN, { duration: Overview.ANIMATION_TIME, @@ -277,4 +531,8 @@ class ControlsManager extends St.Widget { get searchEntry() { return this._searchEntry; } + + get appDisplay() { + return this._appDisplay; + } }); diff --git a/js/ui/shellDBus.js b/js/ui/shellDBus.js index 14e449f74..3028dfb68 100644 --- a/js/ui/shellDBus.js +++ b/js/ui/shellDBus.js @@ -101,7 +101,7 @@ var GnomeShell = class { FocusApp(id) { this.ShowApplications(); - Main.overview.viewSelector.appDisplay.selectApp(id); + Main.overview.appDisplay.selectApp(id); } ShowApplications() { diff --git a/js/ui/viewSelector.js b/js/ui/viewSelector.js index a87348cd5..a5ab6ff3a 100644 --- a/js/ui/viewSelector.js +++ b/js/ui/viewSelector.js @@ -3,14 +3,10 @@ const { Clutter, GObject, Shell, St } = imports.gi; -const AppDisplay = imports.ui.appDisplay; const Main = imports.ui.main; const OverviewControls = imports.ui.overviewControls; const Search = imports.ui.search; const ShellEntry = imports.ui.shellEntry; -const Util = imports.misc.util; -const WorkspaceThumbnail = imports.ui.workspaceThumbnail; -const WorkspacesView = imports.ui.workspacesView; var ViewPage = { ACTIVITIES: 1, @@ -36,126 +32,13 @@ function getTermsForSearchString(searchString) { return terms; } - -var ActivitiesContainer = GObject.registerClass( -class ActivitiesContainer extends St.Widget { - _init(thumbnailsBox, workspacesDisplay, appDisplay, overviewAdjustment) { - super._init(); - - // 0 for window picker, 1 for app grid - this._adjustment = new St.Adjustment({ - actor: this, - value: 0, - lower: 0, - upper: 1, - }); - this._adjustment.connect('notify::value', () => { - this._update(); - this.queue_relayout(); - }); - - overviewAdjustment.connect('notify::value', () => { - const { ControlsState } = OverviewControls; - - const overviewState = overviewAdjustment.value; - - this._appDisplay.visible = - overviewState >= ControlsState.WINDOW_PICKER; - this._adjustment.value = Math.max(0, - overviewAdjustment.value - ControlsState.WINDOW_PICKER); - }); - - this._thumbnailsBox = thumbnailsBox; - this.add_child(thumbnailsBox); - - this._appDisplay = appDisplay; - this.add_child(appDisplay); - - this._workspacesDisplay = workspacesDisplay; - this.add_child(workspacesDisplay); - - this.connect('notify::mapped', () => { - workspacesDisplay.setPrimaryWorkspaceVisible(this.mapped); - }); - - this._update(); - } - - _update() { - const progress = this._adjustment.value; - - this._appDisplay.opacity = progress * 255; - - this._thumbnailsBox.set({ - scale_x: Util.lerp(1, 0.5, progress), - scale_y: Util.lerp(1, 0.5, progress), - translation_y: Util.lerp(0, this._thumbnailsBox.height, progress), - opacity: Util.lerp(0, 255, 1 - progress), - visible: (1 - progress) !== 0, - }); - - const { fitModeAdjustment } = this._workspacesDisplay; - fitModeAdjustment.value = Util.lerp( - WorkspacesView.FitMode.SINGLE, - WorkspacesView.FitMode.ALL, - progress); - } - - _getWorkspacesBoxes(box, thumbnailsHeight) { - const initialBox = box.copy(); - initialBox.y1 += thumbnailsHeight; - - const finalBox = box.copy(); - finalBox.set_size( - box.get_width(), - Math.round(box.get_height() * 0.15)); - - return [initialBox, finalBox]; - } - - vfunc_allocate(box) { - this.set_allocation(box); - - // Workspace Thumbnails - let thumbnailsHeight = 0; - if (this._thumbnailsBox.visible) { - const maxThumbnailScale = WorkspaceThumbnail.MAX_THUMBNAIL_SCALE; - const primaryMonitor = Main.layoutManager.primaryMonitor; - const [width, height] = box.get_size(); - - [, thumbnailsHeight] = - this._thumbnailsBox.get_preferred_height(width); - thumbnailsHeight = Math.min( - thumbnailsHeight, - (primaryMonitor ? primaryMonitor.height : height) * maxThumbnailScale); - - const thumbnailsBox = new Clutter.ActorBox(); - thumbnailsBox.set_origin(0, 0); - thumbnailsBox.set_size(width, thumbnailsHeight); - this._thumbnailsBox.allocate(thumbnailsBox); - } - - const progress = this._adjustment.value; - const [initialBox, finalBox] = - this._getWorkspacesBoxes(box, thumbnailsHeight); - const workspacesBox = initialBox.interpolate(finalBox, progress); - this._workspacesDisplay.allocate(workspacesBox); - - if (this._appDisplay.visible) { - const appDisplayBox = box.copy(); - appDisplayBox.y1 += Math.ceil(finalBox.get_height()); - this._appDisplay.allocate(appDisplayBox); - } - } -}); - var ViewSelector = GObject.registerClass({ Signals: { 'page-changed': {}, 'page-empty': {}, }, }, class ViewSelector extends Shell.Stack { - _init(searchEntry, workspaceAdjustment, showAppsButton, overviewAdjustment) { + _init(searchEntry, showAppsButton) { super._init({ name: 'viewSelector', x_expand: true, @@ -200,45 +83,9 @@ var ViewSelector = GObject.registerClass({ this._iconClickedId = 0; this._capturedEventId = 0; - this._thumbnailsBox = - new WorkspaceThumbnail.ThumbnailsBox(workspaceAdjustment); - this._workspacesDisplay = - new WorkspacesView.WorkspacesDisplay(workspaceAdjustment, overviewAdjustment); - this.appDisplay = new AppDisplay.AppDisplay(); - - const activitiesContainer = new ActivitiesContainer( - this._thumbnailsBox, - this._workspacesDisplay, - this.appDisplay, - overviewAdjustment); + const dummy = new St.Widget(); this._activitiesPage = - this._addPage(activitiesContainer, _('Activities'), 'view-app-grid-symbolic'); - - Main.ctrlAltTabManager.addGroup( - this.appDisplay, - _('Applications'), - 'edit-find-symbolic', { - proxy: this, - focusCallback: () => { - this._showPage(this._activitiesPage); - this._showAppsButton.checked = true; - this.appDisplay.navigate_focus( - null, St.DirectionType.TAB_FORWARD, false); - }, - }); - - Main.ctrlAltTabManager.addGroup( - this._workspacesDisplay, - _('Windows'), - 'focus-windows-symbolic', { - proxy: this, - focusCallback: () => { - this._showPage(this._activitiesPage); - this._showAppsButton.checked = false; - this._workspacesDisplay.navigate_focus( - null, St.DirectionType.TAB_FORWARD, false); - }, - }); + this._addPage(dummy, _('Activities'), 'view-app-grid-symbolic'); this._searchResults = new Search.SearchResultsView(); this._searchPage = this._addPage(this._searchResults); @@ -269,28 +116,15 @@ var ViewSelector = GObject.registerClass({ } prepareToEnterOverview() { - this.show(); this.reset(); - this._workspacesDisplay.prepareToEnterOverview(); this._activePage = null; this._showPage(this._activitiesPage); - - if (!this._workspacesDisplay.activeWorkspaceHasMaximizedWindows()) - Main.overview.fadeOutDesktop(); } - prepareToLeaveOverview() { - this._workspacesDisplay.prepareToLeaveOverview(); - - if (!this._workspacesDisplay.activeWorkspaceHasMaximizedWindows()) - Main.overview.fadeInDesktop(); - } - - vfunc_hide() { + vfunc_unmap() { this.reset(); - this._workspacesDisplay.hide(); - super.vfunc_hide(); + super.vfunc_unmap(); } _addPage(actor) { diff --git a/js/ui/workspacesView.js b/js/ui/workspacesView.js index 67936c597..dc875e5e8 100644 --- a/js/ui/workspacesView.js +++ b/js/ui/workspacesView.js @@ -503,7 +503,6 @@ var WorkspacesDisplay = GObject.registerClass( class WorkspacesDisplay extends St.Widget { _init(scrollAdjustment, overviewAdjustment) { super._init({ - visible: false, clip_to_allocation: true, layout_manager: new Clutter.BinLayout(), }); @@ -776,6 +775,7 @@ class WorkspacesDisplay extends St.Widget { if (i === this._primaryIndex) { view.visible = this._primaryVisible; + this.bind_property('opacity', view, 'opacity', GObject.BindingFlags.SYNC_CREATE); this.add_child(view); } else { const { x, y, width, height } =