gnome-shell/js/ui/overviewControls.js
Jonas Dreßler c576f82bdf overviewControls: Make sure thumbnails expandFraction is bound to visibility
We set the expand-fraction property of the workspace thumbnails on changes
to their should-show property. The should-show property defaults to true, and
the expand-fraction property defaults to 1. The should-show property gets
updated in the constructor of the ThumbnailsBox though, and it might become
false (that is the case when starting up the shell) during construction.

In this case we're not yet listening to "notify::should-show" in the
ControlsManager, and therefore forget to update the expand-fraction
accordingly. Fix that and ensure the expandFraction is always set to the value
we expect after hiding or showing the thumbnails (we call _updateThumbnailsBox()
from _update(), which we call in the constructor).

This should be the proper fix for the problem that 9bf550da8 (which caused
the workspace thumbnails to never show) originally tried to fix.

Fixes: 9bf550da88 ("overviewControls: Set expandFraction for ws thumbnails to 0 when hidden")
Closes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/7668
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3323>
2024-06-08 02:40:38 +00:00

865 lines
30 KiB
JavaScript

// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
import GLib from 'gi://GLib';
import Clutter from 'gi://Clutter';
import Gio from 'gi://Gio';
import GObject from 'gi://GObject';
import Meta from 'gi://Meta';
import Shell from 'gi://Shell';
import St from 'gi://St';
import * as AppDisplay from './appDisplay.js';
import * as Dash from './dash.js';
import * as Layout from './layout.js';
import * as Main from './main.js';
import * as Overview from './overview.js';
import * as SearchController from './searchController.js';
import * as Util from '../misc/util.js';
import * as WindowManager from './windowManager.js';
import * as WorkspaceThumbnail from './workspaceThumbnail.js';
import * as WorkspacesView from './workspacesView.js';
export const SMALL_WORKSPACE_RATIO = 0.15;
const DASH_MAX_HEIGHT_RATIO = 0.16;
const VERTICAL_SPACING_RATIO = 0.02;
const THUMBNAILS_SPACING_ADJUSTMENT_TOP = 0.6;
const THUMBNAILS_SPACING_ADJUSTMENT_BOTTOM = 0.4;
const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard';
export const SIDE_CONTROLS_ANIMATION_TIME = 250;
/** @enum {number} */
export const ControlsState = {
HIDDEN: 0,
WINDOW_PICKER: 1,
APP_GRID: 2,
};
const ControlsManagerLayout = GObject.registerClass(
class ControlsManagerLayout extends Clutter.LayoutManager {
_init(searchEntry, appDisplay, workspacesDisplay, workspacesThumbnails,
searchController, dash, stateAdjustment) {
super._init();
this._appDisplay = appDisplay;
this._workspacesDisplay = workspacesDisplay;
this._workspacesThumbnails = workspacesThumbnails;
this._stateAdjustment = stateAdjustment;
this._searchEntry = searchEntry;
this._searchController = searchController;
this._dash = dash;
this._cachedWorkspaceBoxes = new Map();
this._postAllocationCallbacks = [];
stateAdjustment.connect('notify::value', () => this.layout_changed());
this._workAreaBox = new Clutter.ActorBox();
global.display.connectObject(
'workareas-changed', () => this._updateWorkAreaBox(),
this);
Main.layoutManager.connectObject(
'monitors-changed', () => this._updateWorkAreaBox(),
this);
this._updateWorkAreaBox();
}
_updateWorkAreaBox() {
const monitor = Main.layoutManager.primaryMonitor;
if (!monitor)
return;
const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor.index);
const startX = workArea.x - monitor.x;
const startY = workArea.y - monitor.y;
this._workAreaBox.set_origin(startX, startY);
this._workAreaBox.set_size(workArea.width, workArea.height);
}
_computeWorkspacesBoxForState(state, box, searchHeight, dashHeight, thumbnailsHeight, spacing) {
const workspaceBox = box.copy();
const [width, height] = workspaceBox.get_size();
const {y1: startY} = this._workAreaBox;
const {expandFraction} = this._workspacesThumbnails;
switch (state) {
case ControlsState.HIDDEN:
workspaceBox.set_origin(...this._workAreaBox.get_origin());
workspaceBox.set_size(...this._workAreaBox.get_size());
break;
case ControlsState.WINDOW_PICKER:
workspaceBox.set_origin(0,
startY + searchHeight + Math.round(spacing * THUMBNAILS_SPACING_ADJUSTMENT_TOP) +
thumbnailsHeight + Math.round(spacing * THUMBNAILS_SPACING_ADJUSTMENT_BOTTOM) * expandFraction);
workspaceBox.set_size(width,
height -
dashHeight - spacing -
searchHeight - Math.round(spacing * THUMBNAILS_SPACING_ADJUSTMENT_TOP) -
thumbnailsHeight - Math.round(spacing * THUMBNAILS_SPACING_ADJUSTMENT_BOTTOM) * expandFraction);
break;
case ControlsState.APP_GRID:
workspaceBox.set_origin(0, startY + searchHeight + spacing);
workspaceBox.set_size(
width,
Math.round(height * SMALL_WORKSPACE_RATIO));
break;
}
return workspaceBox;
}
_getAppDisplayBoxForState(state, box, searchHeight, dashHeight, workspacesBox, spacing) {
const [width, height] = box.get_size();
const {y1: startY} = this._workAreaBox;
const appDisplayBox = new Clutter.ActorBox();
switch (state) {
case ControlsState.HIDDEN:
case ControlsState.WINDOW_PICKER:
appDisplayBox.set_origin(0, box.y2);
break;
case ControlsState.APP_GRID:
appDisplayBox.set_origin(0,
startY + searchHeight + spacing + workspacesBox.get_height() + spacing);
break;
}
appDisplayBox.set_size(width,
height -
searchHeight - spacing -
workspacesBox.get_height() - spacing -
dashHeight - spacing);
return appDisplayBox;
}
_runPostAllocation() {
if (this._postAllocationCallbacks.length === 0)
return;
this._postAllocationCallbacks.forEach(cb => cb());
this._postAllocationCallbacks = [];
}
vfunc_get_preferred_width(_container, _forHeight) {
// The MonitorConstraint will allocate us a fixed size anyway
return [0, 0];
}
vfunc_get_preferred_height(_container, _forWidth) {
// The MonitorConstraint will allocate us a fixed size anyway
return [0, 0];
}
vfunc_allocate(container, box) {
const childBox = new Clutter.ActorBox();
const startY = this._workAreaBox.y1;
box.y1 += startY;
const [width, height] = box.get_size();
const spacing = Math.round(height * VERTICAL_SPACING_RATIO);
let availableHeight = height;
// Search entry
let [searchHeight] = this._searchEntry.get_preferred_height(width);
childBox.set_origin(0, startY);
childBox.set_size(width, searchHeight);
this._searchEntry.allocate(childBox);
availableHeight -= searchHeight + spacing;
// Dash
const maxDashHeight = Math.round(box.get_height() * DASH_MAX_HEIGHT_RATIO);
this._dash.setMaxSize(width, maxDashHeight);
let [, dashHeight] = this._dash.get_preferred_height(width);
dashHeight = Math.min(dashHeight, maxDashHeight);
childBox.set_origin(0, startY + height - dashHeight);
childBox.set_size(width, dashHeight);
this._dash.allocate(childBox);
availableHeight -= dashHeight + spacing;
// Workspace Thumbnails
let thumbnailsHeight = 0;
if (this._workspacesThumbnails.visible) {
const {expandFraction} = this._workspacesThumbnails;
[thumbnailsHeight] =
this._workspacesThumbnails.get_preferred_height(width);
thumbnailsHeight = Math.min(
thumbnailsHeight * expandFraction,
height * this._workspacesThumbnails.maxThumbnailScale);
childBox.set_origin(0, startY + searchHeight + Math.round(spacing * THUMBNAILS_SPACING_ADJUSTMENT_TOP));
childBox.set_size(width, thumbnailsHeight);
this._workspacesThumbnails.allocate(childBox);
}
// Workspaces
let params = [box, searchHeight, dashHeight, thumbnailsHeight, spacing];
const transitionParams = this._stateAdjustment.getStateTransitionParams();
// Update cached boxes
for (const state of Object.values(ControlsState)) {
this._cachedWorkspaceBoxes.set(
state, this._computeWorkspacesBoxForState(state, ...params));
}
let workspacesBox;
if (!transitionParams.transitioning) {
workspacesBox = this._cachedWorkspaceBoxes.get(transitionParams.currentState);
} else {
const initialBox = this._cachedWorkspaceBoxes.get(transitionParams.initialState);
const finalBox = this._cachedWorkspaceBoxes.get(transitionParams.finalState);
workspacesBox = initialBox.interpolate(finalBox, transitionParams.progress);
}
this._workspacesDisplay.allocate(workspacesBox);
// AppDisplay
if (this._appDisplay.visible) {
const workspaceAppGridBox =
this._cachedWorkspaceBoxes.get(ControlsState.APP_GRID);
params = [box, searchHeight, dashHeight, workspaceAppGridBox, spacing];
let appDisplayBox;
if (!transitionParams.transitioning) {
appDisplayBox =
this._getAppDisplayBoxForState(transitionParams.currentState, ...params);
} else {
const initialBox =
this._getAppDisplayBoxForState(transitionParams.initialState, ...params);
const finalBox =
this._getAppDisplayBoxForState(transitionParams.finalState, ...params);
appDisplayBox = initialBox.interpolate(finalBox, transitionParams.progress);
}
this._appDisplay.allocate(appDisplayBox);
}
// Search
childBox.set_origin(0, startY + searchHeight + spacing);
childBox.set_size(width, availableHeight);
this._searchController.allocate(childBox);
this._runPostAllocation();
}
ensureAllocation() {
this.layout_changed();
return new Promise(
resolve => this._postAllocationCallbacks.push(resolve));
}
getWorkspacesBoxForState(state) {
return this._cachedWorkspaceBoxes.get(state);
}
});
export const OverviewAdjustment = GObject.registerClass({
Properties: {
'gesture-in-progress': GObject.ParamSpec.boolean(
'gesture-in-progress', 'Gesture in progress', 'Gesture in progress',
GObject.ParamFlags.READWRITE,
false),
},
}, class OverviewAdjustment extends St.Adjustment {
_init(actor) {
super._init({
actor,
value: ControlsState.WINDOW_PICKER,
lower: ControlsState.HIDDEN,
upper: ControlsState.APP_GRID,
});
}
getStateTransitionParams() {
const currentState = this.value;
const transition = this.get_transition('value');
let initialState = transition
? transition.get_interval().peek_initial_value()
: currentState;
let finalState = transition
? transition.get_interval().peek_final_value()
: currentState;
if (initialState > finalState) {
initialState = Math.ceil(initialState);
finalState = Math.floor(finalState);
} else {
initialState = Math.floor(initialState);
finalState = Math.ceil(finalState);
}
const length = Math.abs(finalState - initialState);
const progress = length > 0
? Math.abs((currentState - initialState) / length)
: 1;
return {
transitioning: transition !== null || this.gestureInProgress,
currentState,
initialState,
finalState,
progress,
};
}
});
export const ControlsManager = GObject.registerClass(
class ControlsManager extends St.Widget {
_init() {
super._init({
style_class: 'controls-manager',
x_expand: true,
y_expand: true,
clip_to_allocation: true,
});
this._ignoreShowAppsButtonToggle = false;
this._searchEntry = new St.Entry({
style_class: 'search-entry',
/* Translators: this is the text displayed
in the search entry when no search is
active; it should not exceed ~30
characters. */
hint_text: _('Type to search'),
track_hover: true,
can_focus: true,
});
this._searchEntry.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
this._searchEntryBin = new St.Bin({
child: this._searchEntry,
x_align: Clutter.ActorAlign.CENTER,
});
this.dash = new Dash.Dash();
this._workspaceAdjustment = Main.createWorkspacesAdjustment(this);
this._stateAdjustment = new OverviewAdjustment(this);
this._stateAdjustment.connect('notify::value', this._update.bind(this));
this._searchController = new SearchController.SearchController(
this._searchEntry,
this.dash.showAppsButton);
this._searchController.connect('notify::search-active', this._onSearchChanged.bind(this));
Main.layoutManager.connect('monitors-changed', () => {
this._thumbnailsBox.setMonitorIndex(Main.layoutManager.primaryIndex);
});
this._thumbnailsBox = new WorkspaceThumbnail.ThumbnailsBox(
this._workspaceAdjustment, Main.layoutManager.primaryIndex);
this._thumbnailsBox.connect('notify::should-show', () => {
this._thumbnailsBox.show();
this._thumbnailsBox.ease_property('expand-fraction',
this._thumbnailsBox.should_show ? 1 : 0, {
duration: SIDE_CONTROLS_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => this._updateThumbnailsBox(),
});
});
this._workspacesDisplay = new WorkspacesView.WorkspacesDisplay(
this,
this._workspaceAdjustment,
this._stateAdjustment);
this._appDisplay = new AppDisplay.AppDisplay();
this.add_child(this._searchEntryBin);
this.add_child(this._appDisplay);
this.add_child(this.dash);
this.add_child(this._searchController);
this.add_child(this._thumbnailsBox);
this.add_child(this._workspacesDisplay);
this.layout_manager = new ControlsManagerLayout(
this._searchEntryBin,
this._appDisplay,
this._workspacesDisplay,
this._thumbnailsBox,
this._searchController,
this.dash,
this._stateAdjustment);
this.dash.showAppsButton.connect('notify::checked',
this._onShowAppsButtonToggled.bind(this));
Main.ctrlAltTabManager.addGroup(
this.appDisplay,
_('Apps'),
'shell-focus-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'),
'shell-focus-windows-symbolic', {
proxy: this,
focusCallback: () => {
this.dash.showAppsButton.checked = false;
this._workspacesDisplay.navigate_focus(
null, St.DirectionType.TAB_FORWARD, false);
},
});
this._a11ySettings = new Gio.Settings({schema_id: A11Y_SCHEMA});
this._lastOverlayKeyTime = 0;
global.display.connect('overlay-key', () => {
if (this._a11ySettings.get_boolean('stickykeys-enable'))
return;
const {initialState, finalState, transitioning} =
this._stateAdjustment.getStateTransitionParams();
const time = GLib.get_monotonic_time() / 1000;
const timeDiff = time - this._lastOverlayKeyTime;
this._lastOverlayKeyTime = time;
const shouldShift = St.Settings.get().enable_animations
? transitioning && finalState > initialState
: Main.overview.visible && timeDiff < Overview.ANIMATION_TIME;
if (shouldShift)
this._shiftState(Meta.MotionDirection.UP);
else
Main.overview.toggle();
});
// connect_after to give search controller first dibs on the event
global.stage.connect_after('key-press-event', (actor, event) => {
if (this._searchController.searchActive)
return Clutter.EVENT_PROPAGATE;
if (global.stage.key_focus &&
!this.contains(global.stage.key_focus))
return Clutter.EVENT_PROPAGATE;
const {finalState} =
this._stateAdjustment.getStateTransitionParams();
let keynavDisplay;
if (finalState === ControlsState.WINDOW_PICKER)
keynavDisplay = this._workspacesDisplay;
else if (finalState === ControlsState.APP_GRID)
keynavDisplay = this._appDisplay;
if (!keynavDisplay)
return Clutter.EVENT_PROPAGATE;
const symbol = event.get_key_symbol();
if (symbol === Clutter.KEY_Tab || symbol === Clutter.KEY_Down) {
keynavDisplay.navigate_focus(
null, St.DirectionType.TAB_FORWARD, false);
return Clutter.EVENT_STOP;
} else if (symbol === Clutter.KEY_ISO_Left_Tab) {
keynavDisplay.navigate_focus(
null, St.DirectionType.TAB_BACKWARD, false);
return Clutter.EVENT_STOP;
}
return Clutter.EVENT_PROPAGATE;
});
Main.wm.addKeybinding(
'toggle-application-view',
new Gio.Settings({schema_id: WindowManager.SHELL_KEYBINDINGS_SCHEMA}),
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW,
this._toggleAppsPage.bind(this));
Main.wm.addKeybinding('shift-overview-up',
new Gio.Settings({schema_id: WindowManager.SHELL_KEYBINDINGS_SCHEMA}),
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW,
() => this._shiftState(Meta.MotionDirection.UP));
Main.wm.addKeybinding('shift-overview-down',
new Gio.Settings({schema_id: WindowManager.SHELL_KEYBINDINGS_SCHEMA}),
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW,
() => this._shiftState(Meta.MotionDirection.DOWN));
this._update();
this.connect('destroy', this._onDestroy.bind(this));
}
_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 {shouldShow} = this._thumbnailsBox;
const {searchActive} = this._searchController;
const [opacity, scale, translationY] = this._getThumbnailsBoxParams();
const thumbnailsBoxVisible = shouldShow && !searchActive && opacity !== 0;
if (thumbnailsBoxVisible) {
this._thumbnailsBox.opacity = 0;
this._thumbnailsBox.visible = thumbnailsBoxVisible;
}
const params = {
opacity: searchActive ? 0 : opacity,
duration: animate ? SIDE_CONTROLS_ANIMATION_TIME : 0,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
this._thumbnailsBox.set({
visible: thumbnailsBoxVisible,
expandFraction: thumbnailsBoxVisible ? 1.0 : 0.0,
});
},
};
if (!searchActive) {
params.scale_x = scale;
params.scale_y = scale;
params.translation_y = translationY;
}
this._thumbnailsBox.ease(params);
}
_updateAppDisplayVisibility(stateTransitionParams = null) {
if (!stateTransitionParams)
stateTransitionParams = this._stateAdjustment.getStateTransitionParams();
const {initialState, finalState} = stateTransitionParams;
const state = Math.max(initialState, finalState);
this._appDisplay.visible =
state > ControlsState.WINDOW_PICKER &&
!this._searchController.searchActive;
}
_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();
this._updateAppDisplayVisibility(params);
}
_onSearchChanged() {
const {searchActive} = this._searchController;
if (!searchActive) {
this._updateAppDisplayVisibility();
this._workspacesDisplay.reactive = true;
this._workspacesDisplay.setPrimaryWorkspaceVisible(true);
} else {
this._searchController.show();
}
this._updateThumbnailsBox(true);
this._appDisplay.ease({
opacity: searchActive ? 0 : 255,
duration: SIDE_CONTROLS_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => this._updateAppDisplayVisibility(),
});
this._workspacesDisplay.ease({
opacity: searchActive ? 0 : 255,
duration: SIDE_CONTROLS_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
this._workspacesDisplay.reactive = !searchActive;
this._workspacesDisplay.setPrimaryWorkspaceVisible(!searchActive);
},
});
this._searchController.ease({
opacity: searchActive ? 255 : 0,
duration: SIDE_CONTROLS_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => (this._searchController.visible = searchActive),
});
}
_onShowAppsButtonToggled() {
if (this._ignoreShowAppsButtonToggle)
return;
const checked = this.dash.showAppsButton.checked;
const value = checked
? ControlsState.APP_GRID : ControlsState.WINDOW_PICKER;
this._stateAdjustment.remove_transition('value');
this._stateAdjustment.ease(value, {
duration: SIDE_CONTROLS_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
}
_toggleAppsPage() {
if (Main.overview.visible) {
const checked = this.dash.showAppsButton.checked;
this.dash.showAppsButton.checked = !checked;
} else {
Main.overview.show(ControlsState.APP_GRID);
}
}
_shiftState(direction) {
let {currentState, finalState} = this._stateAdjustment.getStateTransitionParams();
if (direction === Meta.MotionDirection.DOWN)
finalState = Math.max(finalState - 1, ControlsState.HIDDEN);
else if (direction === Meta.MotionDirection.UP)
finalState = Math.min(finalState + 1, ControlsState.APP_GRID);
if (finalState === currentState)
return;
if (currentState === ControlsState.HIDDEN &&
finalState === ControlsState.WINDOW_PICKER) {
Main.overview.show();
} else if (finalState === ControlsState.HIDDEN) {
Main.overview.hide();
} else {
this._stateAdjustment.ease(finalState, {
duration: SIDE_CONTROLS_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
this.dash.showAppsButton.checked =
finalState === ControlsState.APP_GRID;
},
});
}
}
vfunc_unmap() {
super.vfunc_unmap();
this._workspacesDisplay?.hide();
}
_onDestroy() {
delete this._searchEntryBin;
delete this._appDisplay;
delete this.dash;
delete this._searchController;
delete this._thumbnailsBox;
delete this._workspacesDisplay;
}
prepareToEnterOverview() {
this._searchController.prepareToEnterOverview();
this._workspacesDisplay.prepareToEnterOverview();
}
prepareToLeaveOverview() {
this._searchController.prepareToLeaveOverview();
this._workspacesDisplay.prepareToLeaveOverview();
}
animateToOverview(state, callback) {
this._ignoreShowAppsButtonToggle = true;
this._stateAdjustment.value = ControlsState.HIDDEN;
this._stateAdjustment.ease(state, {
duration: Overview.ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onStopped: () => {
if (callback)
callback();
},
});
this.dash.showAppsButton.checked =
state === ControlsState.APP_GRID;
this._ignoreShowAppsButtonToggle = false;
}
animateFromOverview(callback) {
this._ignoreShowAppsButtonToggle = true;
this._stateAdjustment.ease(ControlsState.HIDDEN, {
duration: Overview.ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onStopped: () => {
this.dash.showAppsButton.checked = false;
this._ignoreShowAppsButtonToggle = false;
if (callback)
callback();
},
});
}
getWorkspacesBoxForState(state) {
return this.layoutManager.getWorkspacesBoxForState(state);
}
gestureBegin(tracker) {
const baseDistance = global.screen_height;
const progress = this._stateAdjustment.value;
const points = [
ControlsState.HIDDEN,
ControlsState.WINDOW_PICKER,
ControlsState.APP_GRID,
];
const transition = this._stateAdjustment.get_transition('value');
const cancelProgress = transition
? transition.get_interval().peek_final_value()
: Math.round(progress);
this._stateAdjustment.remove_transition('value');
tracker.confirmSwipe(baseDistance, points, progress, cancelProgress);
this.prepareToEnterOverview();
this._stateAdjustment.gestureInProgress = true;
}
gestureProgress(progress) {
this._stateAdjustment.value = progress;
}
gestureEnd(target, duration, onComplete) {
if (target === ControlsState.HIDDEN)
this.prepareToLeaveOverview();
this.dash.showAppsButton.checked =
target === ControlsState.APP_GRID;
this._stateAdjustment.remove_transition('value');
this._stateAdjustment.ease(target, {
duration,
mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
onComplete,
});
this._stateAdjustment.gestureInProgress = false;
}
async runStartupAnimation(callback) {
this._ignoreShowAppsButtonToggle = true;
this.prepareToEnterOverview();
this._stateAdjustment.value = ControlsState.HIDDEN;
this._stateAdjustment.ease(ControlsState.WINDOW_PICKER, {
duration: Overview.ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
this.dash.showAppsButton.checked = false;
this._ignoreShowAppsButtonToggle = false;
// Set the opacity here to avoid a 1-frame flicker
this.opacity = 0;
// We can't run the animation before the first allocation happens
await this.layout_manager.ensureAllocation();
const {STARTUP_ANIMATION_TIME} = Layout;
// Opacity
this.ease({
opacity: 255,
duration: STARTUP_ANIMATION_TIME,
mode: Clutter.AnimationMode.LINEAR,
});
// Search bar falls from the ceiling
const {primaryMonitor} = Main.layoutManager;
const [, y] = this._searchEntryBin.get_transformed_position();
const yOffset = y - primaryMonitor.y;
this._searchEntryBin.translation_y = -(yOffset + this._searchEntryBin.height);
this._searchEntryBin.ease({
translation_y: 0,
duration: STARTUP_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
// The Dash rises from the bottom. This is the last animation to finish,
// so run the callback there.
this.dash.translation_y = this.dash.height + this.dash.margin_bottom;
this.dash.ease({
translation_y: 0,
delay: STARTUP_ANIMATION_TIME,
duration: STARTUP_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => callback(),
});
}
get searchController() {
return this._searchController;
}
get searchEntry() {
return this._searchEntry;
}
get appDisplay() {
return this._appDisplay;
}
});