gnome-shell/js/ui/overviewControls.js
Florian Müllner 812270fee3 overviewControls: Subclass layout manager directly
All the ControlsManagerLayout uses from BoxLayout is the spacing
property, both size requests and allocation are completely custom.

That makes subclassing really questionable, so stop doing that
and just hook up to the `spacing` style property manually.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3104>
2024-01-10 15:26:44 +00:00

871 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.15;
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._spacing = 0;
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) {
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 + this._spacing +
thumbnailsHeight + this._spacing * expandFraction);
workspaceBox.set_size(width,
height -
dashHeight - this._spacing -
searchHeight - this._spacing -
thumbnailsHeight - this._spacing * expandFraction);
break;
case ControlsState.APP_GRID:
workspaceBox.set_origin(0, startY + searchHeight + this._spacing);
workspaceBox.set_size(
width,
Math.round(height * SMALL_WORKSPACE_RATIO));
break;
}
return workspaceBox;
}
_getAppDisplayBoxForState(state, box, searchHeight, dashHeight, appGridBox) {
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 + this._spacing + appGridBox.get_height());
break;
}
appDisplayBox.set_size(width,
height -
searchHeight - this._spacing -
appGridBox.get_height() - this._spacing -
dashHeight);
return appDisplayBox;
}
_runPostAllocation() {
if (this._postAllocationCallbacks.length === 0)
return;
this._postAllocationCallbacks.forEach(cb => cb());
this._postAllocationCallbacks = [];
}
vfunc_set_container(container) {
this._container?.disconnectObject(this);
this._container = container;
this._container?.connectObject('style-changed',
() => {
const node = this._container.get_theme_node();
const spacing = node.get_length('spacing');
if (this._spacing !== spacing) {
this._spacing = spacing;
this.layout_changed();
}
}, this);
}
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();
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 + this._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 + this._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 + this._spacing);
childBox.set_size(width, thumbnailsHeight);
this._workspacesThumbnails.allocate(childBox);
}
// Workspaces
let params = [box, searchHeight, dashHeight, thumbnailsHeight];
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];
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 + this._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.visible = thumbnailsBoxVisible),
};
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._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;
}
});