diff options
Diffstat (limited to 'extensions/46/vertical-workspaces/lib/appDisplay.js')
-rw-r--r-- | extensions/46/vertical-workspaces/lib/appDisplay.js | 2014 |
1 files changed, 0 insertions, 2014 deletions
diff --git a/extensions/46/vertical-workspaces/lib/appDisplay.js b/extensions/46/vertical-workspaces/lib/appDisplay.js deleted file mode 100644 index d94f7df..0000000 --- a/extensions/46/vertical-workspaces/lib/appDisplay.js +++ /dev/null @@ -1,2014 +0,0 @@ -/** - * V-Shell (Vertical Workspaces) - * appDisplay.js - * - * @author GdH <G-dH@github.com> - * @copyright 2022 - 2024 - * @license GPL-3.0 - * - */ - -'use strict'; - -import Clutter from 'gi://Clutter'; -import Gio from 'gi://Gio'; -import GLib from 'gi://GLib'; -import GObject from 'gi://GObject'; -import Graphene from 'gi://Graphene'; -import Meta from 'gi://Meta'; -import Pango from 'gi://Pango'; -import Shell from 'gi://Shell'; -import St from 'gi://St'; - -import * as Main from 'resource:///org/gnome/shell/ui/main.js'; -import * as AppDisplay from 'resource:///org/gnome/shell/ui/appDisplay.js'; -import * as DND from 'resource:///org/gnome/shell/ui/dnd.js'; -import * as PageIndicators from 'resource:///org/gnome/shell/ui/pageIndicators.js'; - -import { IconSize } from './iconGrid.js'; - -let Me; -let opt; -// gettext -let _; - -let _appDisplay; -let _timeouts; - -const APP_ICON_TITLE_EXPAND_TIME = 200; -const APP_ICON_TITLE_COLLAPSE_TIME = 100; - -const shellVersion46 = !Clutter.Container; // Container has been removed in 46 - -function _getCategories(info) { - let categoriesStr = info.get_categories(); - if (!categoriesStr) - return []; - return categoriesStr.split(';'); -} - -function _listsIntersect(a, b) { - for (let itemA of a) { - if (b.includes(itemA)) - return true; - } - return false; -} - -export const AppDisplayModule = class { - constructor(me) { - Me = me; - opt = Me.opt; - _ = Me.gettext; - - _appDisplay = Main.overview._overview.controls._appDisplay; - - this._firstActivation = true; - this.moduleEnabled = false; - this._overrides = null; - - this._appSystemStateConId = 0; - this._appGridLayoutConId = 0; - this._origAppViewItemAcceptDrop = null; - this._updateFolderIcons = 0; - } - - cleanGlobals() { - Me = null; - opt = null; - _ = null; - _appDisplay = null; - } - - update(reset) { - this._removeTimeouts(); - this.moduleEnabled = opt.get('appDisplayModule'); - const conflict = false; - - reset = reset || !this.moduleEnabled || conflict; - - // don't touch the original code if module disabled - if (reset && !this._firstActivation) { - this._disableModule(); - this.moduleEnabled = false; - } else if (!reset) { - this._firstActivation = false; - this._activateModule(); - } - if (reset && this._firstActivation) { - this.moduleEnabled = false; - console.debug(' AppDisplayModule - Keeping untouched'); - } - } - - _activateModule() { - Me.Modules.iconGridModule.update(); - - if (!this._overrides) - this._overrides = new Me.Util.Overrides(); - - _timeouts = {}; - - this._applyOverrides(); - this._updateAppDisplay(); - _appDisplay.add_style_class_name('app-display-46'); - - console.debug(' AppDisplayModule - Activated'); - } - - _disableModule() { - Me.Modules.iconGridModule.update(true); - - if (this._overrides) - this._overrides.removeAll(); - this._overrides = null; - - const reset = true; - this._updateAppDisplay(reset); - this._restoreOverviewGroup(); - - _appDisplay.remove_style_class_name('app-display-46'); - - console.debug(' AppDisplayModule - Disabled'); - } - - _removeTimeouts() { - if (_timeouts) { - Object.values(_timeouts).forEach(t => { - if (t) - GLib.source_remove(t); - }); - _timeouts = null; - } - } - - _applyOverrides() { - // Common/appDisplay - // this._overrides.addOverride('BaseAppViewCommon', AppDisplay.BaseAppView.prototype, BaseAppViewCommon); - // instead of overriding inaccessible BaseAppView class, we override its subclasses - AppDisplay and FolderView - this._overrides.addOverride('BaseAppViewCommonApp', AppDisplay.AppDisplay.prototype, BaseAppViewCommon); - this._overrides.addOverride('AppDisplay', AppDisplay.AppDisplay.prototype, AppDisplayCommon); - this._overrides.addOverride('AppViewItem', AppDisplay.AppViewItem.prototype, AppViewItemCommon); - this._overrides.addOverride('AppGridCommon', AppDisplay.AppGrid.prototype, AppGridCommon); - this._overrides.addOverride('AppIcon', AppDisplay.AppIcon.prototype, AppIcon); - if (opt.ORIENTATION) { - this._overrides.removeOverride('AppGridLayoutHorizontal'); - this._overrides.addOverride('AppGridLayoutVertical', _appDisplay._appGridLayout, BaseAppViewGridLayoutVertical); - } else { - this._overrides.removeOverride('AppGridLayoutVertical'); - this._overrides.addOverride('AppGridLayoutHorizontal', _appDisplay._appGridLayout, BaseAppViewGridLayoutHorizontal); - } - - // Custom folders - this._overrides.addOverride('BaseAppViewCommonFolder', AppDisplay.FolderView.prototype, BaseAppViewCommon); - this._overrides.addOverride('FolderView', AppDisplay.FolderView.prototype, FolderView); - this._overrides.addOverride('AppFolderDialog', AppDisplay.AppFolderDialog.prototype, AppFolderDialog); - this._overrides.addOverride('FolderIcon', AppDisplay.FolderIcon.prototype, FolderIcon); - - // Prevent changing grid page size when showing/hiding _pageIndicators - this._overrides.addOverride('PageIndicators', PageIndicators.PageIndicators.prototype, PageIndicatorsCommon); - } - - _updateAppDisplay(reset) { - const orientation = reset ? Clutter.Orientation.HORIZONTAL : opt.ORIENTATION; - BaseAppViewCommon._adaptForOrientation.bind(_appDisplay)(orientation); - - this._updateFavoritesConnection(reset); - - _appDisplay.visible = true; - if (reset) { - _appDisplay._grid.layoutManager.fixedIconSize = -1; - _appDisplay._grid.layoutManager.allow_incomplete_pages = true; - _appDisplay._grid._currentMode = -1; - _appDisplay._grid.setGridModes(); - _appDisplay._grid.set_style(''); - _appDisplay._prevPageArrow.set_scale(1, 1); - _appDisplay._nextPageArrow.set_scale(1, 1); - if (this._appGridLayoutConId) { - global.settings.disconnect(this._appGridLayoutConId); - this._appGridLayoutConId = 0; - } - this._repopulateAppDisplay(reset); - } else { - _appDisplay._grid._currentMode = -1; - // update grid on layout reset - if (!this._appGridLayoutConId) - this._appGridLayoutConId = global.settings.connect('changed::app-picker-layout', this._updateLayout.bind(this)); - - // avoid resetting appDisplay before startup animation - // x11 shell restart skips startup animation - if (!Main.layoutManager._startingUp) { - this._repopulateAppDisplay(); - } else if (Main.layoutManager._startingUp && Meta.is_restart()) { - _timeouts.three = GLib.idle_add(GLib.PRIORITY_LOW, () => { - this._repopulateAppDisplay(); - _timeouts.three = 0; - return GLib.SOURCE_REMOVE; - }); - } - } - } - - _updateFavoritesConnection(reset) { - if (!reset) { - if (!this._appSystemStateConId && opt.APP_GRID_INCLUDE_DASH >= 3) { - this._appSystemStateConId = Shell.AppSystem.get_default().connect( - 'app-state-changed', - () => { - this._updateFolderIcons = true; - _appDisplay._redisplay(); - } - ); - } - } else if (this._appSystemStateConId) { - Shell.AppSystem.get_default().disconnect(this._appSystemStateConId); - this._appSystemStateConId = 0; - } - } - - _restoreOverviewGroup() { - Main.overview.dash.showAppsButton.checked = false; - Main.layoutManager.overviewGroup.opacity = 255; - Main.layoutManager.overviewGroup.scale_x = 1; - Main.layoutManager.overviewGroup.scale_y = 1; - Main.layoutManager.overviewGroup.hide(); - _appDisplay.translation_x = 0; - _appDisplay.translation_y = 0; - _appDisplay.visible = true; - _appDisplay.opacity = 255; - } - - _updateLayout(settings, key) { - // Reset the app grid only if the user layout has been completely removed - if (!settings.get_value(key).deep_unpack().length) { - this._repopulateAppDisplay(); - } - } - - _repopulateAppDisplay(reset = false, callback) { - // Remove all icons so they can be re-created with the current configuration - // Updating appGrid content while rebasing extensions when session is locked makes no sense (relevant for GS version < 46) - if (!Main.sessionMode.isLocked) - AppDisplayCommon.removeAllItems.bind(_appDisplay)(); - - // appDisplay disabled - if (reset) { - _appDisplay._redisplay(); - return; - } - - _appDisplay._readyToRedisplay = true; - _appDisplay._redisplay(); - - // Setting OffscreenRedirect should improve performance when opacity transitions are used - _appDisplay.offscreen_redirect = Clutter.OffscreenRedirect.ALWAYS; - - if (opt.APP_GRID_PERFORMANCE) - this._realizeAppDisplay(callback); - else if (callback) - callback(); - } - - _realizeAppDisplay(callback) { - // Workaround - silently realize appDisplay - // The realization takes some time and affects animations during the first use - // If we do it invisibly before the user needs the app grid, it can improve the user's experience - _appDisplay.opacity = 1; - - this._exposeAppGrid(); - _appDisplay._redisplay(); - this._exposeAppFolders(); - - // Let the main loop process our changes before we continue - _timeouts.updateAppGrid = GLib.idle_add(GLib.PRIORITY_LOW, () => { - this._restoreAppGrid(); - Me._resetInProgress = false; - - if (callback) - callback(); - - _timeouts.updateAppGrid = 0; - return GLib.SOURCE_REMOVE; - }); - } - - _exposeAppGrid() { - const overviewGroup = Main.layoutManager.overviewGroup; - if (!overviewGroup.visible) { - // scale down the overviewGroup so it don't cover uiGroup - overviewGroup.scale_y = 0.001; - // make it invisible to the eye, but visible for the renderer - overviewGroup.opacity = 1; - // if overview is hidden, show it - overviewGroup.visible = true; - } - } - - _restoreAppGrid() { - if (opt.APP_GRID_PERFORMANCE) - this._hideAppFolders(); - - const overviewGroup = Main.layoutManager.overviewGroup; - if (!Main.overview._shown) - overviewGroup.hide(); - overviewGroup.scale_y = 1; - overviewGroup.opacity = 255; - _appDisplay.opacity = 0; - _appDisplay.visible = false; - } - - _exposeAppFolders() { - _appDisplay._folderIcons.forEach(d => { - d._ensureFolderDialog(); - d._dialog.scale_y = 0.0001; - d._dialog.show(); - d._dialog._updateFolderSize(); - }); - } - - _hideAppFolders() { - _appDisplay._folderIcons.forEach(d => { - if (d._dialog) { - d._dialog.hide(); - d._dialog.scale_y = 1; - } - }); - } -}; - -function _getViewFromIcon(icon) { - icon = icon._sourceItem ? icon._sourceItem : icon; - for (let parent = icon.get_parent(); parent; parent = parent.get_parent()) { - if (parent instanceof AppDisplay.AppDisplay || parent instanceof AppDisplay.FolderView) { - return parent; - } - } - return null; -} - -const AppDisplayCommon = { - _ensureDefaultFolders() { - // disable creation of default folders if user deleted them - }, - - removeAllItems() { - this._orderedItems.slice().forEach(item => { - if (item._dialog) - Main.layoutManager.overviewGroup.remove_child(item._dialog); - - this._removeItem(item); - item.destroy(); - }); - this._folderIcons = []; - }, - - // apps load adapted for custom sorting and including dash items - _loadApps() { - let appIcons = []; - const runningApps = Shell.AppSystem.get_default().get_running().map(a => a.id); - - this._appInfoList = Shell.AppSystem.get_default().get_installed().filter(appInfo => { - try { - appInfo.get_id(); // catch invalid file encodings - } catch (e) { - return false; - } - - const appIsRunning = runningApps.includes(appInfo.get_id()); - const appIsFavorite = this._appFavorites.isFavorite(appInfo.get_id()); - const excludeApp = (opt.APP_GRID_EXCLUDE_RUNNING && appIsRunning) || (opt.APP_GRID_EXCLUDE_FAVORITES && appIsFavorite); - - return this._parentalControlsManager.shouldShowApp(appInfo) && !excludeApp; - }); - - 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_USAGE) { - 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); - }); - } else if (this._updateFolderIcons && opt.APP_GRID_EXCLUDE_RUNNING) { - // if any app changed its running state, update folder icon - icon.icon.update(); - } - - // remove empty folder icons - if (!icon.visible) { - icon.destroy(); - return; - } - - appIcons.push(icon); - this._folderIcons.push(icon); - - icon.getAppIds().forEach(appId => appsInsideFolders.add(appId)); - }); - } - - // reset request to update active icon - this._updateFolderIcons = false; - - // 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_USAGE && 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); - - return appIcons; - }, - - _onDragBegin(overview, source) { - // let sourceId; - // support active preview icons - if (source._sourceItem) { - // sourceId = source._sourceFolder._id; - source = source._sourceItem; - } /* else { - sourceId = source.id; - }*/ - // Prevent switching page when an item on another page is selected - // by removing the focus from all icons - // This is an upstream bug - // this.selectApp(sourceId); - this.grab_key_focus(); - - this._dragMonitor = { - dragMotion: this._onDragMotion.bind(this), - dragDrop: this._onDragDrop.bind(this), - }; - DND.addDragMonitor(this._dragMonitor); - - this._appGridLayout.showPageIndicators(); - this._dragFocus = null; - this._swipeTracker.enabled = false; - - // When dragging from a folder dialog, the dragged app icon doesn't - // exist in AppDisplay. We work around that by adding a placeholder - // icon that is either destroyed on cancel, or becomes the effective - // new icon when dropped. - if (/* AppDisplay.*/_getViewFromIcon(source) instanceof AppDisplay.FolderView || - (opt.APP_GRID_EXCLUDE_FAVORITES && this._appFavorites.isFavorite(source.id))) - this._ensurePlaceholder(source); - }, - - _ensurePlaceholder(source) { - if (this._placeholder) - return; - - if (source._sourceItem) - source = source._sourceItem; - - const appSys = Shell.AppSystem.get_default(); - const app = appSys.lookup_app(source.id); - - const isDraggable = - global.settings.is_writable('favorite-apps') || - global.settings.is_writable('app-picker-layout'); - - this._placeholder = new AppDisplay.AppIcon(app, { isDraggable }); - this._placeholder.connect('notify::pressed', () => { - if (this._placeholder?.pressed) - this.updateDragFocus(this._placeholder); - }); - this._placeholder.scaleAndFade(); - this._redisplay(); - }, - - // accept source from active folder preview - acceptDrop(source) { - if (opt.APP_GRID_USAGE) - return false; - if (source._sourceItem) - source = source._sourceItem; - if (!this._acceptDropCommon(source)) - return false; - - this._savePages(); - - const view = /* AppDisplay.*/_getViewFromIcon(source); - if (view instanceof AppDisplay.FolderView) - view.removeApp(source.app); - - if (this._currentDialog) - this._currentDialog.popdown(); - - if (opt.APP_GRID_EXCLUDE_FAVORITES && this._appFavorites.isFavorite(source.id)) - this._appFavorites.removeFavorite(source.id); - return true; - }, - - _savePages() { - // Skip saving pages when search app grid mode is active - // and the grid is showing search results - if (Main.overview._overview.controls._origAppGridContent) - return; - - const pages = []; - - for (let i = 0; i < this._grid.nPages; i++) { - const pageItems = - this._grid.getItemsAtPage(i).filter(c => c.visible); - const pageData = {}; - - pageItems.forEach((item, index) => { - pageData[item.id] = { - position: GLib.Variant.new_int32(index), - }; - }); - pages.push(pageData); - } - - this._pageManager.pages = pages; - }, -}; - -const BaseAppViewCommon = { - after__init() { - // Only folders can run this init - this._isFolder = true; - - this._adaptForOrientation(opt.ORIENTATION, true); - - // Because the original class prototype is not exported, we need to inject every instance - const overrides = new Me.Util.Overrides(); - if (opt.ORIENTATION) { - overrides.addOverride('FolderGridLayoutVertical', this._appGridLayout, BaseAppViewGridLayoutVertical); - this._pageIndicators.set_style('margin-right: 12px;'); - } else { - overrides.addOverride('FolderGridLayoutHorizontal', this._appGridLayout, BaseAppViewGridLayoutHorizontal); - this._pageIndicators.set_style('margin-bottom: 12px;'); - } - }, - - _adaptForOrientation(orientation, folder) { - const vertical = !!orientation; - - this._grid.layoutManager.fixedIconSize = folder ? opt.APP_GRID_FOLDER_ICON_SIZE : opt.APP_GRID_ICON_SIZE; - this._grid.layoutManager._orientation = orientation; - this._orientation = orientation; - this._swipeTracker.orientation = orientation; - this._swipeTracker._reset(); - - this._adjustment = vertical - ? this._scrollView.get_vscroll_bar().adjustment - : this._scrollView.get_hscroll_bar().adjustment; - - this._prevPageArrow.pivot_point = new Graphene.Point({ x: 0.5, y: 0.5 }); - this._prevPageArrow.rotation_angle_z = vertical ? 90 : 0; - - this._nextPageArrow.pivot_point = new Graphene.Point({ x: 0.5, y: 0.5 }); - this._nextPageArrow.rotation_angle_z = vertical ? 90 : 0; - - const pageIndicators = this._pageIndicators; - pageIndicators.vertical = vertical; - this._box.vertical = !vertical; - pageIndicators.x_expand = !vertical; - pageIndicators.y_align = vertical ? Clutter.ActorAlign.CENTER : Clutter.ActorAlign.START; - pageIndicators.x_align = vertical ? Clutter.ActorAlign.START : Clutter.ActorAlign.CENTER; - - this._grid.layoutManager.allow_incomplete_pages = folder ? false : opt.APP_GRID_ALLOW_INCOMPLETE_PAGES; - const spacing = folder ? opt.APP_GRID_FOLDER_SPACING : opt.APP_GRID_SPACING; - this._grid.set_style(`column-spacing: ${spacing}px; row-spacing: ${spacing}px;`); - - if (vertical) { - this._scrollView.set_policy(St.PolicyType.NEVER, St.PolicyType.EXTERNAL); - if (!this._scrollConId) { - this._scrollConId = this._adjustment.connect('notify::value', adj => { - const value = adj.value / adj.page_size; - this._pageIndicators.setCurrentPosition(value); - }); - } - pageIndicators.remove_style_class_name('page-indicators-horizontal'); - pageIndicators.add_style_class_name('page-indicators-vertical'); - this._prevPageIndicator.add_style_class_name('prev-page-indicator'); - this._nextPageIndicator.add_style_class_name('next-page-indicator'); - this._nextPageArrow.translationY = 0; - this._prevPageArrow.translationY = 0; - this._nextPageIndicator.translationX = 0; - this._prevPageIndicator.translationX = 0; - } else { - this._scrollView.set_policy(St.PolicyType.EXTERNAL, St.PolicyType.NEVER); - if (this._scrollConId) { - this._adjustment.disconnect(this._scrollConId); - this._scrollConId = 0; - } - pageIndicators.remove_style_class_name('page-indicators-vertical'); - pageIndicators.add_style_class_name('page-indicators-horizontal'); - this._prevPageIndicator.remove_style_class_name('prev-page-indicator'); - this._nextPageIndicator.remove_style_class_name('next-page-indicator'); - this._nextPageArrow.translationX = 0; - this._prevPageArrow.translationX = 0; - this._nextPageIndicator.translationY = 0; - this._prevPageIndicator.translationY = 0; - } - - const scale = opt.APP_GRID_SHOW_PAGE_ARROWS ? 1 : 0; - this._prevPageArrow.set_scale(scale, scale); - this._nextPageArrow.set_scale(scale, scale); - }, - - _sortItemsByName(items) { - items.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())); - }, - - _updateItemPositions(icons, allowIncompletePages = false) { - // Avoid recursion when relocating icons - this._grid.layoutManager._skipRelocateSurplusItems = true; - - const { itemsPerPage } = this._grid; - - icons.slice().forEach((icon, index) => { - const [currentPage, currentPosition] = this._grid.layoutManager.getItemPosition(icon); - - let page, position; - if (allowIncompletePages) { - [page, position] = this._getItemPosition(icon); - } else { - page = Math.floor(index / itemsPerPage); - position = index % itemsPerPage; - } - - if (currentPage !== page || currentPosition !== position) { - this._moveItem(icon, page, position); - } - }); - - this._grid.layoutManager._skipRelocateSurplusItems = false; - // Disable animating the icons to their new positions - // since it can cause glitches when the app grid search mode is active - // and many icons are repositioning at once - this._grid.layoutManager._shouldEaseItems = false; - }, - - // Adds sorting options - _redisplay() { - // different options for main app grid and app folders - const thisIsFolder = this instanceof AppDisplay.FolderView; - const thisIsAppDisplay = !thisIsFolder; - - // When an app was dragged from a folder and dropped to the main grid - // folders (if exist) need to be redisplayed even if we temporary block it for the appDisplay - this._folderIcons?.forEach(icon => { - icon.view._redisplay(); - }); - - // Avoid unwanted updates - if (thisIsAppDisplay && !this._readyToRedisplay) - return; - - const oldApps = this._orderedItems.slice(); - const oldAppIds = oldApps.map(icon => icon.id); - - const newApps = this._loadApps(); - const newAppIds = newApps.map(icon => icon.id); - - const addedApps = newApps.filter(icon => !oldAppIds.includes(icon.id)); - const removedApps = oldApps.filter(icon => !newAppIds.includes(icon.id)); - - // Don't update folder without dialog if its content didn't change - if (!addedApps.length && !removedApps.length && thisIsFolder && !this.get_parent()) - return; - - // Remove old app icons - removedApps.forEach(icon => { - this._removeItem(icon); - icon.destroy(); - }); - - // For the main app grid only - let allowIncompletePages = thisIsAppDisplay && opt.APP_GRID_ALLOW_INCOMPLETE_PAGES; - - const customOrder = !((opt.APP_GRID_ORDER && thisIsAppDisplay) || (opt.APP_FOLDER_ORDER && thisIsFolder)); - if (!customOrder) { - allowIncompletePages = false; - - // Sort by name - this._sortItemsByName(newApps); - - // Sort by usage - if ((opt.APP_GRID_USAGE && thisIsAppDisplay) || - (opt.APP_FOLDER_USAGE && thisIsFolder)) { - newApps.sort((a, b) => Shell.AppUsage.get_default().compare(a.app?.id, b.app?.id)); - } - - // Sort favorites first - if (!opt.APP_GRID_EXCLUDE_FAVORITES && opt.APP_GRID_DASH_FIRST) { - const fav = Object.keys(this._appFavorites._favorites); - newApps.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_EXCLUDE_RUNNING && opt.APP_GRID_DASH_FIRST) { - newApps.sort((a, b) => a.app?.get_state() !== Shell.AppState.RUNNING && b.app?.get_state() === Shell.AppState.RUNNING); - } - - // Sort folders first - if (thisIsAppDisplay && opt.APP_GRID_FOLDERS_FIRST) - newApps.sort((a, b) => b._folder && !a._folder); - - // Sort folders last - else if (thisIsAppDisplay && opt.APP_GRID_FOLDERS_LAST) - newApps.sort((a, b) => a._folder && !b._folder); - } else { - // Sort items according to the custom order stored in pageManager - newApps.sort(this._compareItems.bind(this)); - } - - // Add new app icons to the grid - newApps.forEach(icon => { - const [page, position] = this._grid.getItemPosition(icon); - if (page === -1 && position === -1) - this._addItem(icon, -1, -1); - }); - // When a placeholder icon was added to the custom sorted grid during DND from a folder - // update its initial position on the page - if (customOrder) - newApps.sort(this._compareItems.bind(this)); - - this._orderedItems = newApps; - - // Update icon positions if needed - this._updateItemPositions(this._orderedItems, allowIncompletePages); - - // Relocate items with invalid positions - if (thisIsAppDisplay) { - const nPages = this._grid.layoutManager.nPages; - for (let pageIndex = 0; pageIndex < nPages; pageIndex++) - this._grid.layoutManager._relocateSurplusItems(pageIndex); - } - - this.emit('view-loaded'); - }, - - _canAccept(source) { - return source instanceof AppDisplay.AppViewItem; - }, - - // this method is replacing BaseAppVew.acceptDrop which can't be overridden directly - _acceptDropCommon(source) { - const dropTarget = this._dropTarget; - delete this._dropTarget; - if (!this._canAccept(source)) - return false; - - if (dropTarget === this._prevPageIndicator || - dropTarget === this._nextPageIndicator) { - let increment; - increment = dropTarget === this._prevPageIndicator ? -1 : 1; - const { currentPage, nPages } = this._grid; - const page = Math.min(currentPage + increment, nPages); - const position = page < nPages ? -1 : 0; - - this._moveItem(source, page, position); - this.goToPage(page); - } else if (this._delayedMoveData) { - // Dropped before the icon was moved - const { page, position } = this._delayedMoveData; - try { - this._moveItem(source, page, position); - } catch (e) { - console.warn(`Warning:${e}`); - } - this._removeDelayedMove(); - } - - return true; - }, - - // support active preview icons - _onDragMotion(dragEvent) { - if (!(dragEvent.source instanceof AppDisplay.AppViewItem)) - return DND.DragMotionResult.CONTINUE; - - if (dragEvent.source._sourceItem) - dragEvent.source = dragEvent.source._sourceItem; - - const appIcon = dragEvent.source; - - if (appIcon instanceof AppDisplay.AppViewItem) { - if (!this._dragMaybeSwitchPageImmediately(dragEvent)) { - // Two ways of switching pages during DND: - // 1) When "bumping" the cursor against the monitor edge, we switch - // page immediately. - // 2) When hovering over the next-page indicator for a certain time, - // we also switch page. - - const { targetActor } = dragEvent; - - if (targetActor === this._prevPageIndicator || - targetActor === this._nextPageIndicator) - this._maybeSetupDragPageSwitchInitialTimeout(dragEvent); - else - this._resetDragPageSwitch(); - } - } - - const thisIsFolder = this instanceof AppDisplay.FolderView; - const thisIsAppDisplay = !thisIsFolder; - - // Prevent reorganizing the main app grid icons when an app folder is open and when sorting is not custom - // For some reason in V-Shell the drag motion events propagate from folder to main grid, which is not a problem in default code - so test the open dialog - if (!this._currentDialog && (!opt.APP_GRID_ORDER && thisIsAppDisplay) || (!opt.APP_FOLDER_ORDER && thisIsFolder)) - this._maybeMoveItem(dragEvent); - - return DND.DragMotionResult.CONTINUE; - }, -}; - -const BaseAppViewGridLayoutHorizontal = { - _getIndicatorsWidth(box) { - const [width, height] = box.get_size(); - const arrows = [ - this._nextPageArrow, - this._previousPageArrow, - ]; - - let minArrowsWidth; - - minArrowsWidth = arrows.reduce( - (previousWidth, accessory) => { - const [min] = accessory.get_preferred_width(height); - return Math.max(previousWidth, min); - }, 0); - - minArrowsWidth = opt.APP_GRID_SHOW_PAGE_ARROWS ? minArrowsWidth : 0; - - const indicatorWidth = !this._grid._isFolder - ? minArrowsWidth + ((width - minArrowsWidth) * (1 - opt.APP_GRID_PAGE_WIDTH_SCALE)) / 2 - : minArrowsWidth + 6; - - return Math.round(indicatorWidth); - }, - - vfunc_allocate(container, box) { - const ltr = container.get_text_direction() !== Clutter.TextDirection.RTL; - const indicatorsWidth = this._getIndicatorsWidth(box); - - const pageIndicatorsHeight = 20; // _appDisplay._pageIndicators.height is unstable, 20 is determined by the style - const availHeight = box.get_height() - pageIndicatorsHeight; - const vPadding = Math.round((availHeight - availHeight * opt.APP_GRID_PAGE_HEIGHT_SCALE) / 2); - this._grid.indicatorsPadding = new Clutter.Margin({ - left: indicatorsWidth, - right: indicatorsWidth, - top: vPadding + pageIndicatorsHeight, - bottom: vPadding, - }); - - this._scrollView.allocate(box); - - const leftBox = box.copy(); - leftBox.x2 = leftBox.x1 + indicatorsWidth; - - const rightBox = box.copy(); - rightBox.x1 = rightBox.x2 - indicatorsWidth; - - this._previousPageIndicator.allocate(ltr ? leftBox : rightBox); - this._previousPageArrow.allocate_align_fill(ltr ? leftBox : rightBox, - 0.5, 0.5, false, false); - this._nextPageIndicator.allocate(ltr ? rightBox : leftBox); - this._nextPageArrow.allocate_align_fill(ltr ? rightBox : leftBox, - 0.5, 0.5, false, false); - - this._pageWidth = box.get_width(); - - // Center page arrow buttons - this._previousPageArrow.translationY = pageIndicatorsHeight / 2; - this._nextPageArrow.translationY = pageIndicatorsHeight / 2; - // Reset page indicators vertical position - this._nextPageIndicator.translationY = 0; - this._previousPageIndicator.translationY = 0; - }, -}; - -const BaseAppViewGridLayoutVertical = { - _getIndicatorsHeight(box) { - const [width, height] = box.get_size(); - const arrows = [ - this._nextPageArrow, - this._previousPageArrow, - ]; - - let minArrowsHeight; - - minArrowsHeight = arrows.reduce( - (previousHeight, accessory) => { - const [min] = accessory.get_preferred_height(width); - return Math.max(previousHeight, min); - }, 0); - - minArrowsHeight = opt.APP_GRID_SHOW_PAGE_ARROWS ? minArrowsHeight : 0; - - const indicatorHeight = !this._grid._isFolder - ? minArrowsHeight + ((height - minArrowsHeight) * (1 - opt.APP_GRID_PAGE_HEIGHT_SCALE)) / 2 - : minArrowsHeight + 6; - - return Math.round(indicatorHeight); - }, - - _syncPageIndicators() { - if (!this._container) - return; - - const { value } = this._pageIndicatorsAdjustment; - - const { top, bottom } = this._grid.indicatorsPadding; - const topIndicatorOffset = -top * (1 - value); - const bottomIndicatorOffset = bottom * (1 - value); - - this._previousPageIndicator.translationY = - topIndicatorOffset; - this._nextPageIndicator.translationY = - bottomIndicatorOffset; - - const leftArrowOffset = -top * value; - const rightArrowOffset = bottom * value; - - this._previousPageArrow.translationY = - leftArrowOffset; - this._nextPageArrow.translationY = - rightArrowOffset; - - // Page icons - this._translatePreviousPageIcons(value); - this._translateNextPageIcons(value); - - if (this._grid.nPages > 0) { - this._grid.getItemsAtPage(this._currentPage).forEach(icon => { - icon.translationY = 0; - }); - } - }, - - _translatePreviousPageIcons(value) { - if (this._currentPage === 0) - return; - - const pageHeight = this._grid.layoutManager._pageHeight; - const previousPage = this._currentPage - 1; - const icons = this._grid.getItemsAtPage(previousPage).filter(i => i.visible); - if (icons.length === 0) - return; - - const { top } = this._grid.indicatorsPadding; - const { rowSpacing } = this._grid.layoutManager; - const endIcon = icons[icons.length - 1]; - let iconOffset; - - const currentPageOffset = pageHeight * this._currentPage; - iconOffset = currentPageOffset - endIcon.allocation.y1 - endIcon.width + top - rowSpacing; - - for (const icon of icons) - icon.translationY = iconOffset * value; - }, - - _translateNextPageIcons(value) { - if (this._currentPage >= this._grid.nPages - 1) - return; - - const nextPage = this._currentPage + 1; - const icons = this._grid.getItemsAtPage(nextPage).filter(i => i.visible); - if (icons.length === 0) - return; - - const { bottom } = this._grid.indicatorsPadding; - const { rowSpacing } = this._grid.layoutManager; - let iconOffset; - - const pageOffset = this._pageHeight * nextPage; - iconOffset = pageOffset - icons[0].allocation.y1 - bottom + rowSpacing; - - for (const icon of icons) - icon.translationY = iconOffset * value; - }, - - vfunc_allocate(container, box) { - const indicatorsHeight = this._getIndicatorsHeight(box); - - const pageIndicatorsWidth = 20; // _appDisplay._pageIndicators.width is not stable, 20 is determined by the style - const availWidth = box.get_width() - pageIndicatorsWidth; - const hPadding = Math.round((availWidth - availWidth * opt.APP_GRID_PAGE_WIDTH_SCALE) / 2); - - this._grid.indicatorsPadding = new Clutter.Margin({ - top: indicatorsHeight, - bottom: indicatorsHeight, - left: hPadding + pageIndicatorsWidth, - right: hPadding, - }); - - this._scrollView.allocate(box); - - const topBox = box.copy(); - topBox.y2 = topBox.y1 + indicatorsHeight; - - const bottomBox = box.copy(); - bottomBox.y1 = bottomBox.y2 - indicatorsHeight; - - this._previousPageIndicator.allocate(topBox); - this._previousPageArrow.allocate_align_fill(topBox, - 0.5, 0.5, false, false); - this._nextPageIndicator.allocate(bottomBox); - this._nextPageArrow.allocate_align_fill(bottomBox, - 0.5, 0.5, false, false); - - this._pageHeight = box.get_height(); - - // Center page arrow buttons - this._previousPageArrow.translationX = pageIndicatorsWidth / 2; - this._nextPageArrow.translationX = pageIndicatorsWidth / 2; - // Reset page indicators vertical position - this._nextPageIndicator.translationX = 0; - this._previousPageIndicator.translationX = 0; - }, -}; - -const AppGridCommon = { - _updatePadding() { - const { rowSpacing, columnSpacing } = this.layoutManager; - - const padding = this._indicatorsPadding.copy(); - - padding.left += rowSpacing; - padding.right += rowSpacing; - padding.top += columnSpacing; - padding.bottom += columnSpacing; - - this.layoutManager.pagePadding = padding; - }, -}; - -const FolderIcon = { - after__init() { - this.button_mask = St.ButtonMask.ONE | St.ButtonMask.TWO; - if (shellVersion46) - this.add_style_class_name('app-folder-46'); - else - this.add_style_class_name('app-folder-45'); - }, - - open() { - // Prevent switching page when an item on another page is selected - GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { - // Select folder icon to prevent switching page to the one with currently selected icon - this._parentView._selectAppInternal(this._id); - // Remove key focus from the selected icon to prevent switching page after dropping the removed folder icon on another page of the main grid - this._parentView.grab_key_focus(); - this._ensureFolderDialog(); - this._dialog.popup(); - }); - }, - - vfunc_clicked() { - this.open(); - }, - - _canAccept(source) { - if (!(source instanceof AppDisplay.AppIcon)) - return false; - - const view = _getViewFromIcon(source); - if (!view /* || !(view instanceof AppDisplay.AppDisplay)*/) - return false; - - // Disable this test to allow the user to cancel the current DND by dropping the icon on its original source - /* if (this._folder.get_strv('apps').includes(source.id)) - return false;*/ - - return true; - }, - - acceptDrop(source) { - if (source._sourceItem) - source = source._sourceItem; - - const accepted = AppViewItemCommon.acceptDrop.bind(this)(source); - - if (!accepted) - return false; - - // If the icon is already in the folder (user dropped it back on the same folder), skip re-adding it - if (this._folder.get_strv('apps').includes(source.id)) - return true; - - this._onDragEnd(); - - this.view.addApp(source.app); - - return true; - }, -}; - -const FolderView = { - _createGrid() { - let grid = new FolderGrid(); - grid._view = this; - return grid; - }, - - createFolderIcon(size) { - const layout = new Clutter.GridLayout({ - row_homogeneous: true, - column_homogeneous: true, - }); - - let icon = new St.Widget({ - layout_manager: layout, - x_align: Clutter.ActorAlign.CENTER, - style: `width: ${size}px; height: ${size}px;`, - }); - - const numItems = this._orderedItems.length; - // decide what number of icons switch to 3x3 grid - // APP_GRID_FOLDER_ICON_GRID: 3 -> more than 4 - // : 4 -> more than 8 - const threshold = opt.APP_GRID_FOLDER_ICON_GRID % 3 ? 8 : 4; - const gridSize = opt.APP_GRID_FOLDER_ICON_GRID > 2 && numItems > threshold ? 3 : 2; - const FOLDER_SUBICON_FRACTION = gridSize === 2 ? 0.4 : 0.27; - - let subSize = Math.floor(FOLDER_SUBICON_FRACTION * size); - let rtl = icon.get_text_direction() === Clutter.TextDirection.RTL; - for (let i = 0; i < gridSize * gridSize; i++) { - const style = `width: ${subSize}px; height: ${subSize}px;`; - let bin = new St.Bin({ style, reactive: true }); - bin.pivot_point = new Graphene.Point({ x: 0.5, y: 0.5 }); - if (i < numItems) { - if (!opt.APP_GRID_ACTIVE_PREVIEW) { - bin.child = this._orderedItems[i].app.create_icon_texture(subSize); - } else { - const app = this._orderedItems[i].app; - const child = new AppDisplay.AppIcon(app, { - setSizeManually: true, - showLabel: false, - }); - - child._sourceItem = this._orderedItems[i]; - child._sourceFolder = this; - child.icon.style_class = ''; - child.set_style_class_name(''); - child.icon.set_style('margin: 0; padding: 0;'); - child._dot.set_style('margin-bottom: 1px;'); - child.icon.setIconSize(subSize); - child._canAccept = () => false; - - bin.child = child; - - bin.connect('enter-event', () => { - bin.ease({ - duration: 100, - translation_y: -3, - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - }); - }); - bin.connect('leave-event', () => { - bin.ease({ - duration: 100, - translation_y: 0, - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - }); - }); - } - } - - layout.attach(bin, rtl ? (i + 1) % gridSize : i % gridSize, Math.floor(i / gridSize), 1, 1); - } - - return icon; - }, - - _loadApps() { - this._apps = []; - const excludedApps = this._folder.get_strv('excluded-apps'); - const appSys = Shell.AppSystem.get_default(); - const addAppId = appId => { - if (excludedApps.includes(appId)) - return; - - if (opt.APP_GRID_EXCLUDE_FAVORITES && this._appFavorites.isFavorite(appId)) - return; - - const app = appSys.lookup_app(appId); - if (!app) - return; - - if (opt.APP_GRID_EXCLUDE_RUNNING) { - const runningApps = Shell.AppSystem.get_default().get_running().map(a => a.id); - if (runningApps.includes(appId)) - return; - } - - if (!this._parentalControlsManager.shouldShowApp(app.get_app_info())) - return; - - if (this._apps.indexOf(app) !== -1) - return; - - this._apps.push(app); - }; - - const folderApps = this._folder.get_strv('apps'); - folderApps.forEach(addAppId); - - const folderCategories = this._folder.get_strv('categories'); - const appInfos = this._parentView.getAppInfos(); - appInfos.forEach(appInfo => { - let appCategories = /* AppDisplay.*/_getCategories(appInfo); - if (!_listsIntersect(folderCategories, appCategories)) - return; - - addAppId(appInfo.get_id()); - }); - - let items = []; - this._apps.forEach(app => { - let icon = this._items.get(app.get_id()); - if (!icon) - icon = new AppDisplay.AppIcon(app); - - items.push(icon); - }); - - return items; - }, - - acceptDrop(source) { - /* if (!BaseAppViewCommon.acceptDrop.bind(this)(source)) - return false;*/ - if (opt.APP_FOLDER_ORDER) - return false; - if (source._sourceItem) - source = source._sourceItem; - - if (!this._acceptDropCommon(source)) - return false; - - const folderApps = this._orderedItems.map(item => item.id); - this._folder.set_strv('apps', folderApps); - - return true; - }, -}; - -const FolderGrid = GObject.registerClass({ - // Registered name should be unique - GTypeName: `FolderGrid${Math.floor(Math.random() * 1000)}`, -}, class FolderGrid extends AppDisplay.AppGrid { - _init() { - super._init({ - allow_incomplete_pages: false, - // For adaptive size (0), set the numbers high enough to fit all the icons - // to avoid splitting the icons to pages upon creating the grid - columns_per_page: 20, - rows_per_page: 20, - page_halign: Clutter.ActorAlign.CENTER, - page_valign: Clutter.ActorAlign.CENTER, - }); - this.layoutManager._isFolder = true; - this._isFolder = true; - const spacing = opt.APP_GRID_FOLDER_SPACING; - this.set_style(`column-spacing: ${spacing}px; row-spacing: ${spacing}px;`); - this.layoutManager.fixedIconSize = opt.APP_GRID_FOLDER_ICON_SIZE; - - this.setGridModes([ - { - columns: 20, - rows: 20, - }, - ]); - } - - _updatePadding() { - const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage); - const padding = this._indicatorsPadding.copy(); - const pageIndicatorSize = opt.ORIENTATION - ? this._view._pageIndicators.get_preferred_width(1000)[1] / scaleFactor - : this._view._pageIndicators.get_preferred_height(1000)[1] / scaleFactor; - Math.round(Math.min(...this._view._pageIndicators.get_size()));// / scaleFactor);// ~28; - padding.left = opt.ORIENTATION ? pageIndicatorSize : 0; - padding.right = 0; - padding.top = opt.ORIENTATION ? 0 : pageIndicatorSize; - padding.bottom = 0; - this.layoutManager.pagePadding = padding; - } -}); - - -const FOLDER_DIALOG_ANIMATION_TIME = 200; // AppDisplay.FOLDER_DIALOG_ANIMATION_TIME -const AppFolderDialog = { - // injection to _init() - after__init() { - // GS 46 changed the aligning to CENTER which restricts max folder dialog size - this._viewBox.set({ - x_align: Clutter.ActorAlign.FILL, - y_align: Clutter.ActorAlign.FILL, - }); - - // delegate this dialog to the FolderIcon._view - // so its _createFolderIcon function can update the dialog if folder content changed - this._view._dialog = this; - - // right click into the folder popup should close it - this.child.reactive = true; - const clickAction = new Clutter.ClickAction(); - clickAction.connect('clicked', act => { - if (act.get_button() === Clutter.BUTTON_PRIMARY) - return Clutter.EVENT_STOP; - const [x, y] = clickAction.get_coords(); - const actor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y); - // if it's not entry for editing folder title - if (actor !== this._entry) - this.popdown(); - return Clutter.EVENT_STOP; - }); - - this.child.add_action(clickAction); - }, - - after__addFolderNameEntry() { - // edit-folder-button class has been replaced with icon-button class which is not transparent in 46 - this._editButton.add_style_class_name('edit-folder-button'); - - // Edit button - this._removeButton = new St.Button({ - style_class: 'icon-button edit-folder-button', - button_mask: St.ButtonMask.ONE, - toggle_mode: false, - reactive: true, - can_focus: true, - x_align: Clutter.ActorAlign.END, - y_align: Clutter.ActorAlign.CENTER, - child: new St.Icon({ - icon_name: 'user-trash-symbolic', - icon_size: 16, - }), - }); - - this._removeButton.connect('clicked', () => { - if (Date.now() - this._removeButton._lastClick < Clutter.Settings.get_default().double_click_time) { - // Close dialog to avoid crashes - this._isOpen = false; - this._grabHelper.ungrab({ actor: this }); - this.emit('open-state-changed', false); - this.hide(); - this._popdownCallbacks.forEach(func => func()); - this._popdownCallbacks = []; - _appDisplay.ease({ - opacity: 255, - duration: FOLDER_DIALOG_ANIMATION_TIME, - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - }); - - // Reset all keys to delete the relocatable schema - this._view._deletingFolder = true; // Upstream property - let keys = this._folder.settings_schema.list_keys(); - for (const key of keys) - this._folder.reset(key); - - let settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders' }); - let folders = settings.get_strv('folder-children'); - folders.splice(folders.indexOf(this._view._id), 1); - - // remove all abandoned folders (usually my own garbage and unwanted default folders...) - /* const appFolders = _appDisplay._folderIcons.map(icon => icon._id); - folders.forEach(folder => { - if (!appFolders.includes(folder)) { - folders.splice(folders.indexOf(folder._id), 1); - } - });*/ - settings.set_strv('folder-children', folders); - - this._view._deletingFolder = false; - return; - } - this._removeButton._lastClick = Date.now(); - }); - - this._entryBox.add_child(this._removeButton); - this._entryBox.set_child_at_index(this._removeButton, 0); - - this._closeButton = new St.Button({ - style_class: 'icon-button edit-folder-button', - button_mask: St.ButtonMask.ONE, - toggle_mode: false, - reactive: true, - can_focus: true, - x_align: Clutter.ActorAlign.END, - y_align: Clutter.ActorAlign.CENTER, - child: new St.Icon({ - icon_name: 'window-close-symbolic', - icon_size: 16, - }), - }); - - this._closeButton.connect('clicked', () => { - this.popdown(); - }); - - this._entryBox.add_child(this._closeButton); - }, - - popup() { - if (this._isOpen) - return; - - this._isOpen = this._grabHelper.grab({ - actor: this, - focus: this._editButton, - onUngrab: () => this.popdown(), - }); - - if (!this._isOpen) - return; - - this.get_parent().set_child_above_sibling(this, null); - - // _zoomAndFadeIn() is called from the dialog's allocate() - this._needsZoomAndFade = true; - - this.show(); - // force update folder size - this._folderAreaBox = null; - this._updateFolderSize(); - - this.emit('open-state-changed', true); - }, - - _setupPopdownTimeout() { - if (this._popdownTimeoutId > 0) - return; - - // This timeout is handled in the original code and removed in _onDestroy() - // All dialogs are destroyed on extension disable() - this._popdownTimeoutId = - GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => { - this._popdownTimeoutId = 0; - // Following line fixes upstream bug - // https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/6164 - this._view._onDragEnd(); - this.popdown(); - return GLib.SOURCE_REMOVE; - }); - }, - - vfunc_allocate(box) { - this._updateFolderSize(); - - // super.allocate(box) - St.Bin.prototype.vfunc_allocate.bind(this)(box); - - // Override any attempt to resize the folder dialog, that happens when some child gets wild - // Re-allocate the child only if necessary, because it terminates grid animations - if (this._width && this._height && (this._width !== this.child.width || this._height !== this.child.height)) - this._allocateChild(); - - // We can only start zooming after receiving an allocation - if (this._needsZoomAndFade) - this._zoomAndFadeIn(); - }, - - _allocateChild() { - const childBox = new Clutter.ActorBox(); - childBox.set_size(this._width, this._height); - this.child.allocate(childBox); - }, - - // Note that the appDisplay may be off-screen so its coordinates may be shifted - // However, for _updateFolderSize() it doesn't matter - // and when _zoomAndFadeIn() is called, appDisplay is on the right place - _getFolderAreaBox() { - const appDisplay = this._source._parentView; - const folderAreaBox = appDisplay.get_allocation_box().copy(); - const searchEntryHeight = opt.SHOW_SEARCH_ENTRY ? Main.overview._overview.controls._searchEntryBin.height : 0; - folderAreaBox.y1 -= searchEntryHeight; - - // _zoomAndFadeIn() needs an absolute position within a multi-monitor workspace - const monitorGeometry = global.display.get_monitor_geometry(global.display.get_primary_monitor()); - folderAreaBox.x1 += monitorGeometry.x; - folderAreaBox.x2 += monitorGeometry.x; - folderAreaBox.y1 += monitorGeometry.y; - folderAreaBox.y2 += monitorGeometry.y; - - return folderAreaBox; - }, - - _updateFolderSize() { - const view = this._view; - const nItems = view._orderedItems.length; - const [firstItem] = view._grid.layoutManager._container; - if (!firstItem) - return; - - const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage); - const margin = 18; // see stylesheet .app-folder-dialog-container; - - const folderAreaBox = this._getFolderAreaBox(); - - const maxDialogWidth = folderAreaBox.get_width() / scaleFactor; - const maxDialogHeight = folderAreaBox.get_height() / scaleFactor; - - // We can't build folder if the available space is not available - if (!isFinite(maxDialogWidth) || !isFinite(maxDialogHeight) || !maxDialogWidth || !maxDialogHeight) - return; - - // We don't need to recalculate grid if nothing changed - if ( - this._folderAreaBox?.get_width() === folderAreaBox.get_width() && - this._folderAreaBox?.get_height() === folderAreaBox.get_height() && - nItems === this._nItems - ) - return; - - const layoutManager = view._grid.layoutManager; - const spacing = opt.APP_GRID_FOLDER_SPACING; - const padding = 40; - - const titleBoxHeight = - Math.round(this._entryBox.get_preferred_height(-1)[1] / scaleFactor); // ~75 - const minDialogWidth = Math.max(640, - Math.round(this._entryBox.get_preferred_width(-1)[1] / scaleFactor + 2 * margin)); - const navigationArrowsSize = // padding + one arrow width is sufficient for both arrows - Math.round(view._nextPageArrow.get_preferred_width(-1)[1] / scaleFactor); - const pageIndicatorSize = - Math.round(Math.min(...view._pageIndicators.get_size()) / scaleFactor);// ~28; - const horizontalNavigation = opt.ORIENTATION ? pageIndicatorSize : navigationArrowsSize; // either add padding or arrows - const verticalNavigation = opt.ORIENTATION ? navigationArrowsSize : pageIndicatorSize; - - // Horizontal size - const baseWidth = horizontalNavigation + 3 * padding + 2 * margin; - const maxGridPageWidth = maxDialogWidth - baseWidth; - // Vertical size - const baseHeight = titleBoxHeight + verticalNavigation + 2 * padding + 2 * margin; - const maxGridPageHeight = maxDialogHeight - baseHeight; - - // Will be updated to the actual value later - let itemPadding = 55; - const minItemSize = 48 + itemPadding; - - let columns = opt.APP_GRID_FOLDER_COLUMNS; - let rows = opt.APP_GRID_FOLDER_ROWS; - const maxColumns = columns ? columns : 100; - const maxRows = rows ? rows : 100; - - // Find best icon size - let iconSize = opt.APP_GRID_FOLDER_ICON_SIZE < 0 ? opt.APP_GRID_FOLDER_ICON_SIZE_DEFAULT : opt.APP_GRID_FOLDER_ICON_SIZE; - if (opt.APP_GRID_FOLDER_ICON_SIZE === -1) { - let maxIconSize; - if (columns) { - const maxItemWidth = (maxGridPageWidth - (columns - 1) * opt.APP_GRID_FOLDER_SPACING) / columns; - maxIconSize = maxItemWidth - itemPadding; - } - if (rows) { - const maxItemHeight = (maxGridPageHeight - (rows - 1) * spacing) / rows; - maxIconSize = Math.min(maxItemHeight - itemPadding, maxIconSize); - } - - if (maxIconSize) { - // We only need sizes from the default to the smallest - let iconSizes = Object.values(IconSize).sort((a, b) => b - a); - iconSizes = iconSizes.slice(iconSizes.indexOf(iconSize)); - for (const size of iconSizes) { - iconSize = size; - if (iconSize <= maxIconSize) - break; - } - } - } - - if ((!columns && !rows) || opt.APP_GRID_FOLDER_ICON_SIZE !== -1) { - columns = Math.ceil(Math.sqrt(nItems)); - rows = columns; - if (columns * (columns - 1) >= nItems) { - rows = columns - 1; - } else if ((columns + 1) * (columns - 1) >= nItems) { - rows = columns - 1; - columns += 1; - } - } else if (columns && !rows) { - rows = Math.ceil(nItems / columns); - } else if (rows && !columns) { - columns = Math.ceil(nItems / rows); - } - - columns = Math.clamp(columns, 1, maxColumns); - columns = Math.min(nItems, columns); - rows = Math.clamp(rows, 1, maxRows); - - let itemSize = iconSize + itemPadding; - // First run sets the grid before we can read the real icon size - // so we estimate the size from default properties - // and correct it in the second run - if (this.realized) { - firstItem.icon.setIconSize(iconSize); - // Item height is inconsistent because it depends on its label height - const [, firstItemWidth] = firstItem.get_preferred_width(-1); - const realSize = firstItemWidth / scaleFactor; - itemSize = realSize; - itemPadding = realSize - iconSize; - } - - const gridWidth = columns * (itemSize + spacing); - let width = gridWidth + baseWidth; - const gridHeight = rows * (itemSize + spacing); - let height = gridHeight + baseHeight; - - // Folder must fit the appDisplay area plus searchEntryBin if visible - // reduce columns/rows if needed - while (height > maxDialogHeight && rows > 1) { - height -= itemSize + spacing; - rows -= 1; - } - - while (width > maxDialogWidth && columns > 1) { - width -= itemSize + spacing; - columns -= 1; - } - - // Try to compensate for the previous reduction if there is a space - while ((nItems > columns * rows) && ((width + (itemSize + spacing)) <= maxDialogWidth) && (columns < maxColumns)) { - width += itemSize + spacing; - columns += 1; - } - - // remove columns that cannot be displayed - if (((columns * minItemSize + (columns - 1) * spacing)) > maxDialogWidth) - columns = Math.floor(maxDialogWidth / (minItemSize + spacing)); - - while ((nItems > columns * rows) && ((height + (itemSize + spacing)) <= maxDialogHeight) && (rows < maxRows)) { - height += itemSize + spacing; - rows += 1; - } - // remove rows that cannot be displayed - if ((((rows * minItemSize + (rows - 1) * spacing))) > maxDialogHeight) - rows = Math.floor(maxDialogWidth / (minItemSize + spacing)); - - // remove size for rows that are empty - const rowsNeeded = Math.ceil(nItems / columns); - if (rows > rowsNeeded) { - height -= (rows - rowsNeeded) * (itemSize + spacing); - rows -= rows - rowsNeeded; - } - - // Remove space reserved for page controls and indicator if not used - if (rows * columns >= nItems) { - width -= horizontalNavigation; - height -= verticalNavigation; - } - - width = Math.clamp(width, minDialogWidth, maxDialogWidth); - height = Math.min(height, maxDialogHeight); - - layoutManager.columns_per_page = columns; - layoutManager.rows_per_page = rows; - - layoutManager.fixedIconSize = iconSize; - - - // Store data for further use - this._width = width * scaleFactor; - this._height = height * scaleFactor; - this._folderAreaBox = folderAreaBox; - this._nItems = nItems; - - // Set fixed dialog size to prevent size instability - this.child.set_size(this._width, this._height); - this._viewBox.set_style(`width: ${this._width - 2 * margin}px; height: ${this._height - 2 * margin}px;`); - this._viewBox.set_size(this._width - 2 * margin, this._height - 2 * margin); - - view._redisplay(); - }, - - _zoomAndFadeIn() { - let [sourceX, sourceY] = - this._source.get_transformed_position(); - let [dialogX, dialogY] = - this.child.get_transformed_position(); - - const sourceCenterX = sourceX + this._source.width / 2; - const sourceCenterY = sourceY + this._source.height / 2; - - // this. covers the whole screen - let dialogTargetX = dialogX; - let dialogTargetY = dialogY; - - const appDisplay = this._source._parentView; - - const folderAreaBox = this._getFolderAreaBox(); - - let folderAreaX = folderAreaBox.x1; - let folderAreaY = folderAreaBox.y1; - const folderAreaWidth = folderAreaBox.get_width(); - const folderAreaHeight = folderAreaBox.get_height(); - const folder = this.child; - - if (opt.APP_GRID_FOLDER_CENTER) { - dialogTargetX = folderAreaX + folderAreaWidth / 2 - folder.width / 2; - dialogTargetY = folderAreaY + (folderAreaHeight / 2 - folder.height / 2) / 2; - } else { - const { pagePadding } = appDisplay._grid.layoutManager; - const hPadding = (pagePadding.left + pagePadding.right) / 2; - const vPadding = (pagePadding.top + pagePadding.bottom) / 2; - const minX = Math.min(folderAreaX + hPadding, folderAreaX + (folderAreaWidth - folder.width) / 2); - const maxX = Math.max(folderAreaX + folderAreaWidth - hPadding - folder.width, folderAreaX + folderAreaWidth / 2 - folder.width / 2); - const minY = Math.min(folderAreaY + vPadding, folderAreaY + (folderAreaHeight - folder.height) / 2); - const maxY = Math.max(folderAreaY + folderAreaHeight - vPadding - folder.height, folderAreaY + folderAreaHeight / 2 - folder.height / 2); - - dialogTargetX = sourceCenterX - folder.width / 2; - dialogTargetX = Math.clamp(dialogTargetX, minX, maxX); - dialogTargetY = sourceCenterY - folder.height / 2; - dialogTargetY = Math.clamp(dialogTargetY, minY, maxY); - - // keep the dialog in the appDisplay area - dialogTargetX = Math.clamp( - dialogTargetX, - folderAreaX, - folderAreaX + folderAreaWidth - folder.width - ); - - dialogTargetY = Math.clamp( - dialogTargetY, - folderAreaY, - folderAreaY + folderAreaHeight - folder.height - ); - } - - const dialogOffsetX = Math.round(dialogTargetX - dialogX); - const dialogOffsetY = Math.round(dialogTargetY - dialogY); - - 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.child.ease({ - translation_x: dialogOffsetX, - translation_y: dialogOffsetY, - scale_x: 1, - scale_y: 1, - opacity: 255, - duration: FOLDER_DIALOG_ANIMATION_TIME, - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - }); - - appDisplay.ease({ - opacity: 0, - duration: FOLDER_DIALOG_ANIMATION_TIME, - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - }); - - if (opt.SHOW_SEARCH_ENTRY) { - Main.overview.searchEntry.ease({ - opacity: 0, - 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)); - } - }, - - _zoomAndFadeOut() { - if (!this._isOpen) - return; - - if (!this._source.mapped) { - this.hide(); - return; - } - - // if the dialog was shown silently, skip animation - if (this.scale_y < 1) { - this._needsZoomAndFade = false; - this.hide(); - this._popdownCallbacks.forEach(func => func()); - this._popdownCallbacks = []; - return; - } - - let [sourceX, sourceY] = - this._source.get_transformed_position(); - let [dialogX, dialogY] = - this.child.get_transformed_position(); - - this.child.ease({ - translation_x: sourceX - dialogX + this.child.translation_x, - translation_y: sourceY - dialogY + this.child.translation_y, - scale_x: this._source.width / this.child.width, - scale_y: this._source.height / this.child.height, - opacity: 0, - duration: FOLDER_DIALOG_ANIMATION_TIME, - mode: Clutter.AnimationMode.EASE_IN_QUAD, - onComplete: () => { - this.child.set({ - translation_x: 0, - translation_y: 0, - scale_x: 1, - scale_y: 1, - opacity: 255, - }); - this.hide(); - - this._popdownCallbacks.forEach(func => func()); - this._popdownCallbacks = []; - }, - }); - - const appDisplay = this._source._parentView; - appDisplay.ease({ - opacity: 255, - duration: FOLDER_DIALOG_ANIMATION_TIME, - mode: Clutter.AnimationMode.EASE_IN_QUAD, - }); - - if (opt.SHOW_SEARCH_ENTRY) { - Main.overview.searchEntry.ease({ - opacity: 255, - duration: FOLDER_DIALOG_ANIMATION_TIME, - mode: Clutter.AnimationMode.EASE_IN_QUAD, - }); - } - - this._needsZoomAndFade = false; - }, - - _setLighterBackground(lighter) { - let opacity = 255; - if (this._isOpen) - opacity = lighter ? 20 : 0; - - _appDisplay.ease({ - opacity, - duration: FOLDER_DIALOG_ANIMATION_TIME, - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - }); - }, - - vfunc_key_press_event(event) { - if (global.focus_manager.navigate_from_event(event)) - return Clutter.EVENT_STOP; - return Clutter.EVENT_PROPAGATE; - }, - - _showFolderLabel() { - if (this._editButton.checked) - this._editButton.checked = false; - - this._maybeUpdateFolderName(); - this._switchActor(this._entry, this._folderNameLabel); - // This line has been added in 47 to fix focus after editing the folder name - this.navigate_focus(this, St.DirectionType.TAB_FORWARD, false); - }, -}; - -const AppIcon = { - after__init() { - // update the app label behavior - this._updateMultiline(); - }, - - // avoid accepting by placeholder when dragging active preview - // and also by icon if usage sorting is used - _canAccept(source) { - if (source._sourceItem) - source = source._sourceItem; - - // Folders in folder are not supported - if (!(_getViewFromIcon(this) instanceof AppDisplay.AppDisplay) || !this.opacity) - return false; - - const view = /* AppDisplay.*/_getViewFromIcon(source); - return source !== this && - (source instanceof this.constructor) && - // Include drops from folders - // (view instanceof AppDisplay.AppDisplay && - (view && - !opt.APP_GRID_USAGE); - }, -}; - -const AppViewItemCommon = { - _updateMultiline() { - const { label } = this.icon; - if (label) - label.opacity = 255; - if (!this._expandTitleOnHover || !this.icon.label) - return; - - const { clutterText } = label; - - const isHighlighted = this.has_key_focus() || this.hover || this._forcedHighlight; - - if (opt.APP_GRID_NAMES_MODE === 2 && this._expandTitleOnHover) { // !_expandTitleOnHover indicates search result icon - label.opacity = isHighlighted || !this.app ? 255 : 0; - } - if (isHighlighted) - this.get_parent()?.set_child_above_sibling(this, null); - - if (!opt.APP_GRID_NAMES_MODE) { - const layout = clutterText.get_layout(); - if (!layout.is_wrapped() && !layout.is_ellipsized()) - return; - } - - label.remove_transition('allocation'); - - const id = label.connect('notify::allocation', () => { - label.restore_easing_state(); - label.disconnect(id); - }); - - const expand = opt.APP_GRID_NAMES_MODE === 1 || this._forcedHighlight || this.hover || this.has_key_focus(); - - label.save_easing_state(); - label.set_easing_duration(expand - ? APP_ICON_TITLE_EXPAND_TIME - : APP_ICON_TITLE_COLLAPSE_TIME); - clutterText.set({ - line_wrap: expand, - line_wrap_mode: expand ? Pango.WrapMode.WORD_CHAR : Pango.WrapMode.NONE, - ellipsize: expand ? Pango.EllipsizeMode.NONE : Pango.EllipsizeMode.END, - }); - }, - - // support active preview icons - acceptDrop(source, _actor, x) { - if (opt.APP_GRID_USAGE) - return DND.DragMotionResult.NO_DROP; - - this._setHoveringByDnd(false); - - if (!this._canAccept(source)) - return false; - - if (this._withinLeeways(x)) - return false; - - // added - remove app from the source folder after dnd to other folder - let view = /* AppDisplay.*/_getViewFromIcon(source); - if (view instanceof AppDisplay.FolderView) - view.removeApp(source.app); - - return true; - }, - -}; - -const PageIndicatorsCommon = { - after_setNPages() { - this.visible = true; - this.opacity = this._nPages > 1 ? 255 : 0; - }, -}; |