diff --git a/js/ui/overviewControls.js b/js/ui/overviewControls.js index 32a92f4f1..d51404e28 100644 --- a/js/ui/overviewControls.js +++ b/js/ui/overviewControls.js @@ -1,105 +1,15 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported ControlsManager */ -const { Clutter, GObject, Meta, St } = imports.gi; +const { Clutter, GObject, St } = imports.gi; const Dash = imports.ui.dash; const Main = imports.ui.main; -const Params = imports.misc.params; const ViewSelector = imports.ui.viewSelector; -const WorkspaceThumbnail = imports.ui.workspaceThumbnail; const Overview = imports.ui.overview; var SIDE_CONTROLS_ANIMATION_TIME = Overview.ANIMATION_TIME; -function getRtlSlideDirection(direction, actor) { - let rtl = actor.text_direction == Clutter.TextDirection.RTL; - if (rtl) { - direction = direction == SlideDirection.LEFT - ? SlideDirection.RIGHT : SlideDirection.LEFT; - } - return direction; -} - -var SlideDirection = { - LEFT: 0, - RIGHT: 1, -}; - -var SlideLayout = GObject.registerClass({ - Properties: { - 'slide-x': GObject.ParamSpec.double( - 'slide-x', 'slide-x', 'slide-x', - GObject.ParamFlags.READWRITE, - 0, 1, 1), - }, -}, class SlideLayout extends Clutter.FixedLayout { - _init(params) { - this._slideX = 1; - this._direction = SlideDirection.LEFT; - - super._init(params); - } - - vfunc_get_preferred_width(container, forHeight) { - let child = container.get_first_child(); - - let [minWidth, natWidth] = child.get_preferred_width(forHeight); - - minWidth *= this._slideX; - natWidth *= this._slideX; - - return [minWidth, natWidth]; - } - - vfunc_allocate(container, box) { - let child = container.get_first_child(); - - let availWidth = Math.round(box.x2 - box.x1); - let availHeight = Math.round(box.y2 - box.y1); - let [, natWidth] = child.get_preferred_width(availHeight); - - // Align the actor inside the clipped box, as the actor's alignment - // flags only determine what to do if the allocated box is bigger - // than the actor's box. - let realDirection = getRtlSlideDirection(this._direction, child); - let alignX = realDirection == SlideDirection.LEFT - ? availWidth - natWidth - : availWidth - natWidth * this._slideX; - - let actorBox = new Clutter.ActorBox(); - actorBox.x1 = box.x1 + alignX; - actorBox.x2 = actorBox.x1 + (child.x_expand ? availWidth : natWidth); - actorBox.y1 = box.y1; - actorBox.y2 = actorBox.y1 + availHeight; - - child.allocate(actorBox); - } - - // eslint-disable-next-line camelcase - set slide_x(value) { - if (this._slideX == value) - return; - this._slideX = value; - this.notify('slide-x'); - this.layout_changed(); - } - - // eslint-disable-next-line camelcase - get slide_x() { - return this._slideX; - } - - set slideDirection(direction) { - this._direction = direction; - this.layout_changed(); - } - - get slideDirection() { - return this._direction; - } -}); - var FaderControl = GObject.registerClass( class FaderControl extends St.Widget { _init(params) { @@ -149,196 +59,6 @@ class FaderControl extends St.Widget { } }); -var SlidingControl = GObject.registerClass( -class SlidingControl extends FaderControl { - _init(params) { - params = Params.parse(params, { slideDirection: SlideDirection.LEFT }); - - this.layout = new SlideLayout(); - this.layout.slideDirection = params.slideDirection; - super._init({ - layout_manager: this.layout, - style_class: 'overview-controls', - clip_to_allocation: true, - }); - - this._visible = true; - - Main.overview.connect('hiding', this._onOverviewHiding.bind(this)); - } - - _getSlide() { - throw new GObject.NotImplementedError('_getSlide in %s'.format(this.constructor.name)); - } - - _updateSlide() { - this.ease_property('@layout.slide-x', this._getSlide(), { - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - duration: SIDE_CONTROLS_ANIMATION_TIME, - }); - } - - getVisibleWidth() { - let child = this.get_first_child(); - let [, , natWidth] = child.get_preferred_size(); - return natWidth; - } - - _getTranslation() { - let child = this.get_first_child(); - let direction = getRtlSlideDirection(this.layout.slideDirection, child); - let visibleWidth = this.getVisibleWidth(); - - if (direction == SlideDirection.LEFT) - return -visibleWidth; - else - return visibleWidth; - } - - _updateTranslation() { - let translationStart = 0; - let translationEnd = 0; - let translation = this._getTranslation(); - - let shouldShow = this._getSlide() > 0; - if (shouldShow) - translationStart = translation; - else - translationEnd = translation; - - if (this.translation_x === translationEnd) - return; - - this.translation_x = translationStart; - this.ease({ - translation_x: translationEnd, - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - duration: SIDE_CONTROLS_ANIMATION_TIME, - }); - } - - _onOverviewHiding() { - // We need to explicitly slideOut since showing pages - // doesn't imply sliding out, instead, hiding the overview does. - this.slideOut(); - } - - _onDragBegin() { - super._onDragBegin(); - this._updateTranslation(); - this._updateSlide(); - } - - _onDragEnd() { - super._onDragEnd(); - this._updateSlide(); - } - - slideIn() { - this._visible = true; - // we will update slide_x and the translation from pageEmpty - } - - slideOut() { - this._visible = false; - this._updateTranslation(); - // we will update slide_x from pageEmpty - } - - pageEmpty() { - // When pageEmpty is received, there's no visible view in the - // selector; this means we can now safely set the full slide for - // the next page, since slideIn or slideOut might have been called, - // changing the visibility - this.remove_transition('@layout.slide-x'); - this.layout.slide_x = this._getSlide(); - this._updateTranslation(); - } -}); - -var ThumbnailsSlider = GObject.registerClass( -class ThumbnailsSlider extends SlidingControl { - _init(thumbnailsBox) { - super._init({ slideDirection: SlideDirection.RIGHT }); - - this._thumbnailsBox = thumbnailsBox; - - this.request_mode = Clutter.RequestMode.WIDTH_FOR_HEIGHT; - this.reactive = true; - this.track_hover = true; - this.add_actor(this._thumbnailsBox); - - Main.layoutManager.connect('monitors-changed', this._updateSlide.bind(this)); - global.workspace_manager.connect('active-workspace-changed', - this._updateSlide.bind(this)); - global.workspace_manager.connect('notify::n-workspaces', - this._updateSlide.bind(this)); - this.connect('notify::hover', this._updateSlide.bind(this)); - this._thumbnailsBox.bind_property('visible', this, 'visible', GObject.BindingFlags.SYNC_CREATE); - } - - _getAlwaysZoomOut() { - // Always show the pager on hover, during a drag, or if workspaces are - // actually used, e.g. there are windows on any non-active workspace - let workspaceManager = global.workspace_manager; - let alwaysZoomOut = this.hover || - this._inDrag || - !Meta.prefs_get_dynamic_workspaces() || - workspaceManager.n_workspaces > 2 || - workspaceManager.get_active_workspace_index() != 0; - - if (!alwaysZoomOut) { - let monitors = Main.layoutManager.monitors; - let primary = Main.layoutManager.primaryMonitor; - - /* Look for any monitor to the right of the primary, if there is - * one, we always keep zoom out, otherwise its hard to reach - * the thumbnail area without passing into the next monitor. */ - for (let i = 0; i < monitors.length; i++) { - if (monitors[i].x >= primary.x + primary.width) { - alwaysZoomOut = true; - break; - } - } - } - - return alwaysZoomOut; - } - - getNonExpandedWidth() { - let child = this.get_first_child(); - return child.get_theme_node().get_length('visible-width'); - } - - _onDragEnd() { - this.sync_hover(); - super._onDragEnd(); - } - - _getSlide() { - if (!this._visible) - return 0; - - let alwaysZoomOut = this._getAlwaysZoomOut(); - if (alwaysZoomOut) - return 1; - - let child = this.get_first_child(); - let preferredHeight = child.get_preferred_height(-1)[1]; - let expandedWidth = child.get_preferred_width(preferredHeight)[1]; - - return this.getNonExpandedWidth() / expandedWidth; - } - - getVisibleWidth() { - let alwaysZoomOut = this._getAlwaysZoomOut(); - if (alwaysZoomOut) - return super.getVisibleWidth(); - else - return this.getNonExpandedWidth(); - } -}); - var DashFader = GObject.registerClass( class DashFader extends FaderControl { _init(dash) { @@ -391,14 +111,8 @@ class ControlsManager extends St.Widget { workspaceManager.connect('notify::n-workspaces', this._updateAdjustment.bind(this)); - this._thumbnailsBox = - new WorkspaceThumbnail.ThumbnailsBox(this._workspaceAdjustment); - this._thumbnailsSlider = new ThumbnailsSlider(this._thumbnailsBox); - this.viewSelector = new ViewSelector.ViewSelector(searchEntry, this._workspaceAdjustment, this.dash.showAppsButton); - this.viewSelector.connect('page-changed', this._setVisibility.bind(this)); - this.viewSelector.connect('page-empty', this._onPageEmpty.bind(this)); this._group = new St.BoxLayout({ name: 'overview-group', @@ -413,7 +127,6 @@ class ControlsManager extends St.Widget { y_expand: true, }); box.add_child(this.viewSelector); - box.add_child(this._thumbnailsSlider); this._group.add_child(box); this._group.add_actor(this._dashFader); @@ -437,26 +150,4 @@ class ControlsManager extends St.Widget { this._workspaceAdjustment.remove_transition('value'); this._workspaceAdjustment.value = activeIndex; } - - _setVisibility() { - // Ignore the case when we're leaving the overview, since - // actors will be made visible again when entering the overview - // next time, and animating them while doing so is just - // unnecessary noise - if (!Main.overview.visible || - (Main.overview.animationInProgress && !Main.overview.visibleTarget)) - return; - - let activePage = this.viewSelector.getActivePage(); - let thumbnailsVisible = activePage == ViewSelector.ViewPage.ACTIVITIES; - - if (thumbnailsVisible) - this._thumbnailsSlider.slideIn(); - else - this._thumbnailsSlider.slideOut(); - } - - _onPageEmpty() { - this._thumbnailsSlider.pageEmpty(); - } }); diff --git a/js/ui/viewSelector.js b/js/ui/viewSelector.js index 33b761f14..5a22cb215 100644 --- a/js/ui/viewSelector.js +++ b/js/ui/viewSelector.js @@ -9,6 +9,8 @@ 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; const EdgeDragAction = imports.ui.edgeDragAction; @@ -121,7 +123,7 @@ var ShowOverviewAction = GObject.registerClass({ var ActivitiesContainer = GObject.registerClass( class ActivitiesContainer extends St.Widget { - _init(workspacesDisplay, appDisplay, showAppsButton) { + _init(thumbnailsBox, workspacesDisplay, appDisplay, showAppsButton) { super._init(); // 0 for window picker, 1 for app grid @@ -140,6 +142,9 @@ class ActivitiesContainer extends St.Widget { showAppsButton.connect('notify::checked', this._onShowAppsButtonToggled.bind(this)); + this._thumbnailsBox = thumbnailsBox; + this.add_child(thumbnailsBox); + this._appDisplay = appDisplay; this.add_child(appDisplay); @@ -168,10 +173,19 @@ class ActivitiesContainer extends St.Widget { this._appDisplay.opacity = progress * 255; this._appDisplay.visible = progress !== 0; + + 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, + }); } - _getWorkspacesBoxes(box) { + _getWorkspacesBoxes(box, thumbnailsHeight) { const initialBox = box.copy(); + initialBox.y1 += thumbnailsHeight; const finalBox = box.copy(); finalBox.set_size( @@ -184,8 +198,28 @@ class ActivitiesContainer extends St.Widget { 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); + const [initialBox, finalBox] = + this._getWorkspacesBoxes(box, thumbnailsHeight); const workspacesBox = initialBox.interpolate(finalBox, progress); this._workspacesDisplay.allocate(workspacesBox); @@ -247,12 +281,17 @@ var ViewSelector = GObject.registerClass({ this._iconClickedId = 0; this._capturedEventId = 0; + this._thumbnailsBox = + new WorkspaceThumbnail.ThumbnailsBox(workspaceAdjustment); this._workspacesDisplay = new WorkspacesView.WorkspacesDisplay(workspaceAdjustment); this.appDisplay = new AppDisplay.AppDisplay(); const activitiesContainer = new ActivitiesContainer( - this._workspacesDisplay, this.appDisplay, showAppsButton); + this._thumbnailsBox, + this._workspacesDisplay, + this.appDisplay, + showAppsButton); this._activitiesPage = this._addPage(activitiesContainer, _('Activities'), 'view-app-grid-symbolic'); diff --git a/js/ui/workspaceThumbnail.js b/js/ui/workspaceThumbnail.js index 9d7a863ee..431f46eb6 100644 --- a/js/ui/workspaceThumbnail.js +++ b/js/ui/workspaceThumbnail.js @@ -9,7 +9,7 @@ const Main = imports.ui.main; const Workspace = imports.ui.workspace; // The maximum size of a thumbnail is 1/10 the width and height of the screen -let MAX_THUMBNAIL_SCALE = 1 / 10.; +var MAX_THUMBNAIL_SCALE = 1 / 10.; var RESCALE_ANIMATION_TIME = 200; var SLIDE_ANIMATION_TIME = 200;