812270fee3
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>
871 lines
30 KiB
JavaScript
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;
|
|
}
|
|
});
|