2013-02-14 14:41:38 -05:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
2019-01-31 09:07:06 -05:00
|
|
|
/* exported ControlsManager */
|
2013-02-14 14:41:38 -05:00
|
|
|
|
2021-01-02 15:41:54 -05:00
|
|
|
const { Clutter, Gio, GObject, Meta, Shell, St } = imports.gi;
|
2013-02-14 14:41:38 -05:00
|
|
|
|
2021-01-14 18:23:15 -05:00
|
|
|
const AppDisplay = imports.ui.appDisplay;
|
2013-02-25 18:05:45 -05:00
|
|
|
const Dash = imports.ui.dash;
|
2021-01-02 15:41:54 -05:00
|
|
|
const Main = imports.ui.main;
|
2020-12-30 15:45:09 -05:00
|
|
|
const Overview = imports.ui.overview;
|
2021-01-15 07:59:58 -05:00
|
|
|
const SearchController = imports.ui.searchController;
|
2021-01-14 18:23:15 -05:00
|
|
|
const Util = imports.misc.util;
|
2021-01-02 15:41:54 -05:00
|
|
|
const WindowManager = imports.ui.windowManager;
|
2021-01-14 18:23:15 -05:00
|
|
|
const WorkspaceThumbnail = imports.ui.workspaceThumbnail;
|
|
|
|
const WorkspacesView = imports.ui.workspacesView;
|
|
|
|
|
|
|
|
const SMALL_WORKSPACE_RATIO = 0.15;
|
2021-01-22 08:36:37 -05:00
|
|
|
const DASH_MAX_HEIGHT_RATIO = 0.15;
|
2013-02-15 18:25:36 -05:00
|
|
|
|
2020-12-30 15:45:09 -05:00
|
|
|
var SIDE_CONTROLS_ANIMATION_TIME = Overview.ANIMATION_TIME;
|
|
|
|
|
2020-12-30 15:32:03 -05:00
|
|
|
var ControlsState = {
|
|
|
|
HIDDEN: 0,
|
|
|
|
WINDOW_PICKER: 1,
|
|
|
|
APP_GRID: 2,
|
|
|
|
};
|
|
|
|
|
2020-12-30 15:16:18 -05:00
|
|
|
var ControlsManagerLayout = GObject.registerClass(
|
|
|
|
class ControlsManagerLayout extends Clutter.BoxLayout {
|
2021-01-14 18:23:15 -05:00
|
|
|
_init(searchEntry, appDisplay, workspacesDisplay, workspacesThumbnails,
|
2021-01-15 07:59:58 -05:00
|
|
|
searchController, dash, stateAdjustment) {
|
2020-12-30 15:16:18 -05:00
|
|
|
super._init({ orientation: Clutter.Orientation.VERTICAL });
|
|
|
|
|
2021-01-14 18:23:15 -05:00
|
|
|
this._appDisplay = appDisplay;
|
|
|
|
this._workspacesDisplay = workspacesDisplay;
|
|
|
|
this._workspacesThumbnails = workspacesThumbnails;
|
2020-12-30 15:32:03 -05:00
|
|
|
this._stateAdjustment = stateAdjustment;
|
2020-12-30 15:16:18 -05:00
|
|
|
this._searchEntry = searchEntry;
|
2021-01-15 07:59:58 -05:00
|
|
|
this._searchController = searchController;
|
2020-12-30 15:16:18 -05:00
|
|
|
this._dash = dash;
|
2020-12-30 15:32:03 -05:00
|
|
|
|
2021-02-10 16:09:04 -05:00
|
|
|
this._cachedWorkspaceBoxes = new Map();
|
|
|
|
|
2020-12-30 15:32:03 -05:00
|
|
|
stateAdjustment.connect('notify::value', () => this.layout_changed());
|
2020-12-30 15:16:18 -05:00
|
|
|
}
|
|
|
|
|
workspacesView: Interpolate against relative workspace boxes
The overview transition consists of getting the initial and final
states of the overview adjustment, derivating various other internal
states from them (such as the fit mode, opacities, translations, etc),
and finally interpolating the allocation boxes.
When interpolating between the fit mode, WorkspacesView uses the current
allocation box to derivate the SINGLE and ALL fit mode boxes. However,
that creates a curved path during overview transitions. What we really
want to do here is calculate the fit mode box relative to the corresponding
overview state. For example:
+----------------+----------+------------------------+
| Overview State | Fit Mode | Workspaces geometry |
+----------------+----------+------------------------+
| HIDDEN | SINGLE | Cover entire screen |
| WINDOW PICKER | SINGLE | Between minimap & Dash |
| APP GRID | ALL | 15% screen height |
+----------------+----------+------------------------+
Using the table above as the reference, when the overview transitions
between WINDOW PICKER and APP GRID, we must interpolate between
(SINGLE fit mode @ between minimap & Dash) and (ALL fit mode @ 15% screen
height). That way, we always interpolate the final boxes, which corrects
the odd path that workspaces follow during this transition.
Make the WorkspacesView of the primary monitor use these cached boxes
when the overview is in the middle of a transition, and the fit modes of
the initial and final state differ, to calculate the workspaces positions.
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1624>
2021-02-02 14:34:59 -05:00
|
|
|
_computeWorkspacesBoxForState(state, box, searchHeight, dashHeight, thumbnailsHeight) {
|
2021-01-14 18:23:15 -05:00
|
|
|
const workspaceBox = box.copy();
|
|
|
|
const [width, height] = workspaceBox.get_size();
|
|
|
|
const { spacing } = this;
|
2021-02-16 16:03:29 -05:00
|
|
|
const { expandFraction } = this._workspacesThumbnails;
|
2021-01-14 18:23:15 -05:00
|
|
|
|
|
|
|
switch (state) {
|
|
|
|
case ControlsState.HIDDEN:
|
|
|
|
break;
|
|
|
|
case ControlsState.WINDOW_PICKER:
|
|
|
|
workspaceBox.set_origin(0,
|
|
|
|
searchHeight + spacing +
|
2021-02-16 16:03:29 -05:00
|
|
|
thumbnailsHeight + spacing * expandFraction);
|
2021-01-14 18:23:15 -05:00
|
|
|
workspaceBox.set_size(width,
|
|
|
|
height -
|
|
|
|
dashHeight - spacing -
|
|
|
|
searchHeight - spacing -
|
2021-02-16 16:03:29 -05:00
|
|
|
thumbnailsHeight - spacing * expandFraction);
|
2021-01-14 18:23:15 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-01-20 17:25:13 -05:00
|
|
|
_getAppDisplayBoxForState(state, box, searchHeight, dashHeight, appGridBox) {
|
|
|
|
const [width, height] = box.get_size();
|
|
|
|
const appDisplayBox = new Clutter.ActorBox();
|
|
|
|
const { spacing } = this;
|
|
|
|
|
|
|
|
switch (state) {
|
|
|
|
case ControlsState.HIDDEN:
|
|
|
|
case ControlsState.WINDOW_PICKER:
|
|
|
|
appDisplayBox.set_origin(0, box.y2);
|
|
|
|
break;
|
|
|
|
case ControlsState.APP_GRID:
|
|
|
|
appDisplayBox.set_origin(0,
|
|
|
|
searchHeight + spacing + appGridBox.get_height());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
appDisplayBox.set_size(width,
|
|
|
|
height -
|
|
|
|
searchHeight - spacing -
|
|
|
|
appGridBox.get_height() - spacing -
|
|
|
|
dashHeight);
|
|
|
|
|
|
|
|
return appDisplayBox;
|
|
|
|
}
|
|
|
|
|
2020-12-30 15:16:18 -05:00
|
|
|
vfunc_set_container(container) {
|
|
|
|
this._container = container;
|
|
|
|
this.hookup_style(container);
|
|
|
|
}
|
|
|
|
|
|
|
|
vfunc_allocate(container, box) {
|
|
|
|
const childBox = new Clutter.ActorBox();
|
|
|
|
|
|
|
|
const { spacing } = this;
|
|
|
|
|
|
|
|
const [width, height] = box.get_size();
|
|
|
|
let availableHeight = height;
|
|
|
|
|
|
|
|
// Search entry
|
|
|
|
const [searchHeight] = this._searchEntry.get_preferred_height(width);
|
|
|
|
childBox.set_origin(0, 0);
|
|
|
|
childBox.set_size(width, searchHeight);
|
|
|
|
this._searchEntry.allocate(childBox);
|
|
|
|
|
|
|
|
availableHeight -= searchHeight + spacing;
|
|
|
|
|
|
|
|
// Dash
|
2021-01-22 08:36:37 -05:00
|
|
|
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);
|
2020-12-30 15:16:18 -05:00
|
|
|
childBox.set_origin(0, height - dashHeight);
|
|
|
|
childBox.set_size(width, dashHeight);
|
|
|
|
this._dash.allocate(childBox);
|
|
|
|
|
|
|
|
availableHeight -= dashHeight + spacing;
|
|
|
|
|
2021-01-14 18:23:15 -05:00
|
|
|
// Workspace Thumbnails
|
|
|
|
let thumbnailsHeight = 0;
|
|
|
|
if (this._workspacesThumbnails.visible) {
|
2021-02-16 16:03:29 -05:00
|
|
|
const { expandFraction } = this._workspacesThumbnails;
|
2021-01-14 18:23:15 -05:00
|
|
|
[thumbnailsHeight] =
|
|
|
|
this._workspacesThumbnails.get_preferred_height(width);
|
|
|
|
thumbnailsHeight = Math.min(
|
2021-02-16 16:03:29 -05:00
|
|
|
thumbnailsHeight * expandFraction,
|
2021-01-14 18:23:15 -05:00
|
|
|
height * WorkspaceThumbnail.MAX_THUMBNAIL_SCALE);
|
|
|
|
childBox.set_origin(0, searchHeight + spacing);
|
|
|
|
childBox.set_size(width, thumbnailsHeight);
|
|
|
|
this._workspacesThumbnails.allocate(childBox);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Workspaces
|
2021-01-20 17:25:13 -05:00
|
|
|
let params = [box, searchHeight, dashHeight, thumbnailsHeight];
|
2021-01-14 18:23:15 -05:00
|
|
|
const transitionParams = this._stateAdjustment.getStateTransitionParams();
|
|
|
|
|
2021-02-10 16:09:04 -05:00
|
|
|
// Update cached boxes
|
|
|
|
for (const state of Object.values(ControlsState)) {
|
|
|
|
this._cachedWorkspaceBoxes.set(
|
workspacesView: Interpolate against relative workspace boxes
The overview transition consists of getting the initial and final
states of the overview adjustment, derivating various other internal
states from them (such as the fit mode, opacities, translations, etc),
and finally interpolating the allocation boxes.
When interpolating between the fit mode, WorkspacesView uses the current
allocation box to derivate the SINGLE and ALL fit mode boxes. However,
that creates a curved path during overview transitions. What we really
want to do here is calculate the fit mode box relative to the corresponding
overview state. For example:
+----------------+----------+------------------------+
| Overview State | Fit Mode | Workspaces geometry |
+----------------+----------+------------------------+
| HIDDEN | SINGLE | Cover entire screen |
| WINDOW PICKER | SINGLE | Between minimap & Dash |
| APP GRID | ALL | 15% screen height |
+----------------+----------+------------------------+
Using the table above as the reference, when the overview transitions
between WINDOW PICKER and APP GRID, we must interpolate between
(SINGLE fit mode @ between minimap & Dash) and (ALL fit mode @ 15% screen
height). That way, we always interpolate the final boxes, which corrects
the odd path that workspaces follow during this transition.
Make the WorkspacesView of the primary monitor use these cached boxes
when the overview is in the middle of a transition, and the fit modes of
the initial and final state differ, to calculate the workspaces positions.
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1624>
2021-02-02 14:34:59 -05:00
|
|
|
state, this._computeWorkspacesBoxForState(state, ...params));
|
2021-02-10 16:09:04 -05:00
|
|
|
}
|
|
|
|
|
2021-01-14 18:23:15 -05:00
|
|
|
let workspacesBox;
|
|
|
|
if (!transitionParams.transitioning) {
|
2021-02-10 16:09:04 -05:00
|
|
|
workspacesBox = this._cachedWorkspaceBoxes.get(transitionParams.currentState);
|
2021-01-14 18:23:15 -05:00
|
|
|
} else {
|
2021-02-10 16:09:04 -05:00
|
|
|
const initialBox = this._cachedWorkspaceBoxes.get(transitionParams.initialState);
|
|
|
|
const finalBox = this._cachedWorkspaceBoxes.get(transitionParams.finalState);
|
2021-01-14 18:23:15 -05:00
|
|
|
workspacesBox = initialBox.interpolate(finalBox, transitionParams.progress);
|
|
|
|
}
|
|
|
|
|
|
|
|
this._workspacesDisplay.allocate(workspacesBox);
|
|
|
|
|
|
|
|
// AppDisplay
|
|
|
|
const workspaceAppGridBox =
|
2021-02-10 16:09:04 -05:00
|
|
|
this._cachedWorkspaceBoxes.get(ControlsState.APP_GRID);
|
2021-01-14 18:23:15 -05:00
|
|
|
|
2021-01-20 17:25:13 -05:00
|
|
|
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);
|
2021-01-14 18:23:15 -05:00
|
|
|
|
2021-01-20 17:25:13 -05:00
|
|
|
appDisplayBox = initialBox.interpolate(finalBox, transitionParams.progress);
|
|
|
|
}
|
|
|
|
this._appDisplay.allocate(appDisplayBox);
|
2020-12-30 15:32:03 -05:00
|
|
|
|
2021-01-15 07:59:58 -05:00
|
|
|
// Search
|
2020-12-30 15:16:18 -05:00
|
|
|
childBox.set_origin(0, searchHeight + spacing);
|
|
|
|
childBox.set_size(width, availableHeight);
|
2020-12-30 15:32:03 -05:00
|
|
|
|
2021-01-15 07:59:58 -05:00
|
|
|
this._searchController.allocate(childBox);
|
2020-12-30 15:16:18 -05:00
|
|
|
}
|
workspacesView: Interpolate against relative workspace boxes
The overview transition consists of getting the initial and final
states of the overview adjustment, derivating various other internal
states from them (such as the fit mode, opacities, translations, etc),
and finally interpolating the allocation boxes.
When interpolating between the fit mode, WorkspacesView uses the current
allocation box to derivate the SINGLE and ALL fit mode boxes. However,
that creates a curved path during overview transitions. What we really
want to do here is calculate the fit mode box relative to the corresponding
overview state. For example:
+----------------+----------+------------------------+
| Overview State | Fit Mode | Workspaces geometry |
+----------------+----------+------------------------+
| HIDDEN | SINGLE | Cover entire screen |
| WINDOW PICKER | SINGLE | Between minimap & Dash |
| APP GRID | ALL | 15% screen height |
+----------------+----------+------------------------+
Using the table above as the reference, when the overview transitions
between WINDOW PICKER and APP GRID, we must interpolate between
(SINGLE fit mode @ between minimap & Dash) and (ALL fit mode @ 15% screen
height). That way, we always interpolate the final boxes, which corrects
the odd path that workspaces follow during this transition.
Make the WorkspacesView of the primary monitor use these cached boxes
when the overview is in the middle of a transition, and the fit modes of
the initial and final state differ, to calculate the workspaces positions.
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1624>
2021-02-02 14:34:59 -05:00
|
|
|
|
|
|
|
getWorkspacesBoxForState(state) {
|
|
|
|
return this._cachedWorkspaceBoxes.get(state);
|
|
|
|
}
|
2020-12-30 15:16:18 -05:00
|
|
|
});
|
|
|
|
|
2021-02-11 18:20:31 -05:00
|
|
|
var 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 {
|
2021-01-15 13:28:23 -05:00
|
|
|
_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 {
|
2021-02-11 18:20:31 -05:00
|
|
|
transitioning: transition !== null || this.gestureInProgress,
|
2021-01-15 13:28:23 -05:00
|
|
|
currentState,
|
|
|
|
initialState,
|
|
|
|
finalState,
|
|
|
|
progress,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
var ControlsManager = GObject.registerClass(
|
|
|
|
class ControlsManager extends St.Widget {
|
2020-12-30 15:02:14 -05:00
|
|
|
_init() {
|
2019-07-16 05:24:13 -04:00
|
|
|
super._init({
|
2020-12-30 15:16:18 -05:00
|
|
|
style_class: 'controls-manager',
|
2019-07-16 05:24:13 -04:00
|
|
|
x_expand: true,
|
|
|
|
y_expand: true,
|
2019-08-20 17:43:54 -04:00
|
|
|
clip_to_allocation: true,
|
2019-07-16 05:24:13 -04:00
|
|
|
});
|
|
|
|
|
2021-01-01 12:00:30 -05:00
|
|
|
this._ignoreShowAppsButtonToggle = false;
|
|
|
|
|
2020-12-30 15:02:14 -05:00
|
|
|
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);
|
|
|
|
const searchEntryBin = new St.Bin({
|
|
|
|
child: this._searchEntry,
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
});
|
|
|
|
|
2013-02-25 18:05:45 -05:00
|
|
|
this.dash = new Dash.Dash();
|
|
|
|
|
2019-07-08 04:03:20 -04:00
|
|
|
let workspaceManager = global.workspace_manager;
|
|
|
|
let activeWorkspaceIndex = workspaceManager.get_active_workspace_index();
|
|
|
|
|
|
|
|
this._workspaceAdjustment = new St.Adjustment({
|
2020-06-16 16:03:07 -04:00
|
|
|
actor: this,
|
2019-07-08 04:03:20 -04:00
|
|
|
value: activeWorkspaceIndex,
|
|
|
|
lower: 0,
|
|
|
|
page_increment: 1,
|
|
|
|
page_size: 1,
|
|
|
|
step_increment: 0,
|
|
|
|
upper: workspaceManager.n_workspaces,
|
|
|
|
});
|
|
|
|
|
2021-01-15 13:28:23 -05:00
|
|
|
this._stateAdjustment = new OverviewAdjustment(this);
|
2021-01-14 18:23:15 -05:00
|
|
|
this._stateAdjustment.connect('notify::value', this._update.bind(this));
|
2020-12-30 15:32:03 -05:00
|
|
|
|
2019-07-08 04:03:20 -04:00
|
|
|
this._nWorkspacesNotifyId =
|
|
|
|
workspaceManager.connect('notify::n-workspaces',
|
|
|
|
this._updateAdjustment.bind(this));
|
|
|
|
|
2021-01-15 07:59:58 -05:00
|
|
|
this._searchController = new SearchController.SearchController(
|
|
|
|
this._searchEntry,
|
2021-01-14 18:23:15 -05:00
|
|
|
this.dash.showAppsButton);
|
2021-01-15 07:59:58 -05:00
|
|
|
this._searchController.connect('notify::search-active', this._onSearchChanged.bind(this));
|
2021-01-14 18:23:15 -05:00
|
|
|
|
|
|
|
this._thumbnailsBox =
|
|
|
|
new WorkspaceThumbnail.ThumbnailsBox(this._workspaceAdjustment);
|
2021-02-16 16:03:29 -05:00
|
|
|
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(),
|
|
|
|
});
|
|
|
|
});
|
2021-02-16 14:40:43 -05:00
|
|
|
|
2021-01-14 18:23:15 -05:00
|
|
|
this._workspacesDisplay = new WorkspacesView.WorkspacesDisplay(
|
workspacesView: Interpolate against relative workspace boxes
The overview transition consists of getting the initial and final
states of the overview adjustment, derivating various other internal
states from them (such as the fit mode, opacities, translations, etc),
and finally interpolating the allocation boxes.
When interpolating between the fit mode, WorkspacesView uses the current
allocation box to derivate the SINGLE and ALL fit mode boxes. However,
that creates a curved path during overview transitions. What we really
want to do here is calculate the fit mode box relative to the corresponding
overview state. For example:
+----------------+----------+------------------------+
| Overview State | Fit Mode | Workspaces geometry |
+----------------+----------+------------------------+
| HIDDEN | SINGLE | Cover entire screen |
| WINDOW PICKER | SINGLE | Between minimap & Dash |
| APP GRID | ALL | 15% screen height |
+----------------+----------+------------------------+
Using the table above as the reference, when the overview transitions
between WINDOW PICKER and APP GRID, we must interpolate between
(SINGLE fit mode @ between minimap & Dash) and (ALL fit mode @ 15% screen
height). That way, we always interpolate the final boxes, which corrects
the odd path that workspaces follow during this transition.
Make the WorkspacesView of the primary monitor use these cached boxes
when the overview is in the middle of a transition, and the fit modes of
the initial and final state differ, to calculate the workspaces positions.
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1624>
2021-02-02 14:34:59 -05:00
|
|
|
this,
|
2020-12-30 15:45:09 -05:00
|
|
|
this._workspaceAdjustment,
|
|
|
|
this._stateAdjustment);
|
2021-01-14 18:23:15 -05:00
|
|
|
this._appDisplay = new AppDisplay.AppDisplay();
|
2013-02-15 18:25:36 -05:00
|
|
|
|
2020-12-30 15:16:18 -05:00
|
|
|
this.add_child(searchEntryBin);
|
2021-01-14 18:23:15 -05:00
|
|
|
this.add_child(this._appDisplay);
|
2020-12-30 15:16:18 -05:00
|
|
|
this.add_child(this.dash);
|
2021-01-15 07:59:58 -05:00
|
|
|
this.add_child(this._searchController);
|
2021-01-14 18:23:15 -05:00
|
|
|
this.add_child(this._thumbnailsBox);
|
|
|
|
this.add_child(this._workspacesDisplay);
|
2013-02-25 18:05:45 -05:00
|
|
|
|
2020-12-30 15:16:18 -05:00
|
|
|
this.layout_manager = new ControlsManagerLayout(searchEntryBin,
|
2021-01-14 18:23:15 -05:00
|
|
|
this._appDisplay,
|
|
|
|
this._workspacesDisplay,
|
|
|
|
this._thumbnailsBox,
|
2021-01-15 07:59:58 -05:00
|
|
|
this._searchController,
|
2021-01-14 18:23:15 -05:00
|
|
|
this.dash,
|
|
|
|
this._stateAdjustment);
|
2013-02-17 23:45:24 -05:00
|
|
|
|
2020-12-30 15:45:09 -05:00
|
|
|
this.dash.showAppsButton.connect('notify::checked',
|
|
|
|
this._onShowAppsButtonToggled.bind(this));
|
|
|
|
|
2021-01-14 18:23:15 -05:00
|
|
|
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);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2021-01-02 15:41:54 -05:00
|
|
|
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));
|
|
|
|
|
2020-06-13 07:26:09 -04:00
|
|
|
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));
|
|
|
|
|
2019-07-08 04:03:20 -04:00
|
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
2021-01-14 18:23:15 -05:00
|
|
|
|
|
|
|
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) {
|
2021-02-16 14:40:43 -05:00
|
|
|
const { shouldShow } = this._thumbnailsBox;
|
2021-01-15 07:59:58 -05:00
|
|
|
const { searchActive } = this._searchController;
|
2021-01-14 18:23:15 -05:00
|
|
|
const [opacity, scale, translationY] = this._getThumbnailsBoxParams();
|
|
|
|
|
2021-02-16 14:40:43 -05:00
|
|
|
const thumbnailsBoxVisible = shouldShow && !searchActive && opacity !== 0;
|
2021-01-14 18:23:15 -05:00
|
|
|
if (thumbnailsBoxVisible) {
|
|
|
|
this._thumbnailsBox.opacity = 0;
|
|
|
|
this._thumbnailsBox.visible = thumbnailsBoxVisible;
|
|
|
|
}
|
|
|
|
|
|
|
|
const params = {
|
2021-01-15 07:50:39 -05:00
|
|
|
opacity: searchActive ? 0 : opacity,
|
2021-01-14 18:23:15 -05:00
|
|
|
duration: animate ? SIDE_CONTROLS_ANIMATION_TIME : 0,
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
onComplete: () => (this._thumbnailsBox.visible = thumbnailsBoxVisible),
|
|
|
|
};
|
|
|
|
|
2021-01-15 07:50:39 -05:00
|
|
|
if (!searchActive) {
|
2021-01-14 18:23:15 -05:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2021-01-15 07:50:39 -05:00
|
|
|
_onSearchChanged() {
|
2021-01-15 07:59:58 -05:00
|
|
|
const { searchActive } = this._searchController;
|
2021-01-14 18:23:15 -05:00
|
|
|
|
2021-01-15 07:50:39 -05:00
|
|
|
if (!searchActive) {
|
2021-01-14 18:23:15 -05:00
|
|
|
this._appDisplay.show();
|
|
|
|
this._workspacesDisplay.reactive = true;
|
|
|
|
this._workspacesDisplay.setPrimaryWorkspaceVisible(true);
|
|
|
|
} else {
|
2021-01-15 07:59:58 -05:00
|
|
|
this._searchController.show();
|
2021-01-14 18:23:15 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
this._updateThumbnailsBox(true);
|
|
|
|
|
|
|
|
this._appDisplay.ease({
|
2021-01-15 07:50:39 -05:00
|
|
|
opacity: searchActive ? 0 : 255,
|
2021-01-14 18:23:15 -05:00
|
|
|
duration: SIDE_CONTROLS_ANIMATION_TIME,
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
2021-01-15 07:50:39 -05:00
|
|
|
onComplete: () => (this._appDisplay.visible = !searchActive),
|
2021-01-14 18:23:15 -05:00
|
|
|
});
|
|
|
|
this._workspacesDisplay.ease({
|
2021-01-15 07:50:39 -05:00
|
|
|
opacity: searchActive ? 0 : 255,
|
2021-01-14 18:23:15 -05:00
|
|
|
duration: SIDE_CONTROLS_ANIMATION_TIME,
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
onComplete: () => {
|
2021-01-15 07:50:39 -05:00
|
|
|
this._workspacesDisplay.reactive = !searchActive;
|
|
|
|
this._workspacesDisplay.setPrimaryWorkspaceVisible(!searchActive);
|
2021-01-14 18:23:15 -05:00
|
|
|
},
|
|
|
|
});
|
2021-01-15 07:59:58 -05:00
|
|
|
this._searchController.ease({
|
2021-01-15 07:50:39 -05:00
|
|
|
opacity: searchActive ? 255 : 0,
|
2021-01-14 18:23:15 -05:00
|
|
|
duration: SIDE_CONTROLS_ANIMATION_TIME,
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
2021-01-15 07:59:58 -05:00
|
|
|
onComplete: () => (this._searchController.visible = searchActive),
|
2021-01-14 18:23:15 -05:00
|
|
|
});
|
2019-07-08 04:03:20 -04:00
|
|
|
}
|
|
|
|
|
2020-12-30 15:45:09 -05:00
|
|
|
_onShowAppsButtonToggled() {
|
2021-01-01 12:00:30 -05:00
|
|
|
if (this._ignoreShowAppsButtonToggle)
|
|
|
|
return;
|
|
|
|
|
2020-12-30 15:45:09 -05:00
|
|
|
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,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-01-02 15:41:54 -05:00
|
|
|
_toggleAppsPage() {
|
|
|
|
if (Main.overview.visible) {
|
|
|
|
const checked = this.dash.showAppsButton.checked;
|
|
|
|
this.dash.showAppsButton.checked = !checked;
|
|
|
|
} else {
|
|
|
|
Main.overview.show(ControlsState.APP_GRID);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-13 07:26:09 -04:00
|
|
|
_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;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-08 04:03:20 -04:00
|
|
|
_onDestroy() {
|
|
|
|
global.workspace_manager.disconnect(this._nWorkspacesNotifyId);
|
|
|
|
}
|
|
|
|
|
|
|
|
_updateAdjustment() {
|
|
|
|
let workspaceManager = global.workspace_manager;
|
|
|
|
let newNumWorkspaces = workspaceManager.n_workspaces;
|
|
|
|
let activeIndex = workspaceManager.get_active_workspace_index();
|
|
|
|
|
|
|
|
this._workspaceAdjustment.upper = newNumWorkspaces;
|
|
|
|
|
|
|
|
// A workspace might have been inserted or removed before the active
|
|
|
|
// one, causing the adjustment to go out of sync, so update the value
|
2020-04-27 19:04:08 -04:00
|
|
|
this._workspaceAdjustment.remove_transition('value');
|
2019-07-08 04:03:20 -04:00
|
|
|
this._workspaceAdjustment.value = activeIndex;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2020-12-30 15:02:14 -05:00
|
|
|
|
2021-01-14 18:23:15 -05:00
|
|
|
vfunc_unmap() {
|
|
|
|
this._workspacesDisplay.hide();
|
|
|
|
super.vfunc_unmap();
|
|
|
|
}
|
|
|
|
|
2021-01-02 15:19:37 -05:00
|
|
|
animateToOverview(state, callback) {
|
2021-01-02 15:28:44 -05:00
|
|
|
this._ignoreShowAppsButtonToggle = true;
|
|
|
|
|
2021-01-15 07:59:58 -05:00
|
|
|
this._searchController.prepareToEnterOverview();
|
2021-01-14 18:23:15 -05:00
|
|
|
this._workspacesDisplay.prepareToEnterOverview();
|
|
|
|
if (!this._workspacesDisplay.activeWorkspaceHasMaximizedWindows())
|
|
|
|
Main.overview.fadeOutDesktop();
|
2021-01-01 12:00:30 -05:00
|
|
|
|
|
|
|
this._stateAdjustment.value = ControlsState.HIDDEN;
|
2021-01-02 15:19:37 -05:00
|
|
|
this._stateAdjustment.ease(state, {
|
2021-01-01 12:00:30 -05:00
|
|
|
duration: Overview.ANIMATION_TIME,
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
2021-01-01 12:18:18 -05:00
|
|
|
onStopped: () => {
|
|
|
|
if (callback)
|
|
|
|
callback();
|
|
|
|
},
|
2021-01-01 12:00:30 -05:00
|
|
|
});
|
2021-01-02 15:28:44 -05:00
|
|
|
|
|
|
|
this.dash.showAppsButton.checked =
|
|
|
|
state === ControlsState.APP_GRID;
|
|
|
|
|
|
|
|
this._ignoreShowAppsButtonToggle = false;
|
2021-01-01 11:39:07 -05:00
|
|
|
}
|
|
|
|
|
2021-01-01 12:18:18 -05:00
|
|
|
animateFromOverview(callback) {
|
2021-01-01 12:00:30 -05:00
|
|
|
this._ignoreShowAppsButtonToggle = true;
|
|
|
|
|
2021-01-14 18:23:15 -05:00
|
|
|
this._workspacesDisplay.prepareToLeaveOverview();
|
|
|
|
if (!this._workspacesDisplay.activeWorkspaceHasMaximizedWindows())
|
|
|
|
Main.overview.fadeInDesktop();
|
2021-01-01 12:00:30 -05:00
|
|
|
|
|
|
|
this._stateAdjustment.ease(ControlsState.HIDDEN, {
|
|
|
|
duration: Overview.ANIMATION_TIME,
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
onStopped: () => {
|
|
|
|
this.dash.showAppsButton.checked = false;
|
|
|
|
this._ignoreShowAppsButtonToggle = false;
|
2021-01-01 12:18:18 -05:00
|
|
|
|
|
|
|
if (callback)
|
|
|
|
callback();
|
2021-01-01 12:00:30 -05:00
|
|
|
},
|
|
|
|
});
|
2021-01-01 11:39:07 -05:00
|
|
|
}
|
|
|
|
|
workspacesView: Interpolate against relative workspace boxes
The overview transition consists of getting the initial and final
states of the overview adjustment, derivating various other internal
states from them (such as the fit mode, opacities, translations, etc),
and finally interpolating the allocation boxes.
When interpolating between the fit mode, WorkspacesView uses the current
allocation box to derivate the SINGLE and ALL fit mode boxes. However,
that creates a curved path during overview transitions. What we really
want to do here is calculate the fit mode box relative to the corresponding
overview state. For example:
+----------------+----------+------------------------+
| Overview State | Fit Mode | Workspaces geometry |
+----------------+----------+------------------------+
| HIDDEN | SINGLE | Cover entire screen |
| WINDOW PICKER | SINGLE | Between minimap & Dash |
| APP GRID | ALL | 15% screen height |
+----------------+----------+------------------------+
Using the table above as the reference, when the overview transitions
between WINDOW PICKER and APP GRID, we must interpolate between
(SINGLE fit mode @ between minimap & Dash) and (ALL fit mode @ 15% screen
height). That way, we always interpolate the final boxes, which corrects
the odd path that workspaces follow during this transition.
Make the WorkspacesView of the primary monitor use these cached boxes
when the overview is in the middle of a transition, and the fit modes of
the initial and final state differ, to calculate the workspaces positions.
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1624>
2021-02-02 14:34:59 -05:00
|
|
|
getWorkspacesBoxForState(state) {
|
|
|
|
return this.layoutManager.getWorkspacesBoxForState(state);
|
|
|
|
}
|
|
|
|
|
2021-01-04 10:23:45 -05:00
|
|
|
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);
|
|
|
|
|
|
|
|
tracker.confirmSwipe(baseDistance, points, progress, cancelProgress);
|
|
|
|
this._workspacesDisplay.prepareToEnterOverview();
|
|
|
|
this._searchController.prepareToEnterOverview();
|
|
|
|
this._stateAdjustment.gestureInProgress = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
gestureProgress(progress) {
|
|
|
|
this._stateAdjustment.value = progress;
|
|
|
|
}
|
|
|
|
|
|
|
|
gestureEnd(target, duration, onComplete) {
|
|
|
|
if (target === ControlsState.HIDDEN)
|
|
|
|
this._workspacesDisplay.prepareToLeaveOverview();
|
|
|
|
|
|
|
|
this.dash.showAppsButton.checked =
|
|
|
|
target === ControlsState.APP_GRID;
|
|
|
|
|
|
|
|
this._stateAdjustment.remove_transition('value');
|
|
|
|
this._stateAdjustment.ease(target, {
|
|
|
|
duration,
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
onComplete,
|
|
|
|
});
|
|
|
|
|
|
|
|
this._stateAdjustment.gestureInProgress = false;
|
|
|
|
}
|
|
|
|
|
2020-12-30 15:02:14 -05:00
|
|
|
get searchEntry() {
|
|
|
|
return this._searchEntry;
|
|
|
|
}
|
2021-01-14 18:23:15 -05:00
|
|
|
|
|
|
|
get appDisplay() {
|
|
|
|
return this._appDisplay;
|
|
|
|
}
|
2019-07-16 05:24:13 -04:00
|
|
|
});
|