summaryrefslogtreecommitdiffstats
path: root/extensions/vertical-workspaces/appDisplay.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--extensions/vertical-workspaces/appDisplay.js944
1 files changed, 944 insertions, 0 deletions
diff --git a/extensions/vertical-workspaces/appDisplay.js b/extensions/vertical-workspaces/appDisplay.js
new file mode 100644
index 0000000..4690fcf
--- /dev/null
+++ b/extensions/vertical-workspaces/appDisplay.js
@@ -0,0 +1,944 @@
+/**
+ * Vertical Workspaces
+ * appDisplay.js
+ *
+ * @author GdH <G-dH@github.com>
+ * @copyright 2022 - 2023
+ * @license GPL-3.0
+ *
+ */
+
+'use strict';
+
+const { Clutter, GLib, GObject, Graphene, Meta, Shell, St } = imports.gi;
+
+const DND = imports.ui.dnd;
+const Main = imports.ui.main;
+const AppDisplay = imports.ui.appDisplay;
+const IconGrid = imports.ui.iconGrid;
+const { AppMenu } = imports.ui.appMenu;
+const PopupMenu = imports.ui.popupMenu;
+const BoxPointer = imports.ui.boxpointer;
+
+const ExtensionUtils = imports.misc.extensionUtils;
+const Me = ExtensionUtils.getCurrentExtension();
+const IconGridOverride = Me.imports.iconGrid;
+
+const _Util = Me.imports.util;
+let _overrides;
+
+let _appGridLayoutSettings;
+let _appDisplayScrollConId;
+let _appSystemStateConId;
+let _appGridLayoutConId;
+let _updateAppGridTimeoutId;
+let _origAppDisplayAcceptDrop;
+let _origAppViewItemAcceptDrop;
+let _origAppViewItemHandleDragOver;
+
+let opt;
+let shellVersion = _Util.shellVersion;
+
+
+function update(reset = false) {
+ if (_overrides) {
+ _overrides.removeAll();
+ }
+
+ if (reset) {
+ _setAppDisplayOrientation(false);
+ _updateAppGridProperties(reset);
+ _updateAppGridDND(reset);
+ _restoreOverviewGroup();
+ _overrides = null;
+ opt = null;
+ return;
+ }
+
+ opt = Me.imports.settings.opt;
+
+ _overrides = new _Util.Overrides();
+
+ if (opt.ORIENTATION === Clutter.Orientation.VERTICAL) {
+ _overrides.addOverride('AppDisplayVertical', AppDisplay.AppDisplay.prototype, AppDisplayVertical);
+ _overrides.addOverride('BaseAppViewVertical', AppDisplay.BaseAppView.prototype, BaseAppViewVertical);
+ }
+
+ _overrides.addOverride('AppSearchProvider', AppDisplay.AppSearchProvider.prototype, AppSearchProvider);
+
+ // Custom App Grid
+ _overrides.addOverride('AppFolderDialog', AppDisplay.AppFolderDialog.prototype, AppFolderDialog);
+ if (shellVersion >= 43) {
+ // const defined class needs to be touched before real access
+ AppDisplay.BaseAppViewGridLayout;
+ _overrides.addOverride('BaseAppViewGridLayout', AppDisplay.BaseAppViewGridLayout.prototype, BaseAppViewGridLayout);
+ _overrides.addOverride('IconGrid', IconGrid.IconGrid.prototype, IconGridOverride.IconGrid);
+ }
+ _overrides.addOverride('FolderView', AppDisplay.FolderView.prototype, FolderView);
+ _overrides.addInjection('AppFolderDialog', AppDisplay.AppFolderDialog.prototype, AppFolderDialogInjections);
+ _overrides.addOverride('AppIcon', AppDisplay.AppIcon.prototype, AppIcon);
+ _overrides.addOverride('BaseAppView', AppDisplay.BaseAppView.prototype, BaseAppView);
+ _overrides.addOverride('AppDisplay', AppDisplay.AppDisplay.prototype, AppDisplayCommon);
+
+
+ _setAppDisplayOrientation(opt.ORIENTATION === Clutter.Orientation.VERTICAL);
+ _updateAppGridProperties();
+ _updateAppGridDND();
+}
+
+//---------------------------------------------------------------------------------------------------------
+
+function _setAppDisplayOrientation(vertical = false) {
+ const CLUTTER_ORIENTATION = vertical ? Clutter.Orientation.VERTICAL : Clutter.Orientation.HORIZONTAL;
+ const scroll = vertical ? 'vscroll' : 'hscroll';
+ // app display to vertical has issues - page indicator not working
+ // global appDisplay orientation switch is not built-in
+ let appDisplay = Main.overview._overview._controls._appDisplay;
+ // following line itself only changes in which axis will operate overshoot detection which switches appDisplay pages while dragging app icon to vertical
+ appDisplay._orientation = CLUTTER_ORIENTATION;
+ appDisplay._grid.layoutManager._orientation = CLUTTER_ORIENTATION;
+ appDisplay._swipeTracker.orientation = CLUTTER_ORIENTATION;
+ appDisplay._swipeTracker._reset();
+ if (vertical) {
+ appDisplay._scrollView.set_policy(St.PolicyType.NEVER, St.PolicyType.EXTERNAL);
+
+ // move and change orientation of page indicators
+ // needs corrections in appgrid page calculations, e.g. appDisplay.adaptToSize() fnc,
+ // which complicates use of super call inside the function
+ const pageIndicators = appDisplay._pageIndicators;
+ pageIndicators.vertical = true;
+ appDisplay._box.vertical = false;
+ pageIndicators.x_expand = false;
+ pageIndicators.y_align = Clutter.ActorAlign.CENTER;
+ pageIndicators.x_align = Clutter.ActorAlign.START;
+
+ const scrollContainer = appDisplay._scrollView.get_parent();
+ if (shellVersion < 43) {
+ // remove touch friendly side navigation bars / arrows
+ appDisplay._hintContainer && scrollContainer.remove_child(appDisplay._hintContainer);
+ } else {
+ // moving these bars needs more patching of the appDisplay's code
+ // for now we just change bars style to be more like vertically oriented arrows indicating direction to prev/next page
+ appDisplay._nextPageIndicator.add_style_class_name('nextPageIndicator');
+ appDisplay._prevPageIndicator.add_style_class_name('prevPageIndicator');
+ }
+
+ // setting their x_scale to 0 removes the arrows and avoid allocation issues compared to .hide() them
+ appDisplay._nextPageArrow.scale_x = 0;
+ appDisplay._prevPageArrow.scale_x = 0;
+ } else {
+ appDisplay._scrollView.set_policy(St.PolicyType.EXTERNAL, St.PolicyType.NEVER);
+ if (_appDisplayScrollConId) {
+ appDisplay._adjustment.disconnect(_appDisplayScrollConId);
+ _appDisplayScrollConId = 0;
+ }
+
+ // restore original page indicators
+ const pageIndicators = appDisplay._pageIndicators;
+ pageIndicators.vertical = false;
+ appDisplay._box.vertical = true;
+ pageIndicators.x_expand = true;
+ pageIndicators.y_align = Clutter.ActorAlign.END;
+ pageIndicators.x_align = Clutter.ActorAlign.CENTER;
+
+ // put back touch friendly navigation bars/buttons
+ const scrollContainer = appDisplay._scrollView.get_parent();
+ appDisplay._hintContainer && appDisplay._hintContainer.get_parent() == null && scrollContainer.add_child(appDisplay._hintContainer);
+ appDisplay._nextPageArrow.scale_x = 1;
+ appDisplay._prevPageArrow.scale_x = 1;
+
+ appDisplay._nextPageIndicator.remove_style_class_name('nextPageIndicator');
+ appDisplay._prevPageIndicator.remove_style_class_name('prevPageIndicator');
+ }
+
+ // value for page indicator is calculated from scroll adjustment, horizontal needs to be replaced by vertical
+ appDisplay._adjustment = appDisplay._scrollView[scroll].adjustment;
+
+ // no need to connect already connected signal (wasn't removed the original one before)
+ if (!vertical) {
+ // reset used appdisplay properties
+ Main.overview._overview._controls._appDisplay.scale_y = 1;
+ Main.overview._overview._controls._appDisplay.scale_x = 1;
+ Main.overview._overview._controls._appDisplay.opacity = 255;
+ return;
+ }
+
+ // update appGrid dot pages indicators
+ _appDisplayScrollConId = appDisplay._adjustment.connect('notify::value', adj => {
+ const value = adj.value / adj.page_size;
+ appDisplay._pageIndicators.setCurrentPosition(value);
+ });
+}
+
+// Set App Grid columns, rows, icon size, incomplete pages
+function _updateAppGridProperties(reset = false) {
+ // columns, rows, icon size
+ const appDisplay = Main.overview._overview._controls._appDisplay;
+ appDisplay.visible = true;
+
+ // replace isFavorite function to always return false to allow dnd with favorite apps
+ if (!reset && opt.APP_GRID_INCLUDE_DASH) {
+ if (!appDisplay._appFavorites._backupIsFavorite) {
+ appDisplay._appFavorites._backupIsFavorite = appDisplay._appFavorites.isFavorite;
+ }
+ appDisplay._appFavorites.isFavorite = () => false;
+ } else {
+ if (appDisplay._appFavorites._backupIsFavorite) {
+ appDisplay._appFavorites.isFavorite = appDisplay._appFavorites._backupIsFavorite;
+ appDisplay._appFavorites._backupIsFavorite = undefined;
+ }
+ }
+
+ if (reset) {
+ appDisplay._grid.layout_manager.fixedIconSize = -1;
+ appDisplay._grid.layoutManager.allow_incomplete_pages = true;
+ appDisplay._grid.setGridModes();
+ if (_appGridLayoutSettings) {
+ _appGridLayoutSettings.disconnect(_appGridLayoutConId);
+ _appGridLayoutConId = 0;
+ _appGridLayoutSettings = null;
+ }
+ appDisplay._redisplay();
+ // secondary call is necessary to properly update app grid
+ appDisplay._redisplay();
+ } else {
+ // update grid on layout reset
+ if (!_appGridLayoutSettings) {
+ _appGridLayoutSettings = ExtensionUtils.getSettings('org.gnome.shell');
+ _appGridLayoutConId = _appGridLayoutSettings.connect('changed::app-picker-layout', _resetAppGrid);
+ }
+
+ // remove icons from App Grid
+ _resetAppGrid();
+
+ const updateGrid = function(rows, columns) {
+ if (rows === -1 || columns === -1) {
+ appDisplay._grid.setGridModes();
+ } else {
+ appDisplay._grid.setGridModes(
+ [{ rows, columns }]
+ );
+ }
+ appDisplay._grid._setGridMode(0);
+ }
+
+ appDisplay._grid._currentMode = -1;
+ if (opt.APP_GRID_ALLOW_CUSTOM) {
+ updateGrid(opt.APP_GRID_ROWS, opt.APP_GRID_COLUMNS);
+ } else {
+ appDisplay._grid.setGridModes();
+ updateGrid(-1, -1);
+ }
+ appDisplay._grid.layoutManager.fixedIconSize = opt.APP_GRID_ICON_SIZE;
+ appDisplay._grid.layoutManager.allow_incomplete_pages = opt.APP_GRID_ALLOW_INCOMPLETE_PAGES;
+
+ // force rebuild icons. size shouldn't be the same as the current one, otherwise can be arbitrary
+ appDisplay._grid.layoutManager.adaptToSize(200, 200);
+ appDisplay._redisplay();
+
+ _realizeAppDisplay();
+ }
+}
+
+//------ App Grid - DND ----------------------------------------------------------
+
+function _updateAppGridDND(reset) {
+ if (opt.APP_GRID_ORDER && !reset) {
+ if (!_appSystemStateConId)
+ _appSystemStateConId = Shell.AppSystem.get_default().connect('app-state-changed', () => Main.overview._overview._controls._appDisplay._redisplay());
+
+ // deny dnd from dash to app grid
+ if (!_origAppDisplayAcceptDrop)
+ _origAppDisplayAcceptDrop = AppDisplay.AppDisplay.prototype.acceptDrop;
+ AppDisplay.AppDisplay.prototype.acceptDrop = function() { return false; };
+
+ // deny creating folders by dnd on other icon
+ if (!_origAppViewItemHandleDragOver)
+ _origAppViewItemHandleDragOver = AppDisplay.AppViewItem.prototype.handleDragOver;
+ AppDisplay.AppViewItem.prototype.handleDragOver = () => DND.DragMotionResult.NO_DROP;
+
+ if (!_origAppViewItemAcceptDrop)
+ _origAppViewItemAcceptDrop = AppDisplay.AppViewItem.prototype.acceptDrop;
+ AppDisplay.AppViewItem.prototype.acceptDrop = () => false;
+ } else {
+ if (_appSystemStateConId) {
+ Shell.AppSystem.get_default().disconnect(_appSystemStateConId);
+ _appSystemStateConId = 0;
+ }
+
+ if (_origAppDisplayAcceptDrop)
+ AppDisplay.AppDisplay.prototype.acceptDrop = _origAppDisplayAcceptDrop;
+
+ if (_origAppViewItemHandleDragOver)
+ AppDisplay.AppViewItem.prototype.handleDragOver = _origAppViewItemHandleDragOver;
+
+ if (_origAppViewItemAcceptDrop)
+ AppDisplay.AppViewItem.prototype.acceptDrop = _origAppViewItemAcceptDrop;
+ }
+}
+
+function _realizeAppDisplay() {
+ // force app grid to build all icons before the first visible animation to remove possible stuttering
+ // let the main loop realize previous changes before continuing
+
+ // don't do this during shell startup
+ if (Main.layoutManager._startingUp)
+ return;
+
+ if (_updateAppGridTimeoutId) {
+ GLib.source_remove(_updateAppGridTimeoutId);
+ }
+
+ _updateAppGridTimeoutId = GLib.timeout_add(
+ GLib.PRIORITY_DEFAULT,
+ 1000,
+ () => {
+ Main.layoutManager.overviewGroup.opacity = 1;
+ Main.layoutManager.overviewGroup.scale_x = 0.1;
+ Main.layoutManager.overviewGroup.show();
+ Main.overview.dash.showAppsButton.checked = true;
+
+ GLib.source_remove(_updateAppGridTimeoutId);
+
+ _updateAppGridTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
+ 500,
+ () => {
+ _restoreOverviewGroup();
+ _updateAppGridTimeoutId = 0;
+ return GLib.SOURCE_REMOVE;
+ });
+ }
+ );
+}
+
+function _restoreOverviewGroup() {
+ Main.overview.dash.showAppsButton.checked = false;
+ Main.layoutManager.overviewGroup.opacity = 255;
+ Main.layoutManager.overviewGroup.scale_x = 1;
+ Main.layoutManager.overviewGroup.hide();
+}
+
+//------ appDisplay - Vertical --------------------------------------------------------------------------------
+
+var AppDisplayVertical = {
+ // correction of the appGrid size when page indicators were moved from the bottom to the right
+ adaptToSize: function(width, height) {
+ const [, indicatorWidth] = this._pageIndicators.get_preferred_width(-1);
+ width -= indicatorWidth;
+
+ this._grid.findBestModeForSize(width, height);
+
+ const adaptToSize = AppDisplay.BaseAppView.prototype.adaptToSize.bind(this);
+ adaptToSize(width, height);
+ },
+}
+
+
+//------ AppDisplay.AppSearchProvider ----------------------------------------------------------------------------------
+
+// App search result size
+var AppSearchProvider = {
+ createResultObject: function(resultMeta) {
+ if (resultMeta.id.endsWith('.desktop')) {
+ const icon = new AppDisplay.AppIcon(this._appSys.lookup_app(resultMeta['id']), {
+ expandTitleOnHover: false,
+ });
+ icon.icon.setIconSize(opt.SEARCH_ICON_SIZE);
+ return icon;
+ } else {
+ const icon = new AppDisplay.SystemActionIcon(this, resultMeta);
+ icon.icon._setSizeManually = true;
+ icon.icon.setIconSize(opt.SEARCH_ICON_SIZE);
+ return icon;
+ }
+ }
+}
+
+var BaseAppViewVertical = {
+ // this fixes dnd from appDisplay to the workspace thumbnail on the left if appDisplay is on page 1 because of appgrid left overshoot
+ _pageForCoords: function(x, y) {
+ return AppDisplay.SidePages.NONE;
+ },
+}
+
+// ------ AppDisplay - Custom App Grid ------------------------------------------------------------------------
+
+var AppDisplayCommon = {
+ _ensureDefaultFolders: function() {
+ // disable creation of default folders if user deleted them
+ },
+
+ // apps load adapted for custom sorting and including dash items
+ _loadApps: function() {
+ let appIcons = [];
+ this._appInfoList = Shell.AppSystem.get_default().get_installed().filter(appInfo => {
+ try {
+ appInfo.get_id(); // catch invalid file encodings
+ } catch (e) {
+ return false;
+ }
+ return (opt.APP_GRID_INCLUDE_DASH || !this._appFavorites.isFavorite(appInfo.get_id())) &&
+ this._parentalControlsManager.shouldShowApp(appInfo);
+ });
+
+ let apps = this._appInfoList.map(app => app.get_id());
+
+ let appSys = Shell.AppSystem.get_default();
+
+ const appsInsideFolders = new Set();
+ this._folderIcons = [];
+ if (!opt.APP_GRID_ORDER) {
+
+ let folders = this._folderSettings.get_strv('folder-children');
+ folders.forEach(id => {
+ let path = `${this._folderSettings.path}folders/${id}/`;
+ let icon = this._items.get(id);
+ if (!icon) {
+ icon = new AppDisplay.FolderIcon(id, path, this);
+ icon.connect('apps-changed', () => {
+ this._redisplay();
+ this._savePages();
+ });
+ icon.connect('notify::pressed', () => {
+ if (icon.pressed)
+ this.updateDragFocus(icon);
+ });
+ }
+
+ // Don't try to display empty folders
+ if (!icon.visible) {
+ icon.destroy();
+ return;
+ }
+
+ appIcons.push(icon);
+ this._folderIcons.push(icon);
+
+ icon.getAppIds().forEach(appId => appsInsideFolders.add(appId));
+ });
+ }
+
+ // Allow dragging of the icon only if the Dash would accept a drop to
+ // change favorite-apps. There are no other possible drop targets from
+ // the app picker, so there's no other need for a drag to start,
+ // at least on single-monitor setups.
+ // This also disables drag-to-launch on multi-monitor setups,
+ // but we hope that is not used much.
+ const isDraggable =
+ global.settings.is_writable('favorite-apps') ||
+ global.settings.is_writable('app-picker-layout');
+
+ apps.forEach(appId => {
+ if (!opt.APP_GRID_ORDER && appsInsideFolders.has(appId))
+ return;
+
+ let icon = this._items.get(appId);
+ if (!icon) {
+ let app = appSys.lookup_app(appId);
+
+ icon = new AppDisplay.AppIcon(app, { isDraggable });
+ icon.connect('notify::pressed', () => {
+ if (icon.pressed)
+ this.updateDragFocus(icon);
+ });
+ }
+
+ appIcons.push(icon);
+ });
+
+ // At last, if there's a placeholder available, add it
+ if (this._placeholder)
+ appIcons.push(this._placeholder);
+
+ //const runningIDs = Shell.AppSystem.get_default().get_running().map(app => app.get_id());
+
+ // remove running apps
+ /*if (!APP_GRID_INCLUDE_DASH) { // !icon.app means folder
+ appIcons = appIcons.filter((icon) => this._folderIcons.includes(icon) || !(runningIDs.includes(icon.app.id) || this._appFavorites.isFavorite(icon.id)));
+ }*/
+
+ return appIcons;
+ },
+}
+
+var BaseAppView = {
+ // adds sorting options and option to add favorites and running apps
+ _redisplay: function() {
+ let oldApps = this._orderedItems.slice();
+ let oldAppIds = oldApps.map(icon => icon.id);
+
+ let newApps = this._loadApps().sort(this._compareItems.bind(this));
+ let newAppIds = newApps.map(icon => icon.id);
+
+ let addedApps = newApps.filter(icon => !oldAppIds.includes(icon.id));
+ let removedApps = oldApps.filter(icon => !newAppIds.includes(icon.id));
+
+ // Remove old app icons
+ removedApps.forEach(icon => {
+ this._removeItem(icon);
+ icon.destroy();
+ });
+
+ // Add new app icons, or move existing ones
+ newApps.forEach(icon => {
+ const [page, position] = this._getItemPosition(icon);
+ if (addedApps.includes(icon))
+ this._addItem(icon, page, position);
+ else if (page !== -1 && position !== -1) {
+ this._moveItem(icon, page, position);
+ }
+
+ });
+
+ // Reorder App Grid by usage
+ // sort all alphabetically
+ if(opt.APP_GRID_ORDER > 0) {
+ const { itemsPerPage } = this._grid;
+ let appIcons = this._orderedItems;
+ appIcons.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));
+ // then sort used apps by usage
+ if (opt.APP_GRID_ORDER === 2) {
+ appIcons.sort((a, b) => Shell.AppUsage.get_default().compare(a.app.id, b.app.id));
+ }
+ // sort favorites first
+ if (opt.APP_GRID_INCLUDE_DASH === 2) {
+ const fav = Object.keys(this._appFavorites._favorites);
+ appIcons.sort((a, b) => {
+ let aFav = fav.indexOf(a.id);
+ if (aFav < 0) aFav = 999;
+ let bFav = fav.indexOf(b.id);
+ if (bFav < 0) bFav = 999;
+ return bFav < aFav;
+ });
+ }
+
+ // sort running first
+ if (opt.APP_GRID_INCLUDE_DASH === 2) {
+ appIcons.sort((a, b) => a.app.get_state() !== Shell.AppState.RUNNING && b.app.get_state() === Shell.AppState.RUNNING);
+ }
+
+ appIcons.forEach((icon, i) => {
+ const page = Math.floor(i / itemsPerPage);
+ const position = i % itemsPerPage;
+ this._moveItem(icon, page, position);
+ });
+
+ this._orderedItems = appIcons;
+ }
+
+ this.emit('view-loaded');
+ },
+
+ _canAccept: function(source) {
+ return (opt.APP_GRID_ORDER ? false : source instanceof AppDisplay.AppViewItem);
+ },
+
+ // GS <= 42 only, Adapt app grid so it can use all available space
+ adaptToSize(width, height) {
+ let box = new Clutter.ActorBox({
+ x2: width,
+ y2: height,
+ });
+ box = this.get_theme_node().get_content_box(box);
+ box = this._scrollView.get_theme_node().get_content_box(box);
+ box = this._grid.get_theme_node().get_content_box(box);
+
+ const availWidth = box.get_width();
+ const availHeight = box.get_height();
+
+ const gridRatio = this._grid.layout_manager.columnsPerPage /
+ this._grid.layout_manager.rowsPerPage;
+ const spaceRatio = availWidth / availHeight;
+ let pageWidth, pageHeight;
+
+ if (spaceRatio > gridRatio * 1.1) {
+ // Enough room for some preview
+ pageHeight = availHeight;
+ pageWidth = Math.ceil(availHeight * gridRatio);
+
+ if (spaceRatio > gridRatio * 1.5) {
+ // Ultra-wide layout, give some extra space for
+ // the page area, but up to an extent.
+ const extraPageSpace = Math.min(
+ Math.floor((availWidth - pageWidth) / 2), 200); // AppDisplay.MAX_PAGE_PADDING == 200
+ pageWidth += extraPageSpace;
+ this._grid.layout_manager.pagePadding.left =
+ Math.floor(extraPageSpace / 2);
+ this._grid.layout_manager.pagePadding.right =
+ Math.ceil(extraPageSpace / 2);
+ }
+ } else {
+ // Not enough room, needs to shrink horizontally
+ pageWidth = Math.ceil(availWidth * 0.95); // width limiter, original is 0.8
+ pageHeight = availHeight;
+ this._grid.layout_manager.pagePadding.left =
+ Math.floor(availWidth * 0.02);
+ this._grid.layout_manager.pagePadding.right =
+ Math.ceil(availWidth * 0.02);
+ }
+
+ this._grid.adaptToSize(pageWidth, pageHeight);
+
+ const leftPadding = Math.floor(
+ (availWidth - this._grid.layout_manager.pageWidth) / 2);
+ const rightPadding = Math.ceil(
+ (availWidth - this._grid.layout_manager.pageWidth) / 2);
+ const topPadding = Math.floor(
+ (availHeight - this._grid.layout_manager.pageHeight) / 2);
+ const bottomPadding = Math.ceil(
+ (availHeight - this._grid.layout_manager.pageHeight) / 2);
+
+ this._scrollView.content_padding = new Clutter.Margin({
+ left: leftPadding,
+ right: rightPadding,
+ top: topPadding,
+ bottom: bottomPadding,
+ });
+
+ this._availWidth = availWidth;
+ this._availHeight = availHeight;
+
+ this._pageIndicatorOffset = leftPadding;
+ this._pageArrowOffset = Math.max(
+ leftPadding - 80, 0); // 80 is AppDisplay.PAGE_PREVIEW_MAX_ARROW_OFFSET
+ }
+}
+
+var BaseAppViewGridLayout = {
+ _getIndicatorsWidth(box) {
+ const [width, height] = box.get_size();
+ const arrows = [
+ this._nextPageArrow,
+ this._previousPageArrow,
+ ];
+
+ const minArrowsWidth = arrows.reduce(
+ (previousWidth, accessory) => {
+ const [min] = accessory.get_preferred_width(height);
+ return Math.max(previousWidth, min);
+ }, 0);
+
+ const idealIndicatorWidth = (width * 0.1/*PAGE_PREVIEW_RATIO*/) / 2;
+
+ return Math.max(idealIndicatorWidth, minArrowsWidth);
+ }
+}
+
+// ------------------ AppDisplay.AppFolderDialog - injection --------------------------------------------------------------
+
+var AppFolderDialogInjections = {
+ _init: function() {
+ const iconSize = opt.APP_GRID_FOLDER_ICON_SIZE < 0 ? 96 : opt.APP_GRID_FOLDER_ICON_SIZE;
+ let width = opt.APP_GRID_FOLDER_COLUMNS * (iconSize + 64);
+ width = Math.max(640, Math.round(width + width / 10));
+ let height = opt.APP_GRID_FOLDER_ROWS * (iconSize + 64) + 150;
+ opt.APP_GRID_ALLOW_CUSTOM && this.child.set_style(`
+ width: ${width}px;
+ height: ${height}px;
+ padding: 30px;
+ `);
+ }
+}
+
+
+// ------------------ AppDisplay.FolderGrid -----------------------------------------------------------------------
+
+var FolderView = {
+ _createGrid: function() {
+ let grid;
+ if (shellVersion < 43) {
+ grid = new FolderGrid();
+ } else {
+ grid = new FolderGrid43();
+ }
+ return grid;
+ }
+}
+
+// folder columns and rows
+
+var FolderGrid = GObject.registerClass(
+class FolderGrid extends IconGrid.IconGrid {
+ _init() {
+ super._init({
+ allow_incomplete_pages: false,
+ columns_per_page: opt.APP_GRID_ALLOW_CUSTOM ? opt.APP_GRID_FOLDER_COLUMNS : 3,
+ rows_per_page: opt.APP_GRID_ALLOW_CUSTOM ? opt.APP_GRID_FOLDER_ROWS : 3,
+ page_halign: Clutter.ActorAlign.CENTER,
+ page_valign: Clutter.ActorAlign.CENTER,
+ });
+
+ opt.APP_GRID_ALLOW_CUSTOM && this.set_style('column-spacing: 10px; row-spacing: 10px;');
+ this.layout_manager.fixedIconSize = opt.APP_GRID_FOLDER_ICON_SIZE;
+ }
+
+ adaptToSize(width, height) {
+ this.layout_manager.adaptToSize(width, height);
+ }
+});
+
+
+// only the first access to the const AppDisplay.AppGrid throws an error, so touch it before it's really needed
+let FolderGrid43;
+AppDisplay.AppGrid;
+if (AppDisplay.AppGrid) {
+ FolderGrid43 = GObject.registerClass(
+ class FolderGrid43 extends AppDisplay.AppGrid {
+ _init() {
+ super._init({
+ allow_incomplete_pages: false,
+ columns_per_page: opt.APP_GRID_ALLOW_CUSTOM ? opt.APP_GRID_FOLDER_COLUMNS : 3,
+ rows_per_page: opt.APP_GRID_ALLOW_CUSTOM ? opt.APP_GRID_FOLDER_ROWS : 3,
+ page_halign: Clutter.ActorAlign.CENTER,
+ page_valign: Clutter.ActorAlign.CENTER,
+ });
+
+ opt.APP_GRID_ALLOW_CUSTOM && this.set_style('column-spacing: 10px; row-spacing: 10px;');
+ this.layout_manager.fixedIconSize = opt.APP_GRID_FOLDER_ICON_SIZE;
+
+ this.setGridModes([
+ {
+ rows: opt.APP_GRID_ALLOW_CUSTOM ? opt.APP_GRID_FOLDER_ROWS : 3,
+ columns: opt.APP_GRID_ALLOW_CUSTOM ? opt.APP_GRID_FOLDER_COLUMNS : 3,
+ },
+ ]);
+ }
+
+ adaptToSize(width, height) {
+ this.layout_manager.adaptToSize(width, height);
+ }
+ });
+
+}
+
+var FOLDER_DIALOG_ANIMATION_TIME = 200; // AppDisplay.FOLDER_DIALOG_ANIMATION_TIME
+var AppFolderDialog = {
+ _zoomAndFadeIn: function() {
+ let [sourceX, sourceY] =
+ this._source.get_transformed_position();
+ let [dialogX, dialogY] =
+ this.child.get_transformed_position();
+
+ this.child.set({
+ translation_x: sourceX - dialogX,
+ translation_y: sourceY - dialogY,
+ scale_x: this._source.width / this.child.width,
+ scale_y: this._source.height / this.child.height,
+ opacity: 0,
+ });
+
+ this.ease({
+ background_color: Clutter.Color.from_pixel(0x00000033), // DIALOG_SHADE_NORMAL
+ duration: FOLDER_DIALOG_ANIMATION_TIME,
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+ });
+
+
+ this.child.ease({
+ translation_x: 0,
+ translation_y: 0,
+ scale_x: 1,
+ scale_y: 1,
+ opacity: 255,
+ duration: FOLDER_DIALOG_ANIMATION_TIME,
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+ });
+
+ this._needsZoomAndFade = false;
+
+ if (this._sourceMappedId === 0) {
+ this._sourceMappedId = this._source.connect(
+ 'notify::mapped', this._zoomAndFadeOut.bind(this));
+ }
+ },
+}
+
+function _resetAppGrid(settings = null, key = null) {
+ if (settings) {
+ const currentValue = JSON.stringify(settings.get_value('app-picker-layout').deep_unpack());
+ const emptyValue = JSON.stringify([]);
+ if (key === 'app-picker-layout' && currentValue != emptyValue)
+ return;
+ }
+ const appDisplay = Main.overview._overview._controls._appDisplay;
+ const items = appDisplay._orderedItems;
+ for (let i = items.length - 1; i > -1; i--) {
+ Main.overview._overview._controls._appDisplay._removeItem(items[i]);
+ }
+ // redisplay only from callback
+ if (settings)
+ appDisplay._redisplay();
+}
+
+// ------------------ AppDisplay.AppIcon - override ---------------------------------------------------------------
+
+function _getWindowApp(metaWin) {
+ const tracker = Shell.WindowTracker.get_default();
+ return tracker.get_window_app(metaWin);
+}
+
+function _getAppLastUsedWindow(app) {
+ let recentWin;
+ global.display.get_tab_list(Meta.TabList.NORMAL_ALL, null).forEach(metaWin => {
+ const winApp = _getWindowApp(metaWin);
+ if (!recentWin && winApp == app) {
+ recentWin = metaWin;
+ }
+ });
+ return recentWin;
+}
+
+function _getAppRecentWorkspace(app) {
+ const recentWin = _getAppLastUsedWindow(app)
+ if (recentWin)
+ return recentWin.get_workspace();
+
+ return null;
+}
+
+var AppIcon = {
+ activate: function(button) {
+ const event = Clutter.get_current_event();
+ const modifiers = event ? event.get_state() : 0;
+ const isMiddleButton = button && button == Clutter.BUTTON_MIDDLE;
+ const isCtrlPressed = (modifiers & Clutter.ModifierType.CONTROL_MASK) != 0;
+ const isShiftPressed = (modifiers & Clutter.ModifierType.SHIFT_MASK) != 0;
+ const openNewWindow = this.app.can_open_new_window() &&
+ this.app.state == Shell.AppState.RUNNING &&
+ (isCtrlPressed || isMiddleButton);
+
+ const currentWS = global.workspace_manager.get_active_workspace();
+ const appRecentWorkspace = _getAppRecentWorkspace(this.app);
+
+ let targetWindowOnCurrentWs = false;
+ if (opt.DASH_FOLLOW_RECENT_WIN) {
+ targetWindowOnCurrentWs = appRecentWorkspace === currentWS;
+ } else {
+ this.app.get_windows().forEach(
+ w => targetWindowOnCurrentWs = targetWindowOnCurrentWs || (w.get_workspace() === currentWS)
+ );
+ }
+
+ if ((this.app.state == Shell.AppState.STOPPED || openNewWindow) && !isShiftPressed)
+ this.animateLaunch();
+
+ if (openNewWindow) {
+ this.app.open_new_window(-1);
+ // if the app has more than one window (option: and has no window on the current workspace),
+ // don't activate the app, only move the overview to the workspace with the app's recent window
+ } else if (opt.DASH_SHOW_WINS_BEFORE && !isShiftPressed && this.app.get_n_windows() > 1 && !targetWindowOnCurrentWs) {
+ this._scroll = true;
+ this._scrollTime = Date.now();
+ //const appWS = this.app.get_windows()[0].get_workspace();
+ Main.wm.actionMoveWorkspace(appRecentWorkspace);
+ Main.overview.dash.showAppsButton.checked = false;
+ return;
+ } else if (opt.DASH_SHIFT_CLICK_MV && isShiftPressed && this.app.get_windows().length) {
+ this._moveAppToCurrentWorkspace();
+ return;
+ } else if (isShiftPressed) {
+ return;
+ } else {
+ this.app.activate();
+ }
+
+ Main.overview.hide();
+ },
+
+ _moveAppToCurrentWorkspace: function() {
+ this.app.get_windows().forEach(w => w.change_workspace(global.workspace_manager.get_active_workspace()));
+ },
+
+ popupMenu: function(side = St.Side.LEFT) {
+ if (shellVersion >= 42)
+ this.setForcedHighlight(true);
+ this._removeMenuTimeout();
+ this.fake_release();
+
+ if (!this._getWindowsOnCurrentWs) {
+ this._getWindowsOnCurrentWs = function() {
+ const winList = [];
+ this.app.get_windows().forEach(w => {
+ if(w.get_workspace() === global.workspace_manager.get_active_workspace()) winList.push(w)
+ });
+ return winList;
+ };
+
+ this._windowsOnOtherWs = function() {
+ return (this.app.get_windows().length - this._getWindowsOnCurrentWs().length) > 0;
+ };
+ }
+
+ if (!this._menu) {
+ this._menu = new AppMenu(this, side, {
+ favoritesSection: true,
+ showSingleWindows: true,
+ });
+
+ this._menu.setApp(this.app);
+ this._openSigId = this._menu.connect('open-state-changed', (menu, isPoppedUp) => {
+ if (!isPoppedUp)
+ this._onMenuPoppedDown();
+ });
+ //Main.overview.connectObject('hiding',
+ this._hidingSigId = Main.overview.connect('hiding',
+ () => this._menu.close(), this);
+
+ Main.uiGroup.add_actor(this._menu.actor);
+ this._menuManager.addMenu(this._menu);
+ }
+
+ // once the menu is created, it stays unchanged and we need to modify our items based on current situation
+ if (this._addedMenuItems && this._addedMenuItems.length) {
+ this._addedMenuItems.forEach(i => i.destroy());
+ }
+
+ const popupItems =[];
+
+ const separator = new PopupMenu.PopupSeparatorMenuItem();
+ this._menu.addMenuItem(separator);
+
+ if (this.app.get_n_windows()) {
+ if (/*opt.APP_MENU_FORCE_QUIT*/true) {
+ popupItems.push([_('Force Quit'), () => {
+ this.app.get_windows()[0].kill();
+ }]);
+ }
+
+ if (/*opt.APP_MENU_CLOSE_WS*/true) {
+ const nWin = this._getWindowsOnCurrentWs().length;
+ if (nWin) {
+ popupItems.push([_(`Close ${nWin} Windows on Current Workspace`), () => {
+ const windows = this._getWindowsOnCurrentWs();
+ let time = global.get_current_time();
+ for (let win of windows) {
+ // increase time by 1 ms for each window to avoid errors from GS
+ win.delete(time++);
+ }
+ }]);
+ }
+ }
+
+ if (/*opt.APP_MENU_MV_TO_WS && */this._windowsOnOtherWs()) {
+ popupItems.push([_('Move App to Current Workspace'), this._moveAppToCurrentWorkspace]);
+ }
+ }
+
+ this._addedMenuItems = [];
+ this._addedMenuItems.push(separator);
+ popupItems.forEach(i => {
+ let item = new PopupMenu.PopupMenuItem(i[0]);
+ this._menu.addMenuItem(item);
+ item.connect('activate', i[1].bind(this));
+ this._addedMenuItems.push(item);
+ });
+
+ this.emit('menu-state-changed', true);
+
+ this._menu.open(BoxPointer.PopupAnimation.FULL);
+ this._menuManager.ignoreRelease();
+ this.emit('sync-tooltip');
+
+ return false;
+ }
+}