1
0
Fork 0
gnome-shell-extensions-extra/extensions/44/vertical-workspaces/lib/iconGrid.js
Daniel Baumann 9657f2cada
Merging upstream version 20231210.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-02-09 23:16:18 +01:00

345 lines
11 KiB
JavaScript

/**
* V-Shell (Vertical Workspaces)
* iconGrid.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 - 2023
* @license GPL-3.0
*
*/
'use strict';
const St = imports.gi.St;
const IconGrid = imports.ui.iconGrid;
let Me;
let opt;
// added sizes for better scaling
const IconSize = {
LARGEST: 256,
224: 224,
208: 208,
192: 192,
176: 176,
160: 160,
144: 144,
128: 128,
112: 112,
LARGE: 96,
80: 80,
64: 64,
TINY: 48,
};
const PAGE_WIDTH_CORRECTION = 100;
var IconGridModule = class {
constructor(me) {
Me = me;
opt = Me.opt;
this._firstActivation = true;
this.moduleEnabled = false;
this._overrides = null;
}
cleanGlobals() {
Me = null;
opt = null;
}
update(reset) {
this.moduleEnabled = opt.get('appDisplayModule');
// if notifications are enabled no override is needed
reset = reset || !this.moduleEnabled;
// don't touch original code if module disabled
if (reset && !this._firstActivation) {
this._disableModule();
} else if (!reset) {
this._firstActivation = false;
this._activateModule();
}
}
_activateModule() {
if (!this._overrides)
this._overrides = new Me.Util.Overrides();
if (Me.shellVersion < 43 && IconGridCommon._findBestModeForSize) {
IconGridCommon['findBestModeForSize'] = IconGridCommon._findBestModeForSize;
delete IconGridCommon['_findBestModeForSize'];
}
this._overrides.addOverride('IconGrid', IconGrid.IconGrid.prototype, IconGridCommon);
this._overrides.addOverride('IconGridLayout', IconGrid.IconGridLayout.prototype, IconGridLayoutCommon);
}
_disableModule() {
if (this._overrides)
this._overrides.removeAll();
this._overrides = null;
}
};
const IconGridCommon = {
getItemsAtPage(page) {
if (page < 0 || page >= this.nPages)
return [];
// throw new Error(`Page ${page} does not exist at IconGrid`);
const layoutManager = this.layout_manager;
return layoutManager.getItemsAtPage(page);
},
_findBestModeForSize(width, height) {
// this function is for main grid only, folder grid calculation is in appDisplay.AppFolderDialog class
if (this._currentMode > -1 || this.layoutManager._isFolder)
return;
const { pagePadding } = this.layout_manager;
const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
const iconPadding = 51 * scaleFactor;
// provided width is usually about 100px wider in horizontal orientation with prev/next page indicators
const pageIndicatorCompensation = opt.ORIENTATION ? 0 : PAGE_WIDTH_CORRECTION;
width -= pagePadding.left + pagePadding.right + pageIndicatorCompensation;
width *= opt.APP_GRID_PAGE_WIDTH_SCALE;
height -= pagePadding.top + pagePadding.bottom;
// store grid max dimensions for icon size algorithm
this.layoutManager._gridWidth = width;
this.layoutManager._gridHeight = height;
width -= 80; // compensation for default padding
height -= 80;
const spacing = opt.APP_GRID_SPACING;
// set the icon size as fixed to avoid changes in size later
const iconSize = opt.APP_GRID_ICON_SIZE > 0 ? opt.APP_GRID_ICON_SIZE : opt.APP_GRID_ICON_SIZE_DEFAULT;
const itemSize = iconSize * scaleFactor + iconPadding;
// if this._gridModes.length === 1, custom grid should be used
// if (iconSize > 0 && this._gridModes.length > 1) {
let columns = opt.APP_GRID_COLUMNS;
let rows = opt.APP_GRID_ROWS;
// 0 means adaptive size
let unusedSpaceH = -1;
let unusedSpaceV = -1;
if (!columns) {
// calculate #columns + 1 without spacing
columns = Math.floor(width / itemSize) + 1;
// check if columns with spacing fits the available width
// and reduce the number until it fits
while (unusedSpaceH < 0) {
columns -= 1;
unusedSpaceH = width - columns * itemSize - (columns - 1) * spacing;
}
}
if (!rows) {
rows = Math.floor(height / itemSize) + 1;
while (unusedSpaceV < 0) {
rows -= 1;
unusedSpaceV = height - rows * itemSize - ((rows - 1) * spacing);
}
}
this._gridModes = [{ columns, rows }];
// }
this._setGridMode(0);
},
};
const IconGridLayoutCommon = {
_findBestIconSize() {
const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
const nColumns = this.columnsPerPage;
const nRows = this.rowsPerPage;
// if grid is not defined return default icon size
if (nColumns < 1 && nRows < 1) {
return this._isFolder
? opt.APP_GRID_FOLDER_ICON_SIZE_DEFAULT : opt.APP_GRID_ICON_SIZE_DEFAULT;
}
const columnSpacingPerPage = opt.APP_GRID_SPACING * (nColumns - 1);
const rowSpacingPerPage = opt.APP_GRID_SPACING * (nRows - 1);
const iconPadding = 55 * scaleFactor;
const paddingH = this._isFolder ? this.pagePadding.left + this.pagePadding.right : 0;
const paddingV = this._isFolder ? this.pagePadding.top + this.pagePadding.bottom : 0;
const width = this._gridWidth ? this._gridWidth : this._pageWidth;
const height = this._gridHeight ? this._gridHeight : this._pageHeight;
if (!width || !height)
return opt.APP_GRID_ICON_SIZE_DEFAULT;
const [firstItem] = this._container;
if (this.fixedIconSize !== -1)
return this.fixedIconSize;
/* if (opt.APP_GRID_ADAPTIVE && !this._isFolder)
return opt.APP_GRID_ICON_SIZE_DEFAULT;*/
let iconSizes = Object.values(IconSize).sort((a, b) => b - a);
// limit max icon size for folders and fully adaptive folder grids, the whole range is for the main grid with active folders
if (this._isFolder && opt.APP_GRID_FOLDER_ADAPTIVE && opt.APP_GRID_FOLDER_ICON_SIZE < 0)
iconSizes = iconSizes.slice(iconSizes.indexOf(opt.APP_GRID_FOLDER_ICON_SIZE_DEFAULT), -1);
else if (this._isFolder)
iconSizes = iconSizes.slice(iconSizes.indexOf(IconSize.LARGE), -1);
else if (opt.APP_GRID_ADAPTIVE && opt.APP_GRID_ICON_SIZE < 0)
iconSizes = iconSizes.slice(iconSizes.indexOf(opt.APP_GRID_ICON_SIZE_DEFAULT), -1);
let sizeInvalid = false;
for (const size of iconSizes) {
let usedWidth, usedHeight;
if (firstItem) {
firstItem.icon.setIconSize(size);
const [firstItemWidth] = firstItem.get_preferred_size();
const itemSize = firstItemWidth;
if (itemSize < size)
sizeInvalid = true;
usedWidth = itemSize * nColumns;
usedHeight = itemSize * nRows;
}
if (!firstItem || sizeInvalid) {
usedWidth = (size + iconPadding) * nColumns;
usedHeight = (size + iconPadding) * nRows;
}
const emptyHSpace =
width - usedWidth - columnSpacingPerPage - paddingH;
// this.pagePadding.left - this.pagePadding.right;
const emptyVSpace =
height - usedHeight - rowSpacingPerPage - paddingV;
// this.pagePadding.top - this.pagePadding.bottom;
if (emptyHSpace >= 0 && emptyVSpace >= 0) {
return size;
}
}
return IconSize.TINY;
},
removeItem(item) {
if (!this._items.has(item)) {
console.error(`Item ${item} is not part of the IconGridLayout`);
return;
// throw new Error(`Item ${item} is not part of the IconGridLayout`);
}
if (!this._container)
return;
this._shouldEaseItems = true;
this._container.remove_child(item);
this._removeItemData(item);
},
addItem(item, page = -1, index = -1) {
if (this._items.has(item)) {
console.error(`iconGrid: Item ${item} already added to IconGridLayout`);
return;
// throw new Error(`Item ${item} already added to IconGridLayout`);
}
if (page > this._pages.length) {
console.error(`iconGrid: Cannot add ${item} to page ${page}`);
page = -1;
index = -1;
// throw new Error(`Cannot add ${item} to page ${page}`);
}
if (!this._container)
return;
if (page !== -1 && index === -1)
page = this._findBestPageToAppend(page);
this._shouldEaseItems = true;
this._container.add_child(item);
this._addItemToPage(item, page, index);
},
moveItem(item, newPage, newPosition) {
if (!this._items.has(item)) {
console.error(`iconGrid: Item ${item} is not part of the IconGridLayout`);
return;
// throw new Error(`Item ${item} is not part of the IconGridLayout`);
}
this._shouldEaseItems = true;
this._removeItemData(item);
if (newPage !== -1 && newPosition === -1)
newPage = this._findBestPageToAppend(newPage);
this._addItemToPage(item, newPage, newPosition);
},
_addItemToPage(item, pageIndex, index) {
// Ensure we have at least one page
if (this._pages.length === 0)
this._appendPage();
// Append a new page if necessary
if (pageIndex === this._pages.length)
this._appendPage();
if (pageIndex >= this._pages.length) {
pageIndex = -1;
index = -1;
}
if (pageIndex === -1)
pageIndex = this._pages.length - 1;
if (index === -1)
index = this._pages[pageIndex].children.length;
this._items.set(item, {
actor: item,
pageIndex,
destroyId: item.connect('destroy', () => this._removeItemData(item)),
visibleId: item.connect('notify::visible', () => {
const itemData = this._items.get(item);
this._updateVisibleChildrenForPage(itemData.pageIndex);
if (item.visible)
this._relocateSurplusItems(itemData.pageIndex);
else if (!this.allowIncompletePages)
this._fillItemVacancies(itemData.pageIndex);
}),
queueRelayoutId: item.connect('queue-relayout', () => {
this._childrenMaxSize = -1;
}),
});
item.icon.setIconSize(this._iconSize);
this._pages[pageIndex].children.splice(index, 0, item);
this._updateVisibleChildrenForPage(pageIndex);
this._relocateSurplusItems(pageIndex);
},
_findBestPageToAppend(startPage) {
const itemsPerPage = this.columnsPerPage * this.rowsPerPage;
for (let i = startPage; i < this._pages.length; i++) {
const visibleItems = this._pages[i].visibleChildren;
if (visibleItems.length < itemsPerPage)
return i;
}
return this._pages.length;
},
};